Skip to content

Commit 1df7faa

Browse files
fefactor: use declarative component-based configuration for content mode
1 parent d8053a0 commit 1df7faa

10 files changed

Lines changed: 98 additions & 40 deletions

File tree

apps/builder/app/builder/features/settings-panel/props-section/use-props-logic.ts

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@ import {
1616
$registeredComponentMetas,
1717
} from "~/shared/nano-states";
1818
import { isRichText } from "~/shared/content-model";
19-
import { $selectedInstance, $selectedInstancePath } from "~/shared/awareness";
19+
import { $selectedInstancePath } from "~/shared/awareness";
2020
import {
2121
$selectedInstanceInitialPropNames,
2222
$selectedInstancePropsMetas,
2323
showAttributeMeta,
2424
type PropValue,
2525
} from "../shared";
26-
import { $instanceTags } from "../../style-panel/shared/model";
2726

2827
type PropOrName = { prop?: Prop; propName: string };
2928

@@ -158,60 +157,32 @@ const $canHaveTextContent = computed(
158157
}
159158
);
160159

161-
const contentModePropertiesByTag: Partial<Record<string, string[]>> = {
162-
img: ["src", "width", "height", "alt"],
163-
a: ["href"],
164-
};
165-
166-
const contentModePropertiesByComponent: Partial<Record<string, string[]>> = {
167-
YouTube: ["url"],
168-
Vimeo: ["url"],
169-
};
170-
171-
const $selectedInstanceTag = computed(
172-
[$selectedInstance, $instanceTags],
173-
(selectedInstance, instanceTags) => {
174-
if (selectedInstance === undefined) {
175-
return;
176-
}
177-
return instanceTags.get(selectedInstance.id);
178-
}
179-
);
180-
181160
/** usePropsLogic expects that key={instanceId} is used on the ancestor component */
182161
export const usePropsLogic = ({
183162
instance,
184163
props,
185164
updateProp,
186165
}: UsePropsLogicInput) => {
187166
const isContentMode = useStore($isContentMode);
188-
const selectedInstanceTag = useStore($selectedInstanceTag);
167+
const propsMetas = useStore($selectedInstancePropsMetas);
189168

190169
/**
191-
* In content edit mode we show only Image and Link props
170+
* In content edit mode we show only props marked with contentMode: true
192171
* In the future I hope the only thing we will show will be Components
193172
*/
194173
const isPropVisible = (propName: string) => {
195174
if (!isContentMode) {
196175
return true;
197176
}
198-
const allowedPropertiesByTag =
199-
contentModePropertiesByTag[selectedInstanceTag ?? ""] ?? [];
200-
const allowedPropertiesByComponent =
201-
contentModePropertiesByComponent[instance.component] ?? [];
202-
return (
203-
allowedPropertiesByTag.includes(propName) ||
204-
allowedPropertiesByComponent.includes(propName)
205-
);
177+
const propMeta = propsMetas.get(propName);
178+
return propMeta?.contentMode === true;
206179
};
207180

208181
const savedProps = props;
209182

210183
// we will delete items from these maps as we categorize the props
211184
const unprocessedSaved = new Map(savedProps.map((prop) => [prop.name, prop]));
212185

213-
const propsMetas = useStore($selectedInstancePropsMetas);
214-
215186
const initialPropNames = useStore($selectedInstanceInitialPropNames);
216187

217188
const systemProps: PropAndMeta[] = [];

apps/builder/app/builder/features/settings-panel/shared.tsx

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
SYSTEM_VARIABLE_ID,
2828
systemParameter,
2929
} from "@webstudio-is/sdk";
30-
import type { PropMeta, Prop, Asset } from "@webstudio-is/sdk";
30+
import type { PropMeta, Prop, Asset, WsComponentMeta } from "@webstudio-is/sdk";
3131
import { InfoCircleIcon } from "@webstudio-is/icons";
3232
import {
3333
Label as BaseLabel,
@@ -458,6 +458,35 @@ const attributeToMeta = (attribute: Attribute): PropMeta => {
458458
throw Error("impossible case");
459459
};
460460

461+
// Derive tag → content-mode attribute names from registered component metas,
462+
// so a prop marked `contentMode: true` in a .ws.ts file also surfaces on
463+
// `ws:element` instances rendering the same tag.
464+
const getContentModeAttributesByTag = (metas: Map<string, WsComponentMeta>) => {
465+
const byTag = new Map<string, Set<string>>();
466+
for (const componentMeta of metas.values()) {
467+
const tags = Object.keys(componentMeta.presetStyle ?? {});
468+
if (tags.length === 0) {
469+
continue;
470+
}
471+
for (const [propName, propMeta] of Object.entries(
472+
componentMeta.props ?? {}
473+
)) {
474+
if (propMeta.contentMode !== true) {
475+
continue;
476+
}
477+
for (const tag of tags) {
478+
let names = byTag.get(tag);
479+
if (names === undefined) {
480+
names = new Set();
481+
byTag.set(tag, names);
482+
}
483+
names.add(propName);
484+
}
485+
}
486+
}
487+
return byTag;
488+
};
489+
461490
export const $selectedInstancePropsMetas = computed(
462491
[$selectedInstance, $registeredComponentMetas, $instanceTags],
463492
(instance, metas, instanceTags): Map<string, PropMeta> => {
@@ -467,22 +496,32 @@ export const $selectedInstancePropsMetas = computed(
467496
const meta = metas.get(instance.component);
468497
const tag = instanceTags.get(instance.id);
469498
const propsMetas = new Map<Prop["name"], PropMeta>();
499+
const contentModeAttributesByTag = getContentModeAttributesByTag(metas);
500+
const contentModeAttributes =
501+
tag === undefined ? undefined : contentModeAttributesByTag.get(tag);
502+
const toAttributeMeta = (attribute: Attribute): PropMeta => {
503+
const propMeta = attributeToMeta(attribute);
504+
if (contentModeAttributes?.has(attribute.name)) {
505+
return { ...propMeta, contentMode: true };
506+
}
507+
return propMeta;
508+
};
470509
// add html attributes only when instance has tag
471510
if (tag) {
472511
if (elementsByTag[tag].categories.includes("html-element")) {
473512
for (const attribute of [...ariaAttributes].reverse()) {
474-
propsMetas.set(attribute.name, attributeToMeta(attribute));
513+
propsMetas.set(attribute.name, toAttributeMeta(attribute));
475514
}
476515
// include global attributes only for html elements
477516
if (attributesByTag["*"]) {
478517
for (const attribute of [...attributesByTag["*"]].reverse()) {
479-
propsMetas.set(attribute.name, attributeToMeta(attribute));
518+
propsMetas.set(attribute.name, toAttributeMeta(attribute));
480519
}
481520
}
482521
}
483522
if (attributesByTag[tag]) {
484523
for (const attribute of [...attributesByTag[tag]].reverse()) {
485-
propsMetas.set(attribute.name, attributeToMeta(attribute));
524+
propsMetas.set(attribute.name, toAttributeMeta(attribute));
486525
}
487526
}
488527
}

packages/sdk-components-react/src/image.ws.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,25 @@ export const meta: WsComponentMeta = {
5353
label: "Source",
5454
required: false,
5555
accept: "image/*",
56+
contentMode: true,
57+
},
58+
width: {
59+
type: "number",
60+
control: "number",
61+
required: false,
62+
contentMode: true,
63+
},
64+
height: {
65+
type: "number",
66+
control: "number",
67+
required: false,
68+
contentMode: true,
69+
},
70+
alt: {
71+
type: "string",
72+
control: "text",
73+
required: false,
74+
contentMode: true,
5675
},
5776
},
5877
};

packages/sdk-components-react/src/link.ws.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const meta: WsComponentMeta = {
2323
type: "string",
2424
control: "url",
2525
required: false,
26+
contentMode: true,
2627
},
2728
},
2829
};

packages/sdk-components-react/src/markdown-embed.ws.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const meta: WsComponentMeta = {
2828
control: "code",
2929
language: "markdown",
3030
type: "string",
31+
contentMode: true,
3132
},
3233
},
3334
};

packages/sdk-components-react/src/video.ws.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ export const meta: WsComponentMeta = {
3939
label: "Source",
4040
required: false,
4141
accept: ".mp4,.webm,.mpg,.mpeg,.mov",
42+
contentMode: true,
43+
},
44+
width: {
45+
type: "number",
46+
control: "number",
47+
required: false,
48+
contentMode: true,
49+
},
50+
height: {
51+
type: "number",
52+
control: "number",
53+
required: false,
54+
contentMode: true,
4255
},
4356
},
4457
};

packages/sdk-components-react/src/vimeo-preview-image.ws.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const meta: WsComponentMeta = {
1919
control: "file",
2020
label: "Source",
2121
required: false,
22+
contentMode: true,
2223
},
2324
},
2425
};

packages/sdk-components-react/src/vimeo.ws.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,11 @@ export const meta: WsComponentMeta = {
3434
},
3535
presetStyle: { div },
3636
initialProps,
37-
props,
37+
props: {
38+
...props,
39+
url: {
40+
...props.url,
41+
contentMode: true,
42+
},
43+
},
3844
};

packages/sdk-components-react/src/youtube.ws.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,11 @@ export const meta: WsComponentMeta = {
4444
},
4545
presetStyle: { div },
4646
initialProps,
47-
props,
47+
props: {
48+
...props,
49+
url: {
50+
...props.url,
51+
contentMode: true,
52+
},
53+
},
4854
};

packages/sdk/src/schema/prop-meta.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const common = {
1414
label: z.string().optional(),
1515
description: z.string().optional(),
1616
required: z.boolean(),
17+
contentMode: z.boolean().optional(),
1718
};
1819

1920
const Tag = z.object({

0 commit comments

Comments
 (0)