Square Library modal thumbs and add stage previous/next.

This commit is contained in:
Towsty
2026-09-28 20:40:45 -05:00
parent a33cadb6c2
commit f9903ed76e
4 changed files with 203 additions and 34 deletions
+50 -22
View File
@@ -218,6 +218,21 @@
</button>
</div>
<button
v-if="family.length > 1"
type="button"
class="stage-nav stage-nav-prev"
aria-label="Previous"
@click.stop="stepFamily(-1)"
>‹</button>
<button
v-if="family.length > 1"
type="button"
class="stage-nav stage-nav-next"
aria-label="Next"
@click.stop="stepFamily(1)"
>›</button>
<template v-if="selected && isHidden(selected)">
<p class="placeholder-still">{{ selected.kind==='track' ? 'Track loaded · preview hidden' : selected.role==='input' ? 'Still loaded · preview hidden' : 'Output hidden' }}</p>
</template>
@@ -440,7 +455,7 @@ const studioQueue=ref<any[]>([])
const liveMusic=ref<Record<string,any>>({})
const negativeDraft=ref('')
const libraryOpen=ref(false)
const previewForcedHidden=ref<Set<string>>(new Set())
const eyeOpen=ref(!xaigen)
const form=reactive<any>({mode:'generate',engine:'flux',enhancePrompt:readStoredEnhancePrompt(),turbo:readStoredTurbo(),imageStyles:{positive:[],negative:readStoredNegatives()},lockFace:true,lockOutfit:true,hideInput:xaigen,folderId:'',identityStillId:'',imageAId:'',imageBId:'',maskId:'',startClipId:'',endStillId:'',guides:[],photos:[],promptSections:sectionsFrom({}),settings:{aspect:'auto',fps:24,steps:24,cfg:4,seed:null,seedMode:'random',duration:5,turbo:true,loraStack:[]}})
const promptArea=ref<HTMLTextAreaElement|null>(null)
const promptCaret=ref({start:0,end:0})
@@ -798,30 +813,31 @@ function isHidden(asset:any){
const input=asset.role==='input'
return input?classicHideInput.value:classicHideOutput.value
}
if(xaigen) return previewHidden(asset,revealed.value,xaigen)
return previewForcedHidden.value.has(asset.id)
// Session chrome: eye gates the stage preview across neighbors.
return !eyeOpen.value
}
function pickerThumbHidden(asset:any){
if(pickerRevealAll.value)return false
return isHidden(asset)
if(!asset?.id)return false
if(isClassic.value)return classicHideInput.value
if(xaigen) return previewHidden(asset,revealed.value,xaigen)
return false
}
function slotHidden(id:string){
if(!xaigen || !id)return false
if(isClassic.value)return classicHideInput.value
return !revealed.value.has(id)
}
function toggleReveal(id:string){
if(!id)return
if(xaigen){
const next=new Set(revealed.value)
if(next.has(id))next.delete(id);else next.add(id)
revealed.value=next
if(import.meta.client)localStorage.setItem(`studio2-revealed:${form.folderId}`,JSON.stringify([...next]))
return
}
const next=new Set(previewForcedHidden.value)
if(next.has(id))next.delete(id);else next.add(id)
previewForcedHidden.value=next
function toggleReveal(_id?:string){
eyeOpen.value=!eyeOpen.value
}
function stepFamily(delta:number){
const list=family.value
if(list.length < 2)return
const i=list.findIndex((a:any)=>a.id===selected.value?.id)
const from=i < 0 ? 0 : i
const next=list[(from + delta + list.length) % list.length]
if(next) selectOutput(next)
}
function onLibraryUseAsPhoto(item:any){
if(item)selectOutput(item)
@@ -839,14 +855,25 @@ function onLibraryDelete(item:any){
if(item)selectOutput(item)
void deleteOutput()
}
function onLibraryHotkey(e:KeyboardEvent){
if(e.key!=='l' && e.key!=='L')return
function onStageHotkey(e:KeyboardEvent){
if(e.metaKey||e.ctrlKey||e.altKey)return
const t=e.target as HTMLElement|null
if(t && (t.tagName==='INPUT'||t.tagName==='TEXTAREA'||t.isContentEditable))return
if(libraryOpen.value||picker.value)return
e.preventDefault()
libraryOpen.value=true
if(e.key==='l' || e.key==='L'){
e.preventDefault()
libraryOpen.value=true
return
}
if(e.key==='ArrowLeft'){
e.preventDefault()
stepFamily(-1)
return
}
if(e.key==='ArrowRight'){
e.preventDefault()
stepFamily(1)
}
}
function closePicker(){
const key=picker.value
@@ -1022,11 +1049,11 @@ onMounted(async()=>{
if(asset){selected.value={...asset,kind:route.query.clip?'clip':route.query.track?'track':'still'};if(route.query.use==='1'&&selected.value.kind!=='track')restore(selected.value);if(xaigen)toggleReveal(asset.id)}
if(route.query.task==='music')form.mode='music'
const poll=async()=>{try{await refresh();const health=await $fetch<any>('/api/health');healthState.value=health}catch(e:any){healthState.value=null;error.value=e.data?.statusMessage||e.message}if(!disposed)timer=setTimeout(poll,3000)};void poll()
if(import.meta.client)window.addEventListener('keydown',onLibraryHotkey)
if(import.meta.client)window.addEventListener('keydown',onStageHotkey)
})
onBeforeUnmount(()=>{
disposed=true;clearTimeout(timer);clearInterval(clock)
if(import.meta.client)window.removeEventListener('keydown',onLibraryHotkey)
if(import.meta.client)window.removeEventListener('keydown',onStageHotkey)
})
function randomizeSeed(){form.settings.seedMode='random';form.settings.seed=randomSeed()}
@@ -1078,6 +1105,7 @@ const mobileCtx=computed(()=>({
toggleReveal,
chooseTask,
selectOutput,
stepFamily,
deleteOutput,
queue,
upload,