Make library hide/reveal instant instead of rewriting the whole catalog.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -109,7 +109,6 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="rounded-full bg-zinc-950/80 p-1.5 text-amber-100 hover:bg-zinc-900 disabled:opacity-50"
|
class="rounded-full bg-zinc-950/80 p-1.5 text-amber-100 hover:bg-zinc-900 disabled:opacity-50"
|
||||||
:title="still.hideInput ? 'Show thumbnail' : 'Hide thumbnail'"
|
:title="still.hideInput ? 'Show thumbnail' : 'Hide thumbnail'"
|
||||||
:disabled="hidingId === still.id"
|
|
||||||
@click.stop="toggleHidden(still)"
|
@click.stop="toggleHidden(still)"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="h-4 w-4">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="h-4 w-4">
|
||||||
@@ -237,6 +236,7 @@ const activeFolderId = ref(props.folderId)
|
|||||||
const tab = ref<'input' | 'output'>('input')
|
const tab = ref<'input' | 'output'>('input')
|
||||||
const revealAll = ref(false)
|
const revealAll = ref(false)
|
||||||
const revealedIds = ref<string[]>([])
|
const revealedIds = ref<string[]>([])
|
||||||
|
const forceHiddenIds = ref<string[]>([])
|
||||||
const picking = ref(false)
|
const picking = ref(false)
|
||||||
const keepingId = ref('')
|
const keepingId = ref('')
|
||||||
const hidingId = ref('')
|
const hidingId = ref('')
|
||||||
@@ -285,28 +285,42 @@ function isRevealed(id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function thumbBlurred(still: PickerStill) {
|
function thumbBlurred(still: PickerStill) {
|
||||||
|
if (forceHiddenIds.value.includes(still.id)) return true
|
||||||
return stillHidden(still) && !isRevealed(still.id)
|
return stillHidden(still) && !isRevealed(still.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleHidden(still: PickerStill) {
|
async function toggleHidden(still: PickerStill) {
|
||||||
if (hidingId.value) return
|
if (hidingId.value === still.id) return
|
||||||
|
const nextHide = still.hideInput !== true
|
||||||
|
const previous = still.hideInput === true
|
||||||
|
still.hideInput = nextHide
|
||||||
|
if (nextHide) {
|
||||||
|
revealedIds.value = revealedIds.value.filter(id => id !== still.id)
|
||||||
|
if (revealAll.value && !forceHiddenIds.value.includes(still.id)) {
|
||||||
|
forceHiddenIds.value = [...forceHiddenIds.value, still.id]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
forceHiddenIds.value = forceHiddenIds.value.filter(id => id !== still.id)
|
||||||
|
}
|
||||||
hidingId.value = still.id
|
hidingId.value = still.id
|
||||||
try {
|
try {
|
||||||
emit('updated', await $fetch(`/api/library/stills/${still.id}`, {
|
emit('updated', await $fetch(`/api/library/stills/${still.id}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: { hideInput: still.hideInput !== true }
|
body: { hideInput: nextHide }
|
||||||
}))
|
}))
|
||||||
revealedIds.value = revealedIds.value.filter(id => id !== still.id)
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
still.hideInput = previous
|
||||||
|
forceHiddenIds.value = forceHiddenIds.value.filter(id => id !== still.id)
|
||||||
emit('error', error?.data?.statusMessage || 'Could not update thumbnail')
|
emit('error', error?.data?.statusMessage || 'Could not update thumbnail')
|
||||||
} finally {
|
} finally {
|
||||||
hidingId.value = ''
|
if (hidingId.value === still.id) hidingId.value = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleRevealAll() {
|
function toggleRevealAll() {
|
||||||
revealAll.value = !revealAll.value
|
revealAll.value = !revealAll.value
|
||||||
revealedIds.value = []
|
revealedIds.value = []
|
||||||
|
forceHiddenIds.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unlock() {
|
async function unlock() {
|
||||||
|
|||||||
+23
-1
@@ -3626,7 +3626,29 @@ function applyInstancePrivacy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyLibrary(data: { folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string>; loraTriggers?: Record<string, string>; keepPrompts?: KeepPromptLists } | unknown) {
|
function applyLibrary(data: { folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string>; loraTriggers?: Record<string, string>; keepPrompts?: KeepPromptLists } | unknown) {
|
||||||
const payload = (data || {}) as { folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string>; loraTriggers?: Record<string, string>; keepPrompts?: KeepPromptLists }
|
const payload = (data || {}) as {
|
||||||
|
id?: string
|
||||||
|
hideInput?: boolean
|
||||||
|
hideThumbnail?: boolean
|
||||||
|
folders?: LibraryFolder[]
|
||||||
|
clips?: LibraryClip[]
|
||||||
|
stills?: LibraryStill[]
|
||||||
|
drafts?: RetryDraft[]
|
||||||
|
loraAliases?: Record<string, string>
|
||||||
|
loraTriggers?: Record<string, string>
|
||||||
|
keepPrompts?: KeepPromptLists
|
||||||
|
}
|
||||||
|
if (payload.id && !payload.folders && !payload.clips && !payload.stills) {
|
||||||
|
if (typeof payload.hideInput === 'boolean') {
|
||||||
|
const still = stills.value.find(item => item.id === payload.id)
|
||||||
|
if (still) still.hideInput = payload.hideInput
|
||||||
|
}
|
||||||
|
if (typeof payload.hideThumbnail === 'boolean') {
|
||||||
|
const clip = clips.value.find(item => item.id === payload.id)
|
||||||
|
if (clip) clip.hideThumbnail = payload.hideThumbnail
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
folders.value = payload.folders || []
|
folders.value = payload.folders || []
|
||||||
clips.value = payload.clips || []
|
clips.value = payload.clips || []
|
||||||
stills.value = payload.stills || []
|
stills.value = payload.stills || []
|
||||||
|
|||||||
+61
-3
@@ -215,6 +215,7 @@
|
|||||||
v-else-if="focused.kind === 'still' && previewStillSrc"
|
v-else-if="focused.kind === 'still' && previewStillSrc"
|
||||||
:src="previewStillSrc"
|
:src="previewStillSrc"
|
||||||
alt=""
|
alt=""
|
||||||
|
decoding="async"
|
||||||
class="aspect-video w-full object-contain"
|
class="aspect-video w-full object-contain"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -236,7 +237,7 @@
|
|||||||
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"
|
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'"
|
:title="detailsHidden ? 'Show preview' : 'Hide preview'"
|
||||||
:aria-label="detailsHidden ? 'Show preview' : 'Hide preview'"
|
:aria-label="detailsHidden ? 'Show preview' : 'Hide preview'"
|
||||||
@click.stop="detailsHidden = !detailsHidden"
|
@click.stop="toggleFocusedHidden"
|
||||||
>
|
>
|
||||||
<svg v-if="detailsHidden" class="h-4 w-4" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
<svg v-if="detailsHidden" class="h-4 w-4" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||||
<path d="M3 3l18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
<path d="M3 3l18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||||||
@@ -410,6 +411,8 @@ const renameInput = ref<HTMLInputElement | null>(null)
|
|||||||
const deleteBusy = ref(false)
|
const deleteBusy = ref(false)
|
||||||
const detailsHidden = ref(false)
|
const detailsHidden = ref(false)
|
||||||
const revealAll = ref(false)
|
const revealAll = ref(false)
|
||||||
|
const hidingFocused = ref(false)
|
||||||
|
const forceHiddenIds = ref<string[]>([])
|
||||||
const pendingDelete = ref<{ title: string; name: string; detail?: string; consequence: string; keys: string[] } | null>(null)
|
const pendingDelete = ref<{ title: string; name: string; detail?: string; consequence: string; keys: string[] } | null>(null)
|
||||||
const keepingInputBusy = ref(false)
|
const keepingInputBusy = ref(false)
|
||||||
const toasts = ref<Array<{ id: number; message: string }>>([])
|
const toasts = ref<Array<{ id: number; message: string }>>([])
|
||||||
@@ -488,7 +491,7 @@ const previewVideo = computed(() => {
|
|||||||
const previewStillSrc = computed(() => {
|
const previewStillSrc = computed(() => {
|
||||||
if (focused.value?.kind !== 'still' || !focused.value.stillGroup) return ''
|
if (focused.value?.kind !== 'still' || !focused.value.stillGroup) return ''
|
||||||
const id = focusedPartId.value || focused.value.stillGroup.head.id
|
const id = focusedPartId.value || focused.value.stillGroup.head.id
|
||||||
return `/api/library/stills/${id}`
|
return `/api/library/stills/${id}?thumb=1`
|
||||||
})
|
})
|
||||||
const focusedParts = computed(() => {
|
const focusedParts = computed(() => {
|
||||||
if (focused.value?.kind === 'clip') return focused.value.group?.parts || []
|
if (focused.value?.kind === 'clip') return focused.value.group?.parts || []
|
||||||
@@ -545,7 +548,25 @@ function toast(message: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyLibrary(data: unknown) {
|
function applyLibrary(data: unknown) {
|
||||||
const payload = (data || {}) as { folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[] }
|
const payload = (data || {}) as {
|
||||||
|
id?: string
|
||||||
|
hideInput?: boolean
|
||||||
|
hideThumbnail?: boolean
|
||||||
|
folders?: LibraryFolder[]
|
||||||
|
clips?: LibraryClip[]
|
||||||
|
stills?: LibraryStill[]
|
||||||
|
}
|
||||||
|
if (payload.id && !payload.folders && !payload.clips && !payload.stills) {
|
||||||
|
if (typeof payload.hideInput === 'boolean') {
|
||||||
|
const still = stills.value.find(item => item.id === payload.id)
|
||||||
|
if (still) still.hideInput = payload.hideInput
|
||||||
|
}
|
||||||
|
if (typeof payload.hideThumbnail === 'boolean') {
|
||||||
|
const clip = clips.value.find(item => item.id === payload.id)
|
||||||
|
if (clip) clip.hideThumbnail = payload.hideThumbnail
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
folders.value = payload.folders || []
|
folders.value = payload.folders || []
|
||||||
clips.value = payload.clips || []
|
clips.value = payload.clips || []
|
||||||
stills.value = payload.stills || []
|
stills.value = payload.stills || []
|
||||||
@@ -585,6 +606,8 @@ function itemHidden(entry: LibraryEntry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function thumbHidden(entry: LibraryEntry) {
|
function thumbHidden(entry: LibraryEntry) {
|
||||||
|
const id = entry.kind === 'clip' ? entry.group?.head.id : entry.stillGroup?.head.id
|
||||||
|
if (id && forceHiddenIds.value.includes(id)) return true
|
||||||
return itemHidden(entry) && !revealAll.value
|
return itemHidden(entry) && !revealAll.value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,11 +615,46 @@ const hasHiddenThumbs = computed(() => entries.value.some(entry => itemHidden(en
|
|||||||
|
|
||||||
function toggleRevealAll() {
|
function toggleRevealAll() {
|
||||||
revealAll.value = !revealAll.value
|
revealAll.value = !revealAll.value
|
||||||
|
forceHiddenIds.value = []
|
||||||
if (focused.value && itemHidden(focused.value)) {
|
if (focused.value && itemHidden(focused.value)) {
|
||||||
detailsHidden.value = !revealAll.value
|
detailsHidden.value = !revealAll.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function toggleFocusedHidden() {
|
||||||
|
const entry = focused.value
|
||||||
|
if (!entry || hidingFocused.value) return
|
||||||
|
const hide = !detailsHidden.value
|
||||||
|
detailsHidden.value = hide
|
||||||
|
const id = entry.kind === 'clip'
|
||||||
|
? (focusedPartId.value || entry.group?.head.id || '')
|
||||||
|
: (focusedStill.value?.id || '')
|
||||||
|
if (hide && revealAll.value && id && !forceHiddenIds.value.includes(id)) {
|
||||||
|
forceHiddenIds.value = [...forceHiddenIds.value, id]
|
||||||
|
} else if (!hide && id) {
|
||||||
|
forceHiddenIds.value = forceHiddenIds.value.filter(item => item !== id)
|
||||||
|
}
|
||||||
|
hidingFocused.value = true
|
||||||
|
try {
|
||||||
|
if (entry.kind === 'clip' && entry.group) {
|
||||||
|
const id = focusedPartId.value || entry.group.head.id
|
||||||
|
const clip = clips.value.find(item => item.id === id)
|
||||||
|
if (clip) clip.hideThumbnail = hide
|
||||||
|
applyLibrary(await $fetch(`/api/library/clips/${id}`, { method: 'PATCH', body: { hideThumbnail: hide } }))
|
||||||
|
} else if (focusedStill.value) {
|
||||||
|
const id = focusedStill.value.id
|
||||||
|
const still = stills.value.find(item => item.id === id)
|
||||||
|
if (still) still.hideInput = hide
|
||||||
|
applyLibrary(await $fetch(`/api/library/stills/${id}`, { method: 'PATCH', body: { hideInput: hide } }))
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
detailsHidden.value = !hide
|
||||||
|
toast(error?.data?.statusMessage || 'Could not update thumbnail')
|
||||||
|
} finally {
|
||||||
|
hidingFocused.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function thumbSrc(entry: LibraryEntry) {
|
function thumbSrc(entry: LibraryEntry) {
|
||||||
if (entry.kind === 'clip' && entry.group?.head.hasThumbnail) return `/api/library/clips/${entry.group.head.id}/thumb`
|
if (entry.kind === 'clip' && entry.group?.head.hasThumbnail) return `/api/library/clips/${entry.group.head.id}/thumb`
|
||||||
if (entry.stillGroup) return `/api/library/stills/${entry.stillGroup.head.id}?thumb=1`
|
if (entry.stillGroup) return `/api/library/stills/${entry.stillGroup.head.id}?thumb=1`
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
family: family || hasLocks
|
family: family || hasLocks
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (hasHide) setClipHidden(owner, id, body.hideThumbnail === true, family)
|
if (hasHide) await setClipHidden(owner, id, body.hideThumbnail === true, family)
|
||||||
if (hasHide && !folderId && !hasName && !hasLocks && !hasRefs) {
|
if (hasHide && !folderId && !hasName && !hasLocks && !hasRefs) {
|
||||||
return { id, hideThumbnail: body.hideThumbnail === true }
|
return { id, hideThumbnail: body.hideThumbnail === true }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ export default defineEventHandler(async (event) => {
|
|||||||
if (family) await renameStillFamily(owner, id, body.name || '')
|
if (family) await renameStillFamily(owner, id, body.name || '')
|
||||||
else await renameStill(owner, id, body.name || '')
|
else await renameStill(owner, id, body.name || '')
|
||||||
}
|
}
|
||||||
if (hasHide) setStillHidden(owner, id, body.hideInput === true, family)
|
if (hasHide) await setStillHidden(owner, id, body.hideInput === true, family)
|
||||||
|
if (hasHide && !folderId && !hasName) {
|
||||||
|
return { id, hideInput: body.hideInput === true }
|
||||||
|
}
|
||||||
return publicLibrary(event)
|
return publicLibrary(event)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -298,7 +298,6 @@ function readCatalog(owner: string): Catalog {
|
|||||||
writeFileSync(path, JSON.stringify(catalog, null, 2))
|
writeFileSync(path, JSON.stringify(catalog, null, 2))
|
||||||
return catalog
|
return catalog
|
||||||
}
|
}
|
||||||
if (purgeStrayExtensionStills(owner, catalog).count) writeCatalog(owner, catalog)
|
|
||||||
return catalog
|
return catalog
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user