Add batch still arrows, hide output only on tap, and cut studio lag.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-08-31 07:24:32 -05:00
co-authored by Cursor
parent f51fd4b159
commit 6624fb6f93
8 changed files with 367 additions and 61 deletions
+34 -1
View File
@@ -206,10 +206,17 @@
alt=""
class="aspect-video w-full object-cover"
>
<FamilyNav
:count="focusedNavParts.length"
:index="focusedNavIndex"
:prev-label="focused?.kind === 'clip' ? 'Previous clip' : 'Previous image'"
:next-label="focused?.kind === 'clip' ? 'Next clip' : 'Next image'"
@step="stepFocusedFamily"
/>
<button
v-if="showPrivacyToggles"
type="button"
class="absolute right-2 top-2 z-10 flex h-8 w-8 items-center justify-center rounded-full border border-white/20 bg-black/60 text-zinc-100 hover:border-amber-300/50 hover:text-amber-100"
class="absolute right-2 top-2 z-30 flex h-8 w-8 items-center justify-center rounded-full border border-white/20 bg-black/60 text-zinc-100 hover:border-amber-300/50 hover:text-amber-100"
:title="detailsHidden ? 'Show preview' : 'Hide preview'"
:aria-label="detailsHidden ? 'Show preview' : 'Hide preview'"
@click.stop="detailsHidden = !detailsHidden"
@@ -329,6 +336,8 @@
<script setup lang="ts">
import {
buildLibraryEntries,
chronologicalParts,
chronologicalStills,
formatClipDuration,
formatWhen,
type LibraryClip,
@@ -458,6 +467,17 @@ const focusedParts = computed(() => {
if (focused.value?.kind === 'clip') return focused.value.group?.parts || []
return focused.value?.stillGroup?.parts || []
})
const focusedNavParts = computed(() => {
const parts = focusedParts.value
if (parts.length < 2) return []
if (focused.value?.kind === 'still') return chronologicalStills(parts as LibraryStill[])
return chronologicalParts(parts as LibraryClip[])
})
const focusedNavIndex = computed(() => {
const id = focusedPartId.value
const index = focusedNavParts.value.findIndex(part => part.id === id)
return index < 0 ? 0 : index
})
const detailPrompt = computed(() => {
if (focused.value?.kind === 'clip') return focused.value.group?.head.prompt || ''
return focused.value?.stillGroup?.head.prompt || ''
@@ -559,6 +579,13 @@ function focusPart(id: string) {
focusedPartId.value = id
}
function stepFocusedFamily(delta: number) {
const parts = focusedNavParts.value
if (parts.length < 2) return
const next = parts[(focusedNavIndex.value + delta + parts.length) % parts.length]
if (next) focusPart(next.id)
}
function onScroll() {
if (scrollRaf) return
scrollRaf = requestAnimationFrame(() => {
@@ -806,6 +833,8 @@ async function lockFolder() {
}
function onKey(event: KeyboardEvent) {
const target = event.target as HTMLElement | null
const typing = Boolean(target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT' || target.isContentEditable))
if (event.key === 'Escape') {
if (pendingDelete.value) pendingDelete.value = null
else if (renaming.value) cancelRename()
@@ -816,6 +845,10 @@ function onKey(event: KeyboardEvent) {
selectAll()
}
if (event.key === 'Delete' && selectedKeys.value.length && !renaming.value) askRemoveSelected()
if (!typing && (event.key === 'ArrowLeft' || event.key === 'ArrowRight') && focusedNavParts.value.length > 1) {
event.preventDefault()
stepFocusedFamily(event.key === 'ArrowLeft' ? -1 : 1)
}
}
watch(searchDraft, (value) => {