Keep the Extend panel put and name sibling clips as branches.
A finished job no longer closes a draft or jumps the page, and a second extend from the same part saves as p8b instead of another p8. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+99
-1
@@ -68,7 +68,105 @@ export type LibraryEntry = {
|
||||
}
|
||||
|
||||
export function familyBaseName(name: string) {
|
||||
return String(name || '').trim().replace(/\s+p\d+$/i, '')
|
||||
return String(name || '').trim().replace(/\s+p\d+[a-z]?$/i, '')
|
||||
}
|
||||
|
||||
export function clipPartLabel(name?: string) {
|
||||
const match = String(name || '').trim().match(/\s+p(\d+)([a-z])?$/i)
|
||||
if (!match) return ''
|
||||
return `p${match[1]}${(match[2] || '').toLowerCase()}`
|
||||
}
|
||||
|
||||
export function nextClipPartName(name: string, existingNames: string[] = []) {
|
||||
const trimmed = String(name || '').trim()
|
||||
if (!trimmed) return ''
|
||||
const match = trimmed.match(/^(.*)\s+p(\d+)([a-z])?$/i)
|
||||
const base = (match ? match[1] : trimmed).trim() || trimmed
|
||||
const part = match ? Number(match[2]) + 1 : 2
|
||||
const taken = new Set(existingNames.map(item => String(item || '').trim().toLowerCase()).filter(Boolean))
|
||||
const first = `${base} p${part}`
|
||||
if (!taken.has(first.toLowerCase())) {
|
||||
return first.slice(0, 80)
|
||||
}
|
||||
for (const letter of 'bcdefghijklmnopqrstuvwxyz') {
|
||||
const candidate = `${base} p${part}${letter}`
|
||||
if (!taken.has(candidate.toLowerCase())) return candidate.slice(0, 80)
|
||||
}
|
||||
let extra = 2
|
||||
while (extra < 100) {
|
||||
const candidate = `${base} p${part}-${extra}`
|
||||
if (!taken.has(candidate.toLowerCase())) return candidate.slice(0, 80)
|
||||
extra += 1
|
||||
}
|
||||
return first.slice(0, 80)
|
||||
}
|
||||
|
||||
export type ClipTreeNode = { clip: LibraryClip; depth: number }
|
||||
|
||||
function sortTreeSiblings(a: LibraryClip, b: LibraryClip) {
|
||||
const chain = (a.chainIndex || 0) - (b.chainIndex || 0)
|
||||
if (chain) return chain
|
||||
return a.createdAt - b.createdAt
|
||||
}
|
||||
|
||||
export function treeOrderedParts(parts: LibraryClip[]): ClipTreeNode[] {
|
||||
if (!parts.length) return []
|
||||
const byId = new Map(parts.map(part => [part.id, part]))
|
||||
const children = new Map<string, LibraryClip[]>()
|
||||
const roots: LibraryClip[] = []
|
||||
for (const part of parts) {
|
||||
const parentId = part.parentClipId
|
||||
if (parentId && byId.has(parentId)) {
|
||||
const list = children.get(parentId) || []
|
||||
list.push(part)
|
||||
children.set(parentId, list)
|
||||
} else {
|
||||
roots.push(part)
|
||||
}
|
||||
}
|
||||
roots.sort(sortTreeSiblings)
|
||||
for (const list of children.values()) list.sort(sortTreeSiblings)
|
||||
const out: ClipTreeNode[] = []
|
||||
const walk = (clip: LibraryClip, depth: number) => {
|
||||
out.push({ clip, depth })
|
||||
for (const child of children.get(clip.id) || []) walk(child, depth + 1)
|
||||
}
|
||||
for (const root of roots) walk(root, 0)
|
||||
return out
|
||||
}
|
||||
|
||||
export function familyBranchCount(parts: LibraryClip[]) {
|
||||
const counts = new Map<string, number>()
|
||||
for (const part of parts) {
|
||||
if (!part.parentClipId) continue
|
||||
counts.set(part.parentClipId, (counts.get(part.parentClipId) || 0) + 1)
|
||||
}
|
||||
return [...counts.values()].filter(count => count > 1).length
|
||||
}
|
||||
|
||||
export function familyBranchHint(parts: LibraryClip[]) {
|
||||
const groups = new Map<string, LibraryClip[]>()
|
||||
for (const part of parts) {
|
||||
if (!part.parentClipId) continue
|
||||
const list = groups.get(part.parentClipId) || []
|
||||
list.push(part)
|
||||
groups.set(part.parentClipId, list)
|
||||
}
|
||||
const branched = [...groups.entries()].filter(([, kids]) => kids.length > 1)
|
||||
if (!branched.length) return ''
|
||||
return branched.map(([parentId, kids]) => {
|
||||
const parent = parts.find(part => part.id === parentId)
|
||||
const from = clipPartLabel(parent?.name) || 'a part'
|
||||
const names = kids.map(kid => clipPartLabel(kid.name) || kid.name || 'part').join(', ')
|
||||
return `${from} → ${names}`
|
||||
}).join(' · ')
|
||||
}
|
||||
|
||||
export function clipBranchFromLabel(item: LibraryClip, parts: LibraryClip[]) {
|
||||
if (!item.parentClipId) return ''
|
||||
const parent = parts.find(part => part.id === item.parentClipId)
|
||||
const from = clipPartLabel(parent?.name) || parent?.name
|
||||
return from ? `from ${from}` : ''
|
||||
}
|
||||
|
||||
export function clipFamilyKey(clip: LibraryClip) {
|
||||
|
||||
Reference in New Issue
Block a user