"use client"; import * as React from "react"; import type { ResolvedToolcraftControlSectionSchema, ResolvedToolcraftControlSchema, ToolcraftControlSchema, } from "../../../schema/types"; import { getToolcraftCanvasSizeTargetDimension, isToolcraftRuntimeOwnedTarget, } from "../../../schema/runtime-targets"; import { getToolcraftControlRendererKind } from "../renderers/controls-panel-renderer-registry"; export type ControlEntry = [string, ResolvedToolcraftControlSchema]; export type ControlRenderGroup = | { entries: readonly ControlEntry[]; kind: "colorGroup" } | { entry: ControlEntry; kind: "control" }; export type RenderedControlRenderGroup = { ids: readonly string[]; node: React.ReactNode; }; export function isControlsPanelRenderableControl( control: ToolcraftControlSchema, ): boolean { return getToolcraftControlRendererKind(control.type) !== "canvas-handle"; } export function getControlsPanelRenderableEntries( entries: readonly ControlEntry[], ): ControlEntry[] { return entries.filter(([, control]) => isControlsPanelRenderableControl(control), ); } const hiddenDiscreteMarkerCount = 2; const sectionedCompoundControlTypes = new Set([ "channelMixer", "collectionActions", "sourceCollection", "fontPicker", "gradient", "palette", ]); export function shouldRenderCompoundControlSectionDivider( control: ToolcraftControlSchema, ): boolean { if (control.type === "curves") { return control.variant !== "single"; } return sectionedCompoundControlTypes.has(control.type); } export function withCompoundControlSectionDivider({ children, control, }: { children: React.ReactNode; control: ToolcraftControlSchema; }): React.ReactNode { if (!shouldRenderCompoundControlSectionDivider(control)) { return children; } return (
{children}
); } export function getControlName( id: string, label: boolean | string | undefined, ): string { if (typeof label === "string") { return label; } return id; } export function getControlsRecord( entries: readonly ControlEntry[], ): Record { return Object.fromEntries(entries); } export function getControlRenderGroups( entries: readonly ControlEntry[], sectionHasOnlyColorFields: boolean, ): ControlRenderGroup[] { const groups: ControlRenderGroup[] = []; let index = 0; while (index < entries.length) { const entry = entries[index]; if (!entry) { index += 1; continue; } const colorBankKey = getPlainColorBankKey( entry[1], sectionHasOnlyColorFields, ); if (!colorBankKey) { groups.push({ entry, kind: "control" }); index += 1; continue; } const colorEntries: ControlEntry[] = []; while ( entries[index] && getPlainColorBankKey( entries[index][1], sectionHasOnlyColorFields, ) === colorBankKey ) { const colorEntry = entries[index]; if (colorEntry) { colorEntries.push(colorEntry); } index += 1; } for ( let colorIndex = 0; colorIndex < colorEntries.length; colorIndex += 2 ) { const firstEntry = colorEntries[colorIndex]; const secondEntry = colorEntries[colorIndex + 1]; if (firstEntry && secondEntry) { groups.push({ entries: [firstEntry, secondEntry], kind: "colorGroup" }); } else if (firstEntry) { groups.push({ entry: firstEntry, kind: "control" }); } } } return groups; } function getPlainColorBankKey( control: ToolcraftControlSchema, sectionHasOnlyColorFields: boolean, ): string | null { if (control.type !== "color") { return null; } if (sectionHasOnlyColorFields) { return "color-only-section"; } const semanticGroup = control.semanticGroup?.trim(); return semanticGroup ? `semantic:${semanticGroup}` : null; } export function getControlRenderGroupIds( group: ControlRenderGroup, ): readonly string[] { return group.kind === "colorGroup" ? group.entries.map(([id]) => id) : [group.entry[0]]; } export function countControlsByType( sections: readonly ResolvedToolcraftControlSectionSchema[], type: string, ): number { return sections.reduce( (count, section) => count + Object.values(section.controls).filter((control) => control.type === type) .length, 0, ); } export function shouldCommitTextControlOnBlur( control: ToolcraftControlSchema, ): boolean { return ( control.commitMode === "setting" || Boolean(getToolcraftCanvasSizeTargetDimension(control.target)) ); } function isColorFieldControl( control: ToolcraftControlSchema | undefined, ): boolean { return control?.type === "color" || control?.type === "colorOpacity"; } export function isColorOnlySection(entries: readonly ControlEntry[]): boolean { return ( entries.length > 0 && entries.every(([, control]) => isColorFieldControl(control)) ); } export function getRenderedControlsSectionTitle( section: ResolvedToolcraftControlSectionSchema, ): ResolvedToolcraftControlSectionSchema["title"] { return isStickyFooterActionSection(section) ? undefined : section.title; } export function isRuntimeSetupSection({ entries, section, }: { entries: readonly ControlEntry[]; section: ResolvedToolcraftControlSectionSchema; }): boolean { return ( section.title === "Setup" && entries.some(([, control]) => isToolcraftRuntimeOwnedTarget(control.target)) ); } function isStickyFooterActionSection( section: ResolvedToolcraftControlSectionSchema, ): boolean { return ( section.actionGroup !== undefined && Object.values(section.controls).some( (control) => control.type === "panelActions", ) ); } export function shouldShowColorFieldLabel({ control, sectionHasOnlyColorFields, }: { control: ToolcraftControlSchema; sectionHasOnlyColorFields: boolean; }): boolean { return control.label !== false && !sectionHasOnlyColorFields; } export function getControlMarkerCount( control: ToolcraftControlSchema, markerLimit?: number, ): number | undefined { const markerCount = control.markerCount; if ( markerLimit && control.variant === "discrete" && typeof markerCount === "number" && markerCount > markerLimit ) { return hiddenDiscreteMarkerCount; } return markerCount; }