Make library hide/show instant on the grid and skip disk checks on browse.
The details pane keeps the real file so inspection is not delayed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+80
-31
@@ -26,7 +26,7 @@
|
|||||||
<label class="min-w-[12rem] flex-1 text-sm">
|
<label class="min-w-[12rem] flex-1 text-sm">
|
||||||
<span class="mb-1 block text-xs text-zinc-500">Search</span>
|
<span class="mb-1 block text-xs text-zinc-500">Search</span>
|
||||||
<input
|
<input
|
||||||
v-model="search"
|
v-model="searchDraft"
|
||||||
class="w-full rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm"
|
class="w-full rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm"
|
||||||
placeholder="Name or prompt"
|
placeholder="Name or prompt"
|
||||||
type="search"
|
type="search"
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
@drop.prevent="onFolderDrop(folder.id, $event)"
|
@drop.prevent="onFolderDrop(folder.id, $event)"
|
||||||
>
|
>
|
||||||
{{ folder.name }}
|
{{ folder.name }}
|
||||||
<span class="text-zinc-500">{{ folderOutputCount(folder, clips, stills, filter) }}</span>
|
<span class="text-zinc-500">{{ folderChipCount(folder) }}</span>
|
||||||
<span v-if="folder.protected && !folder.unlocked"> · lock</span>
|
<span v-if="folder.protected && !folder.unlocked"> · lock</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -166,15 +166,15 @@
|
|||||||
type="button"
|
type="button"
|
||||||
role="checkbox"
|
role="checkbox"
|
||||||
class="absolute bottom-2 left-2 flex h-4 w-4 items-center justify-center rounded-[4px] border shadow-[0_1px_4px_rgba(0,0,0,0.55)]"
|
class="absolute bottom-2 left-2 flex h-4 w-4 items-center justify-center rounded-[4px] border shadow-[0_1px_4px_rgba(0,0,0,0.55)]"
|
||||||
:class="selectedKeys.includes(entry.key)
|
:class="selectedSet.has(entry.key)
|
||||||
? 'border-white bg-amber-400 text-zinc-950'
|
? 'border-white bg-amber-400 text-zinc-950'
|
||||||
: 'border-white/30 bg-black/45'"
|
: 'border-white/30 bg-black/45'"
|
||||||
:aria-checked="selectedKeys.includes(entry.key)"
|
:aria-checked="selectedSet.has(entry.key)"
|
||||||
:aria-label="selectedKeys.includes(entry.key) ? 'Deselect' : 'Select'"
|
:aria-label="selectedSet.has(entry.key) ? 'Deselect' : 'Select'"
|
||||||
@click.stop="onSelectClick(entry.key, $event)"
|
@click.stop="onSelectClick(entry.key, $event)"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
v-if="selectedKeys.includes(entry.key)"
|
v-if="selectedSet.has(entry.key)"
|
||||||
class="h-3 w-3"
|
class="h-3 w-3"
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
fill="none"
|
fill="none"
|
||||||
@@ -198,9 +198,8 @@
|
|||||||
<aside class="glass w-full shrink-0 overflow-y-auto rounded-3xl p-4 sm:p-5 lg:h-full lg:w-[22rem]">
|
<aside class="glass w-full shrink-0 overflow-y-auto rounded-3xl p-4 sm:p-5 lg:h-full lg:w-[22rem]">
|
||||||
<template v-if="focused">
|
<template v-if="focused">
|
||||||
<div class="relative overflow-hidden rounded-2xl bg-zinc-900">
|
<div class="relative overflow-hidden rounded-2xl bg-zinc-900">
|
||||||
<span v-if="previewHidden" class="sensitive-glow block aspect-video w-full" />
|
|
||||||
<video
|
<video
|
||||||
v-else-if="focused.kind === 'clip' && previewVideo"
|
v-if="focused.kind === 'clip' && previewVideo"
|
||||||
:src="previewVideo"
|
:src="previewVideo"
|
||||||
class="aspect-video w-full bg-black object-contain"
|
class="aspect-video w-full bg-black object-contain"
|
||||||
controls
|
controls
|
||||||
@@ -224,7 +223,6 @@
|
|||||||
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 disabled:opacity-50"
|
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 disabled:opacity-50"
|
||||||
:title="previewHidden ? 'Show in library' : 'Hide in library'"
|
:title="previewHidden ? 'Show in library' : 'Hide in library'"
|
||||||
:aria-label="previewHidden ? 'Show in library' : 'Hide in library'"
|
:aria-label="previewHidden ? 'Show in library' : 'Hide in library'"
|
||||||
:disabled="hideBusy"
|
|
||||||
@click="togglePreviewHidden"
|
@click="togglePreviewHidden"
|
||||||
>
|
>
|
||||||
<svg v-if="previewHidden" class="h-4 w-4" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
<svg v-if="previewHidden" class="h-4 w-4" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||||
@@ -342,7 +340,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
buildLibraryEntries,
|
buildLibraryEntries,
|
||||||
folderOutputCount,
|
|
||||||
formatClipDuration,
|
formatClipDuration,
|
||||||
formatWhen,
|
formatWhen,
|
||||||
type LibraryClip,
|
type LibraryClip,
|
||||||
@@ -381,6 +378,7 @@ const stills = ref<LibraryStill[]>([])
|
|||||||
const folderId = ref('all')
|
const folderId = ref('all')
|
||||||
const filter = ref<LibraryFilter>('all')
|
const filter = ref<LibraryFilter>('all')
|
||||||
const sort = ref<LibrarySort>('date-desc')
|
const sort = ref<LibrarySort>('date-desc')
|
||||||
|
const searchDraft = ref('')
|
||||||
const search = ref('')
|
const search = ref('')
|
||||||
const selectedKeys = ref<string[]>([])
|
const selectedKeys = ref<string[]>([])
|
||||||
const lastSelectedKey = ref('')
|
const lastSelectedKey = ref('')
|
||||||
@@ -397,11 +395,14 @@ const hideBusy = ref(false)
|
|||||||
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 toasts = ref<Array<{ id: number; message: string }>>([])
|
const toasts = ref<Array<{ id: number; message: string }>>([])
|
||||||
let toastId = 0
|
let toastId = 0
|
||||||
|
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
const scroller = ref<HTMLElement | null>(null)
|
const scroller = ref<HTMLElement | null>(null)
|
||||||
const scrollTop = ref(0)
|
const scrollTop = ref(0)
|
||||||
|
let scrollRaf = 0
|
||||||
const viewportH = ref(800)
|
const viewportH = ref(800)
|
||||||
const viewportW = ref(800)
|
const viewportW = ref(800)
|
||||||
|
|
||||||
|
const selectedSet = computed(() => new Set(selectedKeys.value))
|
||||||
const browseFolder = computed(() => folders.value.find(folder => folder.id === folderId.value) || null)
|
const browseFolder = computed(() => folders.value.find(folder => folder.id === folderId.value) || null)
|
||||||
const entries = computed(() => buildLibraryEntries({
|
const entries = computed(() => buildLibraryEntries({
|
||||||
clips: clips.value,
|
clips: clips.value,
|
||||||
@@ -411,14 +412,31 @@ const entries = computed(() => buildLibraryEntries({
|
|||||||
search: search.value,
|
search: search.value,
|
||||||
sort: sort.value
|
sort: sort.value
|
||||||
}))
|
}))
|
||||||
const allCount = computed(() => buildLibraryEntries({
|
const allCount = computed(() => {
|
||||||
|
if (folderId.value === 'all' && !search.value.trim()) return entries.value.length
|
||||||
|
return buildLibraryEntries({
|
||||||
clips: clips.value,
|
clips: clips.value,
|
||||||
stills: stills.value,
|
stills: stills.value,
|
||||||
folderId: 'all',
|
folderId: 'all',
|
||||||
filter: filter.value,
|
filter: filter.value,
|
||||||
search: '',
|
search: '',
|
||||||
sort: 'date-desc'
|
sort: 'date-desc'
|
||||||
}).length)
|
}).length
|
||||||
|
})
|
||||||
|
const folderChipCounts = computed(() => {
|
||||||
|
const map: Record<string, { videos: number; images: number }> = {}
|
||||||
|
for (const clip of clips.value) {
|
||||||
|
if (clip.hasVideo === false) continue
|
||||||
|
const row = map[clip.folderId] || (map[clip.folderId] = { videos: 0, images: 0 })
|
||||||
|
row.videos += 1
|
||||||
|
}
|
||||||
|
for (const still of stills.value) {
|
||||||
|
if (still.role !== 'output') continue
|
||||||
|
const row = map[still.folderId] || (map[still.folderId] = { videos: 0, images: 0 })
|
||||||
|
row.images += 1
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
})
|
||||||
const moveFolders = computed(() => folders.value.filter(folder => folder.id !== folderId.value))
|
const moveFolders = computed(() => folders.value.filter(folder => folder.id !== folderId.value))
|
||||||
const focused = computed(() => entries.value.find(entry => entry.key === focusedKey.value) || null)
|
const focused = computed(() => entries.value.find(entry => entry.key === focusedKey.value) || null)
|
||||||
const emptyLabel = computed(() => {
|
const emptyLabel = computed(() => {
|
||||||
@@ -448,18 +466,10 @@ const previewStillSrc = computed(() => {
|
|||||||
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}`
|
||||||
})
|
})
|
||||||
const focusedPreviewItem = computed(() => {
|
|
||||||
const entry = focused.value
|
|
||||||
if (!entry) return null
|
|
||||||
if (entry.kind === 'clip') {
|
|
||||||
return entry.group?.parts.find(part => part.id === focusedPartId.value) || entry.group?.head || null
|
|
||||||
}
|
|
||||||
return entry.stillGroup?.parts.find(part => part.id === focusedPartId.value) || entry.stillGroup?.head || null
|
|
||||||
})
|
|
||||||
const previewHidden = computed(() => {
|
const previewHidden = computed(() => {
|
||||||
const item = focusedPreviewItem.value
|
const entry = focused.value
|
||||||
if (!item) return false
|
if (!entry) return false
|
||||||
return 'hideThumbnail' in item ? Boolean(item.hideThumbnail) : Boolean(item.hideInput)
|
return thumbHidden(entry)
|
||||||
})
|
})
|
||||||
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 || []
|
||||||
@@ -514,6 +524,15 @@ async function loadLibrary() {
|
|||||||
applyLibrary(await $fetch('/api/library?light=1'))
|
applyLibrary(await $fetch('/api/library?light=1'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function folderChipCount(folder: LibraryFolder) {
|
||||||
|
if (!folder.unlocked) return (folder.clipCount || 0) + (folder.imageCount || 0)
|
||||||
|
const row = folderChipCounts.value[folder.id]
|
||||||
|
if (!row) return 0
|
||||||
|
if (filter.value === 'videos') return row.videos
|
||||||
|
if (filter.value === 'images') return row.images
|
||||||
|
return row.videos + row.images
|
||||||
|
}
|
||||||
|
|
||||||
function folderChipClass(id: string) {
|
function folderChipClass(id: string) {
|
||||||
if (folderId.value === id) return 'border-amber-300 bg-amber-400/10 text-amber-100'
|
if (folderId.value === id) return 'border-amber-300 bg-amber-400/10 text-amber-100'
|
||||||
if (dropFolderId.value === id) return 'border-amber-300/70 bg-amber-400/10 text-amber-100'
|
if (dropFolderId.value === id) return 'border-amber-300/70 bg-amber-400/10 text-amber-100'
|
||||||
@@ -522,7 +541,7 @@ function folderChipClass(id: string) {
|
|||||||
|
|
||||||
function cardClass(entry: LibraryEntry) {
|
function cardClass(entry: LibraryEntry) {
|
||||||
if (focusedKey.value === entry.key) return 'border-amber-300 ring-2 ring-amber-300/70'
|
if (focusedKey.value === entry.key) return 'border-amber-300 ring-2 ring-amber-300/70'
|
||||||
if (selectedKeys.value.includes(entry.key)) return 'border-amber-300/40 bg-amber-400/5'
|
if (selectedSet.value.has(entry.key)) return 'border-amber-300/40 bg-amber-400/5'
|
||||||
return 'border-white/10'
|
return 'border-white/10'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,7 +577,11 @@ function focusPart(id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onScroll() {
|
function onScroll() {
|
||||||
|
if (scrollRaf) return
|
||||||
|
scrollRaf = requestAnimationFrame(() => {
|
||||||
|
scrollRaf = 0
|
||||||
scrollTop.value = scroller.value?.scrollTop || 0
|
scrollTop.value = scroller.value?.scrollTop || 0
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function measure() {
|
function measure() {
|
||||||
@@ -734,23 +757,43 @@ async function saveRename() {
|
|||||||
|
|
||||||
async function togglePreviewHidden() {
|
async function togglePreviewHidden() {
|
||||||
const entry = focused.value
|
const entry = focused.value
|
||||||
const item = focusedPreviewItem.value
|
if (!entry || hideBusy.value) return
|
||||||
if (!entry || !item || hideBusy.value) return
|
const next = !thumbHidden(entry)
|
||||||
|
applyLocalHide(entry, next)
|
||||||
hideBusy.value = true
|
hideBusy.value = true
|
||||||
const next = !previewHidden.value
|
|
||||||
try {
|
try {
|
||||||
if (entry.kind === 'clip') {
|
if (entry.kind === 'clip' && entry.group) {
|
||||||
applyLibrary(await $fetch(`/api/library/clips/${item.id}`, { method: 'PATCH', body: { hideThumbnail: next } }))
|
await $fetch(`/api/library/clips/${entry.group.head.id}`, {
|
||||||
} else {
|
method: 'PATCH',
|
||||||
applyLibrary(await $fetch(`/api/library/stills/${item.id}`, { method: 'PATCH', body: { hideInput: next } }))
|
body: { hideThumbnail: next, family: true }
|
||||||
|
})
|
||||||
|
} else if (entry.stillGroup) {
|
||||||
|
await $fetch(`/api/library/stills/${entry.stillGroup.head.id}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: { hideInput: next, family: true }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
applyLocalHide(entry, !next)
|
||||||
toast(error?.data?.statusMessage || 'Could not update visibility')
|
toast(error?.data?.statusMessage || 'Could not update visibility')
|
||||||
} finally {
|
} finally {
|
||||||
hideBusy.value = false
|
hideBusy.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyLocalHide(entry: LibraryEntry, hide: boolean) {
|
||||||
|
if (entry.kind === 'clip' && entry.group) {
|
||||||
|
for (const part of entry.group.parts) {
|
||||||
|
part.hideThumbnail = hide
|
||||||
|
part.hasThumbnail = !hide
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (entry.stillGroup) {
|
||||||
|
for (const part of entry.stillGroup.parts) part.hideInput = hide
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function askRemoveFocused() {
|
function askRemoveFocused() {
|
||||||
if (!focused.value) return
|
if (!focused.value) return
|
||||||
askRemoveKeys([focused.value.key])
|
askRemoveKeys([focused.value.key])
|
||||||
@@ -829,6 +872,12 @@ function onKey(event: KeyboardEvent) {
|
|||||||
if (event.key === 'Delete' && selectedKeys.value.length && !renaming.value) askRemoveSelected()
|
if (event.key === 'Delete' && selectedKeys.value.length && !renaming.value) askRemoveSelected()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(searchDraft, (value) => {
|
||||||
|
if (searchTimer) clearTimeout(searchTimer)
|
||||||
|
searchTimer = setTimeout(() => {
|
||||||
|
search.value = value
|
||||||
|
}, 160)
|
||||||
|
})
|
||||||
watch([folderId, filter], () => {
|
watch([folderId, filter], () => {
|
||||||
clearSelection()
|
clearSelection()
|
||||||
if (scroller.value) scroller.value.scrollTop = 0
|
if (scroller.value) scroller.value.scrollTop = 0
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ export default defineEventHandler(async (event) => {
|
|||||||
family: family || hasLocks
|
family: family || hasLocks
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (hasHide) setClipHidden(owner, id, body.hideThumbnail === true)
|
if (hasHide) setClipHidden(owner, id, body.hideThumbnail === true, family)
|
||||||
|
if (hasHide && !folderId && !hasName && !hasLocks && !hasRefs) {
|
||||||
|
return { id, hideThumbnail: body.hideThumbnail === true }
|
||||||
|
}
|
||||||
return publicLibrary(event)
|
return publicLibrary(event)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export default defineEventHandler((event) => {
|
export default defineEventHandler((event) => {
|
||||||
const library = publicLibrary(event)
|
|
||||||
const light = String(getQuery(event).light || '') === '1'
|
const light = String(getQuery(event).light || '') === '1'
|
||||||
|
const library = publicLibrary(event, { light })
|
||||||
if (!light) {
|
if (!light) {
|
||||||
try {
|
try {
|
||||||
const { owner } = assertLibraryOwner(event)
|
const { owner } = assertLibraryOwner(event)
|
||||||
|
|||||||
@@ -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)
|
if (hasHide) setStillHidden(owner, id, body.hideInput === true, family)
|
||||||
|
if (hasHide && !folderId && !hasName) {
|
||||||
|
return { id, hideInput: body.hideInput === true }
|
||||||
|
}
|
||||||
return publicLibrary(event)
|
return publicLibrary(event)
|
||||||
})
|
})
|
||||||
|
|||||||
+26
-11
@@ -453,14 +453,25 @@ export function publicFolder(event: H3Event, folder: LibraryFolder, clipCount =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function publicLibrary(event: H3Event) {
|
export function publicLibrary(event: H3Event, opts?: { light?: boolean }) {
|
||||||
|
const light = opts?.light === true
|
||||||
const owner = libraryOwnerKey(event)
|
const owner = libraryOwnerKey(event)
|
||||||
const catalog = readCatalog(owner)
|
const catalog = readCatalog(owner)
|
||||||
|
const clipIds = new Set(catalog.clips.map(clip => clip.id.toLowerCase()))
|
||||||
|
const clipCounts = new Map<string, number>()
|
||||||
|
const imageCounts = new Map<string, number>()
|
||||||
|
for (const clip of catalog.clips) {
|
||||||
|
clipCounts.set(clip.folderId, (clipCounts.get(clip.folderId) || 0) + 1)
|
||||||
|
}
|
||||||
|
for (const still of catalog.stills) {
|
||||||
|
if (isPipelineStill(catalog, still, clipIds) || stillRole(still) !== 'output') continue
|
||||||
|
imageCounts.set(still.folderId, (imageCounts.get(still.folderId) || 0) + 1)
|
||||||
|
}
|
||||||
const folders = catalog.folders.map(folder => publicFolder(
|
const folders = catalog.folders.map(folder => publicFolder(
|
||||||
event,
|
event,
|
||||||
folder,
|
folder,
|
||||||
catalog.clips.filter(clip => clip.folderId === folder.id).length,
|
clipCounts.get(folder.id) || 0,
|
||||||
catalog.stills.filter(still => still.folderId === folder.id && !isPipelineStill(catalog, still) && stillRole(still) === 'output').length
|
imageCounts.get(folder.id) || 0
|
||||||
))
|
))
|
||||||
const unlockedIds = new Set(folders.filter(folder => folder.unlocked).map(folder => folder.id))
|
const unlockedIds = new Set(folders.filter(folder => folder.unlocked).map(folder => folder.id))
|
||||||
return {
|
return {
|
||||||
@@ -468,11 +479,11 @@ export function publicLibrary(event: H3Event) {
|
|||||||
clips: catalog.clips.filter(clip => unlockedIds.has(clip.folderId)).map(clip => ({
|
clips: catalog.clips.filter(clip => unlockedIds.has(clip.folderId)).map(clip => ({
|
||||||
...clip,
|
...clip,
|
||||||
name: clipTitle(clip),
|
name: clipTitle(clip),
|
||||||
hasThumbnail: !clip.hideThumbnail && existsSync(clipThumbPath(owner, clip.id)),
|
hasThumbnail: light ? !clip.hideThumbnail : !clip.hideThumbnail && existsSync(clipThumbPath(owner, clip.id)),
|
||||||
hasVideo: existsSync(clipVideoPath(owner, clip.id))
|
hasVideo: light ? true : existsSync(clipVideoPath(owner, clip.id))
|
||||||
})),
|
})),
|
||||||
stills: catalog.stills
|
stills: catalog.stills
|
||||||
.filter(still => unlockedIds.has(still.folderId) && !isPipelineStill(catalog, still))
|
.filter(still => unlockedIds.has(still.folderId) && !isPipelineStill(catalog, still, clipIds))
|
||||||
.map(still => ({ ...still, role: stillRole(still) })),
|
.map(still => ({ ...still, role: stillRole(still) })),
|
||||||
drafts: catalog.drafts,
|
drafts: catalog.drafts,
|
||||||
loraAliases: catalog.loraAliases || {}
|
loraAliases: catalog.loraAliases || {}
|
||||||
@@ -1057,10 +1068,11 @@ function clipIdFromStillFilename(name: string) {
|
|||||||
return match ? match[1].toLowerCase() : ''
|
return match ? match[1].toLowerCase() : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPipelineStill(catalog: Catalog, still: LibraryStill) {
|
function isPipelineStill(catalog: Catalog, still: LibraryStill, clipIds?: Set<string>) {
|
||||||
if (lastFrameStillName(still.filename)) return true
|
if (lastFrameStillName(still.filename)) return true
|
||||||
const clipId = clipIdFromStillFilename(still.filename)
|
const clipId = clipIdFromStillFilename(still.filename)
|
||||||
if (!clipId) return false
|
if (!clipId) return false
|
||||||
|
if (clipIds) return clipIds.has(clipId)
|
||||||
return catalog.clips.some(clip => clip.id.toLowerCase() === clipId)
|
return catalog.clips.some(clip => clip.id.toLowerCase() === clipId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1391,11 +1403,13 @@ export function moveStillFamily(owner: string, id: string, folderId: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setStillHidden(owner: string, id: string, hide: boolean) {
|
export function setStillHidden(owner: string, id: string, hide: boolean, family = false) {
|
||||||
return mutate(owner, (catalog) => {
|
return mutate(owner, (catalog) => {
|
||||||
const still = catalog.stills.find(item => item.id === id)
|
const still = catalog.stills.find(item => item.id === id)
|
||||||
if (!still) throw createError({ statusCode: 404, statusMessage: 'Image not found' })
|
if (!still) throw createError({ statusCode: 404, statusMessage: 'Image not found' })
|
||||||
still.hideInput = hide === true
|
const targets = family ? stillsInSameFamily(catalog, still) : [still]
|
||||||
|
if (!targets.some(item => item.id === still.id)) targets.push(still)
|
||||||
|
for (const item of targets) item.hideInput = hide === true
|
||||||
return still
|
return still
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1603,11 +1617,12 @@ export function moveClipFamily(owner: string, id: string, folderId: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setClipHidden(owner: string, id: string, hide: boolean) {
|
export function setClipHidden(owner: string, id: string, hide: boolean, family = false) {
|
||||||
return mutate(owner, (catalog) => {
|
return mutate(owner, (catalog) => {
|
||||||
const clip = catalog.clips.find(item => item.id === id)
|
const clip = catalog.clips.find(item => item.id === id)
|
||||||
if (!clip) throw createError({ statusCode: 404, statusMessage: 'Clip not found' })
|
if (!clip) throw createError({ statusCode: 404, statusMessage: 'Clip not found' })
|
||||||
clip.hideThumbnail = hide === true
|
const targets = family ? clipsInSameFamily(catalog, clip) : [clip]
|
||||||
|
for (const item of targets) item.hideThumbnail = hide === true
|
||||||
return clip
|
return clip
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user