Skip to content

Commit aea46ab

Browse files
authored
feat: insert tag commands (#5248)
Ref #3632 Added commands for all tags satisfying current tree https://github.com/user-attachments/assets/6e7e29c4-1a9a-452d-a967-448e216e4703
1 parent 1e1edf7 commit aea46ab

File tree

1 file changed

+128
-4
lines changed

1 file changed

+128
-4
lines changed

apps/builder/app/builder/features/command-panel/command-panel.tsx

Lines changed: 128 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ import {
2525
collectionComponent,
2626
parseComponentName,
2727
elementComponent,
28+
tags,
2829
} from "@webstudio-is/sdk";
29-
import type { Breakpoint, Page } from "@webstudio-is/sdk";
30+
import type { Breakpoint, Instance, Page } from "@webstudio-is/sdk";
3031
import type { TemplateMeta } from "@webstudio-is/template";
3132
import {
3233
$breakpoints,
3334
$editingPageId,
35+
$instances,
3436
$pages,
37+
$props,
3538
$registeredComponentMetas,
3639
$registeredTemplates,
3740
$selectedBreakpoint,
@@ -44,7 +47,11 @@ import {
4447
} from "~/shared/instance-utils";
4548
import { humanizeString } from "~/shared/string-utils";
4649
import { setCanvasWidth } from "~/builder/features/breakpoints";
47-
import { $selectedPage, selectPage } from "~/shared/awareness";
50+
import {
51+
$selectedInstancePath,
52+
$selectedPage,
53+
selectPage,
54+
} from "~/shared/awareness";
4855
import { mapGroupBy } from "~/shared/shim";
4956
import { setActiveSidebarPanel } from "~/builder/shared/nano-states";
5057
import { $commandMetas } from "~/shared/commands-emitter";
@@ -54,6 +61,7 @@ import {
5461
getInstanceLabel,
5562
InstanceIcon,
5663
} from "~/builder/shared/instance-label";
64+
import { isTreeSatisfyingContentModel } from "~/shared/content-model";
5765

5866
const $commandPanel = atom<
5967
| undefined
@@ -233,6 +241,101 @@ const ComponentOptionsGroup = ({ options }: { options: ComponentOption[] }) => {
233241
);
234242
};
235243

244+
type TagOption = {
245+
tokens: string[];
246+
type: "tag";
247+
tag: string;
248+
};
249+
250+
const $tagOptions = computed(
251+
[$selectedInstancePath, $instances, $props, $registeredComponentMetas],
252+
(instancePath, instances, props, metas) => {
253+
const tagOptions: TagOption[] = [];
254+
if (instancePath === undefined) {
255+
return tagOptions;
256+
}
257+
const [{ instance, instanceSelector }] = instancePath;
258+
const childInstance: Instance = {
259+
type: "instance",
260+
id: "new_instance",
261+
component: elementComponent,
262+
children: [],
263+
};
264+
const newInstances = new Map(instances);
265+
newInstances.set(childInstance.id, childInstance);
266+
newInstances.set(instance.id, {
267+
...instance,
268+
children: [...instance.children, { type: "id", value: childInstance.id }],
269+
});
270+
for (const tag of tags) {
271+
childInstance.tag = tag;
272+
const isSatisfying = isTreeSatisfyingContentModel({
273+
instances: newInstances,
274+
props,
275+
metas,
276+
instanceSelector,
277+
});
278+
if (isSatisfying) {
279+
tagOptions.push({
280+
tokens: ["tags", tag, `<${tag}>`],
281+
type: "tag",
282+
tag,
283+
});
284+
}
285+
}
286+
return tagOptions;
287+
}
288+
);
289+
290+
const TagOptionsGroup = ({ options }: { options: TagOption[] }) => {
291+
return (
292+
<CommandGroup
293+
name="tag"
294+
heading={<CommandGroupHeading>Tags</CommandGroupHeading>}
295+
actions={["add"]}
296+
>
297+
{options.map(({ tag }) => {
298+
return (
299+
<CommandItem
300+
key={tag}
301+
// preserve selected state when rerender
302+
value={tag}
303+
onSelect={() => {
304+
closeCommandPanel();
305+
const newInstance: Instance = {
306+
type: "instance",
307+
id: "new_instance",
308+
component: elementComponent,
309+
tag,
310+
children: [],
311+
};
312+
insertWebstudioFragmentAt({
313+
children: [{ type: "id", value: newInstance.id }],
314+
instances: [newInstance],
315+
props: [],
316+
dataSources: [],
317+
styleSourceSelections: [],
318+
styleSources: [],
319+
styles: [],
320+
breakpoints: [],
321+
assets: [],
322+
resources: [],
323+
});
324+
}}
325+
>
326+
<Flex gap={2}>
327+
<CommandIcon>
328+
<InstanceIcon instance={{ component: elementComponent, tag }} />
329+
</CommandIcon>
330+
<Text variant="labelsSentenceCase">{`<${tag}>`}</Text>
331+
</Flex>
332+
</CommandItem>
333+
);
334+
})}
335+
</CommandGroup>
336+
);
337+
};
338+
236339
type BreakpointOption = {
237340
tokens: string[];
238341
type: "breakpoint";
@@ -423,12 +526,25 @@ const ShortcutOptionsGroup = ({ options }: { options: ShortcutOption[] }) => {
423526
};
424527

425528
const $options = computed(
426-
[$componentOptions, $breakpointOptions, $pageOptions, $shortcutOptions],
427-
(componentOptions, breakpointOptions, pageOptions, commandOptions) => [
529+
[
530+
$componentOptions,
531+
$breakpointOptions,
532+
$pageOptions,
533+
$shortcutOptions,
534+
$tagOptions,
535+
],
536+
(
537+
componentOptions,
538+
breakpointOptions,
539+
pageOptions,
540+
commandOptions,
541+
tagOptions
542+
) => [
428543
...componentOptions,
429544
...breakpointOptions,
430545
...pageOptions,
431546
...commandOptions,
547+
...tagOptions,
432548
]
433549
);
434550

@@ -461,6 +577,14 @@ const CommandDialogContent = () => {
461577
/>
462578
);
463579
}
580+
if (group === "tag") {
581+
return (
582+
<TagOptionsGroup
583+
key={group}
584+
options={matches as TagOption[]}
585+
/>
586+
);
587+
}
464588
if (group === "breakpoint") {
465589
return (
466590
<BreakpointOptionsGroup

0 commit comments

Comments
 (0)