import { computed, reactive, ref, watch, } from 'vue' import type { PillNavItem, TopBarSettingsConfig, } from '../types' import { replaceRecord, } from './controlSchemaUtils' import { mergeControlWorkspaceOptions, } from './controlWorkspaceOptions' import { controlValuesToComponentBindings, controlValuesToComponentBindingsById, controlValuesToComponentProps, } from './controlComponentBindings' import { isControlModelBinding, } from './controlSchemaModel' import { useControlSchema, } from './useControlSchema' import type { ControlComponentBinding, ControlComponentBindingsById, ControlComponentBindingsConfig, ControlComponentPropsByIdMap, ControlComponentPropsMap, ControlModelBinding, ControlSchema, ControlValues, ControlWorkspaceComponentBindings, ControlWorkspaceFormBinding, ControlWorkspaceOptions, ControlWorkspacePillNavBinding, ControlWorkspaceSidebarBinding, ControlWorkspaceTopBarBinding, ControlWorkspaceTopBarSettingsBinding, UseControlWorkspaceReturn, } from './controlSchemaTypes' /** * Prepare shared reactive values plus AppTopBar/AppSidebar/FormBuilder bindings from one simple controls data model. * * @example Recommended * ```ts * const workspace = useControlWorkspace(workspaceModel) * const { form, sidebar, topBar } = workspace.bindings * ``` */ export function useControlWorkspace( controlsOrModel: TControls | (ControlModelBinding & { controls: TControls }), options: ControlWorkspaceOptions = {}, ): UseControlWorkspaceReturn { const model = isControlModelBinding(controlsOrModel) ? controlsOrModel : undefined const controls: TControls = model ? model.controls : controlsOrModel as TControls const workspaceOptions = model ? mergeControlWorkspaceOptions(model.controlOptions, options) : options const { initialValues, topBarSettings, ...schemaOptions } = workspaceOptions const schema = useControlSchema(controls, schemaOptions) const activeView = ref(schema.defaultView) const values = reactive({ ...schema.initialValues, ...(initialValues ?? {}), }) as ControlValues & Record function setActiveView(viewId: string) { syncActiveView(viewId) } function syncActiveView(viewId: string) { if (!schema.viewIds.includes(viewId)) return if (activeView.value !== viewId) activeView.value = viewId if (sidebar.activeView !== viewId) sidebar.activeView = viewId if (pillNav.currentItemId !== viewId) pillNav.currentItemId = viewId } function setValues(nextValues: Record) { Object.assign(values, nextValues) } function resetValues(nextValues: Record = {}) { replaceRecord(values, { ...schema.initialValues, ...nextValues, }) } function getComponentProps( mapping?: ControlComponentPropsMap & Record>, ): Record { return controlValuesToComponentProps(values, mapping) } function getComponentPropsById( mappings?: ControlComponentPropsByIdMap & Record>, ): Record> { if (mappings === undefined) return {} return Object.fromEntries( Object.entries(mappings).map(([id, mapping]) => [ id, controlValuesToComponentProps(values, mapping), ]), ) } function getComponentBindings( bindings?: ControlComponentBindingsConfig & Record>, ): ControlComponentBinding[] { return controlValuesToComponentBindings(values, bindings) } function getComponentBindingsById( bindings?: ControlComponentBindingsConfig & Record>, ): ControlComponentBindingsById { return controlValuesToComponentBindingsById(values, bindings) } const componentProps = computed(() => ( model?.componentProps === undefined ? {} : getComponentProps(model.componentProps) )) const componentPropsById = computed(() => getComponentPropsById(model?.componentPropsById)) const componentBindings = computed(() => getComponentBindings(model?.componentBindings)) const componentBindingsById = computed(() => getComponentBindingsById(model?.componentBindings)) const form = { ...schema.form, modelValue: values, 'onUpdate:modelValue': setValues, } as ControlWorkspaceFormBinding const topBarSettingsConfig: TopBarSettingsConfig = { ...schema.topBarSettingsConfig, ...(topBarSettings ?? {}), values, } const sidebar = reactive({ ...schema.sidebar, activeView: activeView.value, modelValue: values, 'onUpdate:modelValue': setValues, values, 'onUpdate:values': setValues, }) as ControlWorkspaceSidebarBinding const pillNav = reactive({ items: schema.viewItems, currentItemId: activeView.value, onSelect: (item: PillNavItem) => setActiveView(item.id), }) as ControlWorkspacePillNavBinding const topBarSettingsBinding = { showSettings: true, settingsConfig: topBarSettingsConfig, onSettingsValuesChange: setValues, } as ControlWorkspaceTopBarSettingsBinding const topBarProps = computed(() => ({ pillNav: pillNav.items, currentPillId: pillNav.currentItemId, onPillSelect: pillNav.onSelect, ...topBarSettingsBinding, })) const bindings: ControlWorkspaceComponentBindings = { form, sidebar, topBar: topBarProps, topBarSettings: topBarSettingsBinding, pillNav, componentBindings, componentBindingsById, componentProps, componentPropsById, } watch(activeView, syncActiveView, { flush: 'sync' }) watch(() => sidebar.activeView, syncActiveView, { flush: 'sync' }) watch(() => pillNav.currentItemId, syncActiveView, { flush: 'sync' }) return { ...schema, schema, values, activeView, topBarSettingsConfig, form, sidebar, topBar: topBarProps, pillNav, topBarSettings: topBarSettingsBinding, bindings, componentBindings, componentBindingsById, componentProps, componentPropsById, setActiveView, setValues, resetValues, getComponentProps, getComponentPropsById, getComponentBindings, getComponentBindingsById, } }