Skip to content

Commit 0426471

Browse files
committed
feat: add Block class to manage property group
1 parent 675cf54 commit 0426471

File tree

9 files changed

+123
-79
lines changed

9 files changed

+123
-79
lines changed

packages/react/src/editor/PropertyControlGroup/PropertyControlGroup.module.css

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const PropertyControlGroup = () => null;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { PropertyControlGroup } from './PropertyControlGroup';
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/** biome-ignore-all lint/suspicious/noExplicitAny: for arbitrary values */
2+
import type { ComponentProps, ComponentType } from 'react';
3+
import type { PropertySpec } from '../PropertySpec';
4+
5+
export type BlockConfig<
6+
Component extends ComponentType<any> = any,
7+
Props = ComponentProps<Component>,
8+
Key extends keyof Props = keyof Props,
9+
> = {
10+
category?: string;
11+
component: Component;
12+
props: {
13+
[key in Key]: PropertySpec<Props[key]>;
14+
};
15+
};
16+
17+
export class Block<
18+
Component extends ComponentType<any> = any,
19+
Props = ComponentProps<Component>,
20+
Key extends keyof Props = keyof Props,
21+
> {
22+
public static readonly UNCATEGORIZED = 'Uncategorized';
23+
public static readonly UNGROUPED = 'Default';
24+
25+
public readonly name: string;
26+
public readonly category: string;
27+
public readonly component: Component;
28+
public readonly props: {
29+
[key in Key]: PropertySpec<Props[key]>;
30+
};
31+
32+
constructor(name: string, config: BlockConfig) {
33+
this.name = name;
34+
this.category = config.category ?? Block.UNCATEGORIZED;
35+
this.component = config.component;
36+
this.props = config.props as {
37+
[key in Key]: PropertySpec<Props[key]>;
38+
};
39+
40+
this.populateDefaults();
41+
}
42+
43+
public getGroupedProps() {
44+
const propsByGroup: Record<string, Record<string, PropertySpec<any>>> = {};
45+
46+
for (const key in this.props) {
47+
const spec = this.props[key];
48+
const group = spec.group ?? Block.UNGROUPED;
49+
50+
propsByGroup[group] ||= {};
51+
propsByGroup[group][key] = spec;
52+
}
53+
54+
return propsByGroup;
55+
}
56+
57+
private populateDefaults() {
58+
for (const spec of Object.values(this.props)) {
59+
const typedSpec = spec as PropertySpec<any>;
60+
61+
this.setSpecDefault(typedSpec);
62+
}
63+
}
64+
65+
private setSpecDefault(spec: PropertySpec<any>) {
66+
spec.hasDefault = typeof spec.default !== 'undefined';
67+
68+
switch (spec.type) {
69+
case 'array':
70+
spec.default ??= [];
71+
this.setSpecDefault(spec.item);
72+
break;
73+
case 'boolean':
74+
spec.default ??= false;
75+
break;
76+
case 'node':
77+
spec.default ??= null;
78+
break;
79+
case 'number':
80+
spec.default ??= 0;
81+
break;
82+
case 'object':
83+
for (const field of Object.values(spec.fields)) {
84+
this.setSpecDefault(field);
85+
}
86+
spec.default ??= Object.fromEntries(
87+
Object.entries(spec.fields).map(([key, field]) => [key, field.default]),
88+
);
89+
break;
90+
case 'custom':
91+
break;
92+
case 'radio':
93+
spec.default ??= 'value' in spec.options[0] ? spec.options[0].value : spec.options[0];
94+
break;
95+
case 'select':
96+
spec.default ??= spec.options[0]?.value;
97+
break;
98+
case 'text':
99+
case 'textarea':
100+
spec.default ??= '';
101+
break;
102+
default:
103+
throw new Error('Unknown property spec');
104+
}
105+
}
106+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { Block, type BlockConfig } from './Block';
Lines changed: 11 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,16 @@
11
/** biome-ignore-all lint/suspicious/noExplicitAny: for arbitrary values */
2-
import type { ComponentProps, ComponentType } from 'react';
3-
import type { PropertySpec } from '../PropertySpec';
4-
5-
export type Block<
6-
Component extends ComponentType<any> = any,
7-
Props = ComponentProps<Component>,
8-
Key extends keyof Props = keyof Props,
9-
> = {
10-
name: string;
11-
category?: string;
12-
component: Component;
13-
props: {
14-
[key in Key]: PropertySpec<Props[key]>;
15-
};
16-
};
2+
import type { ComponentType } from 'react';
3+
import { Block, type BlockConfig } from '../Block';
174

185
const blocks = new Map<string, Block>();
196

207
export const register = <Component extends ComponentType<any>>(
218
name: string,
22-
block: Omit<Block<Component>, 'name'>,
9+
config: BlockConfig<Component>,
2310
) => {
24-
for (const spec of Object.values(block.props)) {
25-
const typedSpec = spec as PropertySpec<any>;
11+
const block = new Block(name, config);
2612

27-
setSpecDefault(typedSpec);
28-
}
29-
30-
blocks.set(name, {
31-
name,
32-
...block,
33-
});
13+
blocks.set(name, block);
3414
};
3515

3616
export const valid = (names: string[]): boolean => {
@@ -43,10 +23,10 @@ export const missing = (names: string[]): string[] => {
4323

4424
export const get = (name: string) => {
4525
if (name.toLowerCase() === name) {
46-
return {
26+
return new Block(name, {
4727
component: name,
48-
props: {} as Record<string, PropertySpec<any>>,
49-
};
28+
props: {},
29+
});
5030
}
5131

5232
return blocks.get(name);
@@ -56,15 +36,11 @@ export const getAll = (query?: string) => {
5636
const blockList = Array.from(blocks.values())
5737
.filter((block) => block.name.toLowerCase().includes(query?.toLowerCase() ?? ''))
5838
.sort((a, b) => {
59-
const aCategory = a.category ?? '~';
60-
const bCategory = b.category ?? '~';
39+
const aCategory = a.category === Block.UNCATEGORIZED ? '~' : a.category;
40+
const bCategory = b.category === Block.UNCATEGORIZED ? '~' : b.category;
6141

6242
return aCategory < bCategory ? -1 : 1;
63-
})
64-
.map((block) => ({
65-
...block,
66-
category: block.category ?? 'Uncategorized',
67-
}));
43+
});
6844

6945
const blocksByCategory = blockList.reduce(
7046
(acc, block) => {
@@ -88,45 +64,3 @@ export const getAll = (query?: string) => {
8864
export const clear = () => {
8965
blocks.clear();
9066
};
91-
92-
const setSpecDefault = (spec: PropertySpec<any>): void => {
93-
spec.hasDefault = typeof spec.default !== 'undefined';
94-
95-
switch (spec.type) {
96-
case 'array':
97-
spec.default ??= [];
98-
setSpecDefault(spec.item);
99-
break;
100-
case 'boolean':
101-
spec.default ??= false;
102-
break;
103-
case 'node':
104-
spec.default ??= null;
105-
break;
106-
case 'number':
107-
spec.default ??= 0;
108-
break;
109-
case 'object':
110-
for (const field of Object.values(spec.fields)) {
111-
setSpecDefault(field);
112-
}
113-
spec.default ??= Object.fromEntries(
114-
Object.entries(spec.fields).map(([key, field]) => [key, field.default]),
115-
);
116-
break;
117-
case 'custom':
118-
break;
119-
case 'radio':
120-
spec.default ??= 'value' in spec.options[0] ? spec.options[0].value : spec.options[0];
121-
break;
122-
case 'select':
123-
spec.default ??= spec.options[0]?.value;
124-
break;
125-
case 'text':
126-
case 'textarea':
127-
spec.default ??= '';
128-
break;
129-
default:
130-
throw new Error('Unknown property spec');
131-
}
132-
};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { type Block, clear, get, getAll, missing, register, valid } from './Catalog';
1+
export { clear, get, getAll, missing, register, valid } from './Catalog';

packages/react/src/renderer/PropertySpec/PropertySpec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ type DefaultPropertySpec<Value> = {
55
label: string;
66
default?: Value;
77
optional?: boolean;
8+
group?: string;
89
hasDefault?: boolean;
910
};
1011

packages/react/src/renderer/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type { Block } from './Catalog';
1+
export { Block } from './Block';
22
export * as Catalog from './Catalog';
33
export { type Node, NodeManager } from './NodeManager';
44
export * as Parser from './Parser';

0 commit comments

Comments
 (0)