diff --git a/api/src/Database/Type/MySQL.php b/api/src/Database/Type/MySQL.php index fda8c2ecb..4a9103588 100644 --- a/api/src/Database/Type/MySQL.php +++ b/api/src/Database/Type/MySQL.php @@ -135,6 +135,10 @@ class MySQL extends DatabaseParent { 'BLSampleImageAutoScoreClass', 'BLSampleImage_has_AutoScoreClass', + 'Ligand', + 'BLSample_has_Ligand', + 'Ligand_has_PDB', + // Queuing 'ContainerQueueSample', 'ContainerQueue', diff --git a/api/src/Page/DC.php b/api/src/Page/DC.php index 945d59a95..b3e0d4d22 100644 --- a/api/src/Page/DC.php +++ b/api/src/Page/DC.php @@ -19,6 +19,7 @@ class DC extends Page 'aid' => '\d+', 'pjid' => '\d+', 'pid' => '\d+', + 'lid' => '\d+', 'h' => '\d\d', 'dmy' => '\d\d\d\d\d\d\d\d', 'ssid' => '\d+', @@ -259,6 +260,16 @@ function _data_collections($single = null) array_push($args, $this->arg('pid')); } + # Ligands + } else if ($this->has_arg('lid')) { + $info = $this->db->pq("SELECT ligandid FROM ligand l WHERE l.ligandid=:1", array($this->arg('lid'))); + + foreach (array('dc', 'es', 'r', 'xrf') as $i => $t) { + $extj[$i] .= " INNER JOIN blsample_has_ligand bhl ON bhl.blsampleid = smp.blsampleid"; + $sess[$i] = 'bhl.ligandid=:' . (sizeof($args) + 1); + array_push($args, $this->arg('lid')); + } + # Processing job } else if ($this->has_arg('PROCESSINGJOBID')) { $info = $this->db->pq('SELECT processingjobid @@ -1264,7 +1275,16 @@ function _dc_strategies($id) { global $strat_align; - $rows = $this->db->pq("SELECT s.programversion, st.rankingresolution as rankres, ssw.wedgenumber, sssw.subwedgenumber, ssw.chi, ssw.kappa, ssw.phi, dc.datacollectionid as dcid, s.comments, dc.transmission as dctrn, dc.wavelength as lam, dc.imagedirectory imd, dc.imageprefix as imp, dc.comments as dcc, dc.blsampleid as sid, sl.spacegroup as sg, sl.unitcell_a as a, sl.unitcell_b as b, sl.unitcell_c as c, sl.unitcell_alpha as al, sl.unitcell_beta as be, sl.unitcell_gamma as ga, CONCAT(CONCAT(IF(sssw.comments, sssw.comments, IF(ssw.comments, ssw.comments, s.shortcomments)), ' Wedge'), IFNULL(ssw.wedgenumber, '')) as com, sssw.axisstart as st, sssw.exposuretime as time, sssw.transmission as tran, sssw.oscillationrange as oscran, sssw.resolution as res, sssw.numberofimages as nimg, det.numberofpixelsx, det.detectorpixelsizehorizontal, sssw.rotationaxis + $rows = $this->db->pq("SELECT s.programversion, s.comments, + st.rankingresolution as rankres, + ssw.wedgenumber, ssw.chi, ssw.kappa, ssw.phi, ssw.comments as sswcomments, + sssw.subwedgenumber, sssw.axisstart as st, sssw.exposuretime as time, sssw.transmission as tran, + sssw.oscillationrange as oscran, sssw.resolution as res, sssw.numberofimages as nimg, sssw.rotationaxis, + dc.datacollectionid as dcid, dc.transmission as dctrn, dc.wavelength as lam, dc.imagedirectory imd, + dc.imageprefix as imp, dc.comments as dcc, dc.blsampleid as sid, + sl.spacegroup as sg, sl.unitcell_a as a, sl.unitcell_b as b, sl.unitcell_c as c, sl.unitcell_alpha as al, sl.unitcell_beta as be, sl.unitcell_gamma as ga, + det.numberofpixelsx, det.detectorpixelsizehorizontal, + CONCAT(IF(sssw.comments, sssw.comments, IF(ssw.comments, ssw.comments, s.shortcomments)), ' Wedge', IFNULL(ssw.wedgenumber, '')) as com FROM screeningstrategy st INNER JOIN screeningoutput so on st.screeningoutputid = so.screeningoutputid INNER JOIN screening s on so.screeningid = s.screeningid @@ -1292,6 +1312,11 @@ function _dc_strategies($id) } if ($is_align) { + foreach ($r as $k => &$v) { + if (in_array($k, $nf)) { + $v = number_format(floatval($v), 2); + } + } array_push($output[$r['PROGRAMVERSION']]['STRATS'], $r); } else { diff --git a/api/src/Page/Processing.php b/api/src/Page/Processing.php index 5dd43b1b9..4abff179b 100644 --- a/api/src/Page/Processing.php +++ b/api/src/Page/Processing.php @@ -148,22 +148,27 @@ function _screening_status($where, $ids) { INNER JOIN datacollectiongroup dcg on dc.datacollectiongroupid = dcg.datacollectiongroupid INNER JOIN blsession s ON s.sessionid = dcg.sessionid INNER JOIN proposal p ON p.proposalid = s.proposalid - WHERE $where - GROUP BY dc.datacollectionid, sc.programversion", + WHERE sc.autoprocprogramid is null AND $where", $ids ); $statuses = array(); - foreach ($screenings as $screening) { - if (!array_key_exists($screening['DATACOLLECTIONID'], $statuses)) { - $statuses[$screening['DATACOLLECTIONID']][ - 'screening' - ] = array(); + foreach ($screenings as $s) { + if (!array_key_exists($s['DATACOLLECTIONID'], $statuses)) { + $statuses[$s['DATACOLLECTIONID']] = array(); } - - $statuses[$screening['DATACOLLECTIONID']]['screening'][ - $screening['PROGRAMVERSION'] - ] = $this->_map_status($screening["INDEXINGSUCCESS"]); + if (!array_key_exists('screening', $statuses[$s['DATACOLLECTIONID']]) + ) { + $statuses[$s['DATACOLLECTIONID']]['screening'] = array(); + } + if (!array_key_exists($s['PROGRAMVERSION'], $statuses[$s['DATACOLLECTIONID']]['screening']) + ) { + $statuses[$s['DATACOLLECTIONID']]['screening'][$s['PROGRAMVERSION']] = array(); + } + array_push( + $statuses[$s['DATACOLLECTIONID']]['screening'][$s['PROGRAMVERSION']], + $this->_map_status($s['INDEXINGSUCCESS']) + ); } return $statuses; @@ -204,8 +209,9 @@ function _xrc_status($where, $ids) { } function _autoproc_status($where, $ids) { - global $downstream_filter; + global $downstream_filter, $strat_align; $filter = $downstream_filter ? implode("','", $downstream_filter) : ''; + $screening_filter = $strat_align ? implode("','", $strat_align) : ''; $processings = $this->db->union( array( @@ -216,7 +222,9 @@ function _autoproc_status($where, $ids) { INNER JOIN proposal p ON p.proposalid = s.proposalid INNER JOIN autoprocintegration api ON api.datacollectionid = dc.datacollectionid INNER JOIN autoprocprogram app ON api.autoprocprogramid = app.autoprocprogramid - WHERE $where AND app.processingprograms NOT IN ('$filter')", + WHERE $where + AND app.processingprograms NOT IN ('$filter') + AND app.processingprograms NOT IN ('$screening_filter')", "SELECT app.autoprocprogramid, dc.datacollectionid, app.processingprograms, app.processingstatus as status, 'downstream' as type FROM datacollection dc INNER JOIN datacollectiongroup dcg ON dcg.datacollectiongroupid = dc.datacollectiongroupid @@ -225,8 +233,22 @@ function _autoproc_status($where, $ids) { INNER JOIN processingjob pj ON pj.datacollectionid = dc.datacollectionid INNER JOIN autoprocprogram app ON pj.processingjobid = app.processingjobid LEFT OUTER JOIN autoprocintegration api ON api.autoprocprogramid = app.autoprocprogramid - WHERE $where AND api.autoprocintegrationid IS NULL - AND app.processingprograms NOT IN ('$filter')", + WHERE $where + AND api.autoprocintegrationid IS NULL + AND app.processingprograms NOT IN ('$filter') + AND app.processingprograms NOT IN ('$screening_filter')", + "SELECT app.autoprocprogramid, dc.datacollectionid, app.processingprograms, app.processingstatus as status, 'screening' as type + FROM datacollection dc + INNER JOIN datacollectiongroup dcg ON dcg.datacollectiongroupid = dc.datacollectiongroupid + INNER JOIN blsession s ON s.sessionid = dcg.sessionid + INNER JOIN proposal p ON p.proposalid = s.proposalid + INNER JOIN processingjob pj ON pj.datacollectionid = dc.datacollectionid + INNER JOIN autoprocprogram app ON pj.processingjobid = app.processingjobid + LEFT OUTER JOIN autoprocintegration api ON api.autoprocprogramid = app.autoprocprogramid + WHERE $where + AND api.autoprocintegrationid IS NULL + AND app.processingprograms NOT IN ('$filter') + AND app.processingprograms IN ('$screening_filter')", ), $ids ); @@ -280,6 +302,30 @@ function _get_ids() { return array($where, $ids); } + function merge_deep_arrays($a, $b) { + foreach ($b as $key => $value) { + if (isset($a[$key])) { + if (is_array($a[$key]) && is_array($value)) { + // Determine if arrays are associative or numeric + if (array_keys($a[$key]) === range(0, count($a[$key]) - 1) && + array_keys($value) === range(0, count($value) - 1)) { + // Both are numeric arrays - merge them + $a[$key] = array_merge($a[$key], $value); + } else { + // At least one is associative - merge recursively + $a[$key] = $this->merge_deep_arrays($a[$key], $value); + } + } else { + // One is not array, overwrite with $b's value + $a[$key] = $value; + } + } else { + $a[$key] = $value; + } + } + return $a; + } + /** * All screening, auto processing, and downstream statuses */ @@ -308,7 +354,8 @@ function _statuses() { $xrcs = $this->_xrc_status($where, $ids); $autoprocs = $this->_autoproc_status($where, $ids); - $statuses = array_replace_recursive($screenings, $xrcs, $autoprocs); + $combined = $this->merge_deep_arrays($screenings, $autoprocs); + $statuses = array_replace_recursive($xrcs, $combined); $out = array(); foreach ($ids as $id) { @@ -667,8 +714,9 @@ function _ap_message() { } function _get_downstreams($dcid = null, $aid = null) { - global $downstream_filter; + global $downstream_filter, $strat_align; $filter = $downstream_filter ? implode("','", $downstream_filter) : ''; + $screening_filter = $strat_align ? implode("','", $strat_align) : ''; $where = ''; $args = array($this->proposalid); @@ -700,6 +748,7 @@ function _get_downstreams($dcid = null, $aid = null) { INNER JOIN proposal p ON p.proposalid = s.proposalid WHERE api.autoprocintegrationid IS NULL AND p.proposalid=:1 $where AND app.processingprograms NOT IN ('$filter') + AND app.processingprograms NOT IN ('$screening_filter') GROUP BY app.autoprocprogramid", $args ); diff --git a/api/src/Page/Proposal.php b/api/src/Page/Proposal.php index 8cb157cf8..bfe8ed10f 100644 --- a/api/src/Page/Proposal.php +++ b/api/src/Page/Proposal.php @@ -37,6 +37,7 @@ class Proposal extends Page 'SHIPPINGID' => '\d+', 'LABCONTACTID' => '\d+', 'DATACOLLECTIONID' => '\d+', + 'LIGANDID' => '\d+', // proposal 'PROPOSALCODE' => '\w+', @@ -900,6 +901,7 @@ function _lookup() 'SHIPPINGID' => 'sh.shippingid', 'LABCONTACTID' => 'lc.labcontactid', 'DATACOLLECTIONID' => 'dc.datacollectionid', + 'LIGANDID' => 'l.ligandid', ); $field = null; @@ -958,6 +960,7 @@ function _lookup() LEFT OUTER JOIN container c ON c.dewarid = d.dewarid LEFT OUTER JOIN labcontact lc ON lc.proposalid = p.proposalid + LEFT OUTER JOIN ligand l ON l.proposalid = p.proposalid $where ", $args); diff --git a/api/src/Page/Sample.php b/api/src/Page/Sample.php index 69b58bfbe..074eede3f 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -93,6 +93,11 @@ class Sample extends Page 'BLSAMPLETYPEID' => '\d+', 'scid' => '\d+-\d+', + 'LIBRARYNAME' => '^[a-zA-Z0-9\-_\.]+$', + 'LIBRARYBATCHNUMBER' => '^[a-zA-Z0-9\-_\.]+$', + 'PLATEBARCODE' => '^[a-zA-Z0-9\-_\.]+$', + 'SOURCEWELL' => '^[a-zA-Z0-9\-_\.]+$', + 'BLSAMPLEID' => '\d+', 'X' => '\d*(.\d+)?', 'Y' => '\d*(.\d+)?', @@ -191,13 +196,19 @@ class Sample extends Page array('/proteins/lattice', 'post', '_add_protein_lattice'), array('/proteins/lattice/:lid', 'patch', '_update_protein_lattice'), + array('/ligands(/:lid)', 'get', '_ligands'), + array('/ligands', 'post', '_add_ligand'), + array('/ligands/:lid', 'patch', '_update_ligand'), + array('/ligands/add', 'post', '_add_sample_ligand'), + array('/ligands/:sid/lid/:lid', 'delete', '_remove_sample_ligand'), + array('/crystals(/:CRYSTALID)', 'get', '_crystals'), array('/crystals', 'post', '_add_crystal'), array('/crystals/:CRYSTALID', 'patch', '_update_crystal'), - array('/pdbs(/pid/:pid)', 'get', '_get_pdbs'), + array('/pdbs(/pid/:pid)(/lid/:lid)', 'get', '_get_pdbs'), array('/pdbs', 'post', '_add_pdb'), - array('/pdbs(/:pdbid)', 'delete', '_remove_pdb'), + array('/pdbs(/:pdbid)(/lid/:lid)', 'delete', '_remove_pdb'), array('/pdbs/download/:pdbid', 'get', '_download_pdb'), array('/concentrationtypes', 'get', '_concentration_types'), @@ -504,7 +515,7 @@ function _add_simple_sample() if ($info['extension'] == 'pdb' || $info['extension'] == 'cif') { $file = file_get_contents($_FILES[$fileRef]['tmp_name']); - $this->_associate_pdb($info['basename'], $file, '', $ids[$model]['PHASEID']); + $this->_associate_pdb($info['basename'], $file, '', $ids[$model]['PHASEID'], null); } $fileCount++; } @@ -1105,6 +1116,12 @@ function _samples() array_push($args, $this->arg('lt')); } + # For a ligand + if ($this->has_arg('lid')) { + $where .= ' AND bhl.ligandid LIKE :' . (sizeof($args) + 1); + array_push($args, $this->arg('lid')); + } + # For a visit if ($this->has_arg('visit')) { @@ -1191,6 +1208,7 @@ function _samples() INNER JOIN dewar d ON d.dewarid = c.dewarid LEFT OUTER JOIN datacollection dc ON b.blsampleid = dc.blsampleid LEFT OUTER JOIN robotaction r ON r.blsampleid = b.blsampleid AND r.actiontype = 'LOAD' + LEFT OUTER JOIN blsample_has_ligand bhl ON bhl.blsampleid=b.blsampleid $join WHERE $where", $args); $tot = intval($tot[0]['TOT']); @@ -1219,7 +1237,7 @@ function _samples() if ($this->has_arg('sort_by')) { - $cols = array('SAMPLEID' => 'b.blsampleid', 'NAME' => 'b.name', 'ACRONYM' => 'pr.acronym', 'SPACEGROUP' => 'cr.spacegroup', 'COMMENTS' => 'b.comments', 'SHIPMENT' => 'shipment', 'DEWAR' => 'dewar', 'CONTAINER' => 'container', 'b.blsampleid', 'SC' => 'sc', 'SCRESOLUTION' => 'scresolution', 'DC' => 'ap', 'DCRESOLUTION' => 'dcresolution', 'POSITION' => 'TO_NUMBER(b.location)', 'RECORDTIMESTAMP' => 'b.recordtimestamp'); + $cols = array('SAMPLEID' => 'b.blsampleid', 'NAME' => 'b.name', 'ACRONYM' => 'pr.acronym', 'SPACEGROUP' => 'cr.spacegroup', 'COMMENTS' => 'b.comments', 'SHIPMENT' => 'shipment', 'DEWAR' => 'dewar', 'CONTAINER' => 'container', 'b.blsampleid', 'SC' => 'sc', 'SCRESOLUTION' => 'scresolution', 'DC' => 'dc', 'DCRESOLUTION' => 'dcresolution', 'POSITION' => 'TO_NUMBER(b.location)', 'RECORDTIMESTAMP' => 'b.recordtimestamp'); $dir = $this->has_arg('order') ? ($this->arg('order') == 'asc' ? 'ASC' : 'DESC') : 'ASC'; if (array_key_exists($this->arg('sort_by'), $cols)) $order = $cols[$this->arg('sort_by')] . ' ' . $dir; @@ -1230,6 +1248,7 @@ function _samples() , round(min(apss.resolutionlimithigh),2) as dcresolution, round(max(apss.completeness),1) as dccompleteness, dp.anomalousscatterer, dp.requiredresolution, cr.cell_a, cr.cell_b, cr.cell_c, cr.cell_alpha, cr.cell_beta, cr.cell_gamma, b.packingfraction, b.dimension1, b.dimension2, b.dimension3, b.shape, cr.color, cr.theoreticaldensity, cr.name as crystal, pr.name as protein, b.looptype, dp.centringmethod, dp.experimentkind, cq.containerqueueid , TO_CHAR(cq.createdtimestamp, 'DD-MM-YYYY HH24:MI') as queuedtimestamp, b.smiles , $cseq $sseq string_agg(cpr.name) as componentnames, string_agg(cpr.density) as componentdensities + , string_agg(distinct l.name) as ligandnames, string_agg(distinct l.ligandid) as ligandids , string_agg(cpr.proteinid) as componentids, string_agg(cpr.acronym) as componentacronyms, string_agg(cpr.global) as componentglobals, string_agg(chc.abundance) as componentamounts, string_agg(ct.symbol) as componenttypesymbols, b.volume, pct.symbol,ROUND(cr.abundance,3) as abundance, TO_CHAR(b.recordtimestamp, 'DD-MM-YYYY') as recordtimestamp, dp.radiationsensitivity, dp.energy, dp.userpath, dp.strategyoption, dp.minimalresolution as minimumresolution , count(distinct dc.dataCollectionId) as dcc, b.isinsamplechanger @@ -1243,6 +1262,9 @@ function _samples() LEFT OUTER JOIN protein cpr ON cpr.proteinid = chc.componentid LEFT OUTER JOIN concentrationtype ct ON cpr.concentrationtypeid = ct.concentrationtypeid + LEFT OUTER JOIN blsample_has_ligand bhl ON bhl.blsampleid = b.blsampleid + LEFT OUTER JOIN ligand l ON l.ligandid = bhl.ligandid + INNER JOIN container c ON b.containerid = c.containerid INNER JOIN dewar d ON d.dewarid = c.dewarid INNER JOIN shipping s ON s.shippingid = d.shippingid @@ -1282,7 +1304,7 @@ function _samples() ORDER BY $order", $args); foreach ($rows as &$r) { - foreach (array('COMPONENTIDS', 'COMPONENTAMOUNTS', 'COMPONENTACRONYMS', 'COMPONENTTYPESYMBOLS', 'COMPONENTGLOBALS', 'COMPONENTNAMES', 'COMPONENTDENSITIES', 'COMPONENTSEQUENCES') as $k) { + foreach (array('COMPONENTIDS', 'COMPONENTAMOUNTS', 'COMPONENTACRONYMS', 'COMPONENTTYPESYMBOLS', 'COMPONENTGLOBALS', 'COMPONENTNAMES', 'COMPONENTDENSITIES', 'COMPONENTSEQUENCES', 'LIGANDNAMES', 'LIGANDIDS') as $k) { if (array_key_exists($k, $r)) { if ($r[$k]) $r[$k] = explode(',', $r[$k]); @@ -1929,6 +1951,190 @@ function _update_protein() } + # ------------------------------------------------------------------------ + # List of ligands for a proposal + function _ligands() + { + if (!$this->has_arg('prop')) + $this->_error('No proposal specified'); + + $args = array($this->proposalid); + $where = 'l.proposalid=:1'; + + if ($this->has_arg('lid')) { + $where .= ' AND l.ligandid=:' . (sizeof($args) + 1); + array_push($args, $this->arg('lid')); + } + + $tot = $this->db->pq("SELECT count(distinct l.ligandid) as tot FROM ligand l INNER JOIN proposal p ON p.proposalid = l.proposalid WHERE $where", $args); + $tot = intval($tot[0]['TOT']); + + if ($this->has_arg('s')) { + $st = sizeof($args) + 1; + $where .= " AND l.name LIKE CONCAT('%',:" . $st . ", '%')"; + array_push($args, $this->arg('s')); + } + + + $start = 0; + $pp = $this->has_arg('per_page') ? $this->arg('per_page') : 15; + $end = $pp; + + if ($this->has_arg('page')) { + $pg = $this->arg('page') - 1; + $start = $pg * $pp; + $end = $pg * $pp + $pp; + } + + array_push($args, $start); + array_push($args, $end); + + $order = 'l.ligandid DESC'; + + $group = 'l.ligandid'; + + if ($this->has_arg('sort_by')) { + $cols = array( + 'NAME' => 'l.name', + ); + $dir = $this->has_arg('order') ? ($this->arg('order') == 'asc' ? 'ASC' : 'DESC') : 'ASC'; + if (array_key_exists($this->arg('sort_by'), $cols)) + $order = $cols[$this->arg('sort_by')] . ' ' . $dir; + } + + $rows = $this->db->paginate("SELECT l.ligandid, l.name, l.smiles, l.libraryname, l.librarybatchnumber, l.platebarcode, l.sourcewell, + COUNT(DISTINCT dc.datacollectionid) AS dcount, + COUNT(DISTINCT b.blsampleid) AS scount + FROM ligand l + INNER JOIN proposal p ON p.proposalid = l.proposalid + LEFT OUTER JOIN ligand_has_pdb lhp ON lhp.ligandid = l.ligandid + LEFT OUTER JOIN blsample_has_ligand bhl ON bhl.ligandid = l.ligandid + LEFT OUTER JOIN blsample b ON b.blsampleid = bhl.blsampleid + LEFT OUTER JOIN datacollection dc ON b.blsampleid = dc.blsampleid + WHERE $where + GROUP BY $group + ORDER BY $order", $args); + + if ($this->has_arg('lid')) { + if (sizeof($rows)) + $this->_output($rows[0]); + else + $this->_error('No such ligand'); + } else + $this->_output(array( + 'total' => $tot, + 'data' => $rows, + )); + } + + + function _add_sample_ligand() + { + if (!$this->has_arg('prop')) + $this->_error('No proposal specified'); + if (!$this->has_arg('lid')) + $this->_error('No ligand id specified'); + if (!$this->has_arg('sid')) + $this->_error('No sample id specified'); + + $chk = $this->db->pq("SELECT blsampleid, ligandid FROM blsample_has_ligand + WHERE blsampleid=:1 AND ligandid=:2", array($this->arg('sid'), $this->arg('lid'))); + if (sizeof($chk)) + $this->_error('That sample already has that ligand'); + + $this->db->pq( + 'INSERT INTO blsample_has_ligand (blsampleid,ligandid) VALUES (:1,:2)', + array($this->arg('sid'), $this->arg('lid')) + ); + + $this->_output(array('LIGANDID' => $this->arg('lid'))); + } + + + function _remove_sample_ligand() + { + if (!$this->has_arg('prop')) + $this->_error('No proposal specified'); + + if (!$this->has_arg('sid')) + $this->_error('No blsampleid specified'); + + if (!$this->has_arg('lid')) + $this->_error('No ligandid specified'); + + $chk = $this->db->pq("SELECT bhl.blsampleid, bhl.ligandid + FROM blsample_has_ligand bhl + INNER JOIN ligand l ON l.ligandid = bhl.ligandid + WHERE l.proposalid=:1 AND bhl.blsampleid=:2 AND l.ligandid=:3", array($this->proposalid, $this->arg('sid'), $this->arg('lid'))); + + if (!sizeof($chk)) + $this->_error('No such ligand or sample'); + + # Remove association + $this->db->pq("DELETE FROM blsample_has_ligand WHERE blsampleid=:1 and ligandid=:2", array($this->arg('sid'), $this->arg('lid'))); + + $this->_output(1); + } + + + function _add_ligand() + { + if (!$this->has_arg('prop')) + $this->_error('No proposal specified'); + if (!$this->has_arg('NAME')) + $this->_error('No ligand name'); + + $smiles = $this->has_arg('SMILES') ? $this->arg('SMILES') : ''; + $libname = $this->has_arg('LIBRARYNAME') ? $this->arg('LIBRARYNAME') : null; + $libbatch = $this->has_arg('LIBRARYBATCHNUMBER') ? $this->arg('LIBRARYBATCHNUMBER') : null; + $barcode = $this->has_arg('PLATEBARCODE') ? $this->arg('PLATEBARCODE') : null; + $well = $this->has_arg('SOURCEWELL') ? $this->arg('SOURCEWELL') : null; + + $chk = $this->db->pq("SELECT name FROM ligand + WHERE proposalid=:1 AND name=:2", array($this->proposalid, $this->arg('NAME'))); + if (sizeof($chk)) + $this->_error('That ligand name already exists in this proposal'); + + $this->db->pq( + 'INSERT INTO ligand (proposalid,name,smiles,libraryname,librarybatchnumber,platebarcode,sourcewell) + VALUES (:1,:2,:3,:4,:5,:6,:7) RETURNING ligandid INTO :id', + array($this->proposalid, $this->arg('NAME'), $smiles, $libname, $libbatch, $barcode, $well) + ); + + $lid = $this->db->id(); + + $this->_output(array('LIGANDID' => $lid)); + } + + # ------------------------------------------------------------------------ + # Update a particular field for a ligand + function _update_ligand() + { + if (!$this->has_arg('lid')) + $this->_error('No ligandid specified'); + + $lig = $this->db->pq("SELECT l.ligandid FROM ligand l + WHERE l.proposalid = :1 AND l.ligandid = :2", array($this->proposalid, $this->arg('lid'))); + + if (!sizeof($lig)) + $this->_error('No such ligand'); + + if ($this->has_arg('NAME')) { + $chk = $this->db->pq("SELECT l.ligandid FROM ligand l + WHERE l.proposalid = :1 AND l.name = :2", array($this->proposalid, $this->arg('NAME'))); + if (sizeof($chk)) $this->_error('That ligand name already exists in this proposal'); + } + + foreach (array('NAME', 'SMILES', 'LIBRARYNAME', 'LIBRARYBATCHNUMBER', 'PLATEBARCODE', 'SOURCEWELL') as $f) { + if ($this->has_arg($f)) { + $this->db->pq('UPDATE ligand SET ' . $f . '=:1 WHERE ligandid=:2', array($this->arg($f), $this->arg('lid'))); + $this->_output(array($f => $this->arg($f))); + } + } + } + + + # ------------------------------------------------------------------------ # Update a particular field for a sample function _update_sample() @@ -2123,15 +2329,20 @@ function _get_pdbs() if (!$this->has_arg('prop')) $this->_error('No proposal specified'); - $where = 'pr.proposalid=:1'; - $args = array($this->proposalid); + $where = '(pr.proposalid=:1 or l.proposalid=:2)'; + $args = array($this->proposalid, $this->proposalid); + $join = 'LEFT OUTER JOIN protein_has_pdb hp ON p.pdbid = hp.pdbid LEFT OUTER JOIN protein pr ON pr.proteinid = hp.proteinid + LEFT OUTER JOIN ligand_has_pdb lhp ON p.pdbid = lhp.pdbid LEFT OUTER JOIN ligand l ON l.ligandid = lhp.ligandid'; if ($this->has_arg('pid')) { - $where .= ' AND pr.proteinid=:2'; + $where .= ' AND pr.proteinid=:3'; array_push($args, $this->arg('pid')); + } else if ($this->has_arg('lid')) { + $where .= ' AND l.ligandid=:3'; + array_push($args, $this->arg('lid')); } - $rows = $this->db->pq("SELECT distinct hp.proteinhaspdbid, p.pdbid,pr.proteinid, p.name,p.code FROM pdb p INNER JOIN protein_has_pdb hp ON p.pdbid = hp.pdbid INNER JOIN protein pr ON pr.proteinid = hp.proteinid WHERE $where ORDER BY p.pdbid DESC", $args); + $rows = $this->db->pq("SELECT distinct hp.proteinhaspdbid, p.pdbid, pr.proteinid, p.name, p.code, l.ligandid FROM pdb p $join WHERE $where ORDER BY p.pdbid DESC", $args); $this->_output($rows); } @@ -2155,15 +2366,27 @@ function _download_pdb() # Add a new pdb function _add_pdb() { - if (!$this->has_arg('PROTEINID')) - $this->_error('No protein id specified'); + $proteinid = $this->has_arg('PROTEINID') ? $this->arg('PROTEINID') : null; + $ligandid = $this->has_arg('lid') ? $this->arg('lid') : null; + if ($proteinid) { + $prot = $this->db->pq("SELECT pr.proteinid + FROM protein pr + WHERE pr.proposalid = :1 AND pr.proteinid = :2", array($this->proposalid, $proteinid)); - $prot = $this->db->pq("SELECT pr.proteinid - FROM protein pr - WHERE pr.proposalid = :1 AND pr.proteinid = :2", array($this->proposalid, $this->arg('PROTEINID'))); + if (!sizeof($prot)) + $this->_error('No such protein'); - if (!sizeof($prot)) - $this->_error('No such protein'); + } else if ($ligandid) { + $lig = $this->db->pq("SELECT l.ligandid + FROM ligand l + WHERE l.proposalid = :1 AND l.ligandid = :2", array($this->proposalid, $ligandid)); + + if (!sizeof($lig)) + $this->_error('No such ligand'); + + } else { + $this->_error('No protein id or ligand id specified'); + } if (array_key_exists('pdb_file', $_FILES)) { if ($_FILES['pdb_file']['name']) { @@ -2171,34 +2394,47 @@ function _add_pdb() if ($info['extension'] == 'pdb' || $info['extension'] == 'cif') { $file = file_get_contents($_FILES['pdb_file']['tmp_name']); - $this->_associate_pdb($info['basename'], $file, '', $this->arg('PROTEINID')); + $this->_associate_pdb($info['basename'], $file, '', $proteinid, $ligandid); } } } if ($this->has_arg('pdb_code')) { - $this->_associate_pdb($this->arg('pdb_code'), '', $this->arg('pdb_code'), $this->arg('PROTEINID')); + $this->_associate_pdb($this->arg('pdb_code'), '', $this->arg('pdb_code'), $proteinid, $ligandid); } if ($this->has_arg('existing_pdb')) { - $rows = $this->db->pq("SELECT p.pdbid FROM pdb p INNER JOIN protein_has_pdb hp ON p.pdbid = hp.pdbid INNER JOIN protein pr ON pr.proteinid = hp.proteinid WHERE pr.proposalid=:1 AND p.pdbid=:2", array($this->proposalid, $this->arg('existing_pdb'))); + $rows = $this->db->pq("SELECT p.pdbid FROM pdb p + LEFT OUTER JOIN protein_has_pdb hp ON p.pdbid = hp.pdbid + LEFT OUTER JOIN protein pr ON pr.proteinid = hp.proteinid + LEFT OUTER JOIN ligand_has_pdb lhp ON p.pdbid = lhp.pdbid + LEFT OUTER JOIN ligand l ON l.ligandid = lhp.ligandid + WHERE (pr.proposalid=:1 OR l.proposalid=:2) AND p.pdbid=:3", array($this->proposalid, $this->proposalid, $this->arg('existing_pdb'))); if (!sizeof($rows)) $this->_error('The specified pdb doesnt exist'); - $this->db->pq("INSERT INTO protein_has_pdb (proteinhaspdbid,proteinid,pdbid) VALUES (s_protein_has_pdb.nextval,:1,:2)", array($this->arg('PROTEINID'), $this->arg('existing_pdb'))); + if ($proteinid) { + $this->db->pq("INSERT INTO protein_has_pdb (proteinid,pdbid) VALUES (:1,:2)", array($proteinid, $this->arg('existing_pdb'))); + } else if ($ligandid) { + $this->db->pq("INSERT INTO ligand_has_pdb (ligandid,pdbid) VALUES (:1,:2)", array($ligandid, $this->arg('existing_pdb'))); + } } $this->_output(1); } # Duplication :( - function _associate_pdb($name, $contents, $code, $pid) + function _associate_pdb($name, $contents, $code, $pid, $lid) { $this->db->pq("INSERT INTO pdb (pdbid,name,contents,code) VALUES(s_pdb.nextval,:1,:2,:3) RETURNING pdbid INTO :id", array($name, $contents, $code)); $pdbid = $this->db->id(); - $this->db->pq("INSERT INTO protein_has_pdb (proteinhaspdbid,proteinid,pdbid) VALUES (s_protein_has_pdb.nextval,:1,:2)", array($pid, $pdbid)); + if ($pid) { + $this->db->pq("INSERT INTO protein_has_pdb (proteinid,pdbid) VALUES (:1,:2)", array($pid, $pdbid)); + } else if ($lid) { + $this->db->pq("INSERT INTO ligand_has_pdb (ligandid,pdbid) VALUES (:1,:2)", array($lid, $pdbid)); + } } # ------------------------------------------------------------------------ @@ -2207,15 +2443,22 @@ function _remove_pdb() { if (!$this->has_arg('prop')) $this->_error('No proposal specified'); - #if (!$this->has_arg('pid')) $this->_error('No protein specified'); + if (!$this->has_arg('pdbid')) $this->_error('No pdb specified'); - $pdb = $this->db->pq("SELECT pd.pdbid - FROM pdb pd - INNER JOIN protein_has_pdb hp ON hp.pdbid=pd.pdbid - INNER JOIN protein p ON p.proteinid = hp.proteinid - WHERE p.proposalid=:1 AND hp.proteinhaspdbid=:2", array($this->proposalid, $this->arg('pdbid'))); + if ($this->has_arg('lid')) { + $pdb = $this->db->pq("SELECT lhp.pdbid, lhp.ligandid + FROM ligand_has_pdb lhp + INNER JOIN ligand l ON l.ligandid = lhp.ligandid + WHERE l.proposalid=:1 AND lhp.pdbid=:2 AND l.ligandid=:3", array($this->proposalid, $this->arg('pdbid'), $this->arg('lid'))); + } else { + $pdb = $this->db->pq("SELECT pd.pdbid + FROM pdb pd + INNER JOIN protein_has_pdb hp ON hp.pdbid=pd.pdbid + INNER JOIN protein p ON p.proteinid = hp.proteinid + WHERE p.proposalid=:1 AND hp.proteinhaspdbid=:2", array($this->proposalid, $this->arg('pdbid'))); + } if (!sizeof($pdb)) $this->_error('No such pdb'); @@ -2223,10 +2466,14 @@ function _remove_pdb() $pdb = $pdb[0]; # Remove association - $this->db->pq("DELETE FROM protein_has_pdb WHERE proteinhaspdbid=:1", array($this->arg('pdbid'))); + if ($this->has_arg('lid')) { + $this->db->pq("DELETE FROM ligand_has_pdb WHERE pdbid=:1 and ligandid=:2", array($this->arg('pdbid'), $this->arg('lid'))); + } else { + $this->db->pq("DELETE FROM protein_has_pdb WHERE proteinhaspdbid=:1", array($this->arg('pdbid'))); + } # Remove entry if its the last one - $count = $this->db->pq("SELECT pdbid FROM protein_has_pdb WHERE pdbid=:1", array($pdb['PDBID'])); + $count = $this->db->union(array("SELECT pdbid FROM protein_has_pdb WHERE pdbid=:1", "SELECT pdbid FROM ligand_has_pdb WHERE pdbid=:1"), array($pdb['PDBID'])); if (!sizeof($count)) $this->db->pq("DELETE FROM pdb WHERE pdbid=:1", array($pdb['PDBID'])); diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index 9ae571535..5607eeac5 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -21,6 +21,7 @@ class Shipment extends Page 'sid' => '\d+', 'lcid' => '\d+', 'pid' => '\d+', + 'lid' => '\d+', 'iid' => '\d+', @@ -2276,6 +2277,14 @@ function _get_all_containers() array_push($args, $this->arg('pid')); } + if ($this->has_arg('lid')) { + $join .= ' LEFT OUTER JOIN blsample_has_ligand bhl ON bhl.blsampleid = s.blsampleid'; + $where .= ' AND bhl.ligandid=:' . (sizeof($args) + 1); + $totalQuery->joinClause("LEFT OUTER JOIN blsample s ON s.containerid = c.containerid"); + $totalQuery->joinClause("LEFT OUTER JOIN blsample_has_ligand bhl ON bhl.blsampleid = s.blsampleid"); + array_push($args, $this->arg('lid')); + } + if ($this->has_arg('assigned')) { $where .= " AND d.dewarstatus LIKE 'processing' AND c.samplechangerlocation > 0"; $totalQuery->joinClause("INNER JOIN dewar d ON d.dewarid = c.dewarid"); diff --git a/client/src/js/collections/ligands.js b/client/src/js/collections/ligands.js new file mode 100644 index 000000000..112a8dfa6 --- /dev/null +++ b/client/src/js/collections/ligands.js @@ -0,0 +1,58 @@ +define(['underscore', 'backbone.paginator', 'models/ligand'], function(_, PageableCollection, Ligand) { + + return PageableCollection.extend({ + model: Ligand, + mode: 'server', + url: '/sample/ligands', + + state: { + pageSize: 15, + }, + + parseState: function(r, q, state, options) { + return { totalRecords: r.total } + }, + + parseRecords: function(r, options) { + return r.data + }, + + initialize: function(collection, options) { + this.fetched = false + this.on('sync', this.setFetched, this) + if (options && options.pmodel) { + this.pmodel = options.pmodel + this.listenTo(this, 'change add remove', this._update_ligand_ids) + this.listenTo(this.pmodel, 'sync', this._add_ligands) + this._add_ligands() + } + }, + + setFetched: function() { + if (this.fetched) return + this.fetched = true + this.trigger('reset') + }, + + _update_ligand_ids: function() { + var ligs = this.slice(0) + var flds = { + LIGANDIDS: _.map(ligs, function(m) { return m.get('LIGANDID') }), + LIGANDNAMES: _.map(ligs, function(m) { return m.get('NAME') }), + } + this.pmodel.set(flds) + }, + + _add_ligands: function() { + var ids = this.pmodel.get('LIGANDIDS') || [] + var nas = this.pmodel.get('LIGANDNAMES') || [] + var ligs = _.map(ids, function(id, i) { + return { + LIGANDID: id, + NAME: nas[i], + } + }) + this.reset(ligs) + }, + }) +}) diff --git a/client/src/js/models/ligand.js b/client/src/js/models/ligand.js new file mode 100644 index 000000000..1dfdbae82 --- /dev/null +++ b/client/src/js/models/ligand.js @@ -0,0 +1,35 @@ +define(['backbone', 'markdown'], function(Backbone, markdown) { + + return Backbone.Model.extend({ + idAttribute: 'LIGANDID', + urlRoot: '/sample/ligands', + + validation: { + NAME: { + required: true, + pattern: 'wwdash', + }, + SMILES: { + required: false, + pattern: 'smiles' + }, + LIBRARYNAME: { + required: false, + pattern: 'wwdash', + }, + LIBRARYBATCHNUMBER: { + required: false, + pattern: 'wwdash', + }, + PLATEBARCODE: { + required: false, + pattern: 'wwdash', + }, + SOURCEWELL: { + required: false, + pattern: 'wwdash', + }, + }, + }) + +}) diff --git a/client/src/js/models/sample.js b/client/src/js/models/sample.js index e0267e61c..e3694f534 100644 --- a/client/src/js/models/sample.js +++ b/client/src/js/models/sample.js @@ -1,6 +1,6 @@ -define(['backbone', 'collections/components', +define(['backbone', 'collections/components', 'collections/ligands', 'utils/experimentkinds', - 'utils/radiationsensitivity'], function(Backbone, Components, EXP, RS) { + 'utils/radiationsensitivity'], function(Backbone, Components, Ligands, EXP, RS) { return Backbone.Model.extend({ idAttribute: 'BLSAMPLEID', @@ -10,6 +10,7 @@ define(['backbone', 'collections/components', initialize: function(attrs, options) { var addPrimary = (options && options.addPrimary) || (this.collection && this.collection.state.addPrimary) this.set('components', new Components(null, { pmodel: this, addPrimary: addPrimary })) + this.set('ligands', new Ligands(null, { pmodel: this })) this.updateScreeningOptions() this.listenTo(this, 'change:EXPERIMENTKIND', this.updateExpKind) @@ -105,6 +106,8 @@ define(['backbone', 'collections/components', INITIALSAMPLEGROUP: '', COMPONENTIDS: [], COMPONENTAMOUNTS: [], + LIGANDIDS: [], + LIGANDNAMES: [], X: null, Y: null, Z: null, diff --git a/client/src/js/modules/contact/views/addcontact.js b/client/src/js/modules/contact/views/addcontact.js index 25eca2a5d..45c5beb0e 100644 --- a/client/src/js/modules/contact/views/addcontact.js +++ b/client/src/js/modules/contact/views/addcontact.js @@ -68,6 +68,7 @@ define(['views/form', this.countries = new Countries() this.countries.state.pageSize = 9999 this.users = new Users() + this.users.state.pageSize = 9999 this.users.queryParams.login = 1 }, diff --git a/client/src/js/modules/dc/views/apstatusitem.js b/client/src/js/modules/dc/views/apstatusitem.js index d479e1df5..e65ce5358 100644 --- a/client/src/js/modules/dc/views/apstatusitem.js +++ b/client/src/js/modules/dc/views/apstatusitem.js @@ -52,9 +52,16 @@ define(['marionette', 'jquery'], function(Marionette, $) { if (this.getOption('showStrategies')) { this.ui.strat.empty() + var allStrategies = [] _.each(res['screening'], function(sc, n) { - this.ui.strat.append(n+': '+val[sc]+' ') + var strats = {} + _.each(sc, function(a) { + if (!(a in strats)) strats[a] = 0 + strats[a]++ + }) + allStrategies.push(n+': '+_.map(strats, function(c, st) { return c > 1 ? ''+c+'x '+val[st] : val[st]}).join(' ')) }, this) + this.ui.strat.append(allStrategies.join('|')) } if (this.getOption('showProcessing')) { diff --git a/client/src/js/modules/dc/views/autoindexing.js b/client/src/js/modules/dc/views/autoindexing.js index 55d1c0513..ea4389c7c 100644 --- a/client/src/js/modules/dc/views/autoindexing.js +++ b/client/src/js/modules/dc/views/autoindexing.js @@ -8,7 +8,7 @@ define(['marionette', modelEvents: { 'change': 'render' }, getTemplate: function(m) { - return (this.model.get('TYPE') == 'XOalign' || this.model.get('TYPE') == 'dials.align_crystal') ? xotemplate : template + return (this.model.get('TYPE') == 'XOalign' || this.model.get('TYPE').includes('dials.align_crystal')) ? xotemplate : template } }) @@ -31,4 +31,4 @@ define(['marionette', }) -}) \ No newline at end of file +}) diff --git a/client/src/js/modules/samples/collections/pdbs.js b/client/src/js/modules/samples/collections/pdbs.js index b1073d7b6..8d3f5122a 100644 --- a/client/src/js/modules/samples/collections/pdbs.js +++ b/client/src/js/modules/samples/collections/pdbs.js @@ -2,10 +2,13 @@ define(['backbone', 'modules/samples/models/pdb'], function(Backbone, PDB) { return Backbone.Collection.extend({ model: PDB, - url: function() { return '/sample/pdbs'+(this.pid ? '/pid/'+this.pid : '') }, + url: function() { return '/sample/pdbs'+(this.pid ? '/pid/'+this.pid : '')+(this.lid ? '/lid/'+this.lid : '') }, initialize: function(models, options) { - if (options) this.pid = options.pid + if (options) { + this.pid = options.pid + this.lid = options.lid + } }, opts: function() { @@ -14,4 +17,4 @@ define(['backbone', 'modules/samples/models/pdb'], function(Backbone, PDB) { }) -}) \ No newline at end of file +}) diff --git a/client/src/js/modules/samples/components/ligand-add-wrapper.vue b/client/src/js/modules/samples/components/ligand-add-wrapper.vue new file mode 100644 index 000000000..b3a4996ce --- /dev/null +++ b/client/src/js/modules/samples/components/ligand-add-wrapper.vue @@ -0,0 +1,71 @@ + + + diff --git a/client/src/js/modules/samples/components/ligand-list-wrapper.vue b/client/src/js/modules/samples/components/ligand-list-wrapper.vue new file mode 100644 index 000000000..27811df90 --- /dev/null +++ b/client/src/js/modules/samples/components/ligand-list-wrapper.vue @@ -0,0 +1,71 @@ + + + diff --git a/client/src/js/modules/samples/components/ligand-view-wrapper.vue b/client/src/js/modules/samples/components/ligand-view-wrapper.vue new file mode 100644 index 000000000..75da84060 --- /dev/null +++ b/client/src/js/modules/samples/components/ligand-view-wrapper.vue @@ -0,0 +1,78 @@ + + + diff --git a/client/src/js/modules/samples/controller.js b/client/src/js/modules/samples/controller.js index d6f68b16e..0171bf067 100644 --- a/client/src/js/modules/samples/controller.js +++ b/client/src/js/modules/samples/controller.js @@ -5,6 +5,8 @@ define(['marionette', 'collections/samples', 'models/protein', 'collections/proteins', + 'models/ligand', + 'collections/ligands', 'models/crystal', 'collections/crystals', @@ -16,6 +18,7 @@ define(['marionette', GetView, Sample, Samples, Protein, Proteins, + Ligand, Ligands Crystal, Crystals, Instance, ProposalLookup) { @@ -236,6 +239,60 @@ define(['marionette', }) }, + // Ligands + ligandlist: function(s, page) { + app.loading() + var title = GetView.LigandList.title(app.type) + + app.bc.reset([{ title: title+'s', url: '/'+title.toLowerCase()+'s' }]) + page = page ? parseInt(page) : 1 + var params = { s : s } + var ligands = new Ligands(null, { state: { currentPage: page }, queryParams: params }) + ligands.fetch().done(function() { + app.content.show(GetView.LigandList.get(app.type, { collection: ligands, params: { s: s } })) + }) + }, + + ligandview: function(lid) { + app.loading() + var title = GetView.LigandList.title(app.type) + var pbc = { title: title+'s', url: '/'+title.toLowerCase()+'s' } + + var lookup = new ProposalLookup({ field: 'LIGANDID', value: lid }) + lookup.find({ + success: function() { + var ligand = new Ligand({ LIGANDID: lid }) + ligand.fetch({ + success: function() { + app.bc.reset([pbc, { title: ligand.get('NAME') }]) + app.content.show(GetView.LigandView.get(app.type, { model: ligand })) + }, + error: function() { + app.bc.reset([pbc]) + app.message({ title: 'No such '+title, message: 'The specified '+title+' could not be found'}) + }, + }) + }, + + error: function() { + app.bc.reset([pbc]) + app.message({ title: 'No such '+title, message: 'The specified '+title+' could not be found'}) + } + }) + }, + + ligandadd: function() { + var title = GetView.LigandList.title(app.type) + + if (app.proposal && app.proposal.get('ACTIVE') != 1) { + app.message({ title: 'Proposal Not Active', message: 'This proposal is not active so new '+title+'s cannot be added'} ) + } else { + var pbc = { title: title+'s', url: '/'+title.toLowerCase()+'s' } + app.bc.reset([pbc, { title: 'Add '+title }]) + app.content.show(GetView.LigandAdd.get(app.type)) + } + }, + } @@ -279,7 +336,12 @@ define(['marionette', app.navigate('phases/pid/'+pid) controller.proteinview(pid) }) + + app.on('ligands:view', function(lid) { + app.navigate('ligands/lid/'+lid) + controller.ligandview(lid) + }) }) return controller -}) \ No newline at end of file +}) diff --git a/client/src/js/modules/samples/models/pdb.js b/client/src/js/modules/samples/models/pdb.js index c40b9e251..8026670c3 100644 --- a/client/src/js/modules/samples/models/pdb.js +++ b/client/src/js/modules/samples/models/pdb.js @@ -24,8 +24,9 @@ define(['backbone', 'models/wfile'], function(Backbone, File) { initialize: function(options) { this.pid = options.pid + this.lid = options.lid }, }, File)) -}) \ No newline at end of file +}) diff --git a/client/src/js/modules/samples/routes.js b/client/src/js/modules/samples/routes.js index 6983e736e..92e3daaaa 100644 --- a/client/src/js/modules/samples/routes.js +++ b/client/src/js/modules/samples/routes.js @@ -10,6 +10,10 @@ const ProteinListWrapper = () => import(/* webpackChunkName: "samples" */ 'modul const ProteinAddWrapper = () => import(/* webpackChunkName: "samples" */ 'modules/samples/components/protein-add-wrapper.vue') const ProteinViewWrapper = () => import(/* webpackChunkName: "samples" */ 'modules/samples/components/protein-view-wrapper.vue') +const LigandListWrapper = () => import(/* webpackChunkName: "samples" */ 'modules/samples/components/ligand-list-wrapper.vue') +const LigandAddWrapper = () => import(/* webpackChunkName: "samples" */ 'modules/samples/components/ligand-add-wrapper.vue') +const LigandViewWrapper = () => import(/* webpackChunkName: "samples" */ 'modules/samples/components/ligand-view-wrapper.vue') + const SampleListWrapper = () => import(/* webpackChunkName: "samples" */ 'modules/samples/components/sample-list-wrapper.vue') const SampleViewWrapper = () => import(/* webpackChunkName: "samples" */ 'modules/samples/components/sample-view-wrapper.vue') const XpdfSimpleSampleAddWrapper = () => import(/* webpackChunkName: "samples" */ 'modules/samples/components/XpdfSimpleSampleAddWrapper.vue') @@ -50,6 +54,10 @@ app.addInitializer(function() { app.navigate('/proteins/pid/'+pid) }) + app.on('ligands:view', function(lid) { + app.navigate('/ligands/lid/'+lid) + }) + app.on('phases:view', function(pid) { app.navigate('/phases/pid/'+pid) }) @@ -129,6 +137,29 @@ const routes = [ name: 'protein-add', component: ProteinAddWrapper, }, + // Ligands + { + path: '/ligands(/s/)?:s([a-zA-Z0-9_-]+)?(/page/)?:page([0-9]+)?', + name: 'ligand-list', + component: LigandListWrapper, + props: route => ({ + search: route.params.s, + page: +route.params.page || 1, + }) + }, + { + path: '/ligands/lid/:lid', + name: 'ligand-view', + component: LigandViewWrapper, + props: route => ({ + lid: +route.params.lid + }) + }, + { + path: '/ligands/add', + name: 'ligand-add', + component: LigandAddWrapper, + }, // Phases - these were added for xpdf. // Reuse protein components and views { diff --git a/client/src/js/modules/samples/views/addpdb.js b/client/src/js/modules/samples/views/addpdb.js index 5ca24e9ef..b589ce365 100644 --- a/client/src/js/modules/samples/views/addpdb.js +++ b/client/src/js/modules/samples/views/addpdb.js @@ -62,7 +62,7 @@ define(['backbone', 'views/dialog', 'modules/samples/models/pdb', 'modules/sampl createModel: function(options) { - this.model = new PDB({ PROTEINID: options.pid }) + this.model = new PDB({ PROTEINID: options.pid, lid: options.lid }) }, initialize: function(options) { @@ -121,4 +121,4 @@ define(['backbone', 'views/dialog', 'modules/samples/models/pdb', 'modules/sampl }) -}) \ No newline at end of file +}) diff --git a/client/src/js/modules/samples/views/getsampleview.js b/client/src/js/modules/samples/views/getsampleview.js index ffc1290aa..6f253bd2e 100644 --- a/client/src/js/modules/samples/views/getsampleview.js +++ b/client/src/js/modules/samples/views/getsampleview.js @@ -11,6 +11,10 @@ define(['views/getview', 'modules/samples/views/proteinview', 'modules/samples/views/proteinadd', + 'modules/samples/views/ligandlist', + 'modules/samples/views/ligandview', + 'modules/samples/views/ligandadd', + 'modules/types/gen/samples/views/componentlist', 'modules/types/gen/samples/views/componentadd', 'modules/types/gen/samples/views/componentview', @@ -33,6 +37,7 @@ define(['views/getview', SimpleSampleAdd, ProteinList, ProteinView, AddProteinView, + LigandList, LigandView, AddLigandView, GenComponentList, GenComponentAdd, GenComponentView, XPDFPhaseList, XPDFPhaseView, XPDFPhaseAdd, @@ -60,6 +65,11 @@ define(['views/getview', var ProteinDefault = 'Component' + var LigandTitles = { + mx: 'Ligand', + } + + var LigandDefault = 'Ligand' var CrystalTitles = { xpdf: 'Sample', @@ -149,6 +159,38 @@ define(['views/getview', default_title: ProteinDefault, }), + LigandList: new GetView({ + views: { + mx: LigandList, + }, + default: LigandList, + + titles: LigandTitles, + default_title: LigandDefault, + }), + + + LigandAdd: new GetView({ + views: { + mx: AddLigandView, + }, + default: AddLigandView, + + titles: LigandTitles, + default_title: LigandDefault, + }), + + + LigandView: new GetView({ + views: { + mx: LigandView, + }, + default: LigandView, + + titles: LigandTitles, + default_title: LigandDefault, + }), + CrystalList: new GetView({ @@ -189,4 +231,4 @@ define(['views/getview', } -}) \ No newline at end of file +}) diff --git a/client/src/js/modules/samples/views/ligandadd.js b/client/src/js/modules/samples/views/ligandadd.js new file mode 100644 index 000000000..43fe23169 --- /dev/null +++ b/client/src/js/modules/samples/views/ligandadd.js @@ -0,0 +1,35 @@ +define(['marionette', 'views/form', + 'models/ligand', + 'templates/samples/ligandadd.html'], + function(Marionette, TableView, Ligand, template) { + + + return FormView.extend({ + template: template, + + createModel: function() { + this.model = new Ligand() + }, + + success: function(model, response, options) { + console.log('success from ligand add', this.model) + app.trigger('ligands:view', model.get('LIGANDID')) + }, + + failure: function(model, xhr, options) { + console.log(arguments) + json = null + if (xhr.responseText) { + try { + json = $.parseJSON(xhr.responseText) + } catch(err) { + console.error("Error parsing response: ", err) + } + } + + if (json.message) app.alert({ message: json.message }) + else app.alert({ message: 'Something went wrong registering that ligand' }) + } + }) + +}) diff --git a/client/src/js/modules/samples/views/ligandlist.js b/client/src/js/modules/samples/views/ligandlist.js new file mode 100644 index 000000000..b9031b451 --- /dev/null +++ b/client/src/js/modules/samples/views/ligandlist.js @@ -0,0 +1,61 @@ +define(['marionette', 'backgrid', 'views/table', 'utils/table', 'templates/samples/ligandlist.html'], + function(Marionette, Backgrid, TableView, table, Template) { + + var ClickableRow = table.ClickableRow.extend({ + event: 'ligands:view', + argument: 'LIGANDID', + cookie: true, + }) + + return Marionette.LayoutView.extend({ + className: 'content', + template: Template, + regions: { 'wrap': '.wrapper' }, + + clickableRow: ClickableRow, + + title: 'Ligand', + url: 'ligand', + + templateHelpers: function() { + return { + title: this.getOption('title'), + url: this.getOption('url'), + } + }, + + columns: [ + { name: 'NAME', label: 'Name', cell: 'string', editable: false }, + { name: 'SMILES', label: 'SMILES Code', cell: 'string', editable: false }, + { name: 'LIBRARYNAME', label: 'Library Name', cell: 'string', editable: false }, + { name: 'LIBRARYBATCHNUMBER', label: 'Library Batch No', cell: 'string', editable: false }, + { name: 'PLATEBARCODE', label: 'Plate Barcode', cell: 'string', editable: false }, + { name: 'SOURCEWELL', label: 'Source Well', cell: 'string', editable: false }, + { name: 'SCOUNT', label: 'Samples', cell: 'string', editable: false }, + { name: 'DCOUNT', label: 'Data Collections', cell: 'string', editable: false }, + ], + + initialize: function(options) { + + var cols = this.getOption('columns') + + var self = this + this.table = new TableView({ collection: options.collection, columns: cols, tableClass: 'proposals', filter: 's', search: options.params && options.params.s, loading: true, + backgrid: { + row: this.getOption('clickableRow'), + emptyText: function() { + return self.collection.fetched ? 'No '+self.getOption('title')+'s found' : 'Retrieving '+self.getOption('title')+'s' + } + } + }) + }, + + onRender: function() { + this.wrap.show(this.table) + }, + + onShow: function() { + this.table.focusSearch() + }, + }) +}) diff --git a/client/src/js/modules/samples/views/ligandview.js b/client/src/js/modules/samples/views/ligandview.js new file mode 100644 index 000000000..44b33d3b2 --- /dev/null +++ b/client/src/js/modules/samples/views/ligandview.js @@ -0,0 +1,93 @@ +define(['marionette', + + 'utils/editable', + 'collections/datacollections', + 'modules/dc/datacollections', + 'collections/samples', + + 'collections/containers', + 'modules/shipment/views/containers', + + 'modules/samples/collections/pdbs', + 'modules/samples/views/pdbs', + 'modules/samples/views/addpdb', + + 'modules/dc/views/getdcview', + + 'templates/samples/ligand.html', + 'backbone', 'backbone-validation' + ], function(Marionette, Editable, DCCol, DCView, Samples, Containers, ContainersView, + PDBs, PDBView, AddPDBView, + GetDCView, + template, Backbone) { + + + return Marionette.LayoutView.extend({ + className: 'content', + template: template, + + regions: { + pdb: '.pdb', + smp: '.samples', + dc: '.datacollections', + cont: '.conts', + }, + + events: { + 'click a.add': 'addPDB', + }, + + addPDB: function() { + var view = new AddPDBView({ lid: this.model.get('LIGANDID') }) + this.listenTo(view, 'pdb:success', this.getPDBs) + app.dialog.show(view) + }, + + getPDBs: function() { + this.pdbs.fetch() + }, + + initialize: function(options) { + Backbone.Validation.bind(this); + + this.samples = new Samples() + this.samples.state.pageSize = 5 + this.samples.queryParams.lid = this.model.get('LIGANDID') + this.samples.fetch() + + this.dcs = new DCCol(null, { queryParams: { lid: this.model.get('LIGANDID'), pp: 5 } }) + this.dcs.fetch() + + this.pdbs = new PDBs(null, { lid: this.model.get('LIGANDID') }) + this.getPDBs() + + this.containers = new Containers() + this.containers.queryParams.lid = this.model.get('LIGANDID') + this.containers.fetch() + }, + + + onRender: function() { + var self = this + var edit = new Editable({ model: this.model, el: this.$el }) + edit.create('NAME', 'text') + edit.create('SMILES', 'text') + edit.create('LIBRARYNAME', 'text') + edit.create('LIBRARYBATCHNUMBER', 'text') + edit.create('PLATEBARCODE', 'text') + edit.create('SOURCEWELL', 'text') + + + // Prevent cyclic dependency + var GetSampleView = require('modules/samples/views/getsampleview') + // var GetDCView = require('modules/dc/views/getdcview') + + this.pdb.show(new PDBView({ collection: this.pdbs, isLigand: true })) + this.smp.show(GetSampleView.SampleList.get(app.type, { collection: this.samples, noPageUrl: true, noFilterUrl: true, noSearchUrl: true })) + this.dc.show(GetDCView.DCView.get(app.type, { model: this.model, collection: this.dcs, params: { visit: null }, noPageUrl: true, noFilterUrl: true, noSearchUrl: true })) + this.cont.show(new ContainersView({ collection: this.containers, params: {} })) + }, + + }) + +}) diff --git a/client/src/js/modules/samples/views/pdbs.js b/client/src/js/modules/samples/views/pdbs.js index bbbd52b22..ad3b35ac8 100644 --- a/client/src/js/modules/samples/views/pdbs.js +++ b/client/src/js/modules/samples/views/pdbs.js @@ -1,4 +1,4 @@ -define(['marionette', 'utils'], function(Marionette, utils) { +define(['marionette', 'backbone', 'utils'], function(Marionette, Backbone, utils) { var UserItem = Marionette.ItemView.extend({ @@ -9,7 +9,7 @@ define(['marionette', 'utils'], function(Marionette, utils) { 'click a.delete': 'deletePDB', 'click a.download': utils.signHandler, }, - + render: function() { UserItem.__super__.render.call(this) const linkButton = ' EBI' @@ -23,7 +23,19 @@ define(['marionette', 'utils'], function(Marionette, utils) { }, deletePDB: function(e) { - this.model.destroy() + if (this.options.isLigand) { + var self = this + Backbone.ajax({ + url: app.apiurl+'/sample/pdbs/'+this.model.get('PDBID')+'/lid/'+this.model.get('LIGANDID'), + type: 'DELETE', + dataType: 'json', + success: function(response) { + self.model.collection.remove(self.model) + } + }) + } else { + this.model.destroy() + } }, }) @@ -40,6 +52,9 @@ define(['marionette', 'utils'], function(Marionette, utils) { attributes: { 'data-testid': 'protein-pdb-list' }, childView: UserItem, emptyView: EmptyUserItem, + childViewOptions: function(model) { + return this.options + } }) diff --git a/client/src/js/modules/samples/views/sampleligandsview.js b/client/src/js/modules/samples/views/sampleligandsview.js new file mode 100644 index 000000000..3a2b7498c --- /dev/null +++ b/client/src/js/modules/samples/views/sampleligandsview.js @@ -0,0 +1,79 @@ +define(['marionette', 'backbone'], function(Marionette, Backbone) { + + var LigandView = Marionette.ItemView.extend({ + tagName: 'li', + getTemplate: function() { + return _.template('<%-NAME%> ') + }, + + events: { + 'click a.delete': 'removeLigand', + }, + + addLig: function(e) { + Backbone.ajax({ + url: app.apiurl+'/sample/ligands/add', + type: 'POST', + data: { + sid: this.getOption('BLSAMPLEID'), + lid: this.model.get('LIGANDID'), + }, + success: this.ligAdded.bind(this) + }) + }, + + ligAdded: function(response) { + this.model.set('new', false) + this.render() + }, + + removeLigand: function(e) { + e.preventDefault() + + var self = this + Backbone.ajax({ + url: app.apiurl+'/sample/ligands/'+this.getOption('BLSAMPLEID')+'/lid/'+this.model.get('LIGANDID'), + type: 'DELETE', + dataType: 'json', + success: function(response) { + self.model.collection.remove(self.model) + } + }) + + }, + + initialize: function(options) { + Backbone.Validation.bind(this) + + if (this.model.get('new')) { + this.addLig() + } + }, + + }) + + var EmptyView = Marionette.ItemView.extend({ + tagName: 'li', + template: _.template('No Ligands') + }) + + return LigandsView = Marionette.CollectionView.extend({ + tagName: 'ul', + childView: LigandView, + className: function() { + if (this.getOption('showOutline')) return 'visits' + }, + + getEmptyView: function() { + return this.getOption('showEmpty') ? EmptyView : null + }, + + childViewOptions: function() { + return { + BLSAMPLEID: this.getOption('BLSAMPLEID'), + } + }, + }) + + +}) diff --git a/client/src/js/modules/samples/views/view.js b/client/src/js/modules/samples/views/view.js index e61bfe3db..2020e2e52 100644 --- a/client/src/js/modules/samples/views/view.js +++ b/client/src/js/modules/samples/views/view.js @@ -15,6 +15,8 @@ define(['marionette', 'modules/dc/views/getdcview', 'modules/samples/views/componentsview', + 'modules/samples/views/sampleligandsview', + 'collections/ligands', 'modules/imaging/collections/inspectionimages', 'modules/imaging/views/imagehistory', @@ -22,7 +24,7 @@ define(['marionette', 'templates/samples/sample.html', 'backbone', 'backbone-validation' ], function(Marionette, Backgrid, DistinctProteins, SpaceGroups, Anom, CM, EXP, RS, Editable, TableView, table, SubSamples, DCCol, GetDCView, - ComponentsView, + ComponentsView, LigandsView, Ligands, InspectionImages, ImageHistoryView, template, Backbone) { @@ -48,10 +50,12 @@ define(['marionette', history: '.history', imh: '.im_history', comps: '.components', + ligs: '.ligands', }, ui: { comp: 'input[name=COMPONENTID]', + lig: 'input[name=LIGANDID]', }, initialize: function(options) { @@ -67,6 +71,7 @@ define(['marionette', } this.gproteins = new DistinctProteins() + this.ligands = new Ligands() this.subsamples = new SubSamples() this.subsamples.queryParams.sid = this.model.get('BLSAMPLEID') @@ -131,6 +136,7 @@ define(['marionette', console.log('sample', this.model) this.comps.show(new ComponentsView({ showEmpty: true, collection: this.model.get('components'), viewLink: true, editinline: true, CRYSTALID: this.model.get('CRYSTALID') })) + this.ligs.show(new LigandsView({ showEmpty: true, collection: this.model.get('ligands'), showOutline: true, BLSAMPLEID: this.model.get('BLSAMPLEID') })) if (this.model.get('INSPECTIONS') > 0) this.imh.show(new ImageHistoryView({ historyimages: this.inspectionimages, embed: true })) @@ -139,6 +145,11 @@ define(['marionette', select: this.selectGlobalProtein.bind(this) }) + this.ui.lig.autocomplete({ + source: this.getLigands.bind(this), + select: this.selectLigand.bind(this) + }) + var columns = [ { label: '#', cell: table.TemplateCell, editable: false, template: '<%-(RID+1)%>' }, @@ -194,6 +205,37 @@ define(['marionette', } }) }, + + selectLigand: function(e, ui) { + e.preventDefault() + var lig = this.ligands.findWhere({ LIGANDID: ui.item.value }) + if (lig) { + console.log('add lig', lig) + var clone = lig.clone() + var ligs = this.model.get('ligands') + clone.collection = ligs + clone.set('new', true) + ligs.add(clone) + } + this.ui.lig.val('') + }, + + getLigands: function(req, resp) { + var self = this + this.ligands.fetch({ + data: { + s: req.term, + }, + success: function(data) { + resp(self.ligands.map(function(m) { + return { + label: m.get('NAME'), + value: m.get('LIGANDID'), + } + })) + } + }) + }, }) diff --git a/client/src/js/modules/shipment/views/containerplate.js b/client/src/js/modules/shipment/views/containerplate.js index b01c6c898..03c725286 100644 --- a/client/src/js/modules/shipment/views/containerplate.js +++ b/client/src/js/modules/shipment/views/containerplate.js @@ -6,6 +6,7 @@ define(['marionette', 'models/sample', 'collections/samples', 'collections/subsamples', + 'collections/ligands', 'modules/shipment/collections/containerhistory', @@ -54,6 +55,7 @@ define(['marionette', Sample, Samples, Subsamples, + Ligands, ContainerHistory, @@ -661,6 +663,7 @@ define(['marionette', this.ctypes = new PlateTypes() this.gproteins = new DistinctProteins() + this.ligands = new Ligands() this.inspections = new ContainerInspections() this.inspections.queryParams.cid = this.model.get('CONTAINERID') @@ -930,7 +933,7 @@ define(['marionette', this.plateView = new PlateView({ collection: this.samples, type: this.type, showImageStatus: this.model.get('INSPECTIONS') > 0 }) this.listenTo(this.plateView, 'plate:select', this.resetZoom, this) this.plate.show(this.plateView) - this.singlesample = new SingleSample({ proteins: this.proteins, existingContainer: true, gproteins: this.gproteins }) + this.singlesample = new SingleSample({ proteins: this.proteins, existingContainer: true, gproteins: this.gproteins, ligands: this.ligands }) var total = _.map(_.range(1, parseInt(this.model.get('CAPACITY'))+1), function(e) { return e.toString() }) var diff = _.difference(total, this.samples.pluck('LOCATION')) diff --git a/client/src/js/modules/shipment/views/singlesample.js b/client/src/js/modules/shipment/views/singlesample.js index d09361c3a..ce488c620 100644 --- a/client/src/js/modules/shipment/views/singlesample.js +++ b/client/src/js/modules/shipment/views/singlesample.js @@ -8,6 +8,7 @@ define(['backbone', 'utils/anoms', 'modules/samples/views/componentsview', + 'modules/samples/views/sampleligandsview', 'utils/safetylevel', @@ -15,7 +16,7 @@ define(['backbone', 'templates/shipment/singlesamplee.html', ], function(Backbone, utils, FormView, SpaceGroups, Editable, Protein, Anom, - ComponentsView, + ComponentsView, LigandsView, safetyLevel, templatenew, template) { @@ -37,7 +38,9 @@ define(['backbone', cc: 'a.clone-col', cr: 'a.clone-row', comps: '.components', + ligs: '.ligands', comp: 'input[name=COMPONENTID]', + lig: 'input[name=LIGANDID]', abun: 'input[name=ABUNDANCE]', sym: 'span.SYMBOL', cella: 'input[name=CELL_A]', @@ -222,6 +225,7 @@ define(['backbone', this.ready = [] this.extra = false this.gproteins = options.gproteins + this.ligands = options.ligands if (options && options.spacegroups) this.spacegroups = options.spacegroups else { this.spacegroups = new SpaceGroups(null, { state: { pageSize: 9999 } }) @@ -301,6 +305,11 @@ define(['backbone', select: this.selectGlobalProtein.bind(this) }) + this.ui.lig.autocomplete({ + source: this.getLigands.bind(this), + select: this.selectLigand.bind(this) + }) + console.log('render single', this.model) this.compview = new ComponentsView({ showEmpty: true, @@ -311,6 +320,17 @@ define(['backbone', }) this.ui.comps.append(this.compview.render().$el) + // prevent autocomplete from being created multiple times + if (this.ligview) { + this.ligview.remove() + } + this.ligview = new LigandsView({ + showEmpty: true, + BLSAMPLEID: this.model.get('BLSAMPLEID'), + collection: this.model.get('ligands'), + }) + this.ui.ligs.append(this.ligview.render().$el) + if (this.extra) this.$el.find('.extra').addClass('show') } }, @@ -347,7 +367,37 @@ define(['backbone', }) }, - + selectLigand: function(e, ui) { + e.preventDefault() + var lig = this.ligands.findWhere({ LIGANDID: ui.item.value }) + if (lig) { + console.log('add lig', lig) + var clone = lig.clone() + var ligs = this.model.get('ligands') + clone.collection = ligs + clone.set('new', true) + ligs.add(clone) + } + this.ui.lig.val('') + }, + + getLigands: function(req, resp) { + var self = this + this.ligands.fetch({ + data: { + s: req.term, + }, + success: function(data) { + resp(self.ligands.map(function(m) { + return { + label: m.get('NAME'), + value: m.get('LIGANDID'), + } + })) + } + }) + }, + addProtein: function(ui, val) { var validOnly = app.options.get('valid_components') if (!(((validOnly && app.staff) || !validOnly) diff --git a/client/src/js/modules/types/mx/menu.js b/client/src/js/modules/types/mx/menu.js index 0b1273700..4b6f803ad 100644 --- a/client/src/js/modules/types/mx/menu.js +++ b/client/src/js/modules/types/mx/menu.js @@ -10,6 +10,7 @@ define([], function() { 'containers/registry': 'Registered Containers', containers: 'Containers', proteins: 'Proteins', + ligands: 'Ligands', samples: 'Samples', 'samples/groups': 'Sample Groups', stats: 'Statistics', diff --git a/client/src/js/modules/types/mx/shipment/views/mx-container-view.vue b/client/src/js/modules/types/mx/shipment/views/mx-container-view.vue index da6f4f9d9..9a24cd7fb 100644 --- a/client/src/js/modules/types/mx/shipment/views/mx-container-view.vue +++ b/client/src/js/modules/types/mx/shipment/views/mx-container-view.vue @@ -95,15 +95,6 @@ class="tw-absolute top-5 tw-text-content-page-color" >[View] -
  • - Barcode - -
  • diff --git a/client/src/js/templates/dc/dc_xoalign.html b/client/src/js/templates/dc/dc_xoalign.html index 36aec8593..11ca53225 100644 --- a/client/src/js/templates/dc/dc_xoalign.html +++ b/client/src/js/templates/dc/dc_xoalign.html @@ -9,15 +9,29 @@

    <%-TYPE%>

    Kappa Chi Phi + Space Group + A + B + C + α + β + γ <% _.each(STRATS, function(r, i) { %> - <%-r.COMMENTS %> + <%-r.SSWCOMMENTS %> <%-r.KAPPA %> <%-r.CHI %> <%-r.PHI %> + <%-r.SG %> + <%-r.A %> + <%-r.B %> + <%-r.C %> + <%-r.AL %> + <%-r.BE %> + <%-r.GA %> <% }) %> diff --git a/client/src/js/templates/samples/ligand.html b/client/src/js/templates/samples/ligand.html new file mode 100644 index 000000000..dea8d323e --- /dev/null +++ b/client/src/js/templates/samples/ligand.html @@ -0,0 +1,57 @@ +

    View Ligand

    + +

    This page shows details for the selected ligand and a list of samples which make use of it

    + +
    + + +
    + +
    +
    +
    diff --git a/client/src/js/templates/samples/ligandadd.html b/client/src/js/templates/samples/ligandadd.html new file mode 100644 index 000000000..be7f998fa --- /dev/null +++ b/client/src/js/templates/samples/ligandadd.html @@ -0,0 +1,62 @@ +

    New Ligand

    + +
    + +
    +
      + +
    • + + + + +
    • + +
    • + + +
    • + + +
    • + + +
    • + +
    • + + +
    • + +
    • + + +
    • + +
    • + + +
    • + +
    • + + PDB files can be added on the next page +
    • +
    + + +
    + +
    diff --git a/client/src/js/templates/samples/ligandlist.html b/client/src/js/templates/samples/ligandlist.html new file mode 100644 index 000000000..9760895cf --- /dev/null +++ b/client/src/js/templates/samples/ligandlist.html @@ -0,0 +1,8 @@ +

    <%-title%>s

    +

    This page lists all <%-title.toLowerCase()%>s associated with the currently selected proposal.

    + +
    + Add <%-title%> +
    + +
    diff --git a/client/src/js/templates/samples/sample.html b/client/src/js/templates/samples/sample.html index c91892776..16f742c79 100644 --- a/client/src/js/templates/samples/sample.html +++ b/client/src/js/templates/samples/sample.html @@ -40,6 +40,13 @@

    Sample Details

  • +
  • + Ligands +
    Add: +
    + +
  • +
  • Volume <%-VOLUME%> diff --git a/client/src/js/templates/shipment/singlesamplee.html b/client/src/js/templates/shipment/singlesamplee.html index df05c88a8..fe6ceae9b 100644 --- a/client/src/js/templates/shipment/singlesamplee.html +++ b/client/src/js/templates/shipment/singlesamplee.html @@ -20,6 +20,13 @@

    Sample

  • +
  • + Ligands +
    Add:
    +
    + + +
  • SMILES Code <%-SMILES%> <%-SYMBOL%>