Add permanence refs opens picker, labels inject into INHERITS
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<div class="fixed inset-0 z-40 flex items-start justify-center overflow-y-auto bg-black/70 p-4 sm:p-8" @click.self="emit('close')">
|
<div v-if="open" class="fixed inset-0 z-40 flex items-start justify-center overflow-y-auto bg-black/70 p-4 sm:p-8" @click.self="emit('close')">
|
||||||
<div class="glass w-full max-w-3xl rounded-3xl p-5 sm:p-6">
|
<div class="glass w-full max-w-3xl rounded-3xl p-5 sm:p-6">
|
||||||
<div class="flex items-start justify-between gap-3">
|
<div class="flex items-start justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="font-display text-2xl font-bold">{{ heading || 'Choose an image' }}</h2>
|
<h2 class="font-display text-2xl font-bold">{{ heading || 'Choose an image' }}</h2>
|
||||||
<p class="mt-1 text-sm text-zinc-400">{{ tab === 'input' ? 'Uploaded stills only. Generated frames and edits live under Output.' : 'Generated stills from image edits. Video frames stay with their clips.' }}</p>
|
<p class="mt-1 text-sm text-zinc-400">{{ pickerHint }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
@@ -167,14 +167,17 @@ export interface PickerStill {
|
|||||||
name?: string
|
name?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
folders: PickerFolder[]
|
folders: PickerFolder[]
|
||||||
stills: PickerStill[]
|
stills: PickerStill[]
|
||||||
folderId: string
|
folderId: string
|
||||||
hideInputPreview?: boolean
|
hideInputPreview?: boolean
|
||||||
heading?: string
|
heading?: string
|
||||||
stillOnly?: boolean
|
stillOnly?: boolean
|
||||||
}>()
|
open?: boolean
|
||||||
|
}>(), {
|
||||||
|
open: true
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
close: []
|
close: []
|
||||||
@@ -194,6 +197,7 @@ const revealedIds = ref<string[]>([])
|
|||||||
const picking = ref(false)
|
const picking = ref(false)
|
||||||
|
|
||||||
function onKey(event: KeyboardEvent) {
|
function onKey(event: KeyboardEvent) {
|
||||||
|
if (!props.open) return
|
||||||
if (event.key === 'Escape') emit('close')
|
if (event.key === 'Escape') emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,6 +219,12 @@ const visibleStills = computed(() => props.stills.filter((still) => {
|
|||||||
const emptyLabel = computed(() => tab.value === 'input'
|
const emptyLabel = computed(() => tab.value === 'input'
|
||||||
? 'No uploaded stills in this folder yet.'
|
? 'No uploaded stills in this folder yet.'
|
||||||
: 'No generated stills in this folder yet.')
|
: 'No generated stills in this folder yet.')
|
||||||
|
const pickerHint = computed(() => {
|
||||||
|
if (props.stillOnly) return 'Pick a still, then label it Subject / Vehicle / Interior / Object on the strip. That name is what the shot prompt inherits.'
|
||||||
|
return tab.value === 'input'
|
||||||
|
? 'Uploaded stills only. Generated frames and edits live under Output.'
|
||||||
|
: 'Generated stills from image edits. Video frames stay with their clips.'
|
||||||
|
})
|
||||||
const hasHiddenItems = computed(() => visibleStills.value.some(still => stillHidden(still)))
|
const hasHiddenItems = computed(() => visibleStills.value.some(still => stillHidden(still)))
|
||||||
|
|
||||||
function stillHidden(still: PickerStill) {
|
function stillHidden(still: PickerStill) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="label" class="mb-1.5 flex items-baseline justify-between gap-2">
|
<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 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>
|
<p v-if="hint" class="text-[11px] text-zinc-600">{{ hint }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,11 +42,12 @@
|
|||||||
<button
|
<button
|
||||||
v-if="!disabled && refs.length < max"
|
v-if="!disabled && refs.length < max"
|
||||||
type="button"
|
type="button"
|
||||||
class="flex h-12 w-12 flex-col items-center justify-center rounded-lg border border-dashed border-white/15 text-[10px] text-zinc-500 hover:border-amber-300/40 hover:text-amber-100"
|
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"
|
:title="addTitle"
|
||||||
@click="emit('add')"
|
@click.stop="emit('addRef')"
|
||||||
>
|
>
|
||||||
+ Ref
|
{{ refs.length ? '+' : cta }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,7 +56,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PERMANENCE_REF_LABELS, type PermanenceRef } from '~/utils/globalLocks'
|
import { PERMANENCE_REF_LABELS, type PermanenceRef } from '~/utils/globalLocks'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
withDefaults(defineProps<{
|
||||||
refs: PermanenceRef[]
|
refs: PermanenceRef[]
|
||||||
label?: string
|
label?: string
|
||||||
hint?: string
|
hint?: string
|
||||||
@@ -63,16 +64,18 @@ const props = withDefaults(defineProps<{
|
|||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
max?: number
|
max?: number
|
||||||
addTitle?: string
|
addTitle?: string
|
||||||
|
cta?: string
|
||||||
}>(), {
|
}>(), {
|
||||||
max: 6,
|
max: 6,
|
||||||
addTitle: 'Attach a permanence still'
|
addTitle: 'Choose a still to label',
|
||||||
|
cta: 'Add still'
|
||||||
})
|
})
|
||||||
|
|
||||||
defineEmits<{
|
const emit = defineEmits<{
|
||||||
add: []
|
addRef: []
|
||||||
remove: [index: number]
|
remove: [index: number]
|
||||||
updateLabel: [index: number, label: string]
|
updateLabel: [index: number, label: string]
|
||||||
}>()
|
}>(['addRef', 'remove', 'updateLabel'])
|
||||||
|
|
||||||
function labelOptions(current: string) {
|
function labelOptions(current: string) {
|
||||||
if (PERMANENCE_REF_LABELS.includes(current as typeof PERMANENCE_REF_LABELS[number])) return [...PERMANENCE_REF_LABELS]
|
if (PERMANENCE_REF_LABELS.includes(current as typeof PERMANENCE_REF_LABELS[number])) return [...PERMANENCE_REF_LABELS]
|
||||||
|
|||||||
+80
-30
@@ -276,17 +276,27 @@
|
|||||||
</details>
|
</details>
|
||||||
<div v-if="studioMode === 'video'" class="mt-3 space-y-3">
|
<div v-if="studioMode === 'video'" class="mt-3 space-y-3">
|
||||||
<GlobalLocksEditor v-model="globalLocks" />
|
<GlobalLocksEditor v-model="globalLocks" />
|
||||||
<PermanenceRefStrip
|
<div class="rounded-xl border border-white/10 bg-zinc-950/40 px-3 py-2.5">
|
||||||
:refs="familyPermanenceRefs"
|
<PermanenceRefStrip
|
||||||
label="Permanence refs"
|
:refs="familyPermanenceRefs"
|
||||||
hint="Labels for object permanence. Last-frame I2V inherits them in the prompt; they are not extra Comfy identity pictures."
|
label="Permanence refs · all shots"
|
||||||
:hidden="hideInputPreview"
|
hint="Shared stills"
|
||||||
@add="openPermanencePicker()"
|
cta="Add permanence refs"
|
||||||
@remove="removeFamilyRef"
|
add-title="Choose a still to label for every shot"
|
||||||
@update-label="updateFamilyRefLabel"
|
:hidden="hideInputPreview"
|
||||||
/>
|
@add-ref="openPermanencePicker()"
|
||||||
|
@remove="removeFamilyRef"
|
||||||
|
@update-label="updateFamilyRefLabel"
|
||||||
|
/>
|
||||||
|
<p class="mt-2 text-[11px] leading-relaxed text-zinc-500">
|
||||||
|
Add stills, then label each Subject / Vehicle / Interior / Object. Those names are injected into locks and
|
||||||
|
<span class="text-zinc-300">[INHERITS]</span>, so a shot can say “same Subject, same Vehicle.”
|
||||||
|
</p>
|
||||||
|
<p class="mt-1 text-[11px] leading-relaxed text-zinc-500">{{ permanenceComfyNote }}</p>
|
||||||
|
<p v-if="familyRefLabelLine" class="mt-1 text-[11px] text-amber-200/80">{{ familyRefLabelLine }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="shotScriptMode && studioMode === 'video'" class="mt-3 hidden space-y-2 md:block">
|
<div v-if="shotScriptMode && studioMode === 'video'" class="mt-3 space-y-2">
|
||||||
<p v-if="!parsedShots.length" class="text-xs text-zinc-500">Add a first shot, then <span class="text-zinc-300">shot 2</span> for an extension.</p>
|
<p v-if="!parsedShots.length" class="text-xs text-zinc-500">Add a first shot, then <span class="text-zinc-300">shot 2</span> for an extension.</p>
|
||||||
<div
|
<div
|
||||||
v-for="(shot, index) in parsedShots"
|
v-for="(shot, index) in parsedShots"
|
||||||
@@ -301,10 +311,13 @@
|
|||||||
<PermanenceRefStrip
|
<PermanenceRefStrip
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
:refs="shotPermanenceRefs[shot.n] || []"
|
:refs="shotPermanenceRefs[shot.n] || []"
|
||||||
hint="This shot"
|
label="This shot only"
|
||||||
|
hint="Extra stills"
|
||||||
|
cta="Add still"
|
||||||
|
add-title="Choose a still for this shot only"
|
||||||
:hidden="hideInputPreview"
|
:hidden="hideInputPreview"
|
||||||
:max="4"
|
:max="4"
|
||||||
@add="openPermanencePicker(shot.n)"
|
@add-ref="openPermanencePicker(shot.n)"
|
||||||
@remove="(refIndex) => removeShotRef(shot.n, refIndex)"
|
@remove="(refIndex) => removeShotRef(shot.n, refIndex)"
|
||||||
@update-label="(refIndex, label) => updateShotRefLabel(shot.n, refIndex, label)"
|
@update-label="(refIndex, label) => updateShotRefLabel(shot.n, refIndex, label)"
|
||||||
/>
|
/>
|
||||||
@@ -339,10 +352,13 @@
|
|||||||
<PermanenceRefStrip
|
<PermanenceRefStrip
|
||||||
class="mt-3"
|
class="mt-3"
|
||||||
:refs="shotPermanenceRefs[index + 2] || []"
|
:refs="shotPermanenceRefs[index + 2] || []"
|
||||||
hint="This extension"
|
label="This extension only"
|
||||||
|
hint="Extra stills"
|
||||||
|
cta="Add still"
|
||||||
|
add-title="Choose a still for this extension only"
|
||||||
:hidden="hideInputPreview"
|
:hidden="hideInputPreview"
|
||||||
:max="4"
|
:max="4"
|
||||||
@add="openPermanencePicker(index + 2)"
|
@add-ref="openPermanencePicker(index + 2)"
|
||||||
@remove="(refIndex) => removeShotRef(index + 2, refIndex)"
|
@remove="(refIndex) => removeShotRef(index + 2, refIndex)"
|
||||||
@update-label="(refIndex, label) => updateShotRefLabel(index + 2, refIndex, label)"
|
@update-label="(refIndex, label) => updateShotRefLabel(index + 2, refIndex, label)"
|
||||||
/>
|
/>
|
||||||
@@ -545,7 +561,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="useIdentityRefs" class="text-xs text-zinc-500">Picture 1 is the start still and stays Picture 1 on every shot, including queued extensions. Extra stills map to Picture 2–5 only if you add them. Empty slots are left out of the graph, not filled with copies of Picture 1.</p>
|
<p v-if="useIdentityRefs" class="text-xs text-zinc-500">Picture 1 is the start still and stays Picture 1 on every shot, including queued extensions. Extra stills map to Picture 2–5 only if you add them — extra views of that same person, not a second character or a vehicle. Empty slots are left out of the graph, not filled with copies of Picture 1.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1180,8 +1196,9 @@
|
|||||||
v-if="entry.group.parts.length === 1 && !entry.group.head.hideThumbnail"
|
v-if="entry.group.parts.length === 1 && !entry.group.head.hideThumbnail"
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
:refs="entry.group.head.permanenceRefs || []"
|
:refs="entry.group.head.permanenceRefs || []"
|
||||||
|
cta="Add still"
|
||||||
:hidden="entry.group.head.hideInput || hideInputPreview"
|
:hidden="entry.group.head.hideInput || hideInputPreview"
|
||||||
@add="openPermanencePicker(undefined, entry.group.head.id)"
|
@add-ref="openPermanencePicker(undefined, entry.group.head.id)"
|
||||||
@remove="(refIndex) => removeClipRef(entry.group.head, refIndex)"
|
@remove="(refIndex) => removeClipRef(entry.group.head, refIndex)"
|
||||||
@update-label="(refIndex, label) => updateClipRefLabel(entry.group.head, refIndex, label)"
|
@update-label="(refIndex, label) => updateClipRefLabel(entry.group.head, refIndex, label)"
|
||||||
/>
|
/>
|
||||||
@@ -1243,8 +1260,9 @@
|
|||||||
v-if="!item.hideThumbnail"
|
v-if="!item.hideThumbnail"
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
:refs="item.permanenceRefs || []"
|
:refs="item.permanenceRefs || []"
|
||||||
|
cta="Add still"
|
||||||
:hidden="item.hideInput || hideInputPreview"
|
:hidden="item.hideInput || hideInputPreview"
|
||||||
@add="openPermanencePicker(undefined, item.id)"
|
@add-ref="openPermanencePicker(undefined, item.id)"
|
||||||
@remove="(refIndex) => removeClipRef(item, refIndex)"
|
@remove="(refIndex) => removeClipRef(item, refIndex)"
|
||||||
@update-label="(refIndex, label) => updateClipRefLabel(item, refIndex, label)"
|
@update-label="(refIndex, label) => updateClipRefLabel(item, refIndex, label)"
|
||||||
/>
|
/>
|
||||||
@@ -1432,7 +1450,7 @@
|
|||||||
@update:open="promptDrawerOpen = $event"
|
@update:open="promptDrawerOpen = $event"
|
||||||
/>
|
/>
|
||||||
<ImagePickerModal
|
<ImagePickerModal
|
||||||
v-if="pickerOpen"
|
:open="pickerOpen"
|
||||||
:folders="folders"
|
:folders="folders"
|
||||||
:stills="stills"
|
:stills="stills"
|
||||||
:folder-id="folderId"
|
:folder-id="folderId"
|
||||||
@@ -1520,6 +1538,7 @@ import {
|
|||||||
extractGlobalLocks,
|
extractGlobalLocks,
|
||||||
isBlankGlobalLocks,
|
isBlankGlobalLocks,
|
||||||
nextPermanenceLabel,
|
nextPermanenceLabel,
|
||||||
|
permanenceLabels,
|
||||||
type PermanenceRef
|
type PermanenceRef
|
||||||
} from '~/utils/globalLocks'
|
} from '~/utils/globalLocks'
|
||||||
import {
|
import {
|
||||||
@@ -1830,6 +1849,7 @@ const studioJobs = ref<Array<{
|
|||||||
}>>([])
|
}>>([])
|
||||||
const outputStudioJobId = ref('')
|
const outputStudioJobId = ref('')
|
||||||
const pickerOpen = ref(false)
|
const pickerOpen = ref(false)
|
||||||
|
const pickerKind = ref<'image' | 'permanence'>('image')
|
||||||
const pickerSlot = ref<'main' | 'editRef' | number | 'perm' | `perm:${number}` | `clip:${string}`>('main')
|
const pickerSlot = ref<'main' | 'editRef' | number | 'perm' | `perm:${number}` | `clip:${string}`>('main')
|
||||||
const libraryFilter = ref<'all' | 'images' | 'videos'>('all')
|
const libraryFilter = ref<'all' | 'images' | 'videos'>('all')
|
||||||
const selectedKeys = ref<string[]>([])
|
const selectedKeys = ref<string[]>([])
|
||||||
@@ -1851,12 +1871,23 @@ const shotPermanenceRefs = ref<Record<number, PermanenceRef[]>>({})
|
|||||||
const LOCKS_STORE = 'aigen-global-locks'
|
const LOCKS_STORE = 'aigen-global-locks'
|
||||||
const REFS_STORE = 'aigen-permanence-refs'
|
const REFS_STORE = 'aigen-permanence-refs'
|
||||||
const pickerHeading = computed(() => {
|
const pickerHeading = computed(() => {
|
||||||
|
if (pickerKind.value === 'permanence') return 'Choose a permanence still'
|
||||||
if (pickerSlot.value === 'main') return studioMode.value === 'edit' ? 'Choose image 1' : 'Choose an image'
|
if (pickerSlot.value === 'main') return studioMode.value === 'edit' ? 'Choose image 1' : 'Choose an image'
|
||||||
if (pickerSlot.value === 'editRef') return 'Choose image 2'
|
if (pickerSlot.value === 'editRef') return 'Choose image 2'
|
||||||
if (typeof pickerSlot.value === 'string') return 'Choose a permanence still'
|
|
||||||
return `Choose Picture ${Number(pickerSlot.value) + 2}`
|
return `Choose Picture ${Number(pickerSlot.value) + 2}`
|
||||||
})
|
})
|
||||||
const permanencePicker = computed(() => typeof pickerSlot.value === 'string' && pickerSlot.value !== 'main' && pickerSlot.value !== 'editRef')
|
const permanencePicker = computed(() => pickerKind.value === 'permanence')
|
||||||
|
const familyRefLabelLine = computed(() => {
|
||||||
|
const labels = permanenceLabels(familyPermanenceRefs.value)
|
||||||
|
if (!labels.length) return ''
|
||||||
|
return `In the prompt: Permanence: ${labels.join(', ')} stay the same.`
|
||||||
|
})
|
||||||
|
const permanenceComfyNote = computed(() => {
|
||||||
|
if (studioMode.value === 'video' && videoWorkflow.value === 'v2' && useIdentityRefs.value) {
|
||||||
|
return 'Identity-ref is on: Subject stills can map to Picture 2–5 as extra views of the same person only — never a second character or a vehicle.'
|
||||||
|
}
|
||||||
|
return 'Last-frame I2V: these refs are prompt anchors (and last-frame continuity), not extra MiniMax pictures. Turn on identity-ref only for one person.'
|
||||||
|
})
|
||||||
let videoTimer: ReturnType<typeof setInterval> | null = null
|
let videoTimer: ReturnType<typeof setInterval> | null = null
|
||||||
let editTimer: ReturnType<typeof setInterval> | null = null
|
let editTimer: ReturnType<typeof setInterval> | null = null
|
||||||
let videoSource: EventSource | null = null
|
let videoSource: EventSource | null = null
|
||||||
@@ -2865,15 +2896,21 @@ function readFile(next: File, opts?: { keepThumbnailPref?: boolean }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openPicker(slot: 'main' | 'editRef' | number = 'main') {
|
function openPicker(slot: 'main' | 'editRef' | number = 'main') {
|
||||||
|
pickerKind.value = 'image'
|
||||||
pickerSlot.value = slot
|
pickerSlot.value = slot
|
||||||
pickerOpen.value = true
|
void nextTick(() => {
|
||||||
|
pickerOpen.value = true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function openPermanencePicker(shot?: number, clipId?: string) {
|
function openPermanencePicker(shot?: number, clipId?: string) {
|
||||||
|
pickerKind.value = 'permanence'
|
||||||
if (clipId) pickerSlot.value = `clip:${clipId}`
|
if (clipId) pickerSlot.value = `clip:${clipId}`
|
||||||
else if (typeof shot === 'number') pickerSlot.value = `perm:${shot}`
|
else if (typeof shot === 'number') pickerSlot.value = `perm:${shot}`
|
||||||
else pickerSlot.value = 'perm'
|
else pickerSlot.value = 'perm'
|
||||||
pickerOpen.value = true
|
void nextTick(() => {
|
||||||
|
pickerOpen.value = true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPermanenceRef(stillId: string, shot?: number) {
|
function addPermanenceRef(stillId: string, shot?: number) {
|
||||||
@@ -2928,21 +2965,29 @@ function updateClipRefLabel(clip: LibraryClip, index: number, label: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onPickedPermanence(still: { id: string }) {
|
function onPickedPermanence(still: { id: string }) {
|
||||||
|
if (pickerKind.value !== 'permanence') return
|
||||||
const slot = pickerSlot.value
|
const slot = pickerSlot.value
|
||||||
if (slot === 'main' || slot === 'editRef' || typeof slot === 'number') return
|
if (typeof slot !== 'string' || slot === 'main' || slot === 'editRef') return
|
||||||
pickerOpen.value = false
|
if (slot.startsWith('clip:')) {
|
||||||
if (typeof slot === 'string' && slot.startsWith('clip:')) {
|
|
||||||
const clipId = slot.slice(5)
|
const clipId = slot.slice(5)
|
||||||
const clip = clips.value.find(item => item.id === clipId)
|
const clip = clips.value.find(item => item.id === clipId)
|
||||||
if (!clip) return
|
if (!clip) {
|
||||||
if ((clip.permanenceRefs || []).some(item => item.stillId === still.id)) return
|
pickerOpen.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ((clip.permanenceRefs || []).some(item => item.stillId === still.id)) {
|
||||||
|
pickerOpen.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
void persistClipRefs(clip, [...(clip.permanenceRefs || []), { stillId: still.id, label: nextPermanenceLabel(clip.permanenceRefs || []) }])
|
void persistClipRefs(clip, [...(clip.permanenceRefs || []), { stillId: still.id, label: nextPermanenceLabel(clip.permanenceRefs || []) }])
|
||||||
|
pickerOpen.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (typeof slot === 'string' && (slot === 'perm' || slot.startsWith('perm:'))) {
|
if (slot === 'perm' || slot.startsWith('perm:')) {
|
||||||
const shot = slot === 'perm' ? undefined : Number(slot.slice(5))
|
const shot = slot === 'perm' ? undefined : Number(slot.slice(5))
|
||||||
addPermanenceRef(still.id, Number.isFinite(shot) ? shot : undefined)
|
addPermanenceRef(still.id, Number.isFinite(shot) ? shot : undefined)
|
||||||
}
|
}
|
||||||
|
pickerOpen.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function setEditRef(next: File) {
|
function setEditRef(next: File) {
|
||||||
@@ -2979,9 +3024,14 @@ function clearIdentityRef(index: number) {
|
|||||||
identityPreviews.value = previews
|
identityPreviews.value = previews
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPickedImage(next: File, hidden = false) {
|
function onPickedImage(next: File, hidden = false, stillId?: string) {
|
||||||
const slot = pickerSlot.value
|
if (pickerKind.value === 'permanence') {
|
||||||
|
if (stillId) onPickedPermanence({ id: stillId })
|
||||||
|
else pickerOpen.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
pickerOpen.value = false
|
pickerOpen.value = false
|
||||||
|
const slot = pickerSlot.value
|
||||||
if (typeof slot === 'string' && slot !== 'main' && slot !== 'editRef') return
|
if (typeof slot === 'string' && slot !== 'main' && slot !== 'editRef') return
|
||||||
if (slot === 'editRef') {
|
if (slot === 'editRef') {
|
||||||
setEditRef(next)
|
setEditRef(next)
|
||||||
|
|||||||
@@ -94,15 +94,19 @@ function extractState(locks: string) {
|
|||||||
return String(match?.[1] || '').replace(/\s+/g, ' ').trim()
|
return String(match?.[1] || '').replace(/\s+/g, ' ').trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function permanenceLabels(refs: PermanenceRef[] | null | undefined) {
|
||||||
|
return [...new Set(normalizePermanenceRefs(refs).map(item => item.label).filter(Boolean))]
|
||||||
|
}
|
||||||
|
|
||||||
function permanenceLine(refs: PermanenceRef[]) {
|
function permanenceLine(refs: PermanenceRef[]) {
|
||||||
const labels = [...new Set(refs.map(item => item.label).filter(Boolean))]
|
const labels = permanenceLabels(refs)
|
||||||
if (!labels.length) return ''
|
if (!labels.length) return ''
|
||||||
return `Permanence: ${labels.join(', ')} stay the same.`
|
return `Permanence: ${labels.join(', ')} stay the same.`
|
||||||
}
|
}
|
||||||
|
|
||||||
function inheritLine(locks: string, refs: PermanenceRef[]) {
|
function inheritLine(locks: string, refs: PermanenceRef[]) {
|
||||||
const parts = ['[INHERITS] Global locks.']
|
const parts = ['[INHERITS] Global locks.']
|
||||||
const labels = [...new Set(refs.map(item => item.label).filter(Boolean))]
|
const labels = permanenceLabels(refs)
|
||||||
if (labels.length) parts.push(`Same ${labels.join(', ')} as locked.`)
|
if (labels.length) parts.push(`Same ${labels.join(', ')} as locked.`)
|
||||||
const state = extractState(locks)
|
const state = extractState(locks)
|
||||||
if (state) parts.push(`State: ${state}`)
|
if (state) parts.push(`State: ${state}`)
|
||||||
|
|||||||
Reference in New Issue
Block a user