9a2fac6063097ddaa8f0e0063cd9c26c7ce07af7 max Fri Sep 18 08:11:31 2026 -0700 hgSession: address UI feedback on the new Sessions page #Preview2 week - bugs introduced now will need a build patch to fix Wraps the intro text in

tags, renames "Update now" to "Overwrite now" with a visible info bubble, gives the name/description fields outside labels and narrower boxes with a real generated name as the name field's placeholder, moves "Back up custom tracks" out of the Advanced panel to its own bar, and splits Advanced into Load/Save columns. refs #38311 diff --git src/hg/js/hgSession.js src/hg/js/hgSession.js index fae84c8c90f..c5e18662372 100644 --- src/hg/js/hgSession.js +++ src/hg/js/hgSession.js @@ -1,22 +1,23 @@ // hgSession.js - the experimental client-rendered "My Sessions" page. // // An opt-in modern alternative to the classic server-rendered hgSession page, applying hgBlat's // facelift strategy (#37996): hgSession.c emits the session list and page config as an inline JSON // global (hgSessionData) into an empty #sessionApp container, and this file builds the UI - a -// save-current-view card, a searchable/sortable DataTable of saved sessions with inline -// Overwrite/Share/Edit/Delete, and an "Advanced" panel for loading and backup. +// save-current-view card, a custom-track backup button, a searchable/sortable DataTable of saved +// sessions with inline Overwrite/Share/Edit/Delete, and an "Advanced" panel split into a load +// column and a save column. // // The inline table actions POST to small JSON endpoints in hgSession.c (hgS_doDeleteJson, etc.) and // update the table in place. Navigation actions (load a session, load from URL/file, save to file, // reset) are ordinary form submits/links against the existing hgSession actions. // // Styling: shared house-style components in gbModern.css (.gbPill, .gbCard, .gbModal*, .gbTable, // .gbBanner, .gbSection), session-specific layout in hgSession.css. /* global $, hgSessionData, convertTitleTagsToMouseovers, titleTagToMouseover, addMouseover */ /* global htmlEncode, commify, gbShowTimingDialog */ // Cart action variables (must match the hgs* defines in hgSession.h; hgSessionPrefix is "hgS_"). var SESS_ACT = { save: 'hgS_doSaveSessionJson', rename: 'hgS_doRenameSessionJson', @@ -604,49 +605,62 @@ var loadFile = '

Load settings from a file' + '
' + sid + '' + '
'; var saveFile = '
Save settings to a file' + '
' + sid + '' + '' + '
'; - var backup = - '
Back up custom tracks' + - '
' + sid + - '
'; var other = '
Other' + '
'; + // Two columns - what you load from, and what you save to - rather than one undifferentiated + // list; "Other" (reset) belongs to neither, so it spans both underneath. return '
' + '
▸' + 'Advanced — load another user’s session, load from a URL or file, ' + 'save to a file, reset the browser
' + '
'; + '
Load
' + + loadUser + loadUrl + loadFile + '
' + + '
Save
' + saveFile + '
' + + '
' + other + '
' + + ''; +} + +function sessBackupHtml(C) { + // Backing up custom tracks used to be buried in the Advanced panel; it now sits just under the + // "Overwrite now" bar so it is visible without opening Advanced. + if (!C.loggedIn) { return ''; } + var sid = ''; + return '
' + + 'Custom tracks aren’t part of a saved session link.' + + '
' + sid + + '
'; } // ---- build the whole page ----------------------------------------------- function sessAccountHtml(C) { // The signed-in account line (Signed in as X · Sign out · Change password) now lives in the // top-right menu bar, so it is intentionally not rendered here. Kept commented out so QA can // add it back if wanted: /* if (C.loggedIn) { var s = 'Signed in as ' + sessEnc(C.userName) + ''; if (C.logoutUrl) { s += ' · Sign out'; } if (C.changePasswordUrl) { s += ' · Change password'; } @@ -660,55 +674,62 @@ return ''; } function sessSaveCardHtml(C) { if (!C.loggedIn) { return ''; } // Assembly and position separated by a colon (e.g. "hg38: chr7:1-1,000"); assembly is already // the accession for hubs (trackHubSkipHubName on the server). var loc = ''; if (C.db && C.position) { loc = sessEnc(C.db) + ': ' + sessEnc(C.position); } else if (C.db) { loc = sessEnc(C.db); } else if (C.position) { loc = sessEnc(C.position); } if (loc && C.trackCount) { loc += ', ' + sessNum(C.trackCount) + ' track' + (C.trackCount === 1 ? '' : 's') + ' shown'; } var what = loc ? '' + loc + '' : ''; + // Ghost text for the name field is a real generated name, not an explanation of the feature - + // the "Session name" label above the box already says what it is. + var randName = sessEnc(sessRandomShareName()); return '
' + '
' + 'Save the current view as a stable session link' + what + '
' + '
' + + '
Session name' + '' + + 'placeholder="' + randName + '">
' + '' + '' + '
' + + '
Description' + '' + + 'placeholder="Optional — shown on hover and in the Public Sessions gallery">
' + '
'; } function sessRecentHtml(recent) { // A quick shortcut to re-save the session the user most recently saved, keeping its name and - // description. Hidden when there are no saved sessions. + // description. Hidden when there are no saved sessions. The button's own mouseover is easy to + // miss, so a visible (i) bubble carries the same text. if (!recent) { return ''; } + var tip = 'Overwrite this session with the currently active view; keeps the session name and ' + + 'description identical'; return '
Most recently saved session: ' + sessEnc(recent.name) + ' (' + sessEnc(recent.lastUse) + ') ' + - '
'; + ' ⓘ'; } function sessScrollToTable() { // Arriving from another server's Server column or "Go to" button, on a #sessions link. The // browser cannot act on that anchor by itself: the table is built here, long after it has // given up looking for the element, so scroll to it once it exists. if (window.location.hash !== '#sessions') { return; } var el = document.getElementById('sessions'); if (el) { el.scrollIntoView(); } } function sessMirrorLinks(mirrors) { // "genome-euro", or "genome-euro or genome-asia", each linking to that server's own hgSession. var links = mirrors.map(function(m) { return 'Your saved sessions' + '
'; } function sessionBuild() { sessData = hgSessionData; // When the page was loaded with &measureTiming=1 the C side attaches sessData.timing; time the // client-side render too so the dialog shows the full server+client picture. var tBuildStart = (sessData.timing && window.performance) ? performance.now() : 0; var C = sessData.config; var app = $('#sessionApp'); - var intro = '
' + sessAccountHtml(C) + - (sessAccountHtml(C) ? '
' : '') + - 'A session is a stable link to a Genome Browser view that you can save, load later, share ' + - 'or copy into a manuscript. See the ' + + var acctHtml = sessAccountHtml(C); + var intro = '
' + (acctHtml ? '

' + acctHtml + '

' : '') + + '

A session is a stable link to a Genome Browser view that you can save, load later, ' + + 'share or copy into a manuscript. See the ' + 'Sessions User’s Guide and the ' + - 'Session Gallery.

'; + 'Session Gallery.

'; // Every row carries the server it lives on; at this point they are all this server's own. // The sessions merged in later from the other nodes get theirs in sessMergeMirrors(). (sessData.sessions || []).forEach(function(s, i) { s.uid = 'L' + i; s.server = C.thisServer ? C.thisServer.label : ''; s.serverTitle = C.thisServer ? C.thisServer.title : ''; s.serverUrl = ''; // this is that server, so the Server cell here is text, not a link }); - // The session most recently saved/overwritten (max lastUse), for the one-click "Update now". - // Only this server's own sessions: "Update now" saves through this server's cart. + // The session most recently saved/overwritten (max lastUse), for the one-click "Overwrite now". + // Only this server's own sessions: "Overwrite now" saves through this server's cart. var recent = null; (sessData.sessions || []).forEach(function(s) { if (s.remote) { return; } if (!recent || s.lastUseEpoch > recent.lastUseEpoch) { recent = s; } }); app.html( intro + '
' + sessRecentHtml(recent) + + sessBackupHtml(C) + sessSaveCardHtml(C) + sessAdvancedHtml(C) + sessTableHtml(C) ); // Save current view. Enter in either the name or the description field saves. $('#sessSaveBtn').on('click', sessDoSave); $('#sessSaveName, #sessSaveDesc').on('keydown', function(ev) { if (ev.key === 'Enter') { ev.preventDefault(); sessDoSave(); } }); - // One-click update of the most recently saved session. + // One-click overwrite of the most recently saved session. if (recent) { $('#sessUpdateNow').on('click', function() { sessConfirm({ - title: 'Update session', + title: 'Overwrite session', bodyHtml: 'Overwrite ' + sessEnc(recent.name) + ' with the view you are ' + 'looking at now? The session name and description stay the same.', - okLabel: 'Update now', + okLabel: 'Overwrite now', onOk: function() { sessDoOverwrite(recent); } }); }); } // Advanced toggle. $('#sessAdvHead').on('click', function() { var body = document.getElementById('sessAdvBody'); var open = body.style.display !== 'none'; body.style.display = open ? 'none' : 'grid'; $(this).find('.caret').html(open ? '▸' : '▾'); }); // Session table, then the sessions our other servers hold, merged in as they arrive. if (C.loggedIn) { sessBuildTable(); sessScrollToTable(); sessMergeMirrors(); }