Replace the under-stage filmstrip with a Library modal and stage eye.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-28 20:11:14 -05:00
co-authored by Cursor
parent e06afe4a17
commit 7242d0faef
5 changed files with 601 additions and 221 deletions
+62 -104
View File
@@ -48,15 +48,23 @@
<div v-else class="m-chips"><span class="m-chip on">YuE2</span></div>
<div class="m-stage">
<button
v-if="ctx.selected && ctx.xaigen"
type="button"
class="m-eye"
:aria-label="ctx.isHidden(ctx.selected) ? 'Show preview' : 'Hide preview'"
@click.stop="ctx.toggleReveal(ctx.selected.id)"
>
<img :src="ctx.isHidden(ctx.selected) ? '/icons/display/eye-off.svg' : '/icons/display/eye.svg'" alt="" width="16" height="16">
</button>
<div class="m-stage-chrome" aria-label="Stage controls">
<button
type="button"
class="m-library"
:aria-expanded="ctx.libraryOpen ? 'true' : 'false'"
@click="ctx.openLibrary()"
>Library<span v-if="ctx.familyStillCount" class="m-library-count">{{ ctx.familyStillCount }}</span></button>
<button
v-if="ctx.selected"
type="button"
class="m-eye"
:aria-label="ctx.isHidden(ctx.selected) ? 'Show preview' : 'Hide preview'"
@click.stop="ctx.toggleReveal(ctx.selected.id)"
>
<img :src="ctx.isHidden(ctx.selected) ? '/icons/display/eye-off.svg' : '/icons/display/eye.svg'" alt="" width="20" height="20">
</button>
</div>
<template v-if="ctx.selected && ctx.isHidden(ctx.selected)">
<p class="m-muted">{{ ctx.selected.kind==='track' ? 'Track loaded · preview hidden' : 'Preview hidden' }}</p>
</template>
@@ -73,46 +81,22 @@
playsinline
/>
<img v-else-if="ctx.selected" :src="ctx.stillUrl(ctx.selected.id)" alt="Selected still">
<p v-else class="m-muted">Select a still or clip</p>
<p v-else class="m-muted">Open Library to pick a still or clip</p>
</div>
<div class="m-strip" aria-label="Family outputs">
<div
v-for="o in ctx.family"
:key="o.id"
class="m-tile"
:class="{ on: ctx.selected?.id===o.id }"
>
<button type="button" class="m-tile-pick" @click="ctx.selectOutput(o)">
<span v-if="ctx.isHidden(o)" class="m-tile-hid">Hidden</span>
<span v-else-if="o.kind==='track'" class="m-tile-hid">Audio</span>
<img v-else :src="o.kind==='clip' ? `/api/library/clips/${o.id}/thumb` : ctx.stillUrl(o.id)" alt="">
</button>
<button
v-if="ctx.xaigen"
type="button"
class="m-tile-eye"
:aria-label="ctx.isHidden(o) ? 'Show tile' : 'Hide tile'"
@click.stop="ctx.toggleReveal(o.id)"
>
<img :src="ctx.isHidden(o) ? '/icons/display/eye-off.svg' : '/icons/display/eye.svg'" alt="" width="12" height="12">
</button>
<button
v-if="ctx.selected?.id===o.id"
type="button"
class="m-tile-trash"
aria-label="Delete selected"
@click.stop="ctx.deleteOutput"
>🗑</button>
</div>
</div>
<div v-if="ctx.selected && ctx.selected.kind!=='track'" class="m-strip-actions">
<button type="button" @click="ctx.applyUseAsSource">Use as photo</button>
<button type="button" @click="ctx.copySelectedPrompt">Copy prompt</button>
<button type="button" @click="ctx.loadSelectedJob">Load</button>
<button type="button" @click="ctx.deleteOutput">Delete</button>
</div>
<Studio2LibraryModal
:open="!!ctx.libraryOpen"
:assets="ctx.family || []"
:selected="ctx.selected"
:folder-id="ctx.form?.folderId"
:is-hidden="ctx.isHidden"
@close="ctx.closeLibrary()"
@select="ctx.selectOutput"
@use-as-photo="ctx.onLibraryUseAsPhoto"
@copy-prompt="ctx.onLibraryCopyPrompt"
@load="ctx.onLibraryLoad"
@delete="ctx.onLibraryDelete"
/>
<div class="m-dock">
<button type="button" :class="{ on: sheet==='slots' }" @click="toggleSheet('slots')">Photos</button>
@@ -416,56 +400,46 @@ watch(() => props.ctx.form.mode, () => { sheet.value = '' })
object-fit: contain;
}
.m-audio { width: 100%; max-width: 100%; padding: 12px; }
.m-eye {
.m-stage-chrome {
position: absolute;
top: 8px;
right: 8px;
left: 8px;
z-index: 5;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
pointer-events: none;
}
.m-stage-chrome > * { pointer-events: auto; }
.m-library {
display: inline-flex;
min-height: 48px;
align-items: center;
gap: 6px;
border: 1px solid var(--line);
border-radius: 8px;
background: #000a;
padding: 0 14px;
color: var(--text);
font: inherit;
font-weight: 600;
}
.m-library-count {
color: var(--muted);
font-weight: 500;
}
.m-eye {
display: grid;
width: 28px;
height: 28px;
width: 48px;
height: 48px;
flex-shrink: 0;
place-items: center;
border: 1px solid var(--line);
border-radius: 6px;
border-radius: 8px;
background: #000a;
}
.m-strip {
display: flex;
height: 64px;
flex-shrink: 0;
gap: 6px;
overflow-x: auto;
border-top: 1px solid var(--line);
padding: 8px 12px;
}
.m-tile {
position: relative;
width: 72px;
height: 48px;
flex: 0 0 auto;
overflow: hidden;
border: 2px solid transparent;
border-radius: 6px;
background: #222;
}
.m-tile.on { border-color: var(--amber); }
.m-tile-pick { display: block; width: 100%; height: 100%; border: 0; padding: 0; background: transparent; }
.m-tile img { width: 100%; height: 100%; object-fit: cover; }
.m-tile-hid { display: grid; height: 100%; place-items: center; color: var(--muted); font-size: 10px; }
.m-tile-eye, .m-tile-trash {
position: absolute;
top: 2px;
right: 2px;
display: grid;
width: 16px;
height: 16px;
place-items: center;
border: 0;
border-radius: 3px;
background: #000a;
font-size: 10px;
}
.m-tile-trash { right: 20px; }
.m-dock {
display: grid;
grid-template-columns: repeat(4, 1fr) 120px;
@@ -564,22 +538,6 @@ watch(() => props.ctx.form.mode, () => { sheet.value = '' })
.m-neg-chip button { border: 0; background: transparent; color: inherit; }
.m-neg-add { display: flex; gap: 8px; align-items: center; margin-top: 8px; }
.m-neg-add .m-input { flex: 1; margin-top: 0; }
.m-strip-actions {
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 8px 10px;
border-top: 1px solid var(--line);
background: var(--panel);
}
.m-strip-actions button {
border: 1px solid var(--line);
border-radius: 6px;
background: transparent;
color: var(--text);
padding: 4px 8px;
font-size: 12px;
}
.m-seed { display: flex; gap: 8px; margin-top: 4px; }
.m-seed .m-input { margin-top: 0; flex: 1; }
.m-dice {