105 lines
3.3 KiB
Vue
105 lines
3.3 KiB
Vue
<template>
|
||
<div>
|
||
<div v-if="label" class="mb-1.5 flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5">
|
||
<p class="text-[11px] font-medium uppercase tracking-wide text-zinc-500">{{ label }}</p>
|
||
<p v-if="hint" class="text-[11px] text-zinc-600">{{ hint }}</p>
|
||
</div>
|
||
<div class="flex flex-wrap items-start gap-1.5">
|
||
<div
|
||
v-for="(item, index) in refs"
|
||
:key="item.stillId + '-' + index"
|
||
class="flex w-16 flex-col items-center gap-0.5"
|
||
>
|
||
<span class="relative h-12 w-12 overflow-hidden rounded-lg bg-zinc-900">
|
||
<span v-if="hidden" class="sensitive-glow block h-full w-full" />
|
||
<img
|
||
v-else
|
||
:src="`/api/library/stills/${item.stillId}?thumb=1`"
|
||
alt=""
|
||
loading="lazy"
|
||
decoding="async"
|
||
class="h-full w-full object-cover"
|
||
>
|
||
<button
|
||
v-if="!disabled"
|
||
type="button"
|
||
class="absolute right-0 top-0 rounded-bl-md bg-zinc-950/80 px-1 text-[10px] leading-4 text-zinc-300 hover:text-red-200"
|
||
title="Remove"
|
||
@click.stop="emit('remove', index)"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
<input
|
||
class="w-full truncate rounded border border-white/10 bg-zinc-950 px-0.5 py-0 text-center text-[10px] text-zinc-400 outline-none focus:border-amber-300/40"
|
||
:disabled="disabled"
|
||
:value="item.label"
|
||
:list="nameListId"
|
||
:title="item.label || 'Name'"
|
||
placeholder="Name"
|
||
maxlength="24"
|
||
autocomplete="off"
|
||
@input="emit('updateLabel', index, sanitizeRefLabel(($event.target as HTMLInputElement).value))"
|
||
>
|
||
</div>
|
||
<datalist :id="nameListId">
|
||
<option v-for="option in listNames" :key="option" :value="option" />
|
||
</datalist>
|
||
<button
|
||
v-if="!disabled && refs.length < max"
|
||
type="button"
|
||
class="flex min-h-12 items-center justify-center rounded-lg border border-dashed border-white/15 text-[10px] leading-tight text-zinc-400 hover:border-amber-300/40 hover:text-amber-100"
|
||
:class="refs.length ? 'h-12 w-12' : 'min-w-[8.5rem] px-2.5 py-1.5'"
|
||
:title="addTitle"
|
||
@click.stop="emit('addRef')"
|
||
>
|
||
{{ refs.length ? '+' : cta }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, useId } from 'vue'
|
||
import { sanitizeRefLabel, type PermanenceRef } from '~/utils/globalLocks'
|
||
|
||
const props = withDefaults(defineProps<{
|
||
refs: PermanenceRef[]
|
||
nameOptions?: string[]
|
||
label?: string
|
||
hint?: string
|
||
hidden?: boolean
|
||
disabled?: boolean
|
||
max?: number
|
||
addTitle?: string
|
||
cta?: string
|
||
}>(), {
|
||
nameOptions: () => [],
|
||
max: 6,
|
||
addTitle: 'Choose a still to name',
|
||
cta: 'Add still'
|
||
})
|
||
|
||
const emit = defineEmits<{
|
||
addRef: []
|
||
remove: [index: number]
|
||
updateLabel: [index: number, label: string]
|
||
}>()
|
||
|
||
const nameListId = useId()
|
||
|
||
const listNames = computed(() => {
|
||
const seen = new Set<string>()
|
||
const names: string[] = []
|
||
for (const name of [...props.nameOptions, ...props.refs.map(item => item.label)]) {
|
||
const trimmed = String(name || '').trim()
|
||
if (!trimmed) continue
|
||
const id = trimmed.toLowerCase()
|
||
if (seen.has(id)) continue
|
||
seen.add(id)
|
||
names.push(trimmed)
|
||
}
|
||
return names
|
||
})
|
||
</script>
|