diff --git a/components/ImagePickerModal.vue b/components/ImagePickerModal.vue
index 49c3b27..79b8e1d 100644
--- a/components/ImagePickerModal.vue
+++ b/components/ImagePickerModal.vue
@@ -149,8 +149,34 @@
-
{{ still.name || still.filename }}
-
+
+
{{ stillLabel(still) }}
+
@@ -210,6 +236,10 @@ const revealAll = ref(false)
const revealedIds = ref([])
const picking = ref(false)
const keepingId = ref('')
+const renamingId = ref('')
+const renameDraft = ref('')
+const renameInput = ref(null)
+const renameSaving = ref(false)
function onKey(event: KeyboardEvent) {
if (!props.open) return
@@ -316,6 +346,45 @@ function onDrop(event: DragEvent) {
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) {
if (keepingId.value) return
keepingId.value = still.id
diff --git a/pages/index.vue b/pages/index.vue
index 1ef6f29..149025a 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -105,10 +105,15 @@
diff --git a/pages/library.vue b/pages/library.vue
index 95fa2e7..1a303c4 100644
--- a/pages/library.vue
+++ b/pages/library.vue
@@ -81,6 +81,25 @@
+
@@ -380,6 +399,7 @@ const renameDraft = ref('')
const renameInput = ref(null)
const deleteBusy = 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 keepingInputBusy = ref(false)
const toasts = ref>([])
@@ -549,11 +569,24 @@ function cardClass(entry: LibraryEntry) {
return 'border-white/10'
}
-function thumbHidden(entry: LibraryEntry) {
+function itemHidden(entry: LibraryEntry) {
if (entry.kind === 'clip') return Boolean(entry.group?.head.hideThumbnail)
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) {
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`
@@ -639,7 +672,7 @@ function onCardClick(entry: LibraryEntry, event: MouseEvent) {
const sameCard = focusedKey.value === entry.key
focusedKey.value = entry.key
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()
}