ddb85ced5e8b6127a233b5cda5fcb1fbe2260578 max Wed Mar 25 04:22:06 2026 -0700 Add detailsScript trackDb mechanism for JS visualizations on bigBed details pages Changing based on feedback from Jonathan, Chris and Brian after group discussion. Refactored existing Claude-generated code, moving functions into libraries. This is the first use of ES6 modules in the kent js code. In 2026, this should be acceptable? New trackDb syntax: detailsScript.. The C code (bigBedClick.c) collects these settings, exports field values as JSON (bedDetails object), and dynamically imports hgc..js as an ES6 module. Fields used by detailsScript are shown in the HTML table with empty values, filled by JavaScript. Includes hgc.histogram.js module for drawing SVG bar chart histograms from logfmt-encoded data (space-separated key=value pairs). Applied to both the trexplorer and webstr tracks in the strVar supertrack. Also adds jsonWriteJsonElement() helper to jsonWrite.c for writing parsed jsonElement trees into a jsonWrite stream. max, refs #36652 Co-Authored-By: Claude Opus 4.6 (1M context) diff --git src/lib/jsonWrite.c src/lib/jsonWrite.c index 1c8db7f2c23..5f271e5174e 100644 --- src/lib/jsonWrite.c +++ src/lib/jsonWrite.c @@ -231,30 +231,73 @@ void jsonWriteAppend(struct jsonWrite *jwA, char *var, struct jsonWrite *jwB) /* Append jwB's contents to jwA's. If jwB is non-NULL, it must be fully closed (no unclosed * list or object). If var is non-NULL, write it out as a tag before appending. * If both var and jwB are NULL, leave jwA unchanged. */ { if (jwB && jwB->stackIx) errAbort("jsonWriteAppend: second argument must be fully closed but its stackIx is %d not 0", jwB->stackIx); jsonWriteTag(jwA, var); if (jwB) dyStringAppendN(jwA->dy, jwB->dy->string, jwB->dy->stringSize); else if (var) dyStringAppend(jwA->dy, "null"); } +void jsonWriteJsonElement(struct jsonWrite *jw, char *var, struct jsonElement *el) +/* Write a jsonElement (parsed JSON) into jw. Handles all types recursively. */ +{ +switch (el->type) + { + case jsonString: + jsonWriteString(jw, var, el->val.jeString); + break; + case jsonNumber: + jsonWriteNumber(jw, var, el->val.jeNumber); + break; + case jsonDouble: + jsonWriteDouble(jw, var, el->val.jeDouble); + break; + case jsonBoolean: + jsonWriteBoolean(jw, var, el->val.jeBoolean); + break; + case jsonObject: + { + jsonWriteObjectStart(jw, var); + struct hashEl *hel, *helList = hashElListHash(el->val.jeHash); + for (hel = helList; hel != NULL; hel = hel->next) + jsonWriteJsonElement(jw, hel->name, hel->val); + hashElFreeList(&helList); + jsonWriteObjectEnd(jw); + break; + } + case jsonList: + { + jsonWriteListStart(jw, var); + struct slRef *ref; + for (ref = el->val.jeList; ref != NULL; ref = ref->next) + jsonWriteJsonElement(jw, NULL, ref->val); + jsonWriteListEnd(jw); + break; + } + case jsonNull: + jsonWriteTag(jw, var); + dyStringAppend(jw->dy, "null"); + break; + } +} + int jsonWritePopToLevel(struct jsonWrite *jw, uint level) /* Close out the objects and lists that are deeper than level, so we end up at level ready to * add new items. Return the level that we end up with, which may not be the same as level, * if level is deeper than the current stack. */ { int i; for (i = jw->stackIx; i > level; i--) { if (jw->objStack[i].isObject) jsonWriteObjectEnd(jw); else jsonWriteListEnd(jw); } return jw->stackIx; }