3bcf86a0995e5a15ad5a2a8ff5533f48d5d43653 max Fri Sep 18 07:15:28 2026 -0700 hgTracks: tell the user what a session load just did, refs #38157 #Preview2 week - bugs introduced now will need a build patch to fix When a saved session is loaded it replaces the whole cart, and the view the user had before is gone without a word. hgTracks now shows a note on that page saying which session was opened, who saved it and when, the session description if it has one, and that the previous configuration cannot be brought back. Only on that one page: cart.c leaves a marker when a full (non-merge) session load happens and hgTracks takes it out again. Recommended track sets merge into the cart, so they get no note. The note uses a new notifBoxOnce() in utils.js, which has only a Close button: a one-page message does not need 'Don't show again'. Its .notifBoxOnce style is the card grey of the new Sessions page, and it sets no width, so unlike the other notifBoxes it cannot stick out of the window. Switch off with sessionLoadNotice=off in hg.conf. diff --git src/hg/hgTracks/hgTracks.c src/hg/hgTracks/hgTracks.c index b1a0dd632a5..3c6acb6abcb 100644 --- src/hg/hgTracks/hgTracks.c +++ src/hg/hgTracks/hgTracks.c @@ -41,30 +41,31 @@ #include "ctgPos.h" #include "bed.h" #include "bigBed.h" #include "bigWig.h" #include "bedCart.h" #include "udc.h" #include "customTrack.h" #include "trackHub.h" #include "hubConnect.h" #include "cytoBand.h" #include "ensFace.h" #include "pcrResult.h" #include "jsHelper.h" #include "mafTrack.h" #include "hgConfig.h" +#include "ra.h" #include "encode.h" #include "agpFrag.h" #include "imageV2.h" #include "suggest.h" #include "search.h" #include "errCatch.h" #include "iupac.h" #include "botDelay.h" #include "chromInfo.h" #include "extTools.h" #include "basicBed.h" #include "customFactory.h" #include "dupTrack.h" #include "genbank.h" #include "bigWarn.h" @@ -12112,30 +12113,136 @@ } else cartSetString(cart, "highlight", newHighlight); cartRemove(cart, "addHighlight"); } } void notify (char *msg, char *msgId) /* print a message into a hidden DIV tag, and call Javascript to move the DIV under the * tableHeaderForm element and un-hide it. Less obtrusive than a warn() message but still hard to miss. */ { jsInlineF("notifBoxSetup(\"hgTracks\", \"%s\", \"%s\");\n", msgId, msg); jsInlineF("notifBoxShow(\"hgTracks\", \"%s\");\n", msgId); } +void notifyOnce (char *msg, char *msgId) +/* Like notify(), but for a message that only makes sense on this page, e.g. after a session was + * loaded. It has no "Don't show again" button, as it is gone on the next page anyway. */ +{ +jsInlineF("notifBoxOnce(\"%s\", \"%s\");\n", msg, msgId); +} + +static char *jsSafe(char *text) +/* Return text ready to go into a double-quoted Javascript string that is assigned to innerHTML: + * html-encoded first, so no tag or quote from a session name or description can escape, then + * backslash-escaped for the Javascript literal. */ +{ +return javaScriptLiteralEncode(htmlEncode(text)); +} + +static void sessionNoticeText(struct dyString *dy, char *sessionName, char *sessionOwner) +/* Add the body of the note to dy: what was opened, by whom and when, and its description if it + * has one. The date and the description come from hgcentral; if the session is not there + * anymore (it can be deleted after it was loaded) just say what the cart remembers. */ +{ +char *created = NULL, *description = NULL; +struct sqlConnection *conn = hConnectCentral(); +char *encOwner = cgiEncodeFull(sessionOwner); +char *encName = cgiEncodeFull(sessionName); +char query[1024]; +sqlSafef(query, sizeof(query), + "SELECT firstUse, settings FROM %s WHERE userName='%s' AND sessionName='%s'", + namedSessionTable, encOwner, encName); +struct sqlResult *sr = sqlGetResult(conn, query); +char **row = sqlNextRow(sr); +if (row != NULL) + { + created = cloneString(row[0]); + if (isNotEmpty(row[1])) + { + struct hash *settings = raFromString(row[1]); + description = cloneString(hashFindVal(settings, "description")); + hashFree(&settings); + } + } +sqlFreeResult(&sr); +hDisconnectCentral(&conn); +freeMem(encOwner); +freeMem(encName); + +char *loggedIn = wikiLinkUserName(); +boolean isOwn = (loggedIn != NULL && sameString(loggedIn, sessionOwner)); +dyStringPrintf(dy, "You have opened the saved session %s, ", jsSafe(sessionName)); +if (isOwn) + dyStringPrintf(dy, "saved by yourself"); +else + dyStringPrintf(dy, "saved by user %s", jsSafe(sessionOwner)); +if (isNotEmpty(created)) + { + // firstUse is a mysql datetime, "2026-09-18 11:22:33", the day is enough here + char *day = firstWordInLine(created); + dyStringPrintf(dy, " on %s", jsSafe(day)); + } +dyStringPrintf(dy, ". "); + +if (isNotEmpty(description)) + { + // descriptions are stored with the line breaks escaped, and can be long + description = replaceChars(description, "\\n", " "); + description = replaceChars(description, "\\r", " "); + description = trimSpaces(description); + boolean truncated = FALSE; + if (strlen(description) > 300) + { + description[300] = 0; + truncated = TRUE; + } + dyStringPrintf(dy, "Session description: %s%s ", + jsSafe(description), (truncated ? "..." : "")); + } + +dyStringPrintf(dy, "The tracks, position and settings you had in the browser before have been " + "replaced by this session and cannot be brought back. If you want to keep a browser " + "configuration, save it under My Data > My Sessions before you open a session. "); +} + +static void showSessionLoadNotice() +/* If a saved session has just been loaded into this cart, put a note at the top of the page + * saying what was opened and that the previous view is gone. Only on that one page: the marker + * is taken out of the cart here, so the next page does not have it anymore. Recommended track + * sets are left alone: they merge into the cart instead of replacing it and have their own label + * next to the assembly name. */ +{ +if (trackImgOnly || !cartVarExists(cart, hgsSessionJustLoaded)) + return; +cartRemove(cart, hgsSessionJustLoaded); +if (!cfgOptionBooleanDefault("sessionLoadNotice", TRUE)) + return; + +char *sessionName = cartOptionalString(cart, hgsOtherUserSessionName); +char *sessionOwner = cartOptionalString(cart, hgsOtherUserName); +if (isEmpty(sessionName) || isEmpty(sessionOwner) || hasRecTrackSet(cart)) + return; + +struct dyString *dy = dyStringNew(1024); +sessionNoticeText(dy, sessionName, sessionOwner); +dyStringPrintf(dy, "This note is shown only once, it is gone on the next page."); +notifyOnce(dy->string, "sessionLoad"); +dyStringFree(&dy); +} + static boolean noPixVariableSetAndInteractive(void) { /* if the user is a humand and there is no pix variable in the cart, then run a * piece of javascript that determines the screen size and reloads the current * page, with the &pix=xxx variable added and return true. */ return (isEmpty(cartOptionalString(cart, "pix")) && !sameOk(cgiRequestMethod(NULL), "POST") && // page reload after POST would lose all vars !cartUsualBoolean(cart, "hgt.trackImgOnly", FALSE) && // skip if we're hgRenderTracks = no Javascript !cgiWasSpoofed() && // we're not run from the command line !sameOk(cgiUserAgent(), "rtracklayer")); // rtracklayer has no javascript, so skip, see https://github.com/lawremi/rtracklayer/issues/113 } extern boolean issueBotWarning; void doMiddle(struct cart *theCart) @@ -12475,30 +12582,33 @@ jsInline(dy->string); dyStringFree(&dy); if (measureTiming) measureTime("Time at end of doMiddle, next up cart write"); if (cartOptionalString(cart, "udcTimeout")) { char buf[5000]; safef(buf, sizeof(buf), "A hub refresh (udcTimeout) setting is active. " "This is useful when developing hubs, but it reduces " "performance. To clear the setting, click " "here.",cartSessionId(cart)); notify(buf, "udcTimeout"); } + +showSessionLoadNotice(); + #ifdef DEBUG if (cdsQueryCache != NULL) cacheTwoBitRangesPrintStats(cdsQueryCache, stderr); #endif /* DEBUG */ } void labelTrackAsFilteredNumber(struct track *tg, unsigned numOut) /* add text to track long label to indicate filter is active */ { if (numOut > 0) tg->longLabel = labelAsFilteredNumber(tg->longLabel, numOut); } void labelTrackAsFiltered(struct track *tg) /* add text to track long label to indicate filter is active */