Put still actions on the stage and split output vs input hide.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-28 21:26:29 -05:00
co-authored by Cursor
parent f9903ed76e
commit d61f99d6fd
4 changed files with 268 additions and 69 deletions
+91 -34
View File
@@ -49,21 +49,33 @@
<div class="m-stage">
<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)"
<div class="m-stage-chrome-top">
<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.eyeOpen ? 'Hide output' : 'Show output'"
@click.stop="ctx.toggleReveal()"
>
<img :src="ctx.eyeOpen ? '/icons/display/eye.svg' : '/icons/display/eye-off.svg'" alt="" width="20" height="20">
</button>
</div>
<div
v-if="ctx.selected && ctx.selected.kind!=='track'"
class="m-stage-actions"
aria-label="Selected still actions"
>
<img :src="ctx.isHidden(ctx.selected) ? '/icons/display/eye-off.svg' : '/icons/display/eye.svg'" alt="" width="20" height="20">
</button>
<button type="button" @click.stop="ctx.applyUseAsSource">Use as photo</button>
<button type="button" @click.stop="ctx.copySelectedPrompt">Copy prompt</button>
<button type="button" @click.stop="ctx.loadSelectedJob">Load</button>
<button type="button" class="m-danger" @click.stop="ctx.deleteOutput">Delete</button>
</div>
</div>
<button
v-if="(ctx.family || []).length > 1"
@@ -80,7 +92,7 @@
@click.stop="ctx.stepFamily(1)"
>›</button>
<template v-if="ctx.selected && ctx.isHidden(ctx.selected)">
<p class="m-muted">{{ ctx.selected.kind==='track' ? 'Track loaded · preview hidden' : 'Preview hidden' }}</p>
<p class="m-muted">{{ ctx.selected.kind==='track' ? 'Track loaded · preview hidden' : 'Output hidden' }}</p>
</template>
<template v-else-if="ctx.selected?.kind==='track'">
<div class="m-audio">
@@ -103,13 +115,14 @@
:assets="ctx.family || []"
:selected="ctx.selected"
:folder-id="ctx.form?.folderId"
:is-hidden="ctx.isHidden"
:preview-hidden="!ctx.eyeOpen"
@close="ctx.closeLibrary()"
@select="ctx.selectOutput"
@use-as-photo="ctx.onLibraryUseAsPhoto"
@copy-prompt="ctx.onLibraryCopyPrompt"
@load="ctx.onLibraryLoad"
@delete="ctx.onLibraryDelete"
@toggle-preview="ctx.toggleReveal()"
/>
<div class="m-dock">
@@ -133,21 +146,39 @@
<p class="m-muted">Continues from last frame</p>
</template>
<template v-else>
<p v-if="!ctx.form.photos?.length && ctx.photosPlanState.emptyHint" class="m-muted">{{ ctx.photosPlanState.emptyHint }}</p>
<Studio2AssetSlot
v-for="(card, index) in ctx.form.photos"
:key="'m-photo-'+index+'-'+card.stillId"
:asset="ctx.stillById(card.stillId)"
:hidden="ctx.slotHidden(card.stillId)"
:role="card.role"
:role-options="ctx.photoRoleOptions"
@pick="ctx.setPicker('photo:'+index)"
@remove="ctx.removePhoto(index)"
@mention="ctx.mentionPhoto(index)"
@update:role="ctx.setPhotoRole(index, $event)"
/>
<button v-if="(ctx.form.photos?.length||0) < ctx.photosPlanState.max" type="button" class="m-amber" @click="ctx.addPhoto">+ Add photo</button>
<p v-if="ctx.photoRoleWarning" class="m-hint">{{ ctx.photoRoleWarning }}</p>
<div class="m-photos-head">
<button type="button" class="m-link" @click="ctx.toggleHideInputs()">
{{ ctx.hideInputs ? 'Show inputs' : 'Hide inputs' }}
</button>
</div>
<template v-if="ctx.hideInputs">
<p class="m-muted">
{{ ctx.photoFilledCount || 0 }} photo{{ (ctx.photoFilledCount||0)===1 ? '' : 's' }}
<button
v-if="(ctx.form.photos?.length||0) < ctx.photosPlanState.max"
type="button"
class="m-amber"
@click="ctx.showInputsAndAddPhoto()"
>+ Add photo</button>
</p>
</template>
<template v-else>
<p v-if="!ctx.form.photos?.length && ctx.photosPlanState.emptyHint" class="m-muted">{{ ctx.photosPlanState.emptyHint }}</p>
<Studio2AssetSlot
v-for="(card, index) in ctx.form.photos"
:key="'m-photo-'+index+'-'+card.stillId"
:asset="ctx.stillById(card.stillId)"
:hidden="false"
:role="card.role"
:role-options="ctx.photoRoleOptions"
@pick="ctx.setPicker('photo:'+index)"
@remove="ctx.removePhoto(index)"
@mention="ctx.mentionPhoto(index)"
@update:role="ctx.setPhotoRole(index, $event)"
/>
<button v-if="(ctx.form.photos?.length||0) < ctx.photosPlanState.max" type="button" class="m-amber" @click="ctx.addPhoto">+ Add photo</button>
<p v-if="ctx.photoRoleWarning" class="m-hint">{{ ctx.photoRoleWarning }}</p>
</template>
<template v-if="ctx.showIdentityLocks">
<label class="m-check"><input v-model="ctx.form.lockFace" type="checkbox"> Lock face</label>
<label class="m-check"><input v-model="ctx.form.lockOutfit" type="checkbox"> Lock outfit</label>
@@ -421,12 +452,20 @@ watch(() => props.ctx.form.mode, () => { sheet.value = '' })
left: 8px;
z-index: 5;
display: flex;
align-items: center;
justify-content: space-between;
flex-direction: column;
gap: 8px;
pointer-events: none;
}
.m-stage-chrome > * { pointer-events: auto; }
.m-stage-chrome-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.m-stage-chrome-top > *,
.m-stage-actions {
pointer-events: auto;
}
.m-library {
display: inline-flex;
min-height: 48px;
@@ -454,6 +493,23 @@ watch(() => props.ctx.form.mode, () => { sheet.value = '' })
border-radius: 8px;
background: #000a;
}
.m-stage-actions {
display: flex;
flex-wrap: wrap;
gap: 6px;
max-width: calc(100% - 56px);
}
.m-stage-actions button {
min-height: 44px;
border: 1px solid var(--line);
border-radius: 8px;
background: #000a;
color: var(--text);
padding: 0 10px;
font: inherit;
font-size: 12px;
}
.m-stage-actions .m-danger { color: #fca5a5; }
.m-nav {
position: absolute;
top: 50%;
@@ -472,6 +528,7 @@ watch(() => props.ctx.form.mode, () => { sheet.value = '' })
}
.m-nav-prev { left: 8px; }
.m-nav-next { right: 8px; }
.m-photos-head { margin-bottom: 8px; }
.m-dock {
display: grid;
grid-template-columns: repeat(4, 1fr) 120px;