From 61f6d7ce218eeeb8f33442004157aff6f4653149 Mon Sep 17 00:00:00 2001 From: Brendon Heinst Date: Tue, 24 Feb 2026 12:22:46 +0100 Subject: [PATCH] Initial commit --- .gitignore | 31 + README.md | 9 + components/CountryInput.tsx | 54 + components/TrackDisplayTitleInput.tsx | 125 + components/WorkSlugInput.tsx | 144 + eslint.config.mjs | 3 + package-lock.json | 16984 ++++++++++++++++++++++++ package.json | 39 + sanity-typegen.json | 5 + sanity.cli.ts | 15 + sanity.config.ts | 38 + sanity.types.ts | 735 + schema.json | 4110 ++++++ schemaTypes/artist-type.ts | 68 + schemaTypes/blog-options.ts | 11 + schemaTypes/blog-type.ts | 203 + schemaTypes/composer-type.ts | 94 + schemaTypes/concert-type.ts | 126 + schemaTypes/country-list.ts | 199 + schemaTypes/index.ts | 8 + schemaTypes/release-type.ts | 563 + schemaTypes/settings-type.ts | 43 + schemaTypes/work-type.ts | 88 + scripts/regenerate-release-slugs.ts | 50 + static/.gitkeep | 1 + tsconfig.json | 17 + 26 files changed, 23763 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 components/CountryInput.tsx create mode 100644 components/TrackDisplayTitleInput.tsx create mode 100644 components/WorkSlugInput.tsx create mode 100644 eslint.config.mjs create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 sanity-typegen.json create mode 100644 sanity.cli.ts create mode 100644 sanity.config.ts create mode 100644 sanity.types.ts create mode 100644 schema.json create mode 100644 schemaTypes/artist-type.ts create mode 100644 schemaTypes/blog-options.ts create mode 100644 schemaTypes/blog-type.ts create mode 100644 schemaTypes/composer-type.ts create mode 100644 schemaTypes/concert-type.ts create mode 100644 schemaTypes/country-list.ts create mode 100644 schemaTypes/index.ts create mode 100644 schemaTypes/release-type.ts create mode 100644 schemaTypes/settings-type.ts create mode 100644 schemaTypes/work-type.ts create mode 100644 scripts/regenerate-release-slugs.ts create mode 100644 static/.gitkeep create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bfd47dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# Dependencies +/node_modules +/.pnp +.pnp.js + +# Compiled Sanity Studio +/dist + +# Temporary Sanity runtime, generated by the CLI on every dev server start +/.sanity + +# Logs +/logs +*.log + +# Coverage directory used by testing tools +/coverage + +# Misc +.DS_Store +*.pem + +# Typescript +*.tsbuildinfo + +# Dotenv and similar local-only files +.env +.env.* +*.local diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d1c504 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Sanity Clean Content Studio + +Congratulations, you have now installed the Sanity Content Studio, an open-source real-time content editing environment connected to the Sanity backend. + +Now you can do the following things: + +- [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme) +- [Join the Sanity community](https://www.sanity.io/community/join?utm_source=readme) +- [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme) diff --git a/components/CountryInput.tsx b/components/CountryInput.tsx new file mode 100644 index 0000000..b1a18f9 --- /dev/null +++ b/components/CountryInput.tsx @@ -0,0 +1,54 @@ +import React, {useCallback, useMemo, useState} from 'react' +import {Autocomplete, Card, Text} from '@sanity/ui' +import {set, unset, StringInputProps} from 'sanity' +import {countryList} from '../schemaTypes/country-list' + +const options = countryList.map((c) => ({value: c.value, payload: c})) + +export function CountryInput(props: StringInputProps) { + const {value, onChange, readOnly} = props + const [query, setQuery] = useState('') + + const filtered = useMemo(() => { + if (!query) return options + const q = query.toLowerCase() + return options.filter((o) => o.payload.title.toLowerCase().includes(q)) + }, [query]) + + const handleSelect = useCallback( + (val: string) => { + onChange(val ? set(val) : unset()) + }, + [onChange], + ) + + const renderOption = useCallback( + (option: (typeof options)[number]) => ( + + {option.payload.title} + + ), + [], + ) + + const selectedTitle = useMemo(() => { + if (!value) return undefined + return countryList.find((c) => c.value === value)?.title + }, [value]) + + return ( + setQuery(q ?? '')} + placeholder="Search for a country…" + renderOption={renderOption} + value={value || ''} + readOnly={readOnly} + openButton + filterOption={() => true} + renderValue={() => selectedTitle || value || ''} + /> + ) +} diff --git a/components/TrackDisplayTitleInput.tsx b/components/TrackDisplayTitleInput.tsx new file mode 100644 index 0000000..04d73f2 --- /dev/null +++ b/components/TrackDisplayTitleInput.tsx @@ -0,0 +1,125 @@ +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('') + + useEffect(() => { + let cancelled = false + + async function run() { + if (!workRef) { + setWorkTitle('') + return + } + + const doc = await client.fetch(`*[_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 ( + + + + + Display title (stored) + + + Generated once from Work/Movement and saved. It will not change automatically if the + Work changes. + + + + + + +