Rename input stills from the picker and reveal all hidden library thumbs.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -149,8 +149,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between gap-2 px-3 py-2">
|
<div class="flex items-center justify-between gap-2 px-3 py-2">
|
||||||
<span class="truncate text-xs text-zinc-400">{{ still.name || still.filename }}</span>
|
<input
|
||||||
<button type="button" class="text-[10px] text-red-300 hover:text-red-200" @click.stop="removeStill(still)">Delete</button>
|
v-if="renamingId === still.id"
|
||||||
|
ref="renameInput"
|
||||||
|
v-model="renameDraft"
|
||||||
|
class="min-w-0 flex-1 rounded-md border border-amber-300/40 bg-zinc-950 px-2 py-1 text-xs outline-none ring-amber-300/40 focus:ring-2"
|
||||||
|
maxlength="80"
|
||||||
|
aria-label="Still name"
|
||||||
|
@click.stop
|
||||||
|
@keydown.enter.prevent="saveRename(still)"
|
||||||
|
@keydown.escape.prevent="cancelRename"
|
||||||
|
@blur="saveRename(still)"
|
||||||
|
>
|
||||||
|
<span v-else class="min-w-0 flex-1 truncate text-xs text-zinc-400">{{ stillLabel(still) }}</span>
|
||||||
|
<div class="flex shrink-0 items-center gap-1">
|
||||||
|
<button
|
||||||
|
v-if="renamingId !== still.id"
|
||||||
|
type="button"
|
||||||
|
class="rounded-full p-1 text-amber-200 hover:bg-white/5 hover:text-amber-100"
|
||||||
|
title="Rename"
|
||||||
|
@click.stop="startRename(still)"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="h-3.5 w-3.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931z" />
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 7.125L16.862 4.487" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="text-[10px] text-red-300 hover:text-red-200" @click.stop="removeStill(still)">Delete</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
@@ -210,6 +236,10 @@ const revealAll = ref(false)
|
|||||||
const revealedIds = ref<string[]>([])
|
const revealedIds = ref<string[]>([])
|
||||||
const picking = ref(false)
|
const picking = ref(false)
|
||||||
const keepingId = ref('')
|
const keepingId = ref('')
|
||||||
|
const renamingId = ref('')
|
||||||
|
const renameDraft = ref('')
|
||||||
|
const renameInput = ref<HTMLInputElement | null>(null)
|
||||||
|
const renameSaving = ref(false)
|
||||||
|
|
||||||
function onKey(event: KeyboardEvent) {
|
function onKey(event: KeyboardEvent) {
|
||||||
if (!props.open) return
|
if (!props.open) return
|
||||||
@@ -316,6 +346,45 @@ function onDrop(event: DragEvent) {
|
|||||||
if (next) void ingest(next)
|
if (next) void ingest(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stillLabel(still: PickerStill) {
|
||||||
|
return (still.name || still.filename.replace(/\.[a-z0-9]+$/i, '') || 'Untitled still').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRename(still: PickerStill) {
|
||||||
|
renamingId.value = still.id
|
||||||
|
renameDraft.value = stillLabel(still)
|
||||||
|
nextTick(() => {
|
||||||
|
renameInput.value?.focus()
|
||||||
|
renameInput.value?.select()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelRename() {
|
||||||
|
renamingId.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveRename(still: PickerStill) {
|
||||||
|
if (renamingId.value !== still.id || renameSaving.value) return
|
||||||
|
const name = renameDraft.value.trim().slice(0, 80)
|
||||||
|
if (!name) {
|
||||||
|
emit('error', 'A name is required')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (name === stillLabel(still)) {
|
||||||
|
cancelRename()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
renameSaving.value = true
|
||||||
|
try {
|
||||||
|
emit('updated', await $fetch(`/api/library/stills/${still.id}`, { method: 'PATCH', body: { name } }))
|
||||||
|
cancelRename()
|
||||||
|
} catch (error: any) {
|
||||||
|
emit('error', error?.data?.statusMessage || 'Could not rename still')
|
||||||
|
} finally {
|
||||||
|
renameSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function keepAsInput(still: PickerStill) {
|
async function keepAsInput(still: PickerStill) {
|
||||||
if (keepingId.value) return
|
if (keepingId.value) return
|
||||||
keepingId.value = still.id
|
keepingId.value = still.id
|
||||||
|
|||||||
+7
-2
@@ -105,10 +105,15 @@
|
|||||||
<button
|
<button
|
||||||
v-if="!renamingInputStill"
|
v-if="!renamingInputStill"
|
||||||
type="button"
|
type="button"
|
||||||
class="shrink-0 text-xs text-amber-300 hover:text-amber-100"
|
class="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-amber-200 hover:bg-white/5 hover:text-amber-100"
|
||||||
|
title="Rename still"
|
||||||
|
aria-label="Rename still"
|
||||||
@click.stop="startInputStillRename"
|
@click.stop="startInputStillRename"
|
||||||
>
|
>
|
||||||
Rename
|
<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">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931z" />
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 7.125L16.862 4.487" />
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+35
-2
@@ -81,6 +81,25 @@
|
|||||||
<button v-if="entries.length" type="button" class="text-zinc-400 hover:text-white" @click="selectedKeys.length === entries.length ? clearSelection() : selectAll()">
|
<button v-if="entries.length" type="button" class="text-zinc-400 hover:text-white" @click="selectedKeys.length === entries.length ? clearSelection() : selectAll()">
|
||||||
{{ selectedKeys.length && selectedKeys.length === entries.length ? 'Clear selection' : 'Select all' }}
|
{{ selectedKeys.length && selectedKeys.length === entries.length ? 'Clear selection' : 'Select all' }}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="hasHiddenThumbs"
|
||||||
|
type="button"
|
||||||
|
class="rounded-full border px-2.5 py-1 text-[11px]"
|
||||||
|
:class="revealAll ? 'border-amber-300 bg-amber-400/10 text-amber-100' : 'border-white/10 text-zinc-400 hover:text-white'"
|
||||||
|
:title="revealAll ? 'Hide flagged thumbnails' : 'Reveal all hidden thumbnails'"
|
||||||
|
@click="toggleRevealAll"
|
||||||
|
>
|
||||||
|
<span class="inline-flex items-center gap-1.5">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="h-3.5 w-3.5">
|
||||||
|
<path v-if="revealAll" stroke-linecap="round" d="M3 3l18 18M10.6 10.6a2 2 0 102.8 2.8M9.9 5.1A9.8 9.8 0 0121 12a9.8 9.8 0 01-1.7 2.9M6.1 6.1A9.8 9.8 0 003 12a9.8 9.8 0 0012.9 6.1" />
|
||||||
|
<template v-else>
|
||||||
|
<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z" />
|
||||||
|
<circle cx="12" cy="12" r="3" />
|
||||||
|
</template>
|
||||||
|
</svg>
|
||||||
|
{{ revealAll ? 'Hide all' : 'Reveal all' }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
<button v-if="browseFolder?.protected && browseFolder.unlocked" type="button" class="text-zinc-400 hover:text-white" @click="lockFolder">Lock folder</button>
|
<button v-if="browseFolder?.protected && browseFolder.unlocked" type="button" class="text-zinc-400 hover:text-white" @click="lockFolder">Lock folder</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -380,6 +399,7 @@ const renameDraft = ref('')
|
|||||||
const renameInput = ref<HTMLInputElement | null>(null)
|
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 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 }>>([])
|
||||||
@@ -549,11 +569,24 @@ function cardClass(entry: LibraryEntry) {
|
|||||||
return 'border-white/10'
|
return 'border-white/10'
|
||||||
}
|
}
|
||||||
|
|
||||||
function thumbHidden(entry: LibraryEntry) {
|
function itemHidden(entry: LibraryEntry) {
|
||||||
if (entry.kind === 'clip') return Boolean(entry.group?.head.hideThumbnail)
|
if (entry.kind === 'clip') return Boolean(entry.group?.head.hideThumbnail)
|
||||||
return Boolean(entry.stillGroup?.head.hideInput)
|
return Boolean(entry.stillGroup?.head.hideInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function thumbHidden(entry: LibraryEntry) {
|
||||||
|
return itemHidden(entry) && !revealAll.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasHiddenThumbs = computed(() => entries.value.some(entry => itemHidden(entry)))
|
||||||
|
|
||||||
|
function toggleRevealAll() {
|
||||||
|
revealAll.value = !revealAll.value
|
||||||
|
if (focused.value && itemHidden(focused.value)) {
|
||||||
|
detailsHidden.value = !revealAll.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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`
|
||||||
@@ -639,7 +672,7 @@ function onCardClick(entry: LibraryEntry, event: MouseEvent) {
|
|||||||
const sameCard = focusedKey.value === entry.key
|
const sameCard = focusedKey.value === entry.key
|
||||||
focusedKey.value = entry.key
|
focusedKey.value = entry.key
|
||||||
focusedPartId.value = entry.kind === 'clip' ? (entry.group?.head.id || '') : (entry.stillGroup?.head.id || '')
|
focusedPartId.value = entry.kind === 'clip' ? (entry.group?.head.id || '') : (entry.stillGroup?.head.id || '')
|
||||||
if (!sameCard) detailsHidden.value = thumbHidden(entry)
|
if (!sameCard) detailsHidden.value = itemHidden(entry) && !revealAll.value
|
||||||
cancelRename()
|
cancelRename()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user