|
32 | 32 | mutationAuth = "local", |
33 | 33 | readOnlySource = null, |
34 | 34 | projectLocked = false, |
| 35 | + spaceId = null, |
35 | 36 | logoUrls = { light: "/static/trackio/trackio_logo_type_light_transparent.png", dark: "/static/trackio/trackio_logo_type_dark_transparent.png" }, |
36 | 37 | darkMode = false, |
37 | 38 | } = $props(); |
|
80 | 81 | let filteredRunIds = $derived(filteredRuns.map((r) => r.id ?? r.name)); |
81 | 82 |
|
82 | 83 | let latestOnly = $state(false); |
| 84 | + let shareTab = $state("share"); |
83 | 85 |
|
84 | 86 | function toggleLatestOnly() { |
85 | 87 | latestOnly = !latestOnly; |
|
108 | 110 | selectedSystemDevices = [...selectedSystemDevices, device]; |
109 | 111 | } |
110 | 112 | } |
| 113 | +
|
| 114 | + function buildSpaceHost(spaceIdValue) { |
| 115 | + if (!spaceIdValue || !spaceIdValue.includes("/")) return ""; |
| 116 | + const [namespace, name] = spaceIdValue.split("/", 2); |
| 117 | + if (!namespace || !name) return ""; |
| 118 | + return `${namespace}-${name}.hf.space`; |
| 119 | + } |
| 120 | +
|
| 121 | + function selectedRunNamesFromIds(selectedIds, allRuns) { |
| 122 | + const byId = new Map(allRuns.map((run) => [run.id ?? run.name, run.name])); |
| 123 | + return selectedIds.map((id) => byId.get(id)).filter(Boolean); |
| 124 | + } |
| 125 | +
|
| 126 | + let shareUrl = $derived.by(() => { |
| 127 | + const host = buildSpaceHost(spaceId); |
| 128 | + if (!host || !selectedProject) return ""; |
| 129 | + const params = new URLSearchParams(); |
| 130 | + params.set("project", selectedProject); |
| 131 | + if (metricFilter?.trim()) { |
| 132 | + params.set("metrics", metricFilter.trim()); |
| 133 | + } |
| 134 | + const runNames = selectedRunNamesFromIds(selectedRuns, runs); |
| 135 | + if (runNames.length) { |
| 136 | + params.set("runs", runNames.join(",")); |
| 137 | + } |
| 138 | + if (!showHeaders) { |
| 139 | + params.set("accordion", "hidden"); |
| 140 | + } |
| 141 | + params.set("sidebar", "hidden"); |
| 142 | + params.set("navbar", "hidden"); |
| 143 | + return `https://${host}?${params.toString()}`; |
| 144 | + }); |
| 145 | +
|
| 146 | + let embedCode = $derived.by(() => { |
| 147 | + if (!shareUrl) return ""; |
| 148 | + return `<iframe src="${shareUrl}" style="width:1600px; height:500px; border:0;"></iframe>`; |
| 149 | + }); |
| 150 | +
|
| 151 | + async function copyText(value) { |
| 152 | + if (!value) return; |
| 153 | + try { |
| 154 | + await navigator.clipboard.writeText(value); |
| 155 | + } catch { |
| 156 | + const textarea = document.createElement("textarea"); |
| 157 | + textarea.value = value; |
| 158 | + textarea.style.position = "fixed"; |
| 159 | + textarea.style.opacity = "0"; |
| 160 | + document.body.appendChild(textarea); |
| 161 | + textarea.focus(); |
| 162 | + textarea.select(); |
| 163 | + document.execCommand("copy"); |
| 164 | + document.body.removeChild(textarea); |
| 165 | + } |
| 166 | + } |
111 | 167 | </script> |
112 | 168 |
|
113 | 169 | <div class="sidebar" class:collapsed={!open}> |
|
149 | 205 | </div> |
150 | 206 |
|
151 | 207 | {#if variant === "full"} |
| 208 | + {#if spaceId} |
| 209 | + <div class="section"> |
| 210 | + <div class="share-tabs"> |
| 211 | + <button |
| 212 | + class="share-tab-btn" |
| 213 | + class:active={shareTab === "share"} |
| 214 | + onclick={() => { shareTab = "share"; }} |
| 215 | + > |
| 216 | + Share |
| 217 | + </button> |
| 218 | + <button |
| 219 | + class="share-tab-btn" |
| 220 | + class:active={shareTab === "embed"} |
| 221 | + onclick={() => { shareTab = "embed"; }} |
| 222 | + > |
| 223 | + Embed |
| 224 | + </button> |
| 225 | + </div> |
| 226 | + {#if shareTab === "share"} |
| 227 | + <div class="share-field"> |
| 228 | + <span class="section-label">Share this view</span> |
| 229 | + <div class="share-input-row"> |
| 230 | + <input type="text" value={shareUrl} readonly /> |
| 231 | + <button class="copy-btn" onclick={() => copyText(shareUrl)}> |
| 232 | + Copy |
| 233 | + </button> |
| 234 | + </div> |
| 235 | + </div> |
| 236 | + {:else} |
| 237 | + <div class="share-field"> |
| 238 | + <span class="section-label">Embed this view</span> |
| 239 | + <div class="share-input-row"> |
| 240 | + <textarea readonly rows="2" value={embedCode}></textarea> |
| 241 | + <button class="copy-btn" onclick={() => copyText(embedCode)}> |
| 242 | + Copy |
| 243 | + </button> |
| 244 | + </div> |
| 245 | + </div> |
| 246 | + {/if} |
| 247 | + </div> |
| 248 | + {/if} |
| 249 | +
|
152 | 250 | <div class="section"> |
153 | 251 | <div class="runs-header"> |
154 | 252 | <label class="select-all-label"> |
|
467 | 565 | margin-top: 2px; |
468 | 566 | margin-bottom: 18px; |
469 | 567 | } |
| 568 | + .share-tabs { |
| 569 | + display: flex; |
| 570 | + gap: 6px; |
| 571 | + margin-bottom: 8px; |
| 572 | + } |
| 573 | + .share-tab-btn { |
| 574 | + border: 1px solid var(--border-color-primary, #e5e7eb); |
| 575 | + border-radius: var(--radius-md, 6px); |
| 576 | + padding: 4px 8px; |
| 577 | + font-size: 12px; |
| 578 | + color: var(--body-text-color-subdued, #6b7280); |
| 579 | + background: var(--background-fill-primary, white); |
| 580 | + cursor: pointer; |
| 581 | + } |
| 582 | + .share-tab-btn.active { |
| 583 | + color: var(--body-text-color, #1f2937); |
| 584 | + background: var(--background-fill-secondary, #f9fafb); |
| 585 | + } |
| 586 | + .share-field { |
| 587 | + display: flex; |
| 588 | + flex-direction: column; |
| 589 | + gap: 6px; |
| 590 | + } |
| 591 | + .share-input-row { |
| 592 | + display: flex; |
| 593 | + gap: 6px; |
| 594 | + align-items: stretch; |
| 595 | + } |
| 596 | + .share-input-row input, |
| 597 | + .share-input-row textarea { |
| 598 | + width: 100%; |
| 599 | + min-width: 0; |
| 600 | + border: 1px solid var(--border-color-primary, #e5e7eb); |
| 601 | + border-radius: var(--radius-md, 6px); |
| 602 | + padding: 6px 8px; |
| 603 | + font-size: 12px; |
| 604 | + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; |
| 605 | + color: var(--body-text-color, #1f2937); |
| 606 | + background: var(--background-fill-secondary, #f9fafb); |
| 607 | + resize: vertical; |
| 608 | + } |
| 609 | + .copy-btn { |
| 610 | + border: 1px solid var(--border-color-primary, #e5e7eb); |
| 611 | + border-radius: var(--radius-md, 6px); |
| 612 | + padding: 6px 10px; |
| 613 | + font-size: 12px; |
| 614 | + color: var(--body-text-color, #1f2937); |
| 615 | + background: var(--background-fill-primary, white); |
| 616 | + cursor: pointer; |
| 617 | + flex-shrink: 0; |
| 618 | + } |
470 | 619 | .section-label { |
471 | 620 | font-size: 13px; |
472 | 621 | font-weight: 500; |
|
0 commit comments