mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-08 21:15:18 +09:00
experimental: autocomplete path params (#3346)
Here added support for autocompleting inferred path params where their values are not specified by user. <img width="312" alt="Screenshot 2024-05-12 at 14 33 23" src="https://github.com/webstudio-is/webstudio/assets/5635476/0576039d-4fab-4499-923b-61867c12e096">
This commit is contained in:
1 parent
12e00186ab
commit
ea5b5c8a32
9 files changed
+214
-73
No files matched your search
@@ -983,9 +983,22 @@ test("page root scope should prefill default system variable value", () => {
|
||||
expect($pageRootScope.get()).toEqual({
|
||||
aliases: new Map([["$ws$dataSource$systemId", "system"]]),
|
||||
scope: {
|
||||
$ws$dataSource$systemId: { params: {}, search: {} },
|
||||
$ws$dataSource$systemId: {
|
||||
origin: "https://undefined.wstd.work",
|
||||
params: {},
|
||||
search: {},
|
||||
},
|
||||
},
|
||||
variableValues: new Map([["systemId", { params: {}, search: {} }]]),
|
||||
variableValues: new Map([
|
||||
[
|
||||
"systemId",
|
||||
{
|
||||
params: {},
|
||||
search: {},
|
||||
origin: "https://undefined.wstd.work",
|
||||
},
|
||||
],
|
||||
]),
|
||||
});
|
||||
|
||||
$dataSourceVariables.set(
|
||||
@@ -994,10 +1007,21 @@ test("page root scope should prefill default system variable value", () => {
|
||||
expect($pageRootScope.get()).toEqual({
|
||||
aliases: new Map([["$ws$dataSource$systemId", "system"]]),
|
||||
scope: {
|
||||
$ws$dataSource$systemId: { params: { slug: "my-post" }, search: {} },
|
||||
$ws$dataSource$systemId: {
|
||||
params: { slug: "my-post" },
|
||||
search: {},
|
||||
origin: "https://undefined.wstd.work",
|
||||
},
|
||||
},
|
||||
variableValues: new Map([
|
||||
["systemId", { params: { slug: "my-post" }, search: {} }],
|
||||
[
|
||||
"systemId",
|
||||
{
|
||||
params: { slug: "my-post" },
|
||||
search: {},
|
||||
origin: "https://undefined.wstd.work",
|
||||
},
|
||||
],
|
||||
]),
|
||||
});
|
||||
});
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
decodeDataSourceVariable,
|
||||
encodeDataSourceVariable,
|
||||
transpileExpression,
|
||||
type System,
|
||||
} from "@webstudio-is/sdk";
|
||||
import { removeByMutable } from "~/shared/array-utils";
|
||||
import {
|
||||
@@ -27,9 +28,12 @@ import {
|
||||
$dataSourceVariables,
|
||||
$dataSources,
|
||||
$pages,
|
||||
$publishedOrigin,
|
||||
$resourceValues,
|
||||
$selectedInstanceSelector,
|
||||
$selectedPageId,
|
||||
getPageDefaultSystem,
|
||||
mergeSystem,
|
||||
} from "~/shared/nano-states";
|
||||
|
||||
type TreePage = {
|
||||
@@ -345,9 +349,22 @@ const $editingPage = computed(
|
||||
}
|
||||
);
|
||||
|
||||
const $editingPagePath = computed($editingPage, (page) => page?.path);
|
||||
|
||||
const $editingPageDefaultSystem = computed(
|
||||
[$publishedOrigin, $editingPagePath],
|
||||
(origin, path) => getPageDefaultSystem({ origin, path })
|
||||
);
|
||||
|
||||
const $pageRootVariableValues = computed(
|
||||
[$editingPage, $dataSources, $dataSourceVariables, $resourceValues],
|
||||
(page, dataSources, dataSourceVariables, resourceValues) => {
|
||||
[
|
||||
$editingPage,
|
||||
$dataSources,
|
||||
$dataSourceVariables,
|
||||
$resourceValues,
|
||||
$editingPageDefaultSystem,
|
||||
],
|
||||
(page, dataSources, dataSourceVariables, resourceValues, defaultSystem) => {
|
||||
const variableValues = new Map<string, unknown>();
|
||||
if (page === undefined) {
|
||||
return variableValues;
|
||||
@@ -363,11 +380,11 @@ const $pageRootVariableValues = computed(
|
||||
if (variable.type === "parameter") {
|
||||
const value = dataSourceVariables.get(variable.id);
|
||||
variableValues.set(variable.id, value);
|
||||
if (variable.id === page.systemDataSourceId && value === undefined) {
|
||||
variableValues.set(variable.id, {
|
||||
params: {},
|
||||
search: {},
|
||||
});
|
||||
if (variable.id === page.systemDataSourceId) {
|
||||
variableValues.set(
|
||||
variable.id,
|
||||
mergeSystem(defaultSystem, value as undefined | System)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (variable.type === "resource") {
|
||||
|
||||
@@ -64,7 +64,7 @@ export const formatValuePreview = (value: unknown) => {
|
||||
return value.toString();
|
||||
}
|
||||
if (value === undefined) {
|
||||
return "undefined";
|
||||
return "";
|
||||
}
|
||||
if (value === null) {
|
||||
return "null";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { shallowEqual } from "shallow-equal";
|
||||
import type { ExoticComponent } from "react";
|
||||
import { atom } from "nanostores";
|
||||
import {
|
||||
@@ -12,10 +13,10 @@ import {
|
||||
import type { Instance } from "@webstudio-is/sdk";
|
||||
import { decodeDataSourceVariable } from "@webstudio-is/sdk";
|
||||
import type { InstanceSelector } from "../tree-utils";
|
||||
import { $dataSourceVariables, $dataSources, $props } from "./nano-states";
|
||||
import { $dataSources, $props } from "./nano-states";
|
||||
import { $instances, $selectedInstanceSelector } from "./instances";
|
||||
import { $selectedPage } from "./pages";
|
||||
import { shallowEqual } from "shallow-equal";
|
||||
import { $dataSourceVariables } from "./variables";
|
||||
|
||||
const createHookContext = (): HookContext => {
|
||||
const metas = $registeredComponentMetas.get();
|
||||
|
||||
@@ -4,4 +4,5 @@ export * from "./instances";
|
||||
export * from "./props";
|
||||
export * from "./canvas";
|
||||
export * from "./pages";
|
||||
export * from "./variables";
|
||||
export * from "./components";
|
||||
@@ -6,10 +6,8 @@ import type { AuthPermit } from "@webstudio-is/trpc-interface/index.server";
|
||||
import type { ItemDropTarget, Placement } from "@webstudio-is/design-system";
|
||||
import type {
|
||||
Assets,
|
||||
DataSource,
|
||||
DataSources,
|
||||
Instance,
|
||||
Page,
|
||||
Prop,
|
||||
Props,
|
||||
Resource,
|
||||
@@ -18,7 +16,6 @@ import type {
|
||||
StyleSource,
|
||||
StyleSources,
|
||||
StyleSourceSelections,
|
||||
System,
|
||||
} from "@webstudio-is/sdk";
|
||||
import type { Style } from "@webstudio-is/css-engine";
|
||||
import type { Project } from "@webstudio-is/project";
|
||||
@@ -57,30 +54,8 @@ export const $rootInstance = computed(
|
||||
);
|
||||
|
||||
export const $dataSources = atom<DataSources>(new Map());
|
||||
export const $dataSourceVariables = atom<Map<DataSource["id"], unknown>>(
|
||||
new Map()
|
||||
);
|
||||
|
||||
export const updateSystem = (page: Page, update: Partial<System>) => {
|
||||
const dataSourceVariables = new Map($dataSourceVariables.get());
|
||||
const system = dataSourceVariables.get(page.systemDataSourceId) as
|
||||
| undefined
|
||||
| System;
|
||||
|
||||
const newSystem: System = {
|
||||
search: {},
|
||||
params: {},
|
||||
// This value seems like doesn't matter
|
||||
origin: "",
|
||||
...system,
|
||||
...update,
|
||||
};
|
||||
dataSourceVariables.set(page.systemDataSourceId, newSystem);
|
||||
$dataSourceVariables.set(dataSourceVariables);
|
||||
};
|
||||
|
||||
export const $resources = atom(new Map<Resource["id"], Resource>());
|
||||
export const $resourceValues = atom(new Map<Resource["id"], unknown>());
|
||||
|
||||
export const $props = atom<Props>(new Map());
|
||||
export const $propsIndex = computed($props, (props) => {
|
||||
|
||||
@@ -14,15 +14,9 @@ import {
|
||||
computeExpression,
|
||||
} from "./props";
|
||||
import { $pages, $selectedPageId } from "./pages";
|
||||
import {
|
||||
$assets,
|
||||
$dataSourceVariables,
|
||||
$dataSources,
|
||||
$props,
|
||||
$resourceValues,
|
||||
$resources,
|
||||
} from "./nano-states";
|
||||
import { $assets, $dataSources, $props, $resources } from "./nano-states";
|
||||
import { $params } from "~/canvas/stores";
|
||||
import { $dataSourceVariables, $resourceValues } from "./variables";
|
||||
|
||||
setEnv("*");
|
||||
|
||||
@@ -547,6 +541,53 @@ test("compute instance text content bound to expression", () => {
|
||||
cleanStores($propValuesByInstanceSelector);
|
||||
});
|
||||
|
||||
test("use default system values in props", () => {
|
||||
$instances.set(
|
||||
toMap([
|
||||
{
|
||||
id: "body",
|
||||
type: "instance",
|
||||
component: "Body",
|
||||
children: [],
|
||||
},
|
||||
])
|
||||
);
|
||||
$dataSources.set(
|
||||
toMap([
|
||||
{
|
||||
id: "systemId",
|
||||
scopeInstanceId: "body",
|
||||
name: "system",
|
||||
type: "parameter",
|
||||
},
|
||||
])
|
||||
);
|
||||
$props.set(
|
||||
toMap([
|
||||
{
|
||||
id: "1",
|
||||
instanceId: "body",
|
||||
name: "data-origin",
|
||||
type: "expression",
|
||||
value: `$ws$dataSource$systemId.origin`,
|
||||
},
|
||||
])
|
||||
);
|
||||
selectPageRoot("body");
|
||||
expect($propValuesByInstanceSelector.get()).toEqual(
|
||||
new Map([
|
||||
[
|
||||
JSON.stringify(["body"]),
|
||||
new Map<string, unknown>([
|
||||
["data-origin", "https://undefined.wstd.work"],
|
||||
]),
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
cleanStores($propValuesByInstanceSelector);
|
||||
});
|
||||
|
||||
test("compute variable values for root", () => {
|
||||
$instances.set(
|
||||
toMap([{ id: "body", type: "instance", component: "Body", children: [] }])
|
||||
@@ -929,7 +970,14 @@ test("prefill default system variable value", () => {
|
||||
[
|
||||
JSON.stringify(["body"]),
|
||||
new Map<string, unknown>([
|
||||
["systemId", { params: { slug: "my-post" }, search: {} }],
|
||||
[
|
||||
"systemId",
|
||||
{
|
||||
params: { slug: "my-post" },
|
||||
search: {},
|
||||
origin: "https://undefined.wstd.work",
|
||||
},
|
||||
],
|
||||
]),
|
||||
],
|
||||
])
|
||||
|
||||
@@ -20,20 +20,18 @@ import {
|
||||
} from "@webstudio-is/react-sdk";
|
||||
import { isFeatureEnabled } from "@webstudio-is/feature-flags";
|
||||
import { $instances } from "./instances";
|
||||
import {
|
||||
$dataSourceVariables,
|
||||
$dataSources,
|
||||
$props,
|
||||
$assets,
|
||||
$resources,
|
||||
$resourceValues,
|
||||
$publishedOrigin,
|
||||
} from "./nano-states";
|
||||
import { $dataSources, $props, $assets, $resources } from "./nano-states";
|
||||
import { $selectedPage, $pages } from "./pages";
|
||||
import { groupBy } from "../array-utils";
|
||||
import type { InstanceSelector } from "../tree-utils";
|
||||
import { $params } from "~/canvas/stores";
|
||||
import { restResourcesLoader } from "../router-utils";
|
||||
import {
|
||||
$dataSourceVariables,
|
||||
$resourceValues,
|
||||
$selectedPageDefaultSystem,
|
||||
mergeSystem,
|
||||
} from "./variables";
|
||||
|
||||
export const getIndexedInstanceId = (
|
||||
instanceId: Instance["id"],
|
||||
@@ -120,8 +118,14 @@ const getAction = (
|
||||
// result of executing generated code
|
||||
// includes variables, computed expressions and action callbacks
|
||||
const $dataSourcesLogic = computed(
|
||||
[$dataSources, $dataSourceVariables, $resourceValues],
|
||||
(dataSources, dataSourceVariables, resourceValues) => {
|
||||
[
|
||||
$dataSources,
|
||||
$dataSourceVariables,
|
||||
$resourceValues,
|
||||
$selectedPage,
|
||||
$selectedPageDefaultSystem,
|
||||
],
|
||||
(dataSources, dataSourceVariables, resourceValues, page, defaultSystem) => {
|
||||
const values = new Map<string, unknown>();
|
||||
for (const [dataSourceId, dataSource] of dataSources) {
|
||||
if (dataSource.type === "variable") {
|
||||
@@ -131,7 +135,11 @@ const $dataSourcesLogic = computed(
|
||||
);
|
||||
}
|
||||
if (dataSource.type === "parameter") {
|
||||
values.set(dataSourceId, dataSourceVariables.get(dataSourceId));
|
||||
let value = dataSourceVariables.get(dataSourceId);
|
||||
if (dataSource.id === page?.systemDataSourceId) {
|
||||
value = mergeSystem(defaultSystem, value as undefined | System);
|
||||
}
|
||||
values.set(dataSourceId, value);
|
||||
}
|
||||
if (dataSource.type === "resource") {
|
||||
values.set(dataSourceId, resourceValues.get(dataSource.resourceId));
|
||||
@@ -301,7 +309,7 @@ export const $variableValuesByInstanceSelector = computed(
|
||||
$dataSources,
|
||||
$dataSourceVariables,
|
||||
$resourceValues,
|
||||
$publishedOrigin,
|
||||
$selectedPageDefaultSystem,
|
||||
],
|
||||
(
|
||||
instances,
|
||||
@@ -310,7 +318,7 @@ export const $variableValuesByInstanceSelector = computed(
|
||||
dataSources,
|
||||
dataSourceVariables,
|
||||
resourceValues,
|
||||
publishedOrigin
|
||||
defaultSystem
|
||||
) => {
|
||||
const propsByInstanceId = groupBy(
|
||||
props.values(),
|
||||
@@ -361,17 +369,11 @@ export const $variableValuesByInstanceSelector = computed(
|
||||
if (variable.type === "parameter") {
|
||||
const value = dataSourceVariables.get(variable.id);
|
||||
variableValues.set(variable.id, value);
|
||||
if (
|
||||
variable.id === page.systemDataSourceId &&
|
||||
value === undefined
|
||||
) {
|
||||
const systemDefaultValue: System = {
|
||||
params: {},
|
||||
search: {},
|
||||
origin: publishedOrigin,
|
||||
};
|
||||
|
||||
variableValues.set(variable.id, systemDefaultValue);
|
||||
if (variable.id === page.systemDataSourceId) {
|
||||
variableValues.set(
|
||||
variable.id,
|
||||
mergeSystem(defaultSystem, value as undefined | System)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (variable.type === "resource") {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { atom, computed } from "nanostores";
|
||||
import type { DataSource, Page, Resource, System } from "@webstudio-is/sdk";
|
||||
import { tokenizePathnamePattern } from "~/builder/shared/url-pattern";
|
||||
import { $publishedOrigin } from "./nano-states";
|
||||
import { $selectedPage } from "./pages";
|
||||
|
||||
export const $dataSourceVariables = atom<Map<DataSource["id"], unknown>>(
|
||||
new Map()
|
||||
);
|
||||
|
||||
export const $resourceValues = atom(new Map<Resource["id"], unknown>());
|
||||
|
||||
export const getPageDefaultSystem = ({
|
||||
origin,
|
||||
path,
|
||||
}: {
|
||||
origin: string;
|
||||
path?: string;
|
||||
}) => {
|
||||
const defaultSystem: System = {
|
||||
params: {},
|
||||
search: {},
|
||||
origin,
|
||||
};
|
||||
if (path) {
|
||||
const tokens = tokenizePathnamePattern(path);
|
||||
for (const token of tokens) {
|
||||
if (token.type === "param") {
|
||||
defaultSystem.params[token.name] = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultSystem;
|
||||
};
|
||||
|
||||
const $selectedPagePath = computed($selectedPage, (page) => page?.path);
|
||||
|
||||
export const $selectedPageDefaultSystem = computed(
|
||||
[$publishedOrigin, $selectedPagePath],
|
||||
(origin, path) => getPageDefaultSystem({ origin, path })
|
||||
);
|
||||
|
||||
export const mergeSystem = (left: System, right?: System): System => {
|
||||
return {
|
||||
...left,
|
||||
...right,
|
||||
params: {
|
||||
...left.params,
|
||||
...right?.params,
|
||||
},
|
||||
search: {
|
||||
...left.search,
|
||||
...right?.search,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const updateSystem = (page: Page, update: Partial<System>) => {
|
||||
const dataSourceVariables = new Map($dataSourceVariables.get());
|
||||
const system = dataSourceVariables.get(page.systemDataSourceId) as
|
||||
| undefined
|
||||
| System;
|
||||
|
||||
const newSystem: System = {
|
||||
search: {},
|
||||
params: {},
|
||||
origin: $publishedOrigin.get(),
|
||||
...system,
|
||||
...update,
|
||||
};
|
||||
dataSourceVariables.set(page.systemDataSourceId, newSystem);
|
||||
$dataSourceVariables.set(dataSourceVariables);
|
||||
};
|
||||
Reference in new issue
Block a user