sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
133 lines (117 loc) • 3.5 kB
text/typescript
import {kebabCase} from 'lodash'
import {HELP_URL, SerializeError} from '../SerializeError'
import {type Serializable, type SerializeOptions, type SerializePath} from '../StructureNodes'
import {type View} from '../types'
import {validateId} from '../util/validateId'
import {type ComponentViewBuilder} from './ComponentView'
import {type FormViewBuilder} from './FormView'
/**
* Interface for base view
*
* @public */
export interface BaseView {
/** View id */
id: string
/** View Title */
title: string
/** View Icon */
icon?: React.ComponentType | React.ReactNode
}
/**
* Class for building generic views.
*
* @public
*/
export abstract class GenericViewBuilder<TView extends Partial<BaseView>, ConcreteImpl>
implements Serializable<BaseView>
{
/** Generic view option object */
protected spec: TView = {} as TView
/** Set generic view ID
* @param id - generic view ID
* @returns generic view builder based on ID provided.
*/
id(id: string): ConcreteImpl {
return this.clone({id})
}
/** Get generic view ID
* @returns generic view ID
*/
getId(): TView['id'] {
return this.spec.id
}
/** Set generic view title
* @param title - generic view title
* @returns generic view builder based on title provided and (if provided) its ID.
*/
title(title: string): ConcreteImpl {
return this.clone({title, id: this.spec.id || kebabCase(title)})
}
/** Get generic view title
* @returns generic view title
*/
getTitle(): TView['title'] {
return this.spec.title
}
/** Set generic view icon
* @param icon - generic view icon
* @returns generic view builder based on icon provided.
*/
icon(icon: React.ComponentType | React.ReactNode): ConcreteImpl {
return this.clone({icon})
}
/** Get generic view icon
* @returns generic view icon
*/
getIcon(): TView['icon'] {
return this.spec.icon
}
/** Serialize generic view
* @param options - serialization options. See {@link SerializeOptions}
* @returns generic view object based on path provided in options. See {@link BaseView}
*/
serialize(options: SerializeOptions = {path: []}): BaseView {
const {id, title, icon} = this.spec
if (!id) {
throw new SerializeError(
'`id` is required for view item',
options.path,
options.index,
).withHelpUrl(HELP_URL.ID_REQUIRED)
}
if (!title) {
throw new SerializeError(
'`title` is required for view item',
options.path,
options.index,
).withHelpUrl(HELP_URL.TITLE_REQUIRED)
}
return {
id: validateId(id, options.path, options.index),
title,
icon,
}
}
/** Clone generic view builder (allows for options overriding)
* @param withSpec - Partial generic view builder options. See {@link BaseView}
* @returns Generic view builder.
*/
abstract clone(withSpec?: Partial<BaseView>): ConcreteImpl
}
function isSerializable(view: BaseView | Serializable<BaseView>): view is Serializable<BaseView> {
return typeof (view as Serializable<BaseView>).serialize === 'function'
}
/** @internal */
export function maybeSerializeView(
item: View | Serializable<View>,
index: number,
path: SerializePath,
): View {
return isSerializable(item) ? item.serialize({path, index}) : item
}
/**
* View builder. See {@link ComponentViewBuilder} and {@link FormViewBuilder}
*
* @public
*/
export type ViewBuilder = ComponentViewBuilder | FormViewBuilder