Add Prompt Builder sets/framing and harden mobile draft look settings.

Sets and beats filter by shot size; scenes reorder/delete; portrait links Prompt Builder; landscape queue collapses; per-engine look settings and prompt Clear persist across iOS resumes.
This commit is contained in:
Towsty
2026-10-03 19:31:22 -05:00
parent 7799529788
commit a8ae61704e
9 changed files with 1441 additions and 760 deletions
+78 -4
View File
@@ -1,5 +1,5 @@
<template>
<div class="m-shell" data-mobile="1">
<div class="m-shell" data-mobile="1" :data-queue-collapsed="queueRailCollapsed ? '1' : undefined">
<header class="m-header">
<span class="m-brand">{{ ctx.instanceName }}</span>
<label class="m-crumb">
@@ -9,11 +9,15 @@
</label>
<span class="m-grow" />
<span class="m-gpu" :data-tone="ctx.gpu.tone" :title="ctx.gpu.detail">● {{ ctx.gpuChipLabel }}</span>
<button type="button" class="m-chip" @click="toggleSheet('queue')">Queue {{ ctx.queueCount }}</button>
<button type="button" class="m-chip m-queue-chip-portrait" @click="toggleSheet('queue')">Queue {{ ctx.queueCount }}</button>
<button type="button" class="m-chip m-queue-chip-landscape" :aria-expanded="queueRailCollapsed ? 'false' : 'true'" @click="queueRailCollapsed=!queueRailCollapsed">
Queue {{ ctx.queueCount }} {{ queueRailCollapsed ? '▸' : '▾' }}
</button>
<details class="m-more">
<summary aria-label="More">⋯</summary>
<div class="m-more-menu">
<NuxtLink to="/library" @click="closeMenus">Library</NuxtLink>
<NuxtLink to="/prompt-builder" @click="closeMenus">Prompt Builder</NuxtLink>
<NuxtLink to="/queue" @click="closeMenus">Queue</NuxtLink>
<NuxtLink to="/settings" @click="closeMenus">Settings</NuxtLink>
</div>
@@ -224,7 +228,8 @@
<label class="m-label">Lyrics<textarea v-model="ctx.music.lyrics" rows="8" class="m-area" /></label>
</template>
<template v-else>
<label class="m-label">Prompt
<label class="m-label">
<span class="m-label-row">Prompt<button type="button" class="m-link" @click.stop="ctx.clearPrompt()">Clear</button></span>
<textarea
:value="ctx.form.promptSections.action"
rows="5"
@@ -329,7 +334,7 @@
</div>
</aside>
<aside v-if="sheet==='queue'" class="m-sheet" role="dialog" aria-label="Queue">
<aside v-if="sheet==='queue'" class="m-sheet m-queue-sheet-portrait" role="dialog" aria-label="Queue">
<button type="button" class="m-handle" aria-label="Close" @click="sheet=''" />
<div class="m-sheet-head"><strong>Queue</strong><button type="button" @click="sheet=''">Done</button></div>
<div class="m-sheet-body">
@@ -367,6 +372,33 @@
</div>
</aside>
<aside v-show="!queueRailCollapsed" class="m-queue-rail" aria-label="Queue">
<div class="m-sheet-head">
<strong>Queue</strong>
<button type="button" @click="queueRailCollapsed=true">Hide</button>
</div>
<div class="m-sheet-body">
<div
v-for="card in ctx.queueCards"
:key="'rail-'+card.id"
class="m-job"
:class="{ on: ctx.focusedJobId===card.id }"
@click="ctx.focusQueueCard(card)"
>
<div class="m-job-id">{{ card.code }}</div>
<div class="m-job-title">{{ card.title }}</div>
<div class="m-muted">{{ card.meta }}</div>
<div class="m-job-status">
<span>● {{ card.statusLabel }}</span>
<button v-if="card.cancelable" type="button" @click.stop="ctx.cancel(card.job)">Cancel</button>
<button v-else-if="card.dismissable" type="button" @click.stop="ctx.dismissFinishedCard(card)">Dismiss</button>
</div>
</div>
<p v-if="!ctx.queueCards.length" class="m-muted">No active jobs</p>
<button v-if="ctx.canClearFinished" type="button" class="m-link" @click="ctx.clearFinished">Clear finished</button>
</div>
</aside>
<div
v-if="promptEditorOpen"
class="m-prompt-editor"
@@ -393,6 +425,7 @@ export type Studio2MobileCtx = Record<string, any>
const props = defineProps<{ ctx: Studio2MobileCtx }>()
const sheet = ref('')
const queueRailCollapsed = ref(false)
const touchStart = ref<{ x: number; y: number } | null>(null)
const promptEditorOpen = ref(false)
const promptEditorDraft = ref('')
@@ -725,6 +758,7 @@ watch(() => props.ctx.form.mode, () => { sheet.value = '' })
gap: 10px;
}
.m-label { display: block; font-size: 11px; color: var(--muted); }
.m-label-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
.m-area, .m-input {
width: 100%;
margin-top: 4px;
@@ -800,6 +834,46 @@ watch(() => props.ctx.form.mode, () => { sheet.value = '' })
font-size: 16px;
font-weight: 600;
}
.m-queue-chip-landscape { display: none; }
.m-queue-rail { display: none; }
@media (orientation: landscape) {
.m-queue-chip-portrait,
.m-queue-sheet-portrait { display: none !important; }
.m-queue-chip-landscape { display: inline-flex; align-items: center; }
.m-shell { position: relative; }
.m-queue-rail {
position: absolute;
top: 0;
right: 0;
bottom: 0;
z-index: 25;
display: flex;
width: min(42vw, 16rem);
flex-direction: column;
border-left: 1px solid var(--line);
background: var(--panel);
}
.m-shell[data-queue-collapsed='1'] .m-queue-rail { display: none; }
.m-queue-rail .m-sheet-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
border-bottom: 1px solid var(--line);
}
.m-queue-rail .m-sheet-body {
flex: 1;
min-height: 0;
overflow: auto;
padding: 10px 12px;
}
.m-stage { padding-right: 0; }
.m-shell:not([data-queue-collapsed='1']) .m-stage,
.m-shell:not([data-queue-collapsed='1']) .m-dock,
.m-shell:not([data-queue-collapsed='1']) .m-chips {
margin-right: min(42vw, 16rem);
}
}
.m-prompt-editor-area {
flex: 1;
width: 100%;