984a3761c1300e20e463304a81098b312da7bd66
jnavarr5
  Wed Nov 26 15:49:51 2025 -0800
Adding the custom track tutorial, refs #34354

diff --git src/hg/hgCustom/hgCustom.c src/hg/hgCustom/hgCustom.c
index 0781097448b..c22a3ec3eee 100644
--- src/hg/hgCustom/hgCustom.c
+++ src/hg/hgCustom/hgCustom.c
@@ -170,54 +170,54 @@
     }
 else
     /* add form needs clade for assembly menu */
     gotClade = hGotClade();
 
 jsIncludeFile("jquery.js", NULL);
 jsIncludeFile("hgCustom.js", NULL);
 jsIncludeFile("utils.js", NULL);
 jsIncludeFile("ajax.js", NULL);
 if (cfgOptionBooleanDefault("showTutorial", TRUE))
     {
     jsIncludeFile("shepherd.min.js", NULL);
     webIncludeResourceFile("shepherd.css");
     jsIncludeFile("jquery-ui.js", NULL);
     webIncludeResourceFile("jquery-ui.css");
-    //jsIncludeFile("tutorialPopup.js", NULL);
+    jsIncludeFile("tutorialPopup.js", NULL);
     jsIncludeFile("customTrackTutorial.js", NULL);
     if (sameOk(cgiOptionalString("startCustomTutorial"), "true"))
         {
         jsInline("var startCustomTutorialOnLoad = true;");
         }
     }
 
 /* main form */
 printf("<FORM ACTION=\"%s\" METHOD=\"%s\" "
     " ENCTYPE=\"multipart/form-data\" NAME=\"mainForm\" id='mainForm'>\n",
     hgCustomName(), cartUsualString(cart, "formMethod", "POST"));
 jsOnEventById("submit", "mainForm", "$('input[name=Submit]').attr('disabled', 'disabled');");
 cartSaveSession(cart);
 
 if (!isUpdateForm)
     {
     /* Print clade, genome and assembly  */
     /* NOTE: this uses an additional, hidden form (orgForm), below */
     char *onChangeDb = "document.orgForm.db.value = document.mainForm.db.options[document.mainForm.db.selectedIndex].value; document.orgForm.submit();";
     char *onChangeOrg = "document.orgForm.org.value = document.mainForm.org.options[document.mainForm.org.selectedIndex].value; document.orgForm.db.value = 0; document.orgForm.submit();";
     char *onChangeClade = "document.orgForm.clade.value = document.mainForm.clade.options[document.mainForm.clade.selectedIndex].value; document.orgForm.org.value = 0; document.orgForm.db.value = 0; document.orgForm.submit();";
 
-    puts("<TABLE BORDER=0>\n");
+    puts("<TABLE id=\"genome-selection-table\" BORDER=0>\n");
     if (gotClade)
         {
         puts("<TR><TD>Clade\n");
         printCladeListHtml(hOrganism(database), "change", onChangeClade);
         puts("&nbsp;&nbsp;&nbsp;");
         puts("Genome\n");
         printGenomeListForCladeHtml(database, "change", onChangeOrg);
         }
     else
         {
         puts("<TR><TD>genome\n");
         printGenomeListHtml(database, "change", onChangeOrg);
         }
     puts("&nbsp;&nbsp;&nbsp;");
     puts("Assembly\n");
@@ -372,31 +372,31 @@
 	cgiMakeButtonWithOnClick("ContinueWithWarn", "Continue with Warning", NULL, "return submitClick(this);");
 	printf("&nbsp;");
 	jsInline(
 	    "$('textarea').change(function() {\n"
 	    "    $('#ContinueWithWarn').hide();\n"
 	    "});\n");
 	}
     cgiMakeButtonWithOnClick("Submit", "Submit", NULL, "return submitClick(this);");
     printf("<img id='loadingImg' src='../images/loading.gif' />\n");
     cgiTableFieldEnd();
     }
 cgiTableRowEnd();
 
 /* next row - text entry box for  data, and clear button */
 cgiSimpleTableRowStart();
-puts("<TD COLSPAN=2>");
+puts("<TD ID=\"data-input\" COLSPAN=2>");
 if (dataUrl)
     {
     /* can't update via pasting if loaded from URL */
  safef(buf, sizeof buf, "Data was uploaded from URL: %s\n"
      , ctDataUrl(ct));
 
     cgiMakeTextAreaDisableable(hgCtDataText, buf,
                                 TEXT_ENTRY_ROWS, TEXT_ENTRY_COLS, TRUE);
     }
 else
     {
     int rows = (isUpdateForm ? TEXT_ENTRY_ROWS - CONFIG_ENTRY_ROWS :
                                 TEXT_ENTRY_ROWS);
     cgiMakeTextArea(hgCtDataText, cartUsualString(cart, hgCtDataText, ""),
                                     rows, TEXT_ENTRY_COLS);
@@ -432,31 +432,31 @@
 cgiSimpleTableRowStart();
 cgiTableField("Optional track documentation: ");
 if (isUpdateForm && ctHtmlUrl(ct))
     cgiTableField("&nbsp;");
 else
     {
     puts("<TD ALIGN='RIGHT'>");
     puts("Or upload: ");
     cgiMakeFileEntry(hgCtDocFile);
     cgiTableFieldEnd();
     }
 cgiTableRowEnd();
 
 /* next row - text entry for description, and clear button(s) */
 cgiSimpleTableRowStart();
-puts("<TD COLSPAN=2>");
+puts("<TD ID=\"description-input\" COLSPAN=2>");
 
 if (ct && ctHtmlUrl(ct))
     {
     safef(buf, sizeof buf, "Replace doc at URL: %s", dataUrl);
     cgiMakeTextAreaDisableable(hgCtDocText, buf,
                                     TEXT_ENTRY_ROWS, TEXT_ENTRY_COLS, TRUE);
     }
 else
     {
     cgiMakeTextArea(hgCtDocText, cartUsualString(cart, hgCtDocText, ""),
                                     TEXT_ENTRY_ROWS, TEXT_ENTRY_COLS);
     cgiTableFieldEnd();
     cgiSimpleTableFieldStart();
     cgiSimpleTableStart();
     cgiSimpleTableRowStart();
@@ -735,30 +735,43 @@
 struct slPair *valsAndLabels = slPairNew(hgTracksName(), "Genome Browser");
 slAddHead(&valsAndLabels, slPairNew(hgTablesName(), "Table Browser"));
 slAddHead(&valsAndLabels, slPairNew(hgVaiName(), "Variant Annotation Integrator"));
 slAddHead(&valsAndLabels, slPairNew(hgIntegratorName(), "Data Integrator"));
 slReverse(&valsAndLabels);
 return valsAndLabels;
 }
 
 static void makeOtherCgiForm(char *pos)
 /* Make a form for navigating to other CGIs. */
 {
 struct slPair *valsAndLabels = makeOtherCgiValsAndLabels();
 // Default to the first CGI in the menu.
 char *defaultCgi = valsAndLabels->name;
 char *selected = cartUsualString(cart, hgCtNavDest, defaultCgi);
+if (cfgOptionBooleanDefault("showTutorial", TRUE))
+    {
+    jsIncludeFile("shepherd.min.js", NULL);
+    webIncludeResourceFile("shepherd.css");
+    jsIncludeFile("jquery-ui.js", NULL);
+    webIncludeResourceFile("jquery-ui.css");
+    jsIncludeFile("tutorialPopup.js", NULL);
+    jsIncludeFile("customTrackTutorial.js", NULL);
+    if (sameOk(cgiOptionalString("startCustomTutorial"), "true"))
+        {
+        jsInline("var startCustomTutorialOnLoad = true;");
+        }
+    }
 printf("<FORM STYLE=\"margin-bottom:0;\" METHOD=\"GET\" NAME=\"navForm\" ID=\"navForm\""
        " ACTION=\"%s\">\n", selected);
 cartSaveSession(cart);
 if (pos)
     cgiMakeHiddenVar("position", pos);
 printf("view in ");
 // Construct a menu of destination CGIs
 puts(cgiMakeSingleSelectDropList(hgCtNavDest, valsAndLabels, selected, NULL, NULL,
  "change", "var newVal = $('#navSelect').val(); $('#navForm').attr('action', newVal);", NULL, "navSelect"));
 cgiMakeButton("submit", "Go to first annotation");
 puts("&nbsp;<input type='submit' name='submit' id='submitGoBack' value='Return to current position'>");
 jsOnEventByIdF("click", "submitGoBack", "$('#navForm > [name=position]').remove()");
 puts("</FORM>");
 }
 
@@ -827,31 +840,31 @@
 /* place for warning messages to appear */
 if (isNotEmpty(warnMsg))
     {
     char *encoded = htmlEncode(warnMsg);
     printf("<P><B>&nbsp;&nbsp;&nbsp;&nbsp;%s", encoded);
     freeMem(encoded);
     }
 
 /* count up number of custom tracks for this genome */
 int numCts = slCount(ctList);
 
 cgiSimpleTableStart();
 cgiSimpleTableRowStart();
 if (numCts)
     {
-    puts("<TD VALIGN=\"TOP\">");
+    puts("<TD ID=\"resultsTable\" VALIGN=\"TOP\">");
     showCustomTrackList(ctList, numCts);
     }
 else
     puts("<TD VALIGN=\"TOP\"><B><EM>No custom tracks for this genome:<B></EM>&nbsp;&nbsp;");
 puts("</TD>");
 
 /* end mainForm; navigation menu has its own form. */
 
 puts("</FORM>");
 
 puts("<TD VALIGN=\"TOP\">");
 puts("<TABLE BORDER=0>");
 
 /* determine if there's a navigation position for this screen */
 char *pos = NULL;