import type { TopBarVariant } from '../types' import type { FormEnhancements } from '../types/form-builder' import type { ControlModel, ControlModelBinding, ControlSchema, ControlWorkspaceOptions, UseControlWorkspaceReturn, } from './useControlSchema' import { mergeControlWorkspaceOptions, resolveControlModel } from './useControlSchema' /** * Merges `initialValues`/external `modelValue`/`values` on top of a base * `ControlWorkspaceOptions.initialValues`, matching the layering FormBuilder expects: base * defaults, then caller-supplied initial values, then live external values win last. Shared by * {@link controlWorkspaceProps} and {@link bioTemplatePresetWorkspaceProps} so both "generate a * workspace from options, initialValues, and v-model" views merge options identically. */ export function resolveControlOptionsWithExternalValues( base: TOptions, initialValues: Record | undefined, externalValues: Record | undefined, ): TOptions { if (initialValues === undefined && externalValues === undefined) return base return { ...base, initialValues: { ...(base.initialValues ?? {}), ...(initialValues ?? {}), ...(externalValues ?? {}), }, } } export interface ControlWorkspaceModelProps { model?: ControlModel | ControlModelBinding controls?: ControlSchema controlOptions?: ControlWorkspaceOptions initialValues?: Record modelValue?: Record values?: Record } export interface ResolvedControlWorkspaceModel { /** `modelValue` if provided, else `values` — the single external v-model source of truth. */ externalValues: Record | undefined /** `model` resolved through `resolveControlModel()`, or undefined for a raw `controls` schema. */ resolvedModel: ControlModelBinding | undefined /** `controls` if provided, else the resolved model's controls, else `{}`. */ resolvedControls: ControlSchema /** `controlOptions` merged over the resolved model's own options, with initialValues/external values layered on top. */ resolvedControlOptions: ControlWorkspaceOptions } /** * Resolves ControlWorkspaceView's `model`/`controls`/`controlOptions`/`initialValues`/ * `modelValue`/`values` props into the schema + options pair `useControlWorkspace()` needs. * Pure and framework-free so callers wrap each field in their own `computed()`. * * @example Recommended * ```ts * const resolved = computed(() => controlWorkspaceProps(props)) * const resolvedControls = computed(() => resolved.value.resolvedControls) * ``` */ export function controlWorkspaceProps(props: ControlWorkspaceModelProps): ResolvedControlWorkspaceModel { const externalValues = props.modelValue ?? props.values const resolvedModel = resolveControlModel(props.model) const resolvedControls: ControlSchema = props.controls ?? resolvedModel?.controls ?? {} const baseControlOptions = mergeControlWorkspaceOptions(resolvedModel?.controlOptions, props.controlOptions) const resolvedControlOptions = resolveControlOptionsWithExternalValues( baseControlOptions, props.initialValues, externalValues, ) return { externalValues, resolvedModel, resolvedControls, resolvedControlOptions } } export interface DoseDesignWorkspacePassthroughProps { workspace?: UseControlWorkspaceReturn controlOptions?: ControlWorkspaceOptions initialValues?: Record modelValue?: Record values?: Record title?: string subtitle?: string topBarVariant?: TopBarVariant sidebarWidth?: string /** @deprecated No-op, removed in MINT 1.4. AppSidebar has one look now. */ sidebarVariant?: 'default' | 'analysis' responsiveSidebar?: boolean sidebarPosition?: 'left' | 'right' sidebarTitle?: string sidebarSubtitle?: string sidebarBadge?: string | number floating?: boolean dense?: boolean showSettings?: boolean showFormActions?: boolean formEnhancements?: FormEnhancements> formLoading?: boolean formDisabled?: boolean formReadonly?: boolean formSize?: 'sm' | 'md' | 'lg' } /** * Maps DoseDesignWorkspaceView's props straight through to the identically-named * ControlWorkspaceView props it wraps — the ~25-key pass-through list that used to be spelled * out attribute-by-attribute in the template. `model` is the caller's already-resolved dose * design model, not the raw (possibly absent) `props.model`. * * @example Recommended * ```vue * * ``` */ export function doseDesignWorkspaceProps( props: DoseDesignWorkspacePassthroughProps, model: ControlModel | ControlModelBinding, ) { return { model, workspace: props.workspace, controlOptions: props.controlOptions, initialValues: props.initialValues, modelValue: props.modelValue, values: props.values, title: props.title, subtitle: props.subtitle, topBarVariant: props.topBarVariant, sidebarWidth: props.sidebarWidth, sidebarVariant: props.sidebarVariant, responsiveSidebar: props.responsiveSidebar, sidebarPosition: props.sidebarPosition, sidebarTitle: props.sidebarTitle, sidebarSubtitle: props.sidebarSubtitle, sidebarBadge: props.sidebarBadge, floating: props.floating, dense: props.dense, showSettings: props.showSettings, showFormActions: props.showFormActions, formEnhancements: props.formEnhancements, formLoading: props.formLoading, formDisabled: props.formDisabled, formReadonly: props.formReadonly, formSize: props.formSize, } } export interface BioTemplateExperimentWorkspaceStatusProps { status?: { loading?: boolean saving?: boolean error?: string | null hasExperiment?: boolean currentExperimentId?: number lastLoadedAt?: Date | null lastSavedAt?: Date | null } loading?: boolean error?: string | null hasExperiment?: boolean currentExperimentId?: number lastLoadedAt?: Date | null lastSavedAt?: Date | null } export interface ResolvedBioTemplateExperimentStatus { loading: boolean error: string | null | undefined hasExperiment: boolean currentExperimentId: number | undefined lastLoadedAt: Date | null | undefined lastSavedAt: Date | null | undefined } /** * Merges BioTemplateExperimentWorkspaceView's grouped `status` prop over its individual * `loading`/`error`/`hasExperiment`/... props: `status` wins whenever the corresponding field * on it is set, otherwise the flat prop (or its default) is used. Centralizes the six * independent fallback rules generated plugin pages rely on when they pass a request/experiment * `status` object instead of wiring each field by hand. */ export function bioTemplateExperimentWorkspaceProps( props: BioTemplateExperimentWorkspaceStatusProps, ): ResolvedBioTemplateExperimentStatus { const status = props.status return { loading: status?.loading !== undefined || status?.saving !== undefined ? Boolean(status?.loading) || Boolean(status?.saving) : Boolean(props.loading), error: status?.error !== undefined ? status.error : props.error, hasExperiment: status?.hasExperiment ?? props.hasExperiment ?? false, currentExperimentId: status?.currentExperimentId ?? props.currentExperimentId, lastLoadedAt: status?.lastLoadedAt !== undefined ? status.lastLoadedAt : props.lastLoadedAt, lastSavedAt: status?.lastSavedAt !== undefined ? status.lastSavedAt : props.lastSavedAt, } } export interface BioTemplatePackWorkspacePassthroughProps { message?: string dense?: boolean readonly?: boolean showStatus?: boolean showTemplateSummary?: boolean loadLabel?: string resetLabel?: string saveLabel?: string } /** * Assembles the prop object BioTemplatePackWorkspaceView binds onto the * BioTemplateExperimentWorkspaceView it wraps: its own display props passed straight through, * plus the pack-derived `target`/`label`/`status`/`actions` computed by the caller. */ export function bioTemplatePackWorkspaceProps( props: BioTemplatePackWorkspacePassthroughProps, derived: { target: TTarget label: string status: TStatus actions: TActions }, ) { return { target: derived.target, kind: 'collection' as const, label: derived.label, message: props.message, status: derived.status, actions: derived.actions, dense: props.dense, readonly: props.readonly, showStatus: props.showStatus, showTemplateSummary: props.showTemplateSummary, loadLabel: props.loadLabel, resetLabel: props.resetLabel, saveLabel: props.saveLabel, } } export interface BioTemplatePresetWorkspaceOptionsProps { workspaceOptions?: TOptions initialValues?: Record modelValue?: Record values?: Record } /** * Resolves BioTemplatePresetWorkspaceView's `workspaceOptions`/`initialValues`/`modelValue`/ * `values` props into the single options object `useBioTemplatePresetWorkspace()` needs, * reusing the same base/initialValues/external-values layering as {@link controlWorkspaceProps}. */ export function bioTemplatePresetWorkspaceProps( props: BioTemplatePresetWorkspaceOptionsProps, ): { externalValues: Record | undefined; resolvedWorkspaceOptions: TOptions } { const externalValues = props.modelValue ?? props.values const resolvedWorkspaceOptions = resolveControlOptionsWithExternalValues( (props.workspaceOptions ?? {}) as TOptions, props.initialValues, externalValues, ) return { externalValues, resolvedWorkspaceOptions } }