47ea57080b515e5dad5f658c58feb8944a7e7d61 chmalee Thu Jan 29 15:30:26 2026 -0800 Replace clade/assembly dropdowns with a search bar on most CGIs. Add a recents list to hgGateway and to the species bar and to the 'Genomes' dropdown menu. Track recently selected species in localStorage. Add toGenome and fromGenome arguemnts to hubApi/liftOver in order to find appropriate liftover assemblies, refs #36232 diff --git src/hg/js/model/lib/CladeOrgDbMixin.js src/hg/js/model/lib/CladeOrgDbMixin.js index 8b5500cc7ab..179b7294099 100644 --- src/hg/js/model/lib/CladeOrgDbMixin.js +++ src/hg/js/model/lib/CladeOrgDbMixin.js @@ -1,173 +1,151 @@ var CladeOrgDbMixin = function(myPath) { // This is a model mixin that manages UI state and server interaction for // the clade, org and db (aka group, genome, assembly) menus. 'use strict'; /*jshint validthis: true */ var myCartVar = 'cladeOrgDb'; function findNodeByValue(nodeList, selValue) { // Return the member of nodeList whose value field matches selValue, if any. return nodeList.find(function(node) { return node.get('value') === selValue; }); } function findNodeByPath(nodeList, nodePath) { // Return the descendant of Immutable nodeList addressed by plain array nodePath, // or null if not found. var selNode = null; if (nodeList && nodePath.length > 0) { selNode = findNodeByValue(nodeList, nodePath[0]); if (selNode && nodePath.length > 1) { // Not at end of path -- recurse on children: selNode = findNodeByPath(selNode.get('children'), nodePath.slice(1)); } } return selNode; } function myGetIn(mutState, varName) { // getIn myPath+varName return mutState.getIn(myPath.concat(varName)); } function mySetIn(mutState, varName, newValue) { // setIn myPath+varName mutState.setIn(myPath.concat(varName), newValue); } function valLabelsFromNodeList(nodeList) { // Given an Immutable.List of cladeOrgDb tree nodes, return an Immutable value+label list // for menu options. return nodeList.map(function(node) { return Immutable.Map({ value: node.get('value'), label: node.get('label') }); }); } function generateMenuOptions(mutState) { // Use mutState[myPath] to make clade, organism and db menu data structures. // If clade/org/db are inconsistent with cladeOrgDb tree, reset them. var cladeOrgDb = myGetIn(mutState, 'cladeOrgDb'); if (! cladeOrgDb) { // Can't make menus if we're still waiting for cladeOrgDb tree. return; } // If we don't already have a selected clade, default to first clade: var selClade = myGetIn(mutState, 'clade'); var selCladeNode = findNodeByValue(cladeOrgDb, selClade); if (! selCladeNode) { selCladeNode = cladeOrgDb.first(); mySetIn(mutState, 'clade', selCladeNode.get('value')); } // If we don't already have a selected org or db, use the parent node's default: var orgList = selCladeNode.get('children'); var selOrg = myGetIn(mutState, 'org'); var selOrgNode = findNodeByValue(orgList, selOrg); if (! selOrgNode) { selOrgNode = findNodeByValue(orgList, selCladeNode.get('default')); mySetIn(mutState, 'org', selOrgNode.get('value')); } var dbList = selOrgNode.get('children'); var selDb = myGetIn(mutState, 'db'); var selDbNode = findNodeByValue(dbList, selDb); if (! selDbNode) { selDbNode = findNodeByValue(dbList, selOrgNode.get('default')); mySetIn(mutState, 'db', selDbNode.get('value')); } mySetIn(mutState, 'cladeOptions', valLabelsFromNodeList(cladeOrgDb)); mySetIn(mutState, 'orgOptions', valLabelsFromNodeList(orgList)); mySetIn(mutState, 'dbOptions', valLabelsFromNodeList(dbList)); } // Server event handler function codMergeServerResponse(mutState, cartVar, newValue) { // Update internal state and regenerate menu options. if (cartVar === myCartVar) { _.forEach(newValue, function(value, key) { mySetIn(mutState, key, Immutable.fromJS(value)); }); generateMenuOptions(mutState); } else { this.error('CladeOrgDbMixin: expected cart var "' + myCartVar + '" but got "' + cartVar + '"'); } } // UI event handler function changeCladeOrgDb(mutState, uiPath, newValue) { // uiPath is myPath + either 'clade', 'org' or 'db'. // User changed clade, org or db; if clade or org, figure out the new lower-level // selections. Update state, tell the server, and call this.onChangeDb if present. - var cladeOrgDb = myGetIn(mutState, 'cladeOrgDb'); - var which = _.last(uiPath); var oldDb = myGetIn(mutState, 'db'); - var clade, org, db; - var cladeNode, orgNode, dbNode; // Update the changed item in mutState, then handle possible side-effects on lower levels: mutState.setIn(uiPath, newValue); - clade = myGetIn(mutState, 'clade'); - org = myGetIn(mutState, 'org'); - db = myGetIn(mutState, 'db'); - if (which === 'clade') { - cladeNode = findNodeByPath(cladeOrgDb, [clade]); - org = cladeNode.get('default'); - mySetIn(mutState, 'org', org); - } - if (which === 'clade' || which === 'org') { - orgNode = findNodeByPath(cladeOrgDb, [clade, org]); - db = orgNode.get('default'); - mySetIn(mutState, 'db', db); - } - generateMenuOptions(mutState); + var db = myGetIn(mutState, 'db'); if (db !== oldDb) { - dbNode = findNodeByPath(cladeOrgDb, [clade, org, db]); - this.cartSend({ cgiVar: { clade: clade, - org: org, - db: db, - position: dbNode.get('defaultPos') - } }); + this.cartSend({cgiVar: {db: db}}); if (this.onChangeDb) { this.onChangeDb(mutState); } } } function getDbNode(mutState) { // Return the currently selected db's node. var cladeOrgDb = myGetIn(mutState, 'cladeOrgDb'); var clade = myGetIn(mutState, 'clade'); var org = myGetIn(mutState, 'org'); var db = myGetIn(mutState, 'db'); return findNodeByPath(cladeOrgDb, [clade, org, db]); } // Convenience methods for use outside this mixin: function getDb(mutState) { // Return the currently selected db return myGetIn(mutState, 'db'); } function getDefaultPos(mutState) { // Get the default position of the currently selected db. var dbNode = getDbNode(mutState); return dbNode ? dbNode.get('defaultPos') : null; } function initialize() { this.registerCartVarHandler(myCartVar, codMergeServerResponse); this.registerUiHandler(myPath, changeCladeOrgDb); // Install convenience methods for use outside this mixin: this.getDb = getDb; this.getDefaultPos = getDefaultPos; } // Mixin object with initialize return { initialize: initialize }; }; // Without this, jshint complains that CladeOrgDbMixin is not used. Module system would help. CladeOrgDbMixin = CladeOrgDbMixin;