Skip to content

Commit 876b02a

Browse files
authored
feat: Wrap an instance into any component that can do it (#5508)
Closes #5507 ## Description 1. What is this PR about (link the issue and add a short description) ## Steps for reproduction 1. click button 2. expect xyz ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 0000) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file
1 parent f712740 commit 876b02a

26 files changed

Lines changed: 1111 additions & 242 deletions

apps/builder/app/builder/features/command-panel/groups/breakpoints-group.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
$selectedBreakpoint,
1414
$selectedBreakpointId,
1515
} from "~/shared/nano-states";
16-
import { closeCommandPanel } from "../command-state";
16+
import { closeCommandPanel, $isCommandPanelOpen } from "../command-state";
1717
import type { BaseOption } from "../shared/types";
1818
import { setCanvasWidth } from "~/builder/shared/calc-canvas-width";
1919

@@ -24,8 +24,11 @@ export type BreakpointOption = BaseOption & {
2424
};
2525

2626
export const $breakpointOptions = computed(
27-
[$breakpoints, $selectedBreakpoint],
28-
(breakpoints, selectedBreakpoint) => {
27+
[$isCommandPanelOpen, $breakpoints, $selectedBreakpoint],
28+
(isOpen, breakpoints, selectedBreakpoint) => {
29+
if (!isOpen) {
30+
return [];
31+
}
2932
const sortedBreakpoints = Array.from(breakpoints.values()).sort(
3033
compareMedia
3134
);

apps/builder/app/builder/features/command-panel/groups/commands-group.tsx

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,38 @@ export type CommandOption = BaseOption & {
1717
name: string;
1818
label: string;
1919
keys?: string[];
20+
keepCommandPanelOpen?: boolean;
2021
};
2122

22-
export const $commandOptions = computed([$commandMetas], (commandMetas) => {
23-
const commandOptions: CommandOption[] = [];
24-
for (const [name, meta] of commandMetas) {
25-
if (!meta.hidden) {
26-
const label = meta.label ?? humanizeString(name);
27-
const keys = meta.defaultHotkeys?.[0]?.split("+");
28-
commandOptions.push({
29-
terms: ["shortcuts", "commands", label],
30-
type: "command",
31-
name,
32-
label,
33-
keys,
34-
});
23+
import { $isCommandPanelOpen } from "../command-state";
24+
25+
export const $commandOptions = computed(
26+
[$isCommandPanelOpen, $commandMetas],
27+
(isOpen, commandMetas) => {
28+
if (!isOpen) {
29+
return [];
30+
}
31+
const commandOptions: CommandOption[] = [];
32+
for (const [name, meta] of commandMetas) {
33+
if (!meta.hidden) {
34+
const label = meta.label ?? humanizeString(name);
35+
const keys = meta.defaultHotkeys?.[0]?.split("+");
36+
commandOptions.push({
37+
terms: ["shortcuts", "commands", label],
38+
type: "command",
39+
name,
40+
label,
41+
keys,
42+
keepCommandPanelOpen: meta.keepCommandPanelOpen,
43+
});
44+
}
3545
}
46+
commandOptions.sort(
47+
(left, right) => (left.keys ? 0 : 1) - (right.keys ? 0 : 1)
48+
);
49+
return commandOptions;
3650
}
37-
commandOptions.sort(
38-
(left, right) => (left.keys ? 0 : 1) - (right.keys ? 0 : 1)
39-
);
40-
return commandOptions;
41-
});
51+
);
4252

4353
export const CommandsGroup = ({ options }: { options: CommandOption[] }) => {
4454
return (
@@ -47,13 +57,15 @@ export const CommandsGroup = ({ options }: { options: CommandOption[] }) => {
4757
heading={<CommandGroupHeading>Commands</CommandGroupHeading>}
4858
actions={["execute"]}
4959
>
50-
{options.map(({ name, label, keys }) => (
60+
{options.map(({ name, label, keys, keepCommandPanelOpen }) => (
5161
<CommandItem
5262
key={name}
5363
// preserve selected state when rerender
5464
value={name}
5565
onSelect={() => {
56-
closeCommandPanel();
66+
if (!keepCommandPanelOpen) {
67+
closeCommandPanel();
68+
}
5769
emitCommand(name as never);
5870
}}
5971
>

apps/builder/app/builder/features/command-panel/groups/components-group.tsx

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@ import {
88
} from "@webstudio-is/design-system";
99
import { computed } from "nanostores";
1010
import type { TemplateMeta } from "@webstudio-is/template";
11-
import {
12-
componentCategories,
13-
collectionComponent,
14-
elementComponent,
15-
} from "@webstudio-is/sdk";
11+
import { collectionComponent, elementComponent } from "@webstudio-is/sdk";
1612
import {
1713
$registeredComponentMetas,
1814
$registeredTemplates,
@@ -28,8 +24,12 @@ import {
2824
getInstanceLabel,
2925
InstanceIcon,
3026
} from "~/builder/shared/instance-label";
31-
import { closeCommandPanel } from "../command-state";
27+
import { closeCommandPanel, $isCommandPanelOpen } from "../command-state";
3228
import type { BaseOption } from "../shared/types";
29+
import {
30+
shouldFilterCategory,
31+
getComponentScore,
32+
} from "../shared/component-utils";
3333

3434
export type ComponentOption = BaseOption & {
3535
type: "component";
@@ -41,48 +41,77 @@ export type ComponentOption = BaseOption & {
4141
firstInstance: { component: string };
4242
};
4343

44-
const getComponentScore = (meta: ComponentOption) => {
45-
const categoryScore = componentCategories.indexOf(meta.category ?? "hidden");
46-
const componentScore = meta.order ?? Number.MAX_SAFE_INTEGER;
47-
// shift category
48-
return categoryScore * 1000 + componentScore;
49-
};
50-
5144
export const $componentOptions = computed(
52-
[$registeredComponentMetas, $registeredTemplates, $selectedPage],
53-
(metas, templates, selectedPage) => {
45+
[
46+
$isCommandPanelOpen,
47+
$registeredComponentMetas,
48+
$registeredTemplates,
49+
$selectedPage,
50+
],
51+
(isOpen, metas, templates, selectedPage) => {
5452
const componentOptions: ComponentOption[] = [];
55-
for (const [name, meta] of metas) {
56-
const category = meta.category ?? "hidden";
57-
if (category === "hidden" || category === "internal") {
58-
continue;
59-
}
53+
if (!isOpen) {
54+
return componentOptions;
55+
}
6056

57+
const addComponentOption = ({
58+
name,
59+
category,
60+
label,
61+
icon,
62+
order,
63+
firstInstance,
64+
}: {
65+
name: string;
66+
category: TemplateMeta["category"];
67+
label: string;
68+
icon?: string;
69+
order?: number;
70+
firstInstance: { component: string };
71+
}) => {
6172
// show only xml category and collection component in xml documents
6273
if (selectedPage?.meta.documentType === "xml") {
6374
if (category !== "xml" && name !== collectionComponent) {
64-
continue;
75+
return;
6576
}
6677
} else {
6778
// show everything except xml category in html documents
6879
if (category === "xml") {
69-
continue;
80+
return;
7081
}
7182
}
72-
const label = getInstanceLabel({ component: name });
83+
7384
componentOptions.push({
7485
terms: ["components", label, category],
7586
type: "component",
7687
component: name,
7788
label,
7889
category,
90+
icon,
91+
order,
92+
firstInstance,
93+
});
94+
};
95+
96+
for (const [name, meta] of metas) {
97+
if (shouldFilterCategory(meta.category)) {
98+
continue;
99+
}
100+
const category = meta.category ?? "hidden";
101+
const label = meta.label ?? getInstanceLabel({ component: name });
102+
103+
addComponentOption({
104+
name,
105+
category,
106+
label,
79107
icon: meta.icon,
80108
order: meta.order,
81109
firstInstance: { component: name },
82110
});
83111
}
112+
84113
for (const [name, meta] of templates) {
85-
if (meta.category === "hidden" || meta.category === "internal") {
114+
if (shouldFilterCategory(meta.category)) {
86115
continue;
87116
}
88117

@@ -91,17 +120,17 @@ export const $componentOptions = computed(
91120
meta.label ??
92121
componentMeta?.label ??
93122
getInstanceLabel({ component: name });
94-
componentOptions.push({
95-
terms: ["components", label, meta.category],
96-
type: "component",
97-
component: name,
98-
label,
123+
124+
addComponentOption({
125+
name,
99126
category: meta.category,
127+
label,
100128
icon: meta.icon ?? componentMeta?.icon,
101129
order: meta.order,
102130
firstInstance: meta.template.instances[0],
103131
});
104132
}
133+
105134
componentOptions.sort(
106135
(leftOption, rightOption) =>
107136
getComponentScore(leftOption) - getComponentScore(rightOption)

apps/builder/app/builder/features/command-panel/groups/css-variables-group.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { deleteProperty } from "~/builder/features/style-panel/shared/use-style-
2323
import { InstanceList, showInstance } from "../shared/instance-list";
2424
import {
2525
$commandContent,
26+
$isCommandPanelOpen,
2627
closeCommandPanel,
2728
focusCommandPanel,
2829
} from "../command-state";
@@ -38,9 +39,12 @@ export type CssVariableOption = BaseOption & {
3839
};
3940

4041
export const $cssVariableOptions = computed(
41-
[$cssVariableDefinitionsByVariable, $unusedCssVariables],
42-
(definitionsByVariable, unusedVariables) => {
42+
[$isCommandPanelOpen, $cssVariableDefinitionsByVariable, $unusedCssVariables],
43+
(isOpen, definitionsByVariable, unusedVariables) => {
4344
const cssVariableOptions: CssVariableOption[] = [];
45+
if (!isOpen) {
46+
return cssVariableOptions;
47+
}
4448

4549
// Create options for each defined CSS variable on each instance
4650
for (const [property, instanceIds] of definitionsByVariable) {

apps/builder/app/builder/features/command-panel/groups/data-variables-group.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import { $dataSources } from "~/shared/nano-states";
1313
import {
1414
$commandContent,
15+
$isCommandPanelOpen,
1516
closeCommandPanel,
1617
focusCommandPanel,
1718
} from "../command-state";
@@ -36,9 +37,12 @@ export type DataVariableOption = BaseOption & {
3637
};
3738

3839
export const $dataVariableOptions = computed(
39-
[$dataSources, $usedVariablesInInstances],
40-
(dataSources, usedInInstances) => {
40+
[$isCommandPanelOpen, $dataSources, $usedVariablesInInstances],
41+
(isOpen, dataSources, usedInInstances) => {
4142
const dataVariableOptions: DataVariableOption[] = [];
43+
if (!isOpen) {
44+
return dataVariableOptions;
45+
}
4246

4347
for (const dataSource of dataSources.values()) {
4448
if (

apps/builder/app/builder/features/command-panel/groups/pages-group.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { Page } from "@webstudio-is/sdk";
1010
import { $pages, $editingPageId } from "~/shared/nano-states";
1111
import { $selectedPage, selectPage } from "~/shared/awareness";
1212
import { setActiveSidebarPanel } from "~/builder/shared/nano-states";
13-
import { closeCommandPanel } from "../command-state";
13+
import { closeCommandPanel, $isCommandPanelOpen } from "../command-state";
1414
import type { BaseOption } from "../shared/types";
1515

1616
export type PageOption = BaseOption & {
@@ -19,8 +19,11 @@ export type PageOption = BaseOption & {
1919
};
2020

2121
export const $pageOptions = computed(
22-
[$pages, $selectedPage],
23-
(pages, selectedPage) => {
22+
[$isCommandPanelOpen, $pages, $selectedPage],
23+
(isOpen, pages, selectedPage) => {
24+
if (!isOpen) {
25+
return [];
26+
}
2427
const pageOptions: PageOption[] = [];
2528
if (pages) {
2629
for (const page of [pages.homePage, ...pages.pages]) {

apps/builder/app/builder/features/command-panel/groups/tags-group.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { insertWebstudioFragmentAt } from "~/shared/instance-utils";
1818
import { $selectedInstancePath } from "~/shared/awareness";
1919
import { InstanceIcon } from "~/builder/shared/instance-label";
2020
import { isTreeSatisfyingContentModel } from "~/shared/content-model";
21-
import { closeCommandPanel } from "../command-state";
21+
import { closeCommandPanel, $isCommandPanelOpen } from "../command-state";
2222
import type { BaseOption } from "../shared/types";
2323

2424
export type TagOption = BaseOption & {
@@ -27,9 +27,18 @@ export type TagOption = BaseOption & {
2727
};
2828

2929
export const $tagOptions = computed(
30-
[$selectedInstancePath, $instances, $props, $registeredComponentMetas],
31-
(instancePath, instances, props, metas) => {
30+
[
31+
$isCommandPanelOpen,
32+
$selectedInstancePath,
33+
$instances,
34+
$props,
35+
$registeredComponentMetas,
36+
],
37+
(isOpen, instancePath, instances, props, metas) => {
3238
const tagOptions: TagOption[] = [];
39+
if (!isOpen) {
40+
return tagOptions;
41+
}
3342
if (instancePath === undefined) {
3443
return tagOptions;
3544
}

apps/builder/app/builder/features/command-panel/groups/tokens-group.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import { InstanceList, showInstance } from "../shared/instance-list";
2222
import {
2323
$commandContent,
24+
$isCommandPanelOpen,
2425
closeCommandPanel,
2526
focusCommandPanel,
2627
} from "../command-state";
@@ -34,9 +35,12 @@ export type TokenOption = BaseOption & {
3435
};
3536

3637
export const $tokenOptions = computed(
37-
[$styleSources, $styleSourceUsages],
38-
(styleSources, styleSourceUsages) => {
38+
[$isCommandPanelOpen, $styleSources, $styleSourceUsages],
39+
(isOpen, styleSources, styleSourceUsages) => {
3940
const tokenOptions: TokenOption[] = [];
41+
if (!isOpen) {
42+
return tokenOptions;
43+
}
4044
for (const styleSource of styleSources.values()) {
4145
if (styleSource.type !== "token") {
4246
continue;

0 commit comments

Comments
 (0)