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 =
'
';
+ // 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
' +
+ '';
+}
+
+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.' +
+ '
';
}
// ---- 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 + '
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 '
';
+ ' ⓘ';
}
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.
';
// 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(); }