87dbdc612e3d8aeb3a7c564dbdcea1adc77b7e67
jnavarr5
  Tue Apr 29 14:40:44 2025 -0700
Staging the Table Browser tutorial. Adding <DIV> sections to the main table browser page for the tutorial. refs #35126

diff --git src/hg/hgTables/mainPage.c src/hg/hgTables/mainPage.c
index 0fa0f9cddda..4ea5fd10bc8 100644
--- src/hg/hgTables/mainPage.c
+++ src/hg/hgTables/mainPage.c
@@ -446,88 +446,101 @@
 
 jsInlineF("function checkForCsv(event) {\n"
     "var outputType = document.getElementById('outputTypeDropdown').value;\n"
     "if (outputType === 'primaryTable' || outputType === 'selectedFields') {\n"
     "   document.getElementById('%s').parentElement.style.display='';\n"
     "   document.getElementById('excelOutNote').style.display='';\n"
     "} else {\n"
     "   document.getElementById('%s').parentElement.style.display='none';\n"
     "   document.getElementById('excelOutNote').style.display='none';\n"
     "}\n"
     "}\n"
     "$(document).ready(checkForCsv);\n"
     , hgtaOutSep, hgtaOutSep);
 jsAddEventForId("change", "outputTypeDropdown", "checkForCsv");
 
+/*
+ * Code to add in the interactive tutorial*/
+if (cfgOptionBooleanDefault("showTutorial", TRUE))
+    {
+    puts("<script src=\"https://cdn.jsdelivr.net/npm/shepherd.js@11.0.1/dist/js/shepherd.min.js\"></script>");
+    puts("<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/shepherd.js@11.0.1/dist/css/shepherd.css\"/>");
+    jsIncludeFile("tableBrowserTutorial.js",NULL);
+    if (sameOk(cgiOptionalString("startTutorial"), "true"))
+        {
+        jsInline("tableBrowserTour.start();");
+        }
+    }
+
 if (!cfgOptionBooleanDefault("hgta.disableSendOutput", FALSE))
     {
     hPrintf(" Send output to ");
     struct dyString *dy = dyStringNew(256);
     dyStringAppend(dy, 
 	"document.getElementById('checkboxGreat').checked=false;");
     if (isGenomeSpaceEnabled())
 	dyStringAppend(dy, 
 	      	  "document.getElementById('checkboxGenomeSpace').checked=false;");
     dyStringAppend(dy, 
 	      	  "return true;");
     cgiMakeCheckBoxWithId("sendToGalaxy", doGalaxy(), "checkboxGalaxy");
     jsOnEventById("click", "checkboxGalaxy", dy->string);
     hPrintf("<A HREF=\""GALAXY_URL_BASE"\" target=_BLANK>Galaxy</A>\n");
     nbSpaces(2);
     cgiMakeCheckBoxWithId("sendToGreat", doGreat(), "checkboxGreat");
     jsOnEventById("click", "checkboxGreat", "return onSelectGreat();");
     hPrintf(" <A HREF=\"http://great.stanford.edu\" target=_BLANK>GREAT</A>");
     if (isGenomeSpaceEnabled())
 	{
 	nbSpaces(2);
 	cgiMakeCheckBoxWithId("sendToGenomeSpace", doGenomeSpace(), "checkboxGenomeSpace");
 	jsOnEventById("click", "checkboxGenomeSpace", 
 	    "document.getElementById('checkboxGreat').checked=false;"
 	    "document.getElementById('checkboxGalaxy').checked=false; return true;");
 	hPrintf(" <A HREF=\"http://www.genomespace.org\" target=_BLANK>GenomeSpace</A>");
 	}
     }
 
-hPrintf("</TD></TR>\n");
+hPrintf("</DIV></TD></TR>\n");
 }
 
 struct outputType otAllFields = { NULL,	outPrimaryTable,"All fields from selected table", };
 struct outputType otSelected =  { NULL, outSelectedFields,
                                   "Selected fields from primary and related tables",  };
 struct outputType otSequence =  { NULL, outSequence,    "Sequence", };
 struct outputType otPal =       { NULL, outPalOptions,
                                   "CDS FASTA alignment from multiple alignment", };
 struct outputType otGff =         { NULL, outGff,         "GTF - gene transfer format &#40;limited&#41;", };
 struct outputType otBed =         { NULL, outBed,         "BED - browser extensible data", };
 struct outputType otCustomTrack = { NULL, outCustomTrack, "Custom track", };
 struct outputType otHyperlinks =  { NULL, outHyperlinks,  "Hyperlinks to Genome Browser", };
 struct outputType otWigData =     { NULL, outWigData,     "Data points", };
 struct outputType otWigBed =      { NULL, outWigBed,      "Bed format", };
 struct outputType otMaf =         { NULL, outMaf,         "MAF - multiple alignment format", };
 struct outputType otChromGraphData =      { NULL, outChromGraphData,       "Data points", };
 struct outputType otMicroarrayNames =     { NULL, outMicroarrayNames,     "Microarray names", };
 struct outputType otMicroarrayGroupings = { NULL, outMicroarrayGroupings, "Microarray groupings", };
 
 static void showOutputTypeRow(boolean isWig, boolean isBedGr,
     boolean isPositional, boolean isMaf, boolean isChromGraphCt,
     boolean isPal, boolean isMicroarray, boolean isHalSnake)
 /* Print output line. */
 {
 struct outputType *otList = NULL, *otDefault = NULL;
 boolean bedifiedOnly = (anySubtrackMerge(database, curTable) || anyIntersection());
 
-hPrintf("<TR><TD><B>Output format:</B>\n");
+hPrintf("<TR><TD><DIV ID=\"output-select\"><B>Output format:</B>\n");
 
 if (isBedGr)
     {
     if (! bedifiedOnly)
 	{
 	slAddTail(&otList, &otAllFields);
 	slAddTail(&otList, &otSelected);
 	}
     slAddTail(&otList, &otWigData);
     slAddTail(&otList, &otWigBed);
     slAddTail(&otList, &otCustomTrack);
     slAddTail(&otList, &otHyperlinks);
     }
 else if (isWig)
     {
@@ -662,89 +675,89 @@
         isChromGraphCt = FALSE, isPal = FALSE, isArray = FALSE, isBam = FALSE, isVcf = FALSE, isHalSnake = FALSE,
         isLongTabix = FALSE, isHic = FALSE;
 boolean gotClade = hGotClade();
 struct hTableInfo *hti = NULL;
 
 hPrintf("<TABLE BORDER=0>\n");
 
 int stepNumber = 1;
 printStep(stepNumber++);
 
 /* Print clade, genome and assembly line. */
     {
 
     if (gotClade)
         {
-        hPrintf("<TR><TD><B>Clade:</B>\n");
+        hPrintf("<TR><TD><DIV ID=\"genome-select\"><B>Clade:</B>\n");
         printCladeListHtml(hGenome(database), "change", onChangeClade());
         nbSpaces(3);
         hPrintf("<B>Genome:</B>\n");
         printGenomeListForCladeHtml(database, "change", onChangeOrg());
         }
     else
         {
-        hPrintf("<TR><TD><B>Genome:</B>\n");
+        hPrintf("<TR><TD><DIV ID=\"genome-select\"><B>Genome:</B>\n");
         printGenomeListHtml(database, "change", onChangeOrg());
         }
     nbSpaces(3);
     hPrintf("<B>Assembly:</B>\n");
     printAssemblyListHtml(database, "change", onChangeDb());
-    hPrintf("</TD></TR>\n");
+    hPrintf("</DIV></TD></TR>\n");
     }
 
 /* Print group and track line. */
     {
-    hPrintf("<TR><TD>");
+    hPrintf("<TR><TD><DIV ID=\"track-select\">");
     selGroup = showGroupField(hgtaGroup, "change", onChangeGroupOrTrack(), conn, hAllowAllTables());
     nbSpaces(3);
     curTrack = showTrackField(selGroup, hgtaTrack, "change", onChangeGroupOrTrack(), FALSE);
     nbSpaces(3);
-    hPrintf("</TD></TR>\n");
+    hPrintf("</DIV></TD></TR>\n");
     }
 
 /* Print table line. */
     {
-    hPrintf("<TR><TD>");
+    hPrintf("<TR><TD><DIV ID=\"table-select\">");
     curTable = showTableField(curTrack, hgtaTable, TRUE);
     if (isHubTrack(curTable) || hashFindVal(fullTableToTdbHash, curTable) != NULL)  /* In same database */
         {
         hti = getHti(database, curTable, conn);
         isPositional = htiIsPositional(hti);
         }
     isLongTabix = isLongTabixTable( curTable);
     isBam = isBamTable(curTable);
     isHic = isHicTable(curTable);
     isVcf = isVcfTable(curTable, NULL);
     isWig = isWiggle(database, curTable);
     if (isBigWigTable(curTable))
         {
         isPositional = TRUE;
         isWig = TRUE;
         }
     isHalSnake = isHalTable( curTable);
     isMaf = isMafTable(database, curTrack, curTable);
     isBedGr = isBedGraph(curTable);
     isArray = isMicroarray(curTrack, curTable);
     struct trackDb *tdb = findTdbForTable(database, curTrack, curTable, ctLookupName);
     isPal = isPalCompatible(conn, tdb, curTable);
     nbSpaces(1);
     if (isCustomTrack(curTable))
         {
         isChromGraphCt = isChromGraph(tdb);
         }
     cgiMakeButton(hgtaDoSchema, "Data format description");
-    hPrintf("</TD></TR>\n");
+    hPrintf("</DIV></TD></TR>\n");
     }
 
 if (curTrack == NULL)
     {
     struct trackDb *tdb = hTrackDbForTrack(database, curTable);
     struct trackDb *cTdb = hCompositeTrackDbForSubtrack(database, tdb);
     if (cTdb)
         curTrack = cTdb;
     else
         curTrack = tdb;
     isMaf = isMafTable(database, curTrack, curTable);
     }
 
 /* Table-specific options */
 if (isHicTable(curTable))
@@ -763,31 +776,31 @@
 char *regionType;
 if (cartVarExists(cart, "hgFind.matches")) // coming back from a search
     regionType = cartUsualString(cart, hgtaRegionType, hgtaRegionTypeRange);
 else
     regionType = cartUsualString(cart, hgtaRegionType, hgtaRegionTypeGenome);
 
 char *range = cartUsualString(cart, hgtaRange, "");
 
 if (isPositional)
     {
     boolean doEncode = FALSE; 
 
     if (!trackHubDatabase(database))
 	doEncode = sqlTableExists(conn, "encodeRegions");
 
-    hPrintf("<TR><TD><B>Region:</B>\n");
+    hPrintf("<TR><TD><DIV ID=\"position-controls\"><B>Region:</B>\n");
 
     /* If regionType not allowed force it to "genome". */
     if ((sameString(regionType, hgtaRegionTypeUserRegions) &&
 	 userRegionsFileName() == NULL) ||
 	(sameString(regionType, hgtaRegionTypeEncode) && !doEncode))
 	regionType = hgtaRegionTypeGenome;
     // Is "genome" is not allowed because of tdb 'tableBrowser noGenome'?
     boolean disableGenome = ((curTrack && cartTrackDbIsNoGenome(database, curTrack->table)) ||
                              (curTable && cartTrackDbIsNoGenome(database, curTable)));
     // If "genome" is selected but not allowed, force it to "range":
     if (sameString(regionType, hgtaRegionTypeGenome) && disableGenome)
         regionType = hgtaRegionTypeRange;
     jsTrackingVar("regionType", regionType);
     if (disableGenome)
         {
@@ -811,60 +824,61 @@
     	hgtaRange, hgtaRange, range);
     jsOnEventById("focus", hgtaRange, 
 	jsRadioUpdate(hgtaRegionType, "regionType", "range"));
     cgiMakeButton(hgtaDoLookupPosition, "Lookup");
     hPrintf("&nbsp;");
     if (userRegionsFileName() != NULL)
 	{
 	makeRegionButton(hgtaRegionTypeUserRegions, regionType);
 	hPrintf("&nbsp;Defined regions&nbsp;");
 	cgiMakeButton(hgtaDoSetUserRegions, "Change");
 	hPrintf("&nbsp;");
 	cgiMakeButton(hgtaDoClearUserRegions, "Clear");
 	}
     else
 	cgiMakeButton(hgtaDoSetUserRegions, "Define regions");
+    hPrintf("</DIV>");
     hPrintf("</TD></TR>\n");
 
     if (disableGenome) { // no need to check curTrack for NULL, disableGenome can only be set if curTable is set
         hPrintf("<tr><td>");
         printNoGenomeWarning(curTrack);
         hPrintf("</td></tr>");
     }
 
     }
 else
     {
     /* Need to put at least stubs of cgi variables in for JavaScript to work. */
     jsTrackingVar("regionType", regionType);
     cgiMakeHiddenVar(hgtaRange, range);
     cgiMakeHiddenVar(hgtaRegionType, regionType);
     }
 
 /* Select identifiers line (if applicable). */
 if (!isWig && getIdField(database, curTrack, curTable, hti) != NULL)
     {
-    hPrintf("<TR><TD><B>Identifiers (names/accessions):</B>\n");
+    hPrintf("<TR><TD><DIV ID=\"identifiers-controls\"><B>Identifiers (names/accessions):</B>\n");
     cgiMakeButton(hgtaDoPasteIdentifiers, "Paste list");
     hPrintf(" ");
     cgiMakeButton(hgtaDoUploadIdentifiers, "Upload list");
     if (identifierFileName() != NULL)
         {
 	hPrintf("&nbsp;");
 	cgiMakeButton(hgtaDoClearIdentifiers, "Clear list");
 	}
-    hPrintf("</TD></TR>\n");
+    hPrintf("</DIV></TD></TR>\n");
     }
 }
 
 /*   button for option page here (median/log-ratio, etc)  */
 
 printStep(stepNumber++);
 
 /* Filter line. */
 {
 hPrintf("<TR><TD><B>Filter:</B>\n");
 if (anyFilter())
     {
     cgiMakeButton(hgtaDoFilterPage, "Edit");
     hPrintf(" ");
     cgiMakeButton(hgtaDoClearFilter, "Clear");
@@ -963,61 +977,61 @@
 
     hPrintf("</TD></TR>\n");
     }
 
 /* Print output type line. */
 
 printStep(stepNumber++);
 showOutputTypeRow(isWig, isBedGr, isPositional, isMaf, isChromGraphCt, isPal, isArray, isHalSnake);
 
 /* Print output destination line. */
     {
     char *compressType = cartUsualString(cart, hgtaCompressType, textOutCompressNone);
     char *fieldSep = cartUsualString(cart, hgtaOutSep, outTab);
     char *fileName = cartUsualString(cart, hgtaOutFileName, "");
     hPrintf("<TR><TD>\n");
-    hPrintf("<B>Output filename:</B>&nbsp;");
+    hPrintf("<DIV ID=\"filename-select\"><B>Output filename:</B>&nbsp;");
     cgiMakeTextVar(hgtaOutFileName, fileName, 29);
-    hPrintf("&nbsp;(<span id='excelOutNote' style='display:none'>add .csv extension if opening in Excel, </span>leave blank to keep output in browser)</TD></TR>\n");
+    hPrintf("&nbsp;(<span id='excelOutNote' style='display:none'>add .csv extension if opening in Excel, </span>leave blank to keep output in browser)</DIV></TD></TR>\n");
     hPrintf("<TR><TD>\n");
     hPrintf("<B>Output field separator:&nbsp;</B>");
 
     // tab or csv output
     cgiMakeRadioButton(hgtaOutSep, outTab, sameWord(outTab, fieldSep));
     hPrintf("&nbsp;tsv (tab-separated)&nbsp&nbsp;");
 
     cgiMakeRadioButton(hgtaOutSep, outCsv, sameWord(outCsv, fieldSep));
     hPrintf("&nbsp;csv (for excel)&nbsp;");
 
     hPrintf("</TD></TR>\n");
     hPrintf("<TR><TD>\n");
     hPrintf("<B>File type returned:&nbsp;</B>");
     cgiMakeRadioButton(hgtaCompressType, textOutCompressNone,
         sameWord(textOutCompressNone, compressType));
     hPrintf("&nbsp;Plain text&nbsp;");
     cgiMakeRadioButton(hgtaCompressType, textOutCompressGzip,
         sameWord(textOutCompressGzip, compressType));
     hPrintf("&nbsp;Gzip compressed");
     hPrintf("</TD></TR>\n");
     }
 
 hPrintf("</TABLE>\n");
 
 
 /* Submit buttons. */
     {
-    hPrintf("<BR>\n");
+    hPrintf("<BR><DIV ID=\"submit-select\">\n");
     if (isWig || isBam || isVcf || isLongTabix || isHic)
 	{
 	char *name;
 	extern char *maxOutMenu[];
 	char *maxOutput = maxOutMenu[0];
 
 	if (isCustomTrack(curTable))
 	    name=filterFieldVarName("ct", curTable, "_", filterMaxOutputVar);
 	else
 	    name=filterFieldVarName(database,curTable, "_",filterMaxOutputVar);
 
 	maxOutput = cartUsualString(cart, name, maxOutMenu[0]);
 
 	if (isWig)
 	    hPrintf(
@@ -1033,30 +1047,31 @@
 		"</I><BR>", maxOutput);
 	}
     else if (anySubtrackMerge(database, curTable) || anyIntersection())
 	{
 	hPrintf("<I>Note: The all fields and selected fields output formats "
 		"are not available when a%s has been specified.</I><BR>",
 		canSubtrackMerge ? " subtrack merge or intersection" : "n intersection");
 	}
     cgiMakeButton(hgtaDoTopSubmit, "Get output");
     hPrintf(" ");
     if (isPositional || isWig)
 	{
 	cgiMakeButton(hgtaDoSummaryStats, "Summary/statistics");
 	hPrintf(" ");
 	}
+    hPrintf("</DIV>");
 
 #ifdef SOMETIMES
     hPrintf(" ");
     cgiMakeButton(hgtaDoTest, "test");
 #endif /* SOMETIMES */
     }
 hPrintf("<P></P>");
 }
 
 static char *getGenomeSpaceText()
 /* fetch GenomeSpace text if enabled */
 {
 if (isGenomeSpaceEnabled())
     {
     return