Add Reveal all / Hide all to Studio 2 input picker.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-20 21:04:55 -05:00
co-authored by Cursor
parent 52a01e0adc
commit 206984b6ec
2 changed files with 26 additions and 5 deletions
+24 -5
View File
@@ -294,13 +294,23 @@
</aside>
</div>
<div v-if="picker" class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-6" @click.self="picker=''">
<div v-if="picker" class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-6" @click.self="closePicker">
<section role="dialog" aria-modal="true" aria-label="Choose source asset" class="max-h-[80vh] w-full max-w-4xl overflow-auto rounded-[var(--radius)] border border-[var(--line)] bg-[var(--panel)] p-5">
<div class="mb-4 flex justify-between"><h2>Choose {{ picker==='identityStillId'?'hero':'source' }}</h2><button type="button" @click="picker=''">Close</button></div>
<button type="button" class="mb-4 text-[var(--type-sm)] text-[var(--muted)]" @click="pickAsset(null)">Clear slot</button>
<div class="mb-4 flex justify-between"><h2>Choose {{ picker==='identityStillId'?'hero':'source' }}</h2><button type="button" @click="closePicker">Close</button></div>
<div class="mb-4 flex flex-wrap items-center gap-3">
<button type="button" class="text-[var(--type-sm)] text-[var(--muted)]" @click="pickAsset(null)">Clear slot</button>
<button
v-if="xaigen && pickerAssets.length"
type="button"
class="text-[var(--type-sm)]"
:class="pickerRevealAll ? 'text-[var(--amber)]' : 'text-[var(--muted)]'"
:title="pickerRevealAll ? 'Hide all thumbnails in this picker' : 'Reveal all thumbnails in this picker'"
@click="pickerRevealAll=!pickerRevealAll"
>{{ pickerRevealAll ? 'Hide all' : 'Reveal all' }}</button>
</div>
<div class="grid grid-cols-2 gap-3 sm:grid-cols-4 lg:grid-cols-5">
<button v-for="a in pickerAssets" :key="a.id" type="button" class="rounded-[var(--radius)] border border-[var(--line)] p-2 text-left" @click="pickAsset(a)">
<span v-if="isHidden(a)" class="flex h-24 items-center text-xs text-[var(--muted)]">Still loaded · preview hidden</span>
<span v-if="pickerThumbHidden(a)" class="flex h-24 items-center text-xs text-[var(--muted)]">Still loaded · preview hidden</span>
<img v-else :src="a.kind==='clip'?'/api/library/clips/'+a.id+'/thumb':stillUrl(a.id)" class="h-24 w-full object-contain" alt="">
<span class="mt-2 block truncate text-xs">{{ assetLabel(a) }}</span>
</button>
@@ -331,6 +341,7 @@ const {isMobile}=useIsMobile()
const classicHideInput=ref(true)
const classicHideOutput=ref(true)
const picker=ref('')
const pickerRevealAll=ref(false)
const uploading=ref(false),uploadMessage=ref('')
const revealed=ref<Set<string>>(new Set())
const measured=reactive<Record<string,{width:number;height:number}>>({}),seenOutputs=new Set<string>()
@@ -500,6 +511,10 @@ function isHidden(asset:any){
}
return previewHidden(asset,revealed.value,xaigen)
}
function pickerThumbHidden(asset:any){
if(pickerRevealAll.value)return false
return isHidden(asset)
}
function slotHidden(id:string){
if(!xaigen || !id)return false
if(isClassic.value)return classicHideInput.value
@@ -512,6 +527,10 @@ function toggleReveal(id:string){
revealed.value=next
if(import.meta.client)localStorage.setItem(`studio2-revealed:${form.folderId}`,JSON.stringify([...next]))
}
function closePicker(){
picker.value=''
pickerRevealAll.value=false
}
function loadRevealed(folderId:string){
if(!import.meta.client||!xaigen){revealed.value=new Set();return}
try{revealed.value=new Set(JSON.parse(localStorage.getItem(`studio2-revealed:${folderId}`)||'[]'))}
@@ -542,7 +561,7 @@ function selectOutput(item:any){selected.value=item;if(form.mode==='extend' && i
function restore(item:any){Object.assign(form,restoreAll(form,item,xaigen,loraMemory));form.guides=form.guides||[];batchText.value=''}
function focusJob(job:any){selectOutput(job.outputs?.at(-1)||null);focusedJobId.value=job.id}
async function loadLoras(fresh=false){try{loraNames.value=await $fetch('/api/studio-2/loras'+(fresh?'?fresh=1':''))}catch{error.value='Could not refresh LoRAs. The last loaded list is still available.'}}
function pickAsset(asset:any){const key=picker.value;if(key.startsWith('guide:'))form.guides[Number(key.split(':')[1])].stillId=asset?.id||'';else if(key==='startClipId')chooseClip(asset?.id||'');else form[key]=asset?.id||'';picker.value=''}
function pickAsset(asset:any){const key=picker.value;if(key.startsWith('guide:'))form.guides[Number(key.split(':')[1])].stillId=asset?.id||'';else if(key==='startClipId')chooseClip(asset?.id||'');else form[key]=asset?.id||'';closePicker()}
async function clearFinished(){await $fetch('/api/studio-2/clear-finished',{method:'POST'});await refresh()}
async function deleteOutput(){
if(!selected.value)return
+2
View File
@@ -28,6 +28,8 @@ test('hidden xAIGen canvas and family render no image or video elements',async()
const src=readFileSync(new URL('../components/studio2/Bench.vue',import.meta.url),'utf8')
assert.match(src,/aria-label="Current family outputs"/)
assert.match(src,/preview hidden/)
assert.match(src,/Reveal all/)
assert.match(src,/pickerRevealAll/)
assert.doesNotMatch(src,/Hide input stills/)
assert.doesNotMatch(src,/Hide output preview/)
})