import { FieldPresence, type FieldProcessor, FieldSchema, Objects, StructureSchema, ValueType } from '@ephox/boulder'; import { Arr, Fun, Obj, Optional, type Result } from '@ephox/katamari'; import type { AlloyComponent } from '../api/component/ComponentApi'; import type { AlloySpec, SimpleOrSketchSpec, SketchSpec } from '../api/component/SpecTypes'; import type { CompositeSketchDetail } from '../api/ui/Sketcher'; import * as Fields from '../data/Fields'; import * as UiSubstitutes from '../spec/UiSubstitutes'; import * as PartSubstitutes from './PartSubstitutes'; import * as PartType from './PartType'; export interface GeneratedParts { [key: string]: (config: Record) => ConfiguredPart; } export interface UnconfiguredPart { name: string; owner: string; uiType: string; } // TODO: Try and use stronger types for the config, however it's pretty arbitrary so it may not be possible export interface ConfiguredPart extends UnconfiguredPart { config: Record; validated: Record; } export type Substitution = Record; export interface Substitutions { internals: () => Substitution; externals: () => Record Record>; } // TODO: Make more functional if performance isn't an issue. const generate = (owner: string, parts: PartType.PartTypeAdt[]): GeneratedParts => { const r: GeneratedParts = { }; Arr.each(parts, (part) => { PartType.asNamedPart(part).each((np) => { const g: UnconfiguredPart = doGenerateOne(owner, np.pname); r[np.name] = (config) => { const validated = StructureSchema.asRawOrDie('Part: ' + np.name + ' in ' + owner, StructureSchema.objOf(np.schema), config); return { ...g, config, validated }; }; }); }); return r; }; // Does not have the config. const doGenerateOne = (owner: string, pname: string): UnconfiguredPart => ({ uiType: UiSubstitutes.placeholder(), owner, name: pname }); const generateOne = (owner: string, pname: string, config: SimpleOrSketchSpec): ConfiguredPart => ({ uiType: UiSubstitutes.placeholder(), owner, name: pname, config, validated: { } }); const schemas = (parts: PartType.PartTypeAdt[]): FieldProcessor[] => // This actually has to change. It needs to return the schemas for things that will // not appear in the components list, which is only externals Arr.bind(parts, (part: PartType.PartTypeAdt) => part.fold>>( Optional.none, Optional.some, Optional.none, Optional.none ).map((data) => FieldSchema.requiredObjOf(data.name, data.schema.concat([ Fields.snapshot(PartType.original()) ]))).toArray()); const names = (parts: PartType.PartTypeAdt[]): string[] => Arr.map(parts, PartType.name); const substitutes = (owner: string, detail: D, parts: PartType.PartTypeAdt[]): Substitutions => PartSubstitutes.subs(owner, detail, parts); const components = (owner: string, detail: D, internals: Substitution): AlloySpec[] => UiSubstitutes.substitutePlaces(Optional.some(owner), detail, detail.components, internals); const getPart = (component: AlloyComponent, detail: D, partKey: string): Optional => { const uid = detail.partUids[partKey]; return component.getSystem().getByUid(uid).toOptional(); }; const getPartOrDie = (component: AlloyComponent, detail: D, partKey: string): AlloyComponent => getPart(component, detail, partKey).getOrDie('Could not find part: ' + partKey); const getParts = (component: AlloyComponent, detail: D, partKeys: string[]): Record Result> => { const r: Record Result> = { }; const uids = detail.partUids; const system = component.getSystem(); Arr.each(partKeys, (pk) => { r[pk] = Fun.constant(system.getByUid(uids[pk])); }); return r; }; const getAllParts = (component: AlloyComponent, detail: D): Record Result> => { const system = component.getSystem(); return Obj.map(detail.partUids, (pUid, _k) => Fun.constant(system.getByUid(pUid))); }; const getAllPartNames = (detail: D): string[] => Obj.keys(detail.partUids); const getPartsOrDie = (component: AlloyComponent, detail: D, partKeys: string[]): Record AlloyComponent> => { const r: Record AlloyComponent> = { }; const uids = detail.partUids; const system = component.getSystem(); Arr.each(partKeys, (pk) => { r[pk] = Fun.constant(system.getByUid(uids[pk]).getOrDie()); }); return r; }; const defaultUids = (baseUid: string, partTypes: PartType.PartTypeAdt[]): Record => { const partNames = names(partTypes); return Objects.wrapAll( Arr.map(partNames, (pn) => ({ key: pn, value: baseUid + '-' + pn })) ); }; const defaultUidsSchema = (partTypes: PartType.PartTypeAdt[]): FieldProcessor => FieldSchema.field( 'partUids', 'partUids', FieldPresence.mergeWithThunk((spec: SketchSpec) => defaultUids(spec.uid, partTypes)), ValueType.anyValue() ); export { generate, generateOne, schemas, names, substitutes, components, defaultUids, defaultUidsSchema, getAllParts, getAllPartNames, getPart, getPartOrDie, getParts, getPartsOrDie };