125 lines
3.4 KiB
TypeScript
125 lines
3.4 KiB
TypeScript
import React, {useEffect, useMemo, useRef, useState} from 'react'
|
|
import {Button, Card, Flex, Stack, Text, TextInput} from '@sanity/ui'
|
|
import {set, unset, StringInputProps, useClient, useFormValue} from 'sanity'
|
|
|
|
type WorkDoc = {
|
|
_id: string
|
|
title?: string
|
|
}
|
|
|
|
function computeTitle(workTitle: string | undefined, movement: string | undefined) {
|
|
const wt = (workTitle || '').trim()
|
|
const mv = (movement || '').trim()
|
|
|
|
if (wt && mv) return `${wt}: ${mv}`
|
|
if (wt) return wt
|
|
if (mv) return mv
|
|
return ''
|
|
}
|
|
|
|
export function TrackDisplayTitleInput(props: StringInputProps) {
|
|
const {value, onChange, readOnly, elementProps} = props
|
|
|
|
const workRef = useFormValue(['work', '_ref']) as string | undefined
|
|
const movement = useFormValue(['movement']) as string | undefined
|
|
|
|
const client = useClient({apiVersion: '2025-01-01'})
|
|
const [workTitle, setWorkTitle] = useState<string>('')
|
|
|
|
useEffect(() => {
|
|
let cancelled = false
|
|
|
|
async function run() {
|
|
if (!workRef) {
|
|
setWorkTitle('')
|
|
return
|
|
}
|
|
|
|
const doc = await client.fetch<WorkDoc | null>(`*[_type == "work" && _id == $id][0]{title}`, {
|
|
id: workRef,
|
|
})
|
|
|
|
if (!cancelled) setWorkTitle((doc?.title || '').trim())
|
|
}
|
|
|
|
run()
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [client, workRef])
|
|
|
|
const computed = useMemo(() => computeTitle(workTitle, movement), [workTitle, movement])
|
|
|
|
const didAutofill = useRef(false)
|
|
useEffect(() => {
|
|
if (didAutofill.current) return
|
|
if (!workRef) return
|
|
if (value && String(value).trim()) return
|
|
if (!computed) return
|
|
if (readOnly) return
|
|
|
|
didAutofill.current = true
|
|
onChange(set(computed))
|
|
}, [workRef, value, computed, readOnly, onChange])
|
|
|
|
const hasStored = Boolean(value && String(value).trim())
|
|
const inWorkMode = Boolean(workRef)
|
|
|
|
if (inWorkMode) {
|
|
return (
|
|
<Card padding={3} radius={2} border>
|
|
<Stack space={3}>
|
|
<Flex direction="column" gap={2}>
|
|
<Text size={1} weight="semibold">
|
|
Display title (stored)
|
|
</Text>
|
|
<Text size={0} muted>
|
|
Generated once from Work/Movement and saved. It will not change automatically if the
|
|
Work changes.
|
|
</Text>
|
|
</Flex>
|
|
|
|
<TextInput
|
|
{...elementProps}
|
|
value={(value as string) || ''}
|
|
readOnly={true}
|
|
placeholder={computed || 'Select a Work to generate a title'}
|
|
/>
|
|
|
|
<Flex gap={2} wrap="wrap">
|
|
<Button
|
|
text={hasStored ? 'Overwrite from Work/Movement' : 'Generate from Work/Movement'}
|
|
mode="default"
|
|
disabled={readOnly || !computed}
|
|
onClick={() => onChange(set(computed))}
|
|
/>
|
|
<Button
|
|
text="Clear"
|
|
mode="ghost"
|
|
disabled={readOnly || !hasStored}
|
|
onClick={() => onChange(unset())}
|
|
/>
|
|
</Flex>
|
|
|
|
{!computed && (
|
|
<Text size={0} muted>
|
|
No computable title yet. Select a Work (and optionally add Movement).
|
|
</Text>
|
|
)}
|
|
</Stack>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<TextInput
|
|
{...elementProps}
|
|
value={(value as string) || ''}
|
|
readOnly={readOnly}
|
|
onChange={(e) => {
|
|
const next = e.currentTarget.value
|
|
onChange(next && next.trim() ? set(next) : unset())
|
|
}}
|
|
/>
|
|
)
|
|
}
|