diff --git a/calliope-web/src/lib/components/desk/Artboard.svelte b/calliope-web/src/lib/components/desk/Artboard.svelte index c38efd8a..69756981 100644 --- a/calliope-web/src/lib/components/desk/Artboard.svelte +++ b/calliope-web/src/lib/components/desk/Artboard.svelte @@ -59,6 +59,9 @@ onCancel: (job: Job) => void; onSendToSlot: (path: string) => void; onDelete: (job: Job) => void; + canAttach?: boolean; + attachBusy?: boolean; + onAttach?: () => void; } let { @@ -80,9 +83,13 @@ onCancel, onSendToSlot, onDelete, + canAttach = false, + attachBusy = false, + onAttach, }: Props = $props(); let detailsOpen = $state(false); + let deleteOpen = $state(false); let outIndex = $state(0); let compare = $state(false), compareAt = $state(50), @@ -90,6 +97,7 @@ $effect(() => { void job?.id; detailsOpen = false; + deleteOpen = false; compare = false; lightbox = false; }); @@ -132,16 +140,27 @@ return live?.progress ?? 3; }); let dims = $state(''); + let naturalWidth = $state(1), + naturalHeight = $state(1); + let stageWidth = $state(0), + stageHeight = $state(0); + const fitted = $derived( + Math.min(stageWidth / naturalWidth, stageHeight / naturalHeight), + ); $effect.pre(() => { void output; dims = ''; }); function noteImg(e: Event) { const img = e.currentTarget as HTMLImageElement; + naturalWidth = img.naturalWidth; + naturalHeight = img.naturalHeight; dims = `${img.naturalWidth}×${img.naturalHeight}`; } function noteVideo(e: Event) { const v = e.currentTarget as HTMLVideoElement; + naturalWidth = v.videoWidth; + naturalHeight = v.videoHeight; dims = `${v.videoWidth}×${v.videoHeight}`; } @@ -175,40 +194,29 @@
-
+ - {#if compareSource && compareSource !== output}{/if} - {/if} - {#if output}↓ 下载{/if} - -
- +
{#if job && st}{#key mediaKey} -
+
{#if output && video} -
+
0 ? `${naturalWidth * fitted}px` : '100%'} + style:height={fitted > 0 ? `${naturalHeight * fitted}px` : '100%'} + > {#if st.tone === 'ok' && !fileGone}{t('jobst.done')}{/if} @@ -217,6 +225,12 @@ src={assetUrl(output)} controls aria-label="生成的视频,使用播放按钮开始播放" + onkeydown={(e) => { + if (e.code === 'Space' && e.target === e.currentTarget) { + e.preventDefault(); + togglePlayback(); + } + }} onplay={() => (paused = false)} onpause={() => (paused = true)} onended={() => (paused = true)} @@ -228,9 +242,21 @@ }} onerror={() => output && markOutputMissing(output)} > + {#if paused}{/if}
{:else if output} -
+
0 ? `${naturalWidth * fitted}px` : '100%'} + style:height={fitted > 0 ? `${naturalHeight * fitted}px` : '100%'} + > {#if st.tone === 'ok' && !fileGone}{t('jobst.done')}{/if} @@ -287,19 +313,72 @@
{/if}
- {#if video}{/if} - {dims || st?.label} · {job ? fmtDur(elapsedSec(job, nowMs)) : ''} + video + ? videoEl?.requestFullscreen().catch(() => {}) + : (lightbox = true)} + aria-label="放大查看">放大 + 下载 + + + + + + + 素材库 + + {#if job?.status === 'pending' || job?.status === 'running'}{/if} + {#if st?.tone === 'fail'}{/if} {#if compare && output && !video}{/if} - {#if job?.status === 'pending' || job?.status === 'running'}{/if} - {#if st?.tone === 'fail'}{/if}
{#if job && st}
+ {#if video}{/if} + {dims || st?.label} · {job ? fmtDur(elapsedSec(job, nowMs)) : ''} + {#if compare && output && !video}{/if} + {#if output && !video && compareSource && compareSource !== output}{/if} {/if} @@ -409,6 +510,23 @@ {/if} + +

+ 确认移除这条生成记录及生成文件?关联项目中的回流记录也会移除。确认后有 5 + 秒可撤销,提交后无法恢复。 +

+ {#snippet footer()} + {/snippet} +
{#if lightbox && output && !video} .artboard { + position: relative; display: grid; - grid-template-rows: 38px minmax(0, 1fr) 32px; + grid-template-rows: 0 minmax(0, 1fr) 40px; height: 100%; min-height: 0; - border: 1px solid #ffffff0d; - border-radius: 12px; - background: radial-gradient(ellipse at 50% 20%, #202028, #131316 70%); + border: 0; + border-radius: 0; + background: transparent; overflow: hidden; } .artboard-toolbar, @@ -431,10 +550,24 @@ display: flex; align-items: center; justify-content: space-between; - gap: 8px; + gap: 6px; padding: 0 12px; min-width: 0; } + .artboard-toolbar { + position: absolute; + top: 10px; + left: 10px; + z-index: 2; + border-radius: 4px; + background: var(--media-scrim); + color: var(--on-media); + padding: 2px 6px; + max-width: calc(100% - 20px); + border: 0; + cursor: pointer; + text-align: left; + } .result-title { display: flex; align-items: center; @@ -451,25 +584,46 @@ text-overflow: ellipsis; max-width: 220px; } - .canvas-actions { - display: flex; - gap: 4px; - flex-shrink: 0; - } .canvas-btn { - background: #ffffff04; + display: inline-flex; + align-items: center; + gap: 6px; + height: 26px; + box-sizing: border-box; + background: var(--bg-2); color: var(--text-2); - border: 1px solid #ffffff0a; - border-radius: 6px; - padding: 5px 8px; - font-size: 10px; + border: 1px solid var(--line); + border-radius: var(--desk-action-radius); + padding: 0 9px; + font-size: 12px; + font-weight: 500; cursor: pointer; text-decoration: none; white-space: nowrap; } + .action-separator { + width: 1px; + height: 18px; + flex-shrink: 0; + margin: 0 4px; + background: var(--line-2); + } + .delete-output { + width: 26px; + padding: 0; + justify-content: center; + color: var(--err); + background: transparent; + border-color: transparent; + } + .canvas-btn:disabled, + .canvas-btn[aria-disabled='true'] { + opacity: 0.45; + cursor: not-allowed; + } .canvas-btn:hover { color: var(--text); - background: #ffffff0a; + background: var(--bg-3); } .canvas-btn[aria-pressed='true'], .play-button { @@ -477,6 +631,7 @@ border-color: var(--accent-line); } .stage { + grid-row: 2; min-height: 0; min-width: 0; position: relative; @@ -485,7 +640,7 @@ .stage-media { position: absolute; inset: 0; - padding: 8px 20px; + padding: 0; display: grid; place-items: center; min-height: 0; @@ -503,6 +658,20 @@ background: none; box-shadow: none; } + .big-play { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 64px; + height: 64px; + border-radius: 50%; + border: 1px solid var(--on-media-line); + color: var(--on-media); + background: var(--media-scrim); + font-size: 26px; + cursor: pointer; + } .print img, .print video { width: 100%; @@ -540,7 +709,7 @@ .comparison-layer { position: absolute; inset: 0; - background: #17171d; + background: var(--bg-0); pointer-events: none; } .comparison-divider { @@ -552,7 +721,10 @@ pointer-events: none; } .artboard-footer { - justify-content: flex-start; + grid-row: 3; + justify-content: center; + gap: 6px; + padding: 0; font-size: 10px; color: var(--text-3); } @@ -578,7 +750,7 @@ gap: 4px; max-width: 90%; overflow: auto; - background: #101015d9; + background: var(--media-scrim); border: 1px solid var(--line); padding: 4px; border-radius: 8px; diff --git a/calliope-web/src/lib/components/desk/DeskWorkflowRail.svelte b/calliope-web/src/lib/components/desk/DeskWorkflowRail.svelte index cc937438..fc11b8c2 100644 --- a/calliope-web/src/lib/components/desk/DeskWorkflowRail.svelte +++ b/calliope-web/src/lib/components/desk/DeskWorkflowRail.svelte @@ -1,13 +1,16 @@ + +