9f8d33c8b2b6bc61f6d02d781c4e02836f7099f9 max Fri Aug 21 02:05:42 2026 -0700 hgSession: new opt-in JavaScript "My Sessions" page; share gbModern.css with hgBlat. refs #38157 Applies the hgBlat facelift strategy (#37996) to hgSession: an opt-in, client-rendered "My Sessions" page gated by the sessionNewPage / sessionNewPageBanner hg.conf flags (mirroring blatNewForm / blatNewFormBanner), with a banner linking between the classic and new pages so neither is a one-way door. sessionNewPage also flips the site default. hgSession.c stays the data/action backend: it emits the session list and page config as an inline JSON global (hgSessionData) into an empty #sessionApp container, and the new hgSession.js builds the UI - a save-current-view card (name + optional description + "only I can load it"; empty name saves under a random share_ name), a "most recently saved session" one-click Update, a searchable/sortable/paged DataTable of sessions (assembly + position, created with last-used on hover, views, a lock icon on private sessions), inline Share (copy link / email / gallery), Edit (rename + description + private), Overwrite and Delete, and a bulk Select -> Delete-all-selected mode. The mutating actions POST to new JSON endpoints (hgS_doDeleteJson / doShareJson / doGalleryJson / doOverwriteJson / doDescribeJson) that run the same SQL as the classic full-page handlers and return JSON, so the table updates in place; loads, file up/downloads and custom-track backup stay as ordinary form submits/links. The Advanced panel keeps feature parity with the classic page (load another user's session, load from URL/file, save to file, back up custom tracks, reset), minus the login/ change-password links that now live in the top menu. Shared UCSC house-style components (design tokens, .gbPill, .gbCard, .gbStrip, .gbSection, .gbShareBox, .gbBanner, the .gbModal* dialog and a .gbTable) are factored into a new gbModern.css. hgBlat is migrated onto it: its generic .blat* classes are renamed to the shared .gb* names in hgBlat.css / hgBlat.js and the #blatResults / #blatFormBox containers get class="gbApp"; verified pixel-clean against the previous search form and results pages, including the rename modal. hgSession.css holds only session-specific layout. diff --git src/hg/hgBlat/hgBlat.c src/hg/hgBlat/hgBlat.c index 972bedda3f9..82d35e0a512 100644 --- src/hg/hgBlat/hgBlat.c +++ src/hg/hgBlat/hgBlat.c @@ -607,30 +607,31 @@ return locusConn; } static void printBlatResultsApp(struct psl *pslList, char *database, char *organism, char *browserUrl, char *hgcUrl, char *pslName, char *faName, char *customText, char *uiState, char *unhideTrack, struct sqlConnection *locusConn, struct subText *subList) /* "Table" output mode: emit the hit data as an inline JSON object plus an empty container, and let * hgBlat.js build the UI (summary strip, DataTable with identity/coverage bars, detail panel). * All presentation lives in hgBlat.js; this function only assembles data. * On a fresh search the per-hit "Alignment details" links go to hgc's htcUserAli (which reads the * ephemeral trash .pslx/.fa); on a shared-link reopen (pslName NULL) there is no trash, so they go * to htcBlatAlign instead, which rebuilds each alignment from the durable bigPsl custom track. */ { struct psl *psl; jsIncludeDataTablesLibs(); +webIncludeResourceFile("gbModern.css"); // shared house-style components (.gbPill, .gbCard, ...) webIncludeResourceFile("hgBlat.css"); jsIncludeFile("hgBlat.js", NULL); struct jsonWrite *jw = jsonWriteNew(); jsonWriteObjectStart(jw, NULL); jsonWriteObjectStart(jw, "config"); /* For assembly/GenArk hubs the internal names carry a "hub_NNN_" prefix; drop it so the Assembly * field reads cleanly (and doesn't show the prefix twice), matching the BLAT Results page title. */ jsonWriteString(jw, "db", trackHubSkipHubName(database)); jsonWriteString(jw, "organism", trackHubSkipHubName(organism)); jsonWriteString(jw, "queryName", pslList->qName); jsonWriteNumber(jw, "querySize", pslList->qSize); jsonWriteNumber(jw, "hitCount", slCount(pslList)); jsonWriteBoolean(jw, "multiQuery", pslListMultiQuery(pslList)); @@ -780,31 +781,31 @@ for (i = 0; i < n; ++i) jsonWriteString(jw, NULL, words[i]); freeMem(dupe); jsonWriteListEnd(jw); } } sqlFreeResult(&sr); } jsonWriteObjectEnd(jw); freeMem(inTabUrl); freeMem(newTabUrl); } jsonWriteListEnd(jw); // hits jsonWriteObjectEnd(jw); // root -printf("<div id='blatResults'></div>\n"); +printf("<div id='blatResults' class='gbApp'></div>\n"); jsInlineF("var hgBlatData = %s;\n", jw->dy->string); jsonWriteFree(&jw); } static void printBlatBannerStyle() /* Emit the .blatBanner rule for the classic, C-rendered pages. Those don't load hgBlat.js, which * is where the new pages get this class from, so define the same thing here; keep the values in * sync with the .blatBanner rule in hgBlat.js. Only the first call emits anything, so a page may * carry more than one banner without repeating the stylesheet. */ { static boolean styleDone = FALSE; if (styleDone) return; styleDone = TRUE; printf("<style>" @@ -2697,33 +2698,34 @@ * updated by setupGenomeSearchBar itself, and blatFormSetDb() updates the hidden db field and the * db-bearing sidebar links in place. So no submit() - picking a genome no longer reloads. */ /* setupGenomeSelector is the combobox version of setupGenomeSearchBar - same config, caret inside * the field, common assemblies as pills underneath. Swapping the two function names is the whole * change, so the other CGIs on the old widget can move over one at a time. */ jsInlineF( "setupGenomeSelector({\n" " inputId: '%s',\n" " onSelect: function(item) {\n" " blatFormSetDb(dbFromRecentItem(item));\n" " }\n" "});\n" , searchBarId ); -printf("<div id='blatFormBox'></div>\n"); +printf("<div id='blatFormBox' class='gbApp'></div>\n"); printf("</form>\n"); +webIncludeResourceFile("gbModern.css"); // shared house-style components (.gbPill, .gbCard, ...) webIncludeResourceFile("hgBlat.css"); jsIncludeFile("hgBlat.js", NULL); struct jsonWrite *jw = jsonWriteNew(); jsonWriteObjectStart(jw, NULL); jsonWriteString(jw, "db", trackHubSkipHubName(db)); jsonWriteString(jw, "organism", trackHubSkipHubName(organism)); /* Shown as the search bar's own contents rather than in a separate "Current genome:" line: the bar * is wide enough for the full assembly description, and setupGenomeSearchBar keeps it up to date as * the user picks a different one. */ jsonWriteString(jw, "dbLabel", getCurrentGenomeLabel(db)); jsonWriteString(jw, "userSeq", cartUsualString(cart, "userSeq", "")); blatFormJsonList(jw, "types", typeList, ArraySize(typeList)); jsonWriteString(jw, "type", cartUsualString(cart, "type", typeList[0])); /* Sort and output have no dropdown on this page - hgBlat.js submits them as hidden fields, so the