563 lines
15 KiB
TypeScript
563 lines
15 KiB
TypeScript
import {defineArrayMember, defineField, defineType} from 'sanity'
|
|
import {PlayIcon} from '@sanity/icons'
|
|
import {TrackDisplayTitleInput} from '../components/TrackDisplayTitleInput'
|
|
|
|
export const releaseType = defineType({
|
|
name: 'release',
|
|
title: 'Release',
|
|
type: 'document',
|
|
icon: PlayIcon,
|
|
|
|
groups: [
|
|
{name: 'main', title: 'Main', default: true},
|
|
{name: 'text', title: 'Text'},
|
|
{name: 'media', title: 'Media'},
|
|
{name: 'tracklist', title: 'Tracklist'},
|
|
{name: 'links', title: 'Streaming Links'},
|
|
{name: 'references', title: 'Tags'},
|
|
{name: 'reviews', title: 'Reviews'},
|
|
{name: 'creditsSpecs', title: 'Credits & Specs'},
|
|
{name: 'medusa', title: 'Medusa'},
|
|
],
|
|
|
|
fieldsets: [
|
|
{name: 'main', title: 'Release Information', options: {columns: 2}},
|
|
{name: 'links', title: 'Streaming Links', options: {columns: 2}},
|
|
],
|
|
|
|
fields: [
|
|
defineField({
|
|
name: 'name',
|
|
title: 'Title',
|
|
type: 'string',
|
|
group: 'main',
|
|
validation: (Rule) => Rule.required(),
|
|
}),
|
|
defineField({
|
|
name: 'albumArtist',
|
|
title: 'Album Artist',
|
|
type: 'string',
|
|
group: 'main',
|
|
validation: (Rule) => Rule.required(),
|
|
}),
|
|
defineField({
|
|
name: 'catalogNo',
|
|
title: 'Catalog #',
|
|
type: 'string',
|
|
group: 'main',
|
|
fieldset: 'main',
|
|
validation: (Rule) => Rule.required(),
|
|
}),
|
|
defineField({
|
|
name: 'slug',
|
|
title: 'Slug',
|
|
type: 'slug',
|
|
options: {
|
|
source: (doc: Record<string, unknown>) =>
|
|
[doc.albumArtist, doc.name].filter(Boolean).join(' '),
|
|
maxLength: 200,
|
|
},
|
|
validation: (Rule) => Rule.required(),
|
|
group: 'main',
|
|
fieldset: 'main',
|
|
}),
|
|
defineField({name: 'upc', title: 'UPC/EAN', type: 'string', group: 'main', fieldset: 'main'}),
|
|
defineField({
|
|
name: 'releaseDate',
|
|
title: 'Release Date',
|
|
type: 'date',
|
|
group: 'main',
|
|
fieldset: 'main',
|
|
validation: (Rule) => Rule.required(),
|
|
}),
|
|
defineField({
|
|
name: 'format',
|
|
title: 'Format',
|
|
type: 'string',
|
|
group: 'main',
|
|
fieldset: 'main',
|
|
options: {
|
|
list: [
|
|
{title: 'Single', value: 'single'},
|
|
{title: 'EP', value: 'ep'},
|
|
{title: 'Album', value: 'album'},
|
|
{title: 'Boxset', value: 'boxset'},
|
|
],
|
|
},
|
|
}),
|
|
|
|
defineField({
|
|
name: 'label',
|
|
title: 'Label',
|
|
type: 'string',
|
|
group: 'main',
|
|
fieldset: 'main',
|
|
options: {
|
|
list: [
|
|
{title: 'TRPTK', value: 'TRPTK'},
|
|
{title: 'Other', value: 'other'},
|
|
],
|
|
},
|
|
}),
|
|
defineField({
|
|
name: 'shortDescription',
|
|
title: 'Short Description',
|
|
type: 'text',
|
|
rows: 4,
|
|
group: 'text',
|
|
}),
|
|
defineField({
|
|
name: 'description',
|
|
title: 'Description',
|
|
type: 'array',
|
|
of: [defineArrayMember({type: 'block'})],
|
|
group: 'text',
|
|
}),
|
|
defineField({
|
|
name: 'albumCover',
|
|
title: 'Album Cover',
|
|
type: 'image',
|
|
group: 'media',
|
|
validation: (Rule) => Rule.required(),
|
|
}),
|
|
defineField({
|
|
name: 'bookletPdf',
|
|
title: 'Booklet PDF',
|
|
type: 'file',
|
|
options: {accept: 'application/pdf'},
|
|
group: 'media',
|
|
}),
|
|
defineField({
|
|
name: 'tracks',
|
|
title: 'Tracklist',
|
|
group: 'tracklist',
|
|
type: 'array',
|
|
of: [
|
|
defineArrayMember({
|
|
name: 'track',
|
|
title: 'Track',
|
|
type: 'object',
|
|
fields: [
|
|
defineField({
|
|
name: 'work',
|
|
title: 'Work',
|
|
type: 'reference',
|
|
to: [{type: 'work'}],
|
|
options: {disableNew: true},
|
|
}),
|
|
defineField({
|
|
name: 'movement',
|
|
title: 'Movement',
|
|
type: 'string',
|
|
}),
|
|
defineField({
|
|
name: 'displayTitle',
|
|
title: 'Display title',
|
|
type: 'string',
|
|
description: 'Use only when Work and Movement are empty.',
|
|
components: {
|
|
input: TrackDisplayTitleInput,
|
|
},
|
|
}),
|
|
defineField({
|
|
name: 'artist',
|
|
title: 'Artist',
|
|
description: 'If left empty, Album Artist will be used.',
|
|
type: 'string',
|
|
}),
|
|
|
|
defineField({
|
|
name: 'duration',
|
|
title: 'Duration',
|
|
description: 'm:ss, mm:ss, h:mm:ss, or hh:mm:ss',
|
|
type: 'string',
|
|
validation: (Rule) =>
|
|
Rule.required().custom((value) => {
|
|
if (typeof value !== 'string') return 'Duration is required'
|
|
|
|
const trimmed = value.trim()
|
|
|
|
const regex = /^(?:\d{1,2}:[0-5]\d:[0-5]\d|[0-5]?\d:[0-5]\d)$/
|
|
|
|
return regex.test(trimmed)
|
|
? true
|
|
: 'Use m:ss, mm:ss, h:mm:ss, or hh:mm:ss (e.g. 3:07, 03:07, 1:03:07, 12:03:07)'
|
|
}),
|
|
}),
|
|
|
|
defineField({
|
|
name: 'previewMp3',
|
|
title: '30s preview (MP3)',
|
|
type: 'file',
|
|
options: {accept: 'audio/mpeg'},
|
|
}),
|
|
],
|
|
|
|
preview: {
|
|
select: {
|
|
storedTitle: 'displayTitle',
|
|
workTitle: 'work.title',
|
|
movement: 'movement',
|
|
mp3: 'previewMp3',
|
|
composerName: 'work.composer.name',
|
|
arrangerName: 'work.arranger.name',
|
|
artist: 'artist',
|
|
albumArtist: 'albumArtist',
|
|
},
|
|
prepare({
|
|
storedTitle,
|
|
workTitle,
|
|
movement,
|
|
mp3,
|
|
composerName,
|
|
arrangerName,
|
|
artist,
|
|
albumArtist,
|
|
}) {
|
|
const wt = (workTitle || '').trim()
|
|
const mv = (movement || '').trim()
|
|
|
|
const computed = wt && mv ? `${wt}: ${mv}` : wt || mv
|
|
const title = (storedTitle && storedTitle.trim()) || computed || '(Untitled track)'
|
|
|
|
const subtitleParts = [
|
|
mp3 ? '♫' : null,
|
|
composerName || null,
|
|
arrangerName ? `(arr. ${arrangerName})` : null,
|
|
].filter(Boolean)
|
|
|
|
return {title, subtitle: subtitleParts.join(' ')}
|
|
},
|
|
},
|
|
}),
|
|
],
|
|
}),
|
|
defineField({
|
|
name: 'officialUrl',
|
|
title: 'Official Link',
|
|
type: 'url',
|
|
group: 'links',
|
|
fieldset: 'links',
|
|
}),
|
|
defineField({
|
|
name: 'spotifyUrl',
|
|
title: 'Spotify',
|
|
type: 'url',
|
|
group: 'links',
|
|
fieldset: 'links',
|
|
}),
|
|
defineField({
|
|
name: 'appleMusicUrl',
|
|
title: 'Apple Music',
|
|
type: 'url',
|
|
group: 'links',
|
|
fieldset: 'links',
|
|
}),
|
|
defineField({
|
|
name: 'deezerUrl',
|
|
title: 'Deezer',
|
|
type: 'url',
|
|
group: 'links',
|
|
fieldset: 'links',
|
|
}),
|
|
defineField({
|
|
name: 'amazonMusicUrl',
|
|
title: 'Amazon Music',
|
|
type: 'url',
|
|
group: 'links',
|
|
fieldset: 'links',
|
|
}),
|
|
defineField({name: 'tidalUrl', title: 'Tidal', type: 'url', group: 'links', fieldset: 'links'}),
|
|
defineField({name: 'qobuzUrl', title: 'Qobuz', type: 'url', group: 'links', fieldset: 'links'}),
|
|
defineField({
|
|
name: 'nativeDsdUrl',
|
|
title: 'NativeDSD',
|
|
type: 'url',
|
|
group: 'links',
|
|
fieldset: 'links',
|
|
}),
|
|
|
|
defineField({
|
|
name: 'credits',
|
|
title: 'Credits',
|
|
group: 'creditsSpecs',
|
|
type: 'array',
|
|
of: [
|
|
defineArrayMember({
|
|
name: 'credit',
|
|
title: 'Credit',
|
|
type: 'object',
|
|
fields: [
|
|
defineField({
|
|
name: 'role',
|
|
title: 'Role',
|
|
type: 'string',
|
|
description: 'e.g. “Recording engineer”',
|
|
}),
|
|
defineField({
|
|
name: 'name',
|
|
title: 'Name',
|
|
type: 'text',
|
|
rows: 2,
|
|
description: 'You may use “ | ” to indicate multiple names.',
|
|
}),
|
|
],
|
|
preview: {
|
|
select: {role: 'role', name: 'name'},
|
|
prepare({role, name}) {
|
|
return {title: name || '(No role)', subtitle: role}
|
|
},
|
|
},
|
|
}),
|
|
],
|
|
}),
|
|
|
|
defineField({
|
|
name: 'recordingDate',
|
|
title: 'Recording date(s)',
|
|
group: 'creditsSpecs',
|
|
type: 'string',
|
|
description: 'You may use “ | ” to indicate multiple dates.',
|
|
}),
|
|
|
|
defineField({
|
|
name: 'recordingLocation',
|
|
title: 'Recording location(s)',
|
|
group: 'creditsSpecs',
|
|
type: 'string',
|
|
description: 'You may use “ | ” to indicate multiple locations.',
|
|
}),
|
|
|
|
defineField({
|
|
name: 'recordingFormat',
|
|
title: 'Recording format',
|
|
group: 'creditsSpecs',
|
|
type: 'string',
|
|
options: {
|
|
list: [
|
|
{title: 'PCM 352.8 kHz 24 bit', value: 'PCM 352.8 kHz 24 bit'},
|
|
{title: 'PCM 352.8 kHz 32 bit', value: 'PCM 352.8 kHz 32 bit'},
|
|
{title: 'DSD 11.2 MHz 1 bit', value: 'DSD 11.2 MHz 1 bit'},
|
|
],
|
|
},
|
|
}),
|
|
|
|
defineField({
|
|
name: 'masteringFormat',
|
|
title: 'Mastering format',
|
|
group: 'creditsSpecs',
|
|
type: 'string',
|
|
options: {
|
|
list: [
|
|
{title: 'PCM 352.8 kHz 32 bit', value: 'PCM 352.8 kHz 32 bit'},
|
|
{title: 'PCM 352.8 kHz 64 bit', value: 'PCM 352.8 kHz 64 bit'},
|
|
{title: 'DSD 11.2 MHz 1 bit', value: 'DSD 11.2 MHz 1 bit'},
|
|
],
|
|
},
|
|
}),
|
|
|
|
defineField({
|
|
name: 'equipment',
|
|
title: 'Equipment',
|
|
group: 'creditsSpecs',
|
|
type: 'array',
|
|
of: [
|
|
defineArrayMember({
|
|
name: 'equipmentItem',
|
|
title: 'Equipment item',
|
|
type: 'object',
|
|
fields: [
|
|
defineField({
|
|
name: 'type',
|
|
title: 'Type',
|
|
type: 'string',
|
|
description: 'e.g. “Microphones”',
|
|
}),
|
|
defineField({
|
|
name: 'name',
|
|
title: 'Name',
|
|
type: 'text',
|
|
rows: 2,
|
|
description: 'You may use “ | ” to indicate multiple items.',
|
|
}),
|
|
],
|
|
preview: {
|
|
select: {type: 'type', name: 'name'},
|
|
prepare({type, name}) {
|
|
return {title: type || '(No type)', subtitle: name}
|
|
},
|
|
},
|
|
}),
|
|
],
|
|
}),
|
|
|
|
defineField({
|
|
name: 'genre',
|
|
title: 'Genre(s)',
|
|
type: 'array',
|
|
of: [
|
|
defineArrayMember({
|
|
type: 'string',
|
|
options: {
|
|
layout: 'tags' as const,
|
|
list: [
|
|
{title: 'Early Music', value: 'earlyMusic'},
|
|
{title: 'Baroque', value: 'baroque'},
|
|
{title: 'Classical', value: 'classical'},
|
|
{title: 'Romantic', value: 'romantic'},
|
|
{title: 'Contemporary', value: 'contemporary'},
|
|
{title: 'World Music', value: 'worldMusic'},
|
|
{title: 'Jazz', value: 'jazz'},
|
|
{title: 'Crossover', value: 'crossover'},
|
|
{title: 'Electronic', value: 'electronic'},
|
|
{title: 'Minimal', value: 'minimal'},
|
|
{title: 'Pop / Rock', value: 'popRock'},
|
|
],
|
|
},
|
|
} as any),
|
|
],
|
|
group: 'references',
|
|
}),
|
|
defineField({
|
|
name: 'instrumentation',
|
|
title: 'Instrumentation(s)',
|
|
type: 'array',
|
|
of: [
|
|
defineArrayMember({
|
|
type: 'string',
|
|
options: {
|
|
layout: 'tags' as const,
|
|
list: [
|
|
{title: 'Solo', value: 'solo'},
|
|
{title: 'Chamber', value: 'chamber'},
|
|
{title: 'Ensemble', value: 'ensemble'},
|
|
{title: 'Orchestral', value: 'orchestra'},
|
|
{title: 'Vocal / Choral', value: 'vocalChoral'},
|
|
],
|
|
},
|
|
} as any),
|
|
],
|
|
group: 'references',
|
|
}),
|
|
defineField({
|
|
name: 'artists',
|
|
title: 'Artist(s)',
|
|
group: 'references',
|
|
type: 'array',
|
|
of: [
|
|
defineArrayMember({
|
|
type: 'reference',
|
|
to: [{type: 'artist'}],
|
|
}),
|
|
],
|
|
}),
|
|
defineField({
|
|
name: 'reviews',
|
|
title: 'Reviews',
|
|
group: 'reviews',
|
|
type: 'array',
|
|
of: [
|
|
defineArrayMember({
|
|
type: 'object',
|
|
name: 'review',
|
|
fields: [
|
|
defineField({
|
|
name: 'quote',
|
|
title: 'Quote',
|
|
type: 'text',
|
|
rows: 4,
|
|
}),
|
|
defineField({
|
|
name: 'author',
|
|
title: 'Author',
|
|
type: 'string',
|
|
}),
|
|
],
|
|
preview: {
|
|
select: {
|
|
quote: 'quote',
|
|
author: 'author',
|
|
},
|
|
prepare({quote, author}) {
|
|
return {
|
|
title: quote
|
|
? quote.length > 60
|
|
? quote.substring(0, 60) + '...'
|
|
: quote
|
|
: '(Empty review)',
|
|
subtitle: author || '',
|
|
}
|
|
},
|
|
},
|
|
}),
|
|
],
|
|
}),
|
|
defineField({
|
|
name: 'availableVariants',
|
|
title: 'Available Variants',
|
|
type: 'array',
|
|
group: 'medusa',
|
|
of: [defineArrayMember({type: 'string'})],
|
|
options: {
|
|
list: [
|
|
{title: 'CD', value: 'cd'},
|
|
{title: 'SACD', value: 'sacd'},
|
|
{title: 'LP', value: 'lp'},
|
|
{title: 'Stereo FLAC 88/24', value: '88k24b2ch'},
|
|
{title: 'Stereo FLAC 176/24', value: '176k24b2ch'},
|
|
{title: 'Stereo FLAC 352/24', value: '352k24b2ch'},
|
|
{title: 'Stereo FLAC 352/32', value: '352k32b2ch'},
|
|
{title: 'Surround FLAC 88/24', value: '88k24b5ch'},
|
|
{title: 'Surround FLAC 176/24', value: '176k24b5ch'},
|
|
{title: 'Surround FLAC 352/24', value: '352k24b5ch'},
|
|
{title: 'Surround FLAC 352/32', value: '352k32b5ch'},
|
|
{title: 'Dolby Atmos, DTS:X & Auro-3D in MKV', value: 'mkv'},
|
|
{title: 'Auro-3D FLAC', value: 'a3d'},
|
|
{title: 'Dolby Atmos ADM 48kHz', value: 'adm48'},
|
|
{title: 'Dolby Atmos ADM 96kHz', value: 'adm96'},
|
|
{title: 'HD Video', value: 'hd'},
|
|
{title: '4K Video', value: '4k'},
|
|
],
|
|
},
|
|
}),
|
|
],
|
|
|
|
orderings: [
|
|
{
|
|
title: 'Release Date (latest first)',
|
|
name: 'releaseDateDesc',
|
|
by: [{field: 'releaseDate', direction: 'desc'}],
|
|
},
|
|
{
|
|
title: 'Release Date (oldest first)',
|
|
name: 'releaseDateAsc',
|
|
by: [{field: 'releaseDate', direction: 'asc'}],
|
|
},
|
|
{
|
|
title: 'Catalog # (Asc.)',
|
|
name: 'catalogNoAsc',
|
|
by: [{field: 'catalogNo', direction: 'asc'}],
|
|
},
|
|
{
|
|
title: 'Catalog # (Desc.)',
|
|
name: 'catalogNoDesc',
|
|
by: [{field: 'catalogNo', direction: 'desc'}],
|
|
},
|
|
],
|
|
|
|
preview: {
|
|
select: {
|
|
title: 'name',
|
|
artist: 'albumArtist',
|
|
catNo: 'catalogNo',
|
|
media: 'albumCover',
|
|
},
|
|
prepare({title, artist, catNo, media}) {
|
|
return {
|
|
title: title || '(Untitled release)',
|
|
subtitle: artist ? artist : '',
|
|
media,
|
|
}
|
|
},
|
|
},
|
|
})
|