cee097b75fe4d1b29f0debd8af938ba6bf729d53 max Tue Aug 11 08:18:42 2026 -0700 hgBlat: new client-rendered BLAT search form and sortable results page, refs #37893 An opt-in modern alternative to the classic hyperlink output, gated by the blatNewForm / blatNewPage hg.conf flags. hgBlat.c emits the hit data and form config as inline JSON (hgBlatData / hgBlatFormData) into empty containers, and hgBlat.js builds the UI: the search form, a sortable/filterable DataTable with identity and query-coverage bars, a docked per-hit detail panel, a FASTA query viewer, and share-link and rename-track modals. - hgBlat.css: all styling for both pages, loaded via webIncludeResourceFile (not injected from JS); layout uses CSS classes rather than inline styles. - utils.js: setupGenomeSelector, the combobox genome picker the form uses. - hgc.c: blatOldTracks / blatKeepResults - the "Keep results" checkbox lets a user accumulate BLAT result tracks instead of each search replacing the last. - hgConfCatalog: register the blatNewForm / blatNewFormBanner gates. - blatExample.fa: the example query the form loads on demand. Markup is built in JS (escaped values via htmlEncode, everything else set through DOM properties); no HTML strings in the C or a template engine. diff --git src/hg/htdocs/style/hgBlat.css src/hg/htdocs/style/hgBlat.css new file mode 100644 index 00000000000..b3033e1f327 --- /dev/null +++ src/hg/htdocs/style/hgBlat.css @@ -0,0 +1,210 @@ +/* hgBlat.css - styling for the modern BLAT search form and results pages (hgBlat.js). + * UCSC house style (per the project's UCSC UI Style Guide): steel-blue section header, tan/white + * content, navy links with maroon hover, #dbe4ee table header, zebra rows, navy selection bar. + * Loaded on the new pages via webIncludeResourceFile("hgBlat.css"); the classic C-rendered pages + * emit their own small .blatBanner rule in hgBlat.c instead. */ + +/* Plain white page background, matching the track-settings model (gb.css). The framework body + * is cream (#FFF9D2) and the hgInside content table carries a BGCOLOR=#FFFEE8 attribute; override + * both to white so the results sit on one uniform white background. */ +body.cgi { background:#fff; } +table.hgInside { background:#fff; } +/* Main page header, styled like gb.css .gbTrackTitleBanner (hgGtexTrackSettings model): black + * title on the house gold. The framework's #sectTtl already holds " BLAT Results". */ +.subheadingBar { background:#eaca92; padding:9px 16px; margin:0; border:0; box-sizing:border-box; } +.subheadingBar #sectTtl { color:#000; font-weight:700; font-size:18px; + display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px; } +#blatResults, #blatFormBox { --accent:#003a72; --accentHover:#8b1a1a; --navy:#0a2b6b; --ink:#1e2833; + --muted:#5b6572; --faint:#93a0ad; --line:#d0d0d0; --lineSoft:#ececec; --card:#ffffff; + --panel:#ffffff; --section:#4c759c; --headrow:#ededed; --headrowLine:#cccccc; + --sel:#cfe0f5; --hover:#f0f0f0; --stripe:#f7f7f7; + --title:#eaca92; --titleLine:#d9bd82; --titleSub:#5a4a24; + --btn:#ffffff; --btnLine:#999999; --btnText:#003a72; --btnHover:#eef2f7; + --btnDark:#0a2b6b; --btnDarkHover:#0a2350; + font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; color:var(--ink); font-size:14px; } +.blatCard { background:var(--card); border:1px solid var(--line); border-radius:0; overflow:hidden; + box-shadow:0 1px 2px rgba(20,40,70,.10); margin:12px 0 24px; } +/* Page actions sit in the gold main header (#sectTtl); no separate toolbar. */ +.blatHeadActions { display:flex; gap:8px; align-items:center; flex-wrap:wrap; } +.blatStrip { display:flex; align-items:center; gap:24px; flex-wrap:wrap; + padding:11px 18px; border-bottom:1px solid var(--line); background:var(--panel); } +.blatStat { display:flex; flex-direction:column; gap:1px; } +.blatStat .k { font-size:12px; color:var(--muted); font-weight:700; } +.blatStat .v { font-size:14px; font-weight:700; color:var(--ink); } +.blatDiv { width:1px; height:28px; background:var(--line); } +.blatStripActions { margin-left:auto; display:flex; gap:8px; align-items:center; flex-wrap:wrap; } +/* Buttons live both inside the card (#blatResults) and in the gold header (#sectTtl); scope to + * both ids so the framework's link styles (which would add an underline) can't beat them. */ +#blatResults .blatPill, #sectTtl .blatPill, #blatFormBox .blatPill, +#blatResults .blatStripActions input[type=submit], +#blatResults .blatStripActions input[type=button] { + font-family:inherit; font-size:13px; font-weight:700; color:var(--btnText); background:var(--btn); + border:1px solid var(--btnLine); border-radius:0; padding:5px 12px; cursor:pointer; + text-decoration:none; display:inline-block; line-height:1.5; } +#blatResults .blatPill:hover, #sectTtl .blatPill:hover, #blatFormBox .blatPill:hover, +#blatResults .blatStripActions input[type=submit]:hover, +#blatResults .blatStripActions input[type=button]:hover { background:var(--btnHover); text-decoration:none; } +#blatResults .blatPill.primary, #sectTtl .blatPill.primary, +#blatFormBox .blatPill.primary { background:var(--btnDark); color:#fff; border-color:var(--btnDark); } +#blatResults .blatPill.primary:hover, #sectTtl .blatPill.primary:hover, +#blatFormBox .blatPill.primary:hover { background:var(--btnDarkHover); } +/* In the gold header (#sectTtl) the framework's link styles would otherwise beat .blatPill, so + * re-assert the button look with id-level specificity. */ +#sectTtl .blatPill { color:#003a72; background:#fff; border:1px solid #999; text-decoration:none; } +#sectTtl .blatPill:hover { background:#eef2f7; text-decoration:none; } +#sectTtl .blatPill.primary { background:#0a2b6b; color:#fff; border-color:#0a2b6b; } +#sectTtl .blatPill.primary:hover { background:#0a2350; } +.blatShareIcon { vertical-align:-2px; margin-right:6px; } +.blatShareBox { display:flex; align-items:center; gap:10px; flex-wrap:wrap; + padding:11px 18px; border-bottom:1px solid var(--line); background:var(--panel); } +.blatShareMsg { font-size:14px; color:var(--muted); } +.blatShareInput { flex:1; min-width:260px; font-size:13px; padding:5px 8px; + border:1px solid var(--btnLine); border-radius:0; background:#fff; } +.blatBanner { background:#fbf3e2; border:1px solid var(--titleLine); padding:10px 14px; + margin:12px 0 0; font-size:14px; color:var(--ink); } +.blatBanner a { color:var(--accent); } +.blatBanner a:hover { color:var(--accentHover); } +.blatSeqBar { flex:1 1 100%; display:flex; gap:8px; align-items:center; flex-wrap:wrap; } +.blatSeqText { flex:1 1 100%; min-height:150px; margin-top:10px; padding:8px; white-space:pre; + font-family:'Roboto Mono','Courier New',monospace; font-size:13px; color:var(--ink); + border:1px solid var(--btnLine); background:#fff; overflow:auto; } +#blatTable { font-size:13px; width:100%; border-collapse:collapse; } +/* Let the long text columns wrap so the table shrinks to fit smaller screens, while the fixed + * bar columns keep a min-width so they never compress into an overlap; when even that won't fit, + * #blatTable_wrapper scrolls horizontally. */ +#blatTable td.blatPos, #blatTable td.queryCol { white-space:normal; word-break:break-word; } +#blatTable td.scoreCol { min-width:118px; } +#blatTable td.covCol { min-width:120px; } +#blatTable thead th { background:var(--headrow); border-bottom:1px solid var(--headrowLine); + font-size:12px; color:var(--navy); font-weight:700; padding:8px 12px; white-space:nowrap; } +#blatTable tbody td { padding:8px 12px; border-bottom:1px solid var(--lineSoft); white-space:nowrap; + vertical-align:middle; } +#blatTable tbody tr { cursor:pointer; } +#blatTable tbody tr:nth-child(even) { background:var(--stripe); } +#blatTable tbody tr:hover { background:var(--hover); } +#blatTable tbody tr.blatSel { background:var(--sel); box-shadow:inset 3px 0 0 var(--navy); } +#blatTable td.num, #blatTable th.num { text-align:right; font-variant-numeric:tabular-nums; } +#blatTable td.rankCol { color:var(--faint); } +#blatTable td.strandCol { text-align:center; color:var(--muted); } +#blatTable td.actionsCol a { color:var(--accent); font-weight:700; } +#blatTable td.actionsCol .blatActSep { color:var(--line); margin:0 10px; } +/* "New tab" shown as the usual box-with-arrow icon (via a CSS background so it isn't repeated in + * every row's markup), sitting right after the position link with just a space between them. */ +#blatTable a.blatNewTab { display:inline-block; width:11px; height:11px; + margin-left:4px; vertical-align:-1px; background-repeat:no-repeat; background-position:center; + background-size:contain; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath fill='%23003a72' d='M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z'/%3E%3C/svg%3E"); } +#blatTable a.blatNewTab:hover { opacity:.65; } +#blatTable a { color:var(--accent); text-decoration:none; } +#blatTable a:hover { color:var(--accentHover); text-decoration:underline; } +.blatRowHint { padding:11px 18px 2px; font-size:13px; color:var(--muted); } +.blatLocus { max-width:280px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } +.chrNote { color:var(--faint); text-decoration:none; margin-left:4px; } +.chrNote:hover { color:var(--accent); } +.blatScoreWrap { display:flex; align-items:center; gap:9px; justify-content:flex-end; } +.blatScoreVal { font-size:13px; font-weight:700; text-align:right; font-variant-numeric:tabular-nums; } +.blatScoreBar { flex:0 0 54px; height:8px; background:var(--headrow); border:1px solid var(--headrowLine); overflow:hidden; } +.blatScoreBar > i { display:block; height:100%; background:var(--accent); } +.blatIdPct { font-size:13px; font-weight:700; font-variant-numeric:tabular-nums; } +.blatCov { position:relative; display:block; width:150px; max-width:100%; height:10px; background:var(--headrow); border:1px solid var(--headrowLine); } +.blatCov > i { position:absolute; top:0; bottom:0; background:var(--accent); } +#blatTable_filter { float:left; margin:0 0 10px; } +#blatTable_filter input { width:300px; max-width:55vw; border:1px solid var(--btnLine); + border-radius:0; padding:5px 9px; font-size:14px; } +#blatTable_wrapper { padding:6px 0 4px; overflow-x:auto; } +.blatDetail { border-bottom:1px solid var(--line); background:var(--panel); padding:14px 18px 16px; } +.blatDetail .dhead { display:flex; align-items:baseline; gap:10px; margin-bottom:12px; flex-wrap:wrap; } +.blatDetail .dhead .lab { font-size:13px; color:var(--muted); } +.blatSelectHint { font-size:14px; color:var(--ink); } +.blatSelectHint a { color:var(--accent); } +.blatSelectHint a:hover { color:var(--accentHover); } +.blatDetail .dhead .loc { font-size:14px; font-weight:700; color:var(--ink); } +.blatDetailCard { display:flex; gap:26px; flex-wrap:wrap; padding:14px 16px; background:var(--card); + border:1px solid var(--line); border-radius:0; } +.blatTiles { display:grid; grid-template-columns:repeat(4,auto); gap:14px 26px; } +.blatTile .k { font-size:13px; color:var(--muted); cursor:help; } +.blatTile .v { font-size:14px; font-weight:700; color:var(--ink); } +.blatDetailActions { display:flex; gap:8px; margin-top:14px; flex-wrap:wrap; } +/* Detail panel layout (was inline styles in blatDetailSkeleton). */ +.blatDetailCol { display:flex; flex-direction:column; gap:16px; min-width:250px; } +.blatAlignBox { flex:1; min-width:320px; border-left:1px solid var(--line); padding-left:24px; + display:flex; flex-direction:column; justify-content:center; } +.blatAlignBox .blatPill { align-self:flex-start; } +.blatAlignText { font-size:14px; color:var(--muted); line-height:1.55; margin:8px 0 14px; max-width:360px; } +/* Full-width message row inside the flex-wrap share box. */ +.blatShareFull { flex:1 1 100%; } +/* Rename modal. On this page the old C-emitted inline rename form (#renameFormItem / #renameForm) + * is replaced by the JS button + modal below, so keep the C markup hidden even though hgBlat.c + * flips its display to block after buildBigPsl (our !important beats that inline style). */ +#renameFormItem, #renameForm { display:none !important; } +.blatModalBg { position:fixed; top:0; right:0; bottom:0; left:0; z-index:1000; + background:rgba(0,0,0,.4); display:flex; align-items:center; justify-content:center; } +.blatModal { background:#fff; border:1px solid var(--line); border-radius:0; + box-shadow:0 4px 18px rgba(0,0,0,.28); padding:18px 20px; min-width:340px; max-width:92vw; } +.blatModalTitle { font-size:16px; font-weight:700; color:var(--ink); margin-bottom:12px; } +.blatModalText { font-size:13px; color:var(--ink); line-height:1.5; max-width:430px; margin-bottom:14px; } +.blatModalText.blatModalNote { margin-top:14px; } +.blatModalText a { color:var(--accent); } +.blatModalText a:hover { color:var(--accentHover); } +.blatModalLabel { display:block; font-size:12px; font-weight:700; color:var(--muted); + margin:10px 0 3px; } +.blatModalInput { width:100%; box-sizing:border-box; border:1px solid var(--btnLine); + border-radius:0; padding:6px 8px; font-size:14px; font-family:inherit; background:#fff; } +.blatModalBtns { display:flex; gap:8px; justify-content:flex-end; margin-top:18px; } + +/* ---- search form (blatFormBuild) ------------------------------------- + * Shares the tokens, .blatPill buttons, .blatBanner and the gold .subheadingBar page header + * above with the results page, so the two pages read as one design. */ +#blatFormBox { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; color:var(--ink); font-size:14px; } +#blatFormBox .blatFormGrid { display:grid; grid-template-columns:minmax(0,1fr) 300px; gap:28px; + align-items:start; margin:12px 0 24px; } +@media (max-width:900px) { #blatFormBox .blatFormGrid { grid-template-columns:minmax(0,1fr); } } +#blatFormBox .blatSection { background:var(--section); color:#fff; padding:6px 13px; font-size:15px; + font-weight:700; margin:0 0 14px; } +/* flex-start, not flex-end: the genome field carries a "Current genome:" line under its input, + * so bottom-aligning the row would push Query type down by the height of that line. */ +#blatFormBox .blatRow { display:flex; align-items:flex-start; gap:16px; flex-wrap:wrap; margin-bottom:14px; } +#blatFormBox .blatField { display:flex; flex-direction:column; gap:5px; } +#blatFormBox .blatField > span { font-size:13px; font-weight:700; color:var(--muted); } +#blatFormBox .blatField select { padding:6px 8px; font-size:14px; border:1px solid var(--btnLine); + border-radius:0; background:#fff; color:var(--ink); font-family:inherit; } +#blatFormBox .blatGenomeSlot { flex:1 1 650px; min-width:260px; max-width:650px; } +#blatFormBox .blatChecks { display:flex; align-items:center; gap:22px; flex-wrap:wrap; margin-bottom:16px; + font-size:14px; } +#blatFormBox .blatCheck { display:flex; align-items:center; gap:6px; } +#blatFormBox .blatCheck label { display:flex; align-items:center; gap:6px; cursor:pointer; } +#blatFormBox .blatCheck input { margin:0; } +#blatFormBox .blatInfo { display:inline-flex; align-items:center; cursor:help; } +#blatFormBox .blatTabs { display:flex; border-bottom:1px solid var(--line); margin-bottom:12px; } +#blatFormBox .blatTab { padding:8px 16px; font-size:14px; font-weight:700; cursor:pointer; background:none; + border:none; border-bottom:3px solid transparent; color:var(--accent); font-family:inherit; } +#blatFormBox .blatTab.on { border-bottom-color:var(--title); color:var(--ink); } +#blatFormBox .blatPaneHint { display:flex; justify-content:space-between; align-items:baseline; gap:16px; + flex-wrap:wrap; font-size:13px; color:var(--muted); margin-bottom:6px; } +#blatFormBox textarea.blatSeq { width:100%; height:200px; resize:vertical; padding:9px 11px; box-sizing:border-box; + font-family:'Roboto Mono','Courier New',monospace; font-size:13px; line-height:1.6; + border:1px solid var(--btnLine); border-radius:0; color:var(--ink); background:#fff; } +#blatFormBox .blatCount { display:flex; justify-content:space-between; align-items:center; gap:16px; + padding:6px 1px 0; font-size:13px; color:var(--muted); } +#blatFormBox .blatCount .over { color:#b1301f; font-weight:700; } +#blatFormBox .blatDrop { border:2px dashed var(--headrowLine); background:#fafafa; padding:30px 20px; + text-align:center; } +#blatFormBox .blatDrop.hot { background:var(--hover); border-color:var(--accent); } +#blatFormBox .blatDropTitle { font-size:15px; font-weight:700; color:var(--accent); margin-bottom:4px; } +#blatFormBox .blatDropSub { font-size:13px; color:var(--muted); margin-bottom:14px; } +#blatFormBox .blatFileName { margin-top:12px; font-size:13px; color:var(--ink); font-weight:700; } +#blatFormBox .blatActions { display:flex; align-items:center; gap:10px; flex-wrap:wrap; + border-top:1px solid var(--line); padding-top:16px; margin-top:16px; } +#blatFormBox .blatCard { border:1px solid var(--line); padding:14px 16px; background:var(--card); } +#blatFormBox .blatCard + .blatCard { margin-top:16px; } +#blatFormBox .blatCard h3 { margin:0 0 9px; font-size:14px; font-weight:700; color:var(--accent); } +#blatFormBox .blatCard div { font-size:13px; line-height:1.5; color:var(--ink); } +#blatFormBox .blatCard div + div { margin-top:9px; } +#blatFormBox .blatCard a { color:var(--accent); } +#blatFormBox .blatCard a:hover { color:var(--accentHover); } +#blatFormBox .blatLimitRow { display:flex; justify-content:space-between; gap:12px; padding-bottom:7px; + border-bottom:1px solid var(--lineSoft); font-size:13px; } +/* The genome search bar is emitted by hgBlat.c (printGenomeSearchBar) and relocated into the + * layout by JS, so it keeps its real autocomplete over every assembly. */ +/* The genome picker is setupGenomeSelector (utils.js), which builds its own combobox; only + * its width is a page concern. HGStyle.css does not style .gsWrap, so nothing to override. */ +#blatFormBox .gsWrap { max-width:650px; }