refactor: collapse mask definition registry to a single keyed registry

The freeform mask was assembled on every getMaskDefinition("freeform") call,
re-spreading params and injecting a wrong SquareIcon placeholder, with a type
cast to suppress the structural mismatch. Root cause: MasksRegistry was keyed
on BuiltinMaskType, so freeform had no entry.

- Re-key MasksRegistry from BuiltinMaskType to MaskType
- Register freeform alongside builtins in registerDefaultMasks, with
  PenToolAddIcon as its menu icon
- getMaskDefinition and getMaskDefinitionsForMenu become plain registry
  lookups with no branching, no manual spreading, no cast
- Rename RegisteredBuiltinMaskDefinition to RegisteredMaskDefinition and
  tighten computeParamUpdate return type to Partial<BaseMaskParams>

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Maze Winther 2026-05-02 01:33:32 +02:00
parent 23187b0b6a
commit 8b8d65b509
3 changed files with 30 additions and 47 deletions

View File

@ -1,6 +1,6 @@
import { import {
builtinMasksRegistry, masksRegistry,
type BuiltinMaskDefinitionForRegistration, type MaskDefinitionForRegistration,
type MaskIconProps, type MaskIconProps,
} from "../../registry"; } from "../../registry";
import { cinematicBarsMaskDefinition } from "./cinematic-bars"; import { cinematicBarsMaskDefinition } from "./cinematic-bars";
@ -11,6 +11,7 @@ import { rectangleMaskDefinition } from "./rectangle";
import { splitMaskDefinition } from "./split"; import { splitMaskDefinition } from "./split";
import { starMaskDefinition } from "./star"; import { starMaskDefinition } from "./star";
import { textMaskDefinition } from "./text"; import { textMaskDefinition } from "./text";
import { freeformMaskDefinition } from "../../freeform/definition";
import { import {
MinusSignIcon, MinusSignIcon,
PanelRightDashedIcon, PanelRightDashedIcon,
@ -20,23 +21,24 @@ import {
DiamondIcon, DiamondIcon,
StarsIcon, StarsIcon,
TextFontIcon, TextFontIcon,
PenToolAddIcon,
} from "@hugeicons/core-free-icons"; } from "@hugeicons/core-free-icons";
function registerDefaultMask({ function registerDefaultMask({
definition, definition,
icon, icon,
}: { }: {
definition: BuiltinMaskDefinitionForRegistration; definition: MaskDefinitionForRegistration;
icon: MaskIconProps; icon: MaskIconProps;
}) { }) {
if (builtinMasksRegistry.has(definition.type)) { if (masksRegistry.has(definition.type)) {
return; return;
} }
builtinMasksRegistry.registerMask({ definition, icon }); masksRegistry.registerMask({ definition, icon });
} }
export function registerBuiltinMasks(): void { export function registerDefaultMasks(): void {
registerDefaultMask({ registerDefaultMask({
definition: splitMaskDefinition, definition: splitMaskDefinition,
icon: { icon: PanelRightDashedIcon, strokeWidth: 1 }, icon: { icon: PanelRightDashedIcon, strokeWidth: 1 },
@ -69,4 +71,8 @@ export function registerBuiltinMasks(): void {
definition: textMaskDefinition, definition: textMaskDefinition,
icon: { icon: TextFontIcon }, icon: { icon: TextFontIcon },
}); });
registerDefaultMask({
definition: freeformMaskDefinition,
icon: { icon: PenToolAddIcon },
});
} }

View File

@ -1,15 +1,9 @@
import type { Mask, MaskDefaultContext, MaskType } from "@/masks/types"; import type { Mask, MaskDefaultContext, MaskType } from "@/masks/types";
import { import { masksRegistry } from "./registry";
BASE_MASK_PARAM_DEFINITIONS,
builtinMasksRegistry,
type RegisteredBuiltinMaskDefinition,
} from "./registry";
import { freeformMaskDefinition } from "./freeform/definition";
import { generateUUID } from "@/utils/id"; import { generateUUID } from "@/utils/id";
import { SquareIcon } from "@hugeicons/core-free-icons";
export { builtinMasksRegistry } from "./registry"; export { masksRegistry } from "./registry";
export { registerBuiltinMasks as registerDefaultMasks } from "./builtin/definitions"; export { registerDefaultMasks } from "./builtin/definitions";
type MaskWithoutId = Mask extends infer TMask type MaskWithoutId = Mask extends infer TMask
? TMask extends Mask ? TMask extends Mask
@ -40,28 +34,12 @@ function withMaskId({ mask, id }: { mask: MaskWithoutId; id: string }): Mask {
} }
} }
export function getMaskDefinition(maskType: MaskType): RegisteredBuiltinMaskDefinition { export function getMaskDefinition(maskType: MaskType) {
if (maskType === "freeform") { return masksRegistry.get(maskType);
return {
...freeformMaskDefinition,
params: [...freeformMaskDefinition.params, ...BASE_MASK_PARAM_DEFINITIONS],
icon: { icon: SquareIcon },
} as RegisteredBuiltinMaskDefinition;
}
return builtinMasksRegistry.get(maskType);
} }
export function getMaskDefinitionsForMenu() { export function getMaskDefinitionsForMenu() {
return [ return masksRegistry.getAll();
...builtinMasksRegistry.getAll(),
{
...freeformMaskDefinition,
name: "Pen tool",
params: [...freeformMaskDefinition.params, ...BASE_MASK_PARAM_DEFINITIONS],
icon: { icon: SquareIcon },
},
];
} }
export function buildDefaultMaskInstance({ export function buildDefaultMaskInstance({
@ -71,7 +49,7 @@ export function buildDefaultMaskInstance({
maskType: MaskType; maskType: MaskType;
elementSize?: { width: number; height: number }; elementSize?: { width: number; height: number };
}): Mask { }): Mask {
const definition = getMaskDefinition(maskType); const definition = masksRegistry.get(maskType);
const context: MaskDefaultContext = { elementSize }; const context: MaskDefaultContext = { elementSize };
return withMaskId({ return withMaskId({
mask: definition.buildDefault(context), mask: definition.buildDefault(context),

View File

@ -2,7 +2,6 @@ import { MAX_FEATHER } from "@/masks/feather";
import type { ParamDefinition } from "@/params"; import type { ParamDefinition } from "@/params";
import type { import type {
BaseMaskParams, BaseMaskParams,
BuiltinMaskType,
Mask, Mask,
MaskDefaultContext, MaskDefaultContext,
MaskDefinition, MaskDefinition,
@ -24,9 +23,9 @@ type RegisteredMaskWithoutId = Mask extends infer TMask
: never : never
: never; : never;
export type BuiltinMaskDefinitionForRegistration = { export type MaskDefinitionForRegistration = {
[TType in BuiltinMaskType]: MaskDefinition<TType>; [TType in MaskType]: MaskDefinition<TType>;
}[BuiltinMaskType]; }[MaskType];
export const BASE_MASK_PARAM_DEFINITIONS: ParamDefinition< export const BASE_MASK_PARAM_DEFINITIONS: ParamDefinition<
keyof BaseMaskParams & string keyof BaseMaskParams & string
@ -58,7 +57,7 @@ export const BASE_MASK_PARAM_DEFINITIONS: ParamDefinition<
}, },
]; ];
export interface RegisteredBuiltinMaskDefinition { export interface RegisteredMaskDefinition {
type: MaskType; type: MaskType;
name: string; name: string;
features: MaskDefinition["features"]; features: MaskDefinition["features"];
@ -69,13 +68,13 @@ export interface RegisteredBuiltinMaskDefinition {
buildDefault(context: MaskDefaultContext): RegisteredMaskWithoutId; buildDefault(context: MaskDefaultContext): RegisteredMaskWithoutId;
computeParamUpdate( computeParamUpdate(
args: MaskParamUpdateArgs<BaseMaskParams>, args: MaskParamUpdateArgs<BaseMaskParams>,
): ReturnType<MaskDefinition["computeParamUpdate"]>; ): Partial<BaseMaskParams>;
icon: MaskIconProps; icon: MaskIconProps;
} }
export class BuiltinMasksRegistry extends DefinitionRegistry< export class MasksRegistry extends DefinitionRegistry<
BuiltinMaskType, MaskType,
RegisteredBuiltinMaskDefinition RegisteredMaskDefinition
> { > {
constructor() { constructor() {
super("mask"); super("mask");
@ -85,10 +84,10 @@ export class BuiltinMasksRegistry extends DefinitionRegistry<
definition, definition,
icon, icon,
}: { }: {
definition: BuiltinMaskDefinitionForRegistration; definition: MaskDefinitionForRegistration;
icon: MaskIconProps; icon: MaskIconProps;
}): void { }): void {
const withBaseParams: RegisteredBuiltinMaskDefinition = { const withBaseParams: RegisteredMaskDefinition = {
type: definition.type, type: definition.type,
name: definition.name, name: definition.name,
features: definition.features, features: definition.features,
@ -107,4 +106,4 @@ export class BuiltinMasksRegistry extends DefinitionRegistry<
} }
} }
export const builtinMasksRegistry = new BuiltinMasksRegistry(); export const masksRegistry = new MasksRegistry();