diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 73a2494c618b..64d38a25e443 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -135,7 +135,7 @@ jobs: const results = [ await assertSize('./fixtures/ssg/dist/client', 352), await assertSize('./fixtures/react-router-netlify/build/client', 368), - await assertSize('./fixtures/webstudio-features/build/client', 1036), + await assertSize('./fixtures/webstudio-features/build/client', 1052), ] for (const result of results) { if (result.passed) { diff --git a/fixtures/webstudio-features/.webstudio/data.json b/fixtures/webstudio-features/.webstudio/data.json index d395e1c32a40..f5473c7fd0ea 100644 --- a/fixtures/webstudio-features/.webstudio/data.json +++ b/fixtures/webstudio-features/.webstudio/data.json @@ -1,10 +1,10 @@ { "build": { - "id": "3a8c8a9c-cfd9-46b8-a8bf-30d48250bc1b", + "id": "f0dfc2e7-240a-4542-ad28-a4cb68b2d9db", "projectId": "cddc1d44-af37-4cb6-a430-d300cf6f932d", - "version": 554, - "createdAt": "2025-03-26T12:23:17.308+00:00", - "updatedAt": "2025-03-26T12:23:17.308+00:00", + "version": 583, + "createdAt": "2025-04-14T09:45:34.257+00:00", + "updatedAt": "2025-04-14T09:45:34.257+00:00", "pages": { "meta": { "siteName": "KittyGuardedZone", @@ -248,6 +248,49 @@ "include": false }, "path": "/animations" + }, + { + "id": "GgJUcrFoHE6EGMccjLa4t", + "name": "duration", + "title": "\"Untitled\"", + "rootInstanceId": "Lmn0VIRtr_Yn9AvDyjgZG", + "meta": { + "description": "\"\"", + "excludePageFromSearch": "true", + "language": "\"\"", + "socialImageUrl": "\"\"", + "redirect": "\"\"", + "documentType": "html", + "custom": [ + { + "property": "", + "content": "\"\"" + } + ] + }, + "marketplace": { + "include": false + }, + "path": "/duration" + }, + { + "id": "uuYXNj4-XxzMtJpR8O5IH", + "name": "Text Duration", + "title": "\"Untitled\"", + "rootInstanceId": "LTaU1Znm6P4yt3S8kAZi-", + "meta": { + "description": "\"\"", + "excludePageFromSearch": "true", + "language": "\"\"", + "socialImageUrl": "\"\"", + "redirect": "\"\"", + "documentType": "html", + "custom": [] + }, + "marketplace": { + "include": false + }, + "path": "/text-duration" } ], "folders": [ @@ -268,7 +311,9 @@ "FsnS9ui6btzM4W3YELE3Q", "Q1D-6G1cl0SfXyM9Xj4_O", "8fF_9MQOwOqsLM9BhULUG", - "oc1Ra5zelesj-wiJwiw-P" + "oc1Ra5zelesj-wiJwiw-P", + "GgJUcrFoHE6EGMccjLa4t", + "uuYXNj4-XxzMtJpR8O5IH" ] }, { @@ -2096,6 +2141,30 @@ "value": 0 } } + ], + [ + "SdJcDQJhrZ0zxtPqj546x:UoTkWyaFuTYJihS3MFYK5:textAlign:", + { + "styleSourceId": "SdJcDQJhrZ0zxtPqj546x", + "breakpointId": "UoTkWyaFuTYJihS3MFYK5", + "property": "textAlign", + "value": { + "type": "keyword", + "value": "center" + } + } + ], + [ + "hEVSzkOEBn_3dNyo65PAM:UoTkWyaFuTYJihS3MFYK5:textAlign:", + { + "styleSourceId": "hEVSzkOEBn_3dNyo65PAM", + "breakpointId": "UoTkWyaFuTYJihS3MFYK5", + "property": "textAlign", + "value": { + "type": "keyword", + "value": "center" + } + } ] ], "styleSources": [ @@ -2399,6 +2468,20 @@ "type": "local", "id": "hSMd0d2O_5ogkTfvV8g8B" } + ], + [ + "SdJcDQJhrZ0zxtPqj546x", + { + "type": "local", + "id": "SdJcDQJhrZ0zxtPqj546x" + } + ], + [ + "hEVSzkOEBn_3dNyo65PAM", + { + "type": "local", + "id": "hEVSzkOEBn_3dNyo65PAM" + } ] ], "styleSourceSelections": [ @@ -2702,6 +2785,20 @@ "instanceId": "hi1l-ro0pCVQpHFIwIhFO", "values": ["hSMd0d2O_5ogkTfvV8g8B"] } + ], + [ + "tOdjbQbLEf52thwmubL7R", + { + "instanceId": "tOdjbQbLEf52thwmubL7R", + "values": ["SdJcDQJhrZ0zxtPqj546x"] + } + ], + [ + "g__o13UKOkD0KImnp-sWp", + { + "instanceId": "g__o13UKOkD0KImnp-sWp", + "values": ["hEVSzkOEBn_3dNyo65PAM"] + } ] ], "props": [ @@ -3813,6 +3910,207 @@ "type": "number", "value": 20 } + ], + [ + "CKL9YyMMdbB8Bg1o2M0S-", + { + "id": "CKL9YyMMdbB8Bg1o2M0S-", + "instanceId": "Iz65PDmd9YjqrIy5fGtWt", + "name": "action", + "type": "animationAction", + "value": { + "type": "view", + "animations": [ + { + "name": "Parent", + "description": "Parallax the element as it scrolls into the view.", + "keyframes": [ + { + "offset": 0, + "styles": { + "translate": { + "type": "tuple", + "value": [ + { + "type": "unit", + "unit": "number", + "value": 0 + }, + { + "type": "unit", + "unit": "px", + "value": 100 + } + ] + } + } + } + ], + "timing": { + "easing": "linear", + "fill": "backwards", + "duration": { + "type": "unit", + "value": 302, + "unit": "ms" + }, + "rangeStart": [ + "cover", + { + "type": "unit", + "value": 0, + "unit": "%" + } + ], + "rangeEnd": [ + "cover", + { + "type": "unit", + "value": 50, + "unit": "%" + } + ] + } + } + ], + "insetStart": { + "type": "keyword", + "value": "auto" + }, + "insetEnd": { + "type": "keyword", + "value": "auto" + }, + "isPinned": true, + "debug": false + } + } + ], + [ + "UMpi59UWI6MbY9ExyAHPM", + { + "id": "UMpi59UWI6MbY9ExyAHPM", + "instanceId": "Z4ogkQT54jFVlCIFkXKmb", + "name": "action", + "type": "animationAction", + "value": { + "type": "view", + "animations": [ + { + "name": "Parent", + "description": "Parallax the element as it scrolls into the view.", + "keyframes": [ + { + "offset": 0, + "styles": { + "translate": { + "type": "tuple", + "value": [ + { + "type": "unit", + "unit": "number", + "value": 0 + }, + { + "type": "unit", + "unit": "px", + "value": 200 + } + ] + } + } + } + ], + "timing": { + "easing": "linear", + "fill": "backwards", + "duration": { + "type": "unit", + "value": 600, + "unit": "ms" + }, + "rangeStart": [ + "cover", + { + "type": "unit", + "value": 0, + "unit": "%" + } + ], + "rangeEnd": [ + "cover", + { + "type": "unit", + "value": 50, + "unit": "%" + } + ] + } + }, + { + "name": "Parallax In", + "description": "Parallax the element as it scrolls into the view.", + "keyframes": [ + { + "offset": 0, + "styles": { + "opacity": { + "type": "unit", + "unit": "%", + "value": 0 + } + } + } + ], + "timing": { + "easing": "linear", + "fill": "backwards", + "duration": { + "type": "unit", + "value": 600, + "unit": "ms" + }, + "rangeStart": [ + "cover", + { + "type": "unit", + "value": 0, + "unit": "%" + } + ], + "rangeEnd": [ + "cover", + { + "type": "unit", + "value": 50, + "unit": "%" + } + ] + } + } + ], + "insetStart": { + "type": "keyword", + "value": "auto" + }, + "insetEnd": { + "type": "keyword", + "value": "auto" + }, + "isPinned": true, + "debug": false + } + } + ], + [ + "pCJa0iDVD_LkD7VvQcoka", + { + "id": "pCJa0iDVD_LkD7VvQcoka", + "instanceId": "Z4ogkQT54jFVlCIFkXKmb", + "name": "data-ws-show", + "type": "boolean", + "value": true + } ] ], "dataSources": [ @@ -6284,6 +6582,152 @@ "label": "Spacer", "children": [] } + ], + [ + "Lmn0VIRtr_Yn9AvDyjgZG", + { + "type": "instance", + "id": "Lmn0VIRtr_Yn9AvDyjgZG", + "component": "Body", + "children": [ + { + "type": "id", + "value": "Iz65PDmd9YjqrIy5fGtWt" + } + ] + } + ], + [ + "Iz65PDmd9YjqrIy5fGtWt", + { + "type": "instance", + "id": "Iz65PDmd9YjqrIy5fGtWt", + "component": "@webstudio-is/sdk-components-animation:AnimateChildren", + "children": [ + { + "type": "id", + "value": "tOdjbQbLEf52thwmubL7R" + } + ] + } + ], + [ + "tOdjbQbLEf52thwmubL7R", + { + "type": "instance", + "id": "tOdjbQbLEf52thwmubL7R", + "component": "Heading", + "children": [ + { + "type": "text", + "value": "HELLO WORLD" + }, + { + "type": "text", + "value": "\n" + }, + { + "type": "text", + "value": "GOOD" + }, + { + "type": "text", + "value": "\n" + }, + { + "type": "text", + "value": "BAD" + }, + { + "type": "text", + "value": "\n" + }, + { + "type": "text", + "value": "UGLY" + } + ] + } + ], + [ + "LTaU1Znm6P4yt3S8kAZi-", + { + "type": "instance", + "id": "LTaU1Znm6P4yt3S8kAZi-", + "component": "Body", + "children": [ + { + "type": "id", + "value": "Z4ogkQT54jFVlCIFkXKmb" + } + ] + } + ], + [ + "Z4ogkQT54jFVlCIFkXKmb", + { + "type": "instance", + "id": "Z4ogkQT54jFVlCIFkXKmb", + "component": "@webstudio-is/sdk-components-animation:AnimateChildren", + "children": [ + { + "type": "id", + "value": "aqJaUhJ3xDmbIDws8d1TS" + } + ] + } + ], + [ + "aqJaUhJ3xDmbIDws8d1TS", + { + "type": "instance", + "id": "aqJaUhJ3xDmbIDws8d1TS", + "component": "@webstudio-is/sdk-components-animation:AnimateText", + "children": [ + { + "type": "id", + "value": "g__o13UKOkD0KImnp-sWp" + } + ] + } + ], + [ + "g__o13UKOkD0KImnp-sWp", + { + "type": "instance", + "id": "g__o13UKOkD0KImnp-sWp", + "component": "Heading", + "children": [ + { + "type": "text", + "value": "HELLO WORLD" + }, + { + "type": "text", + "value": "\n" + }, + { + "type": "text", + "value": "GOOD" + }, + { + "type": "text", + "value": "\n" + }, + { + "type": "text", + "value": "BAD" + }, + { + "type": "text", + "value": "\n" + }, + { + "type": "text", + "value": "UGLY" + } + ] + } ] ], "deployment": { @@ -6548,6 +6992,49 @@ "include": false }, "path": "/animations" + }, + { + "id": "GgJUcrFoHE6EGMccjLa4t", + "name": "duration", + "title": "\"Untitled\"", + "rootInstanceId": "Lmn0VIRtr_Yn9AvDyjgZG", + "meta": { + "description": "\"\"", + "excludePageFromSearch": "true", + "language": "\"\"", + "socialImageUrl": "\"\"", + "redirect": "\"\"", + "documentType": "html", + "custom": [ + { + "property": "", + "content": "\"\"" + } + ] + }, + "marketplace": { + "include": false + }, + "path": "/duration" + }, + { + "id": "uuYXNj4-XxzMtJpR8O5IH", + "name": "Text Duration", + "title": "\"Untitled\"", + "rootInstanceId": "LTaU1Znm6P4yt3S8kAZi-", + "meta": { + "description": "\"\"", + "excludePageFromSearch": "true", + "language": "\"\"", + "socialImageUrl": "\"\"", + "redirect": "\"\"", + "documentType": "html", + "custom": [] + }, + "marketplace": { + "include": false + }, + "path": "/text-duration" } ], "assets": [ diff --git a/fixtures/webstudio-features/app/__generated__/$resources.sitemap.xml.ts b/fixtures/webstudio-features/app/__generated__/$resources.sitemap.xml.ts index 9264d4801431..d33e6cb8fb0f 100644 --- a/fixtures/webstudio-features/app/__generated__/$resources.sitemap.xml.ts +++ b/fixtures/webstudio-features/app/__generated__/$resources.sitemap.xml.ts @@ -1,26 +1,26 @@ export const sitemap = [ { path: "/", - lastModified: "2025-03-26", + lastModified: "2025-04-14", }, { path: "/_route_with_symbols_", - lastModified: "2025-03-26", + lastModified: "2025-04-14", }, { path: "/form", - lastModified: "2025-03-26", + lastModified: "2025-04-14", }, { path: "/heading-with-id", - lastModified: "2025-03-26", + lastModified: "2025-04-14", }, { path: "/resources", - lastModified: "2025-03-26", + lastModified: "2025-04-14", }, { path: "/nested/nested-page", - lastModified: "2025-03-26", + lastModified: "2025-04-14", }, ]; diff --git a/fixtures/webstudio-features/app/__generated__/[_route_with_symbols_]._index.tsx b/fixtures/webstudio-features/app/__generated__/[_route_with_symbols_]._index.tsx index 8db7da6d603d..9c281b2bb89a 100644 --- a/fixtures/webstudio-features/app/__generated__/[_route_with_symbols_]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[_route_with_symbols_]._index.tsx @@ -8,7 +8,7 @@ import { Image as Image } from "@webstudio-is/sdk-components-react"; export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; -export const lastPublished = "2025-03-26T12:23:17.308Z"; +export const lastPublished = "2025-04-14T09:45:34.257Z"; export const siteName = "KittyGuardedZone"; diff --git a/fixtures/webstudio-features/app/__generated__/[animations]._index.tsx b/fixtures/webstudio-features/app/__generated__/[animations]._index.tsx index 9997f0a10391..39c4c71f49f9 100644 --- a/fixtures/webstudio-features/app/__generated__/[animations]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[animations]._index.tsx @@ -15,7 +15,7 @@ import { export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; -export const lastPublished = "2025-03-26T12:23:17.308Z"; +export const lastPublished = "2025-04-14T09:45:34.257Z"; export const siteName = "KittyGuardedZone"; diff --git a/fixtures/webstudio-features/app/__generated__/[class-names]._index.tsx b/fixtures/webstudio-features/app/__generated__/[class-names]._index.tsx index 37c0eb7cb9f3..0d81948c412c 100644 --- a/fixtures/webstudio-features/app/__generated__/[class-names]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[class-names]._index.tsx @@ -8,7 +8,7 @@ import { Box as Box } from "@webstudio-is/sdk-components-react"; export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; -export const lastPublished = "2025-03-26T12:23:17.308Z"; +export const lastPublished = "2025-04-14T09:45:34.257Z"; export const siteName = "KittyGuardedZone"; diff --git a/fixtures/webstudio-features/app/__generated__/[content-block]._index.tsx b/fixtures/webstudio-features/app/__generated__/[content-block]._index.tsx index d96afc6a84f5..5281a7759002 100644 --- a/fixtures/webstudio-features/app/__generated__/[content-block]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[content-block]._index.tsx @@ -12,7 +12,7 @@ import { export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; -export const lastPublished = "2025-03-26T12:23:17.308Z"; +export const lastPublished = "2025-04-14T09:45:34.257Z"; export const siteName = "KittyGuardedZone"; diff --git a/fixtures/webstudio-features/app/__generated__/[duration]._index.server.tsx b/fixtures/webstudio-features/app/__generated__/[duration]._index.server.tsx new file mode 100644 index 000000000000..b911a743916f --- /dev/null +++ b/fixtures/webstudio-features/app/__generated__/[duration]._index.server.tsx @@ -0,0 +1,37 @@ +/* eslint-disable */ +/* This is a auto generated file for building the project */ + +import type { PageMeta } from "@webstudio-is/sdk"; +import type { System, ResourceRequest } from "@webstudio-is/sdk"; +export const getResources = (_props: { system: System }) => { + const _data = new Map([]); + const _action = new Map([]); + return { data: _data, action: _action }; +}; + +export const getPageMeta = ({ + system, + resources, +}: { + system: System; + resources: Record; +}): PageMeta => { + return { + title: "Untitled", + description: "", + excludePageFromSearch: true, + language: "", + socialImageAssetName: undefined, + socialImageUrl: "", + status: undefined, + redirect: "", + custom: [], + }; +}; + +type Params = Record; +export const getRemixParams = ({ ...params }: Params): Params => { + return params; +}; + +export const contactEmail = "hello@webstudio.is"; diff --git a/fixtures/webstudio-features/app/__generated__/[duration]._index.tsx b/fixtures/webstudio-features/app/__generated__/[duration]._index.tsx new file mode 100644 index 000000000000..f8072156dff5 --- /dev/null +++ b/fixtures/webstudio-features/app/__generated__/[duration]._index.tsx @@ -0,0 +1,89 @@ +/* eslint-disable */ +/* This is a auto generated file for building the project */ + +import { Fragment, useState } from "react"; +import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime"; +import { Body as Body } from "@webstudio-is/sdk-components-react-router"; +import { AnimateChildren as AnimateChildren } from "@webstudio-is/sdk-components-animation"; +import { Heading as Heading } from "@webstudio-is/sdk-components-react"; + +export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; + +export const lastPublished = "2025-04-14T09:45:34.257Z"; + +export const siteName = "KittyGuardedZone"; + +export const breakpoints = [ + { id: "UoTkWyaFuTYJihS3MFYK5" }, + { id: "ZMaWCtWpH-ao0e_kgIHqR", minWidth: 372 }, + { id: "Z8WjyXWkCrr35PXgjHdpY", minWidth: 472 }, +]; + +export const favIconAsset: string | undefined = + "DALL_E_2023-10-30_12.39.46_-_Photo_logo_with_a_bold_cat_silhouette_centered_on_a_contrasting_background_designed_for_clarity_at_small_32x32_favicon_resolution_00h6cEA8u2pJRvVJv7hRe.png"; + +// Font assets on current page (can be preloaded) +export const pageFontAssets: string[] = []; + +export const pageBackgroundImageAssets: string[] = []; + +const Page = (_props: { system: any }) => { + return ( + + + + {"HELLO WORLD"} + {""} +
+ {""} + {"GOOD"} + {""} +
+ {""} + {"BAD"} + {""} +
+ {""} + {"UGLY"} +
+
+ + ); +}; + +export { Page }; diff --git a/fixtures/webstudio-features/app/__generated__/[expressions]._index.tsx b/fixtures/webstudio-features/app/__generated__/[expressions]._index.tsx index 718a88ce3cf4..8aedb43c32a7 100644 --- a/fixtures/webstudio-features/app/__generated__/[expressions]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[expressions]._index.tsx @@ -12,7 +12,7 @@ import { export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; -export const lastPublished = "2025-03-26T12:23:17.308Z"; +export const lastPublished = "2025-04-14T09:45:34.257Z"; export const siteName = "KittyGuardedZone"; diff --git a/fixtures/webstudio-features/app/__generated__/[form]._index.tsx b/fixtures/webstudio-features/app/__generated__/[form]._index.tsx index 74c8f5f2a76a..9709fe9b2d67 100644 --- a/fixtures/webstudio-features/app/__generated__/[form]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[form]._index.tsx @@ -17,7 +17,7 @@ import { export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; -export const lastPublished = "2025-03-26T12:23:17.308Z"; +export const lastPublished = "2025-04-14T09:45:34.257Z"; export const siteName = "KittyGuardedZone"; diff --git a/fixtures/webstudio-features/app/__generated__/[head-tag]._index.tsx b/fixtures/webstudio-features/app/__generated__/[head-tag]._index.tsx index a68fa55f6d4c..6a7000811dbb 100644 --- a/fixtures/webstudio-features/app/__generated__/[head-tag]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[head-tag]._index.tsx @@ -17,7 +17,7 @@ import { export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; -export const lastPublished = "2025-03-26T12:23:17.308Z"; +export const lastPublished = "2025-04-14T09:45:34.257Z"; export const siteName = "KittyGuardedZone"; diff --git a/fixtures/webstudio-features/app/__generated__/[heading-with-id]._index.tsx b/fixtures/webstudio-features/app/__generated__/[heading-with-id]._index.tsx index 7c1dabfd2413..ac381b1d1436 100644 --- a/fixtures/webstudio-features/app/__generated__/[heading-with-id]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[heading-with-id]._index.tsx @@ -8,7 +8,7 @@ import { Heading as Heading } from "@webstudio-is/sdk-components-react"; export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; -export const lastPublished = "2025-03-26T12:23:17.308Z"; +export const lastPublished = "2025-04-14T09:45:34.257Z"; export const siteName = "KittyGuardedZone"; diff --git a/fixtures/webstudio-features/app/__generated__/[nested].[nested-page]._index.tsx b/fixtures/webstudio-features/app/__generated__/[nested].[nested-page]._index.tsx index a373c146270d..02aaaaa3b7f2 100644 --- a/fixtures/webstudio-features/app/__generated__/[nested].[nested-page]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[nested].[nested-page]._index.tsx @@ -8,7 +8,7 @@ import { Heading as Heading } from "@webstudio-is/sdk-components-react"; export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; -export const lastPublished = "2025-03-26T12:23:17.308Z"; +export const lastPublished = "2025-04-14T09:45:34.257Z"; export const siteName = "KittyGuardedZone"; diff --git a/fixtures/webstudio-features/app/__generated__/[radix]._index.tsx b/fixtures/webstudio-features/app/__generated__/[radix]._index.tsx index 03e92952bdfe..f9999e091f25 100644 --- a/fixtures/webstudio-features/app/__generated__/[radix]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[radix]._index.tsx @@ -19,7 +19,7 @@ import { export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; -export const lastPublished = "2025-03-26T12:23:17.308Z"; +export const lastPublished = "2025-04-14T09:45:34.257Z"; export const siteName = "KittyGuardedZone"; diff --git a/fixtures/webstudio-features/app/__generated__/[resources]._index.tsx b/fixtures/webstudio-features/app/__generated__/[resources]._index.tsx index dfc94635cdc6..53250ff8d846 100644 --- a/fixtures/webstudio-features/app/__generated__/[resources]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[resources]._index.tsx @@ -11,7 +11,7 @@ import { export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; -export const lastPublished = "2025-03-26T12:23:17.308Z"; +export const lastPublished = "2025-04-14T09:45:34.257Z"; export const siteName = "KittyGuardedZone"; diff --git a/fixtures/webstudio-features/app/__generated__/[sitemap.xml]._index.tsx b/fixtures/webstudio-features/app/__generated__/[sitemap.xml]._index.tsx index c92a995c16e1..c53f64fd0d5b 100644 --- a/fixtures/webstudio-features/app/__generated__/[sitemap.xml]._index.tsx +++ b/fixtures/webstudio-features/app/__generated__/[sitemap.xml]._index.tsx @@ -10,7 +10,7 @@ import { export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; -export const lastPublished = "2025-03-26T12:23:17.308Z"; +export const lastPublished = "2025-04-14T09:45:34.257Z"; export const siteName = "KittyGuardedZone"; diff --git a/fixtures/webstudio-features/app/__generated__/[text-duration]._index.server.tsx b/fixtures/webstudio-features/app/__generated__/[text-duration]._index.server.tsx new file mode 100644 index 000000000000..b911a743916f --- /dev/null +++ b/fixtures/webstudio-features/app/__generated__/[text-duration]._index.server.tsx @@ -0,0 +1,37 @@ +/* eslint-disable */ +/* This is a auto generated file for building the project */ + +import type { PageMeta } from "@webstudio-is/sdk"; +import type { System, ResourceRequest } from "@webstudio-is/sdk"; +export const getResources = (_props: { system: System }) => { + const _data = new Map([]); + const _action = new Map([]); + return { data: _data, action: _action }; +}; + +export const getPageMeta = ({ + system, + resources, +}: { + system: System; + resources: Record; +}): PageMeta => { + return { + title: "Untitled", + description: "", + excludePageFromSearch: true, + language: "", + socialImageAssetName: undefined, + socialImageUrl: "", + status: undefined, + redirect: "", + custom: [], + }; +}; + +type Params = Record; +export const getRemixParams = ({ ...params }: Params): Params => { + return params; +}; + +export const contactEmail = "hello@webstudio.is"; diff --git a/fixtures/webstudio-features/app/__generated__/[text-duration]._index.tsx b/fixtures/webstudio-features/app/__generated__/[text-duration]._index.tsx new file mode 100644 index 000000000000..75b0d3a18449 --- /dev/null +++ b/fixtures/webstudio-features/app/__generated__/[text-duration]._index.tsx @@ -0,0 +1,111 @@ +/* eslint-disable */ +/* This is a auto generated file for building the project */ + +import { Fragment, useState } from "react"; +import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime"; +import { Body as Body } from "@webstudio-is/sdk-components-react-router"; +import { + AnimateChildren as AnimateChildren, + AnimateText as AnimateText, +} from "@webstudio-is/sdk-components-animation"; +import { Heading as Heading } from "@webstudio-is/sdk-components-react"; + +export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; + +export const lastPublished = "2025-04-14T09:45:34.257Z"; + +export const siteName = "KittyGuardedZone"; + +export const breakpoints = [ + { id: "UoTkWyaFuTYJihS3MFYK5" }, + { id: "ZMaWCtWpH-ao0e_kgIHqR", minWidth: 372 }, + { id: "Z8WjyXWkCrr35PXgjHdpY", minWidth: 472 }, +]; + +export const favIconAsset: string | undefined = + "DALL_E_2023-10-30_12.39.46_-_Photo_logo_with_a_bold_cat_silhouette_centered_on_a_contrasting_background_designed_for_clarity_at_small_32x32_favicon_resolution_00h6cEA8u2pJRvVJv7hRe.png"; + +// Font assets on current page (can be preloaded) +export const pageFontAssets: string[] = []; + +export const pageBackgroundImageAssets: string[] = []; + +const Page = (_props: { system: any }) => { + return ( + + + + + {"HELLO WORLD"} + {""} +
+ {""} + {"GOOD"} + {""} +
+ {""} + {"BAD"} + {""} +
+ {""} + {"UGLY"} +
+
+
+ + ); +}; + +export { Page }; diff --git a/fixtures/webstudio-features/app/__generated__/_index.tsx b/fixtures/webstudio-features/app/__generated__/_index.tsx index 25d137a219bb..36fa9cc5d0d9 100644 --- a/fixtures/webstudio-features/app/__generated__/_index.tsx +++ b/fixtures/webstudio-features/app/__generated__/_index.tsx @@ -17,7 +17,7 @@ import { export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d"; -export const lastPublished = "2025-03-26T12:23:17.308Z"; +export const lastPublished = "2025-04-14T09:45:34.257Z"; export const siteName = "KittyGuardedZone"; diff --git a/fixtures/webstudio-features/app/__generated__/index.css b/fixtures/webstudio-features/app/__generated__/index.css index 91816fd03eb4..3dc409649bcd 100644 --- a/fixtures/webstudio-features/app/__generated__/index.css +++ b/fixtures/webstudio-features/app/__generated__/index.css @@ -414,6 +414,9 @@ .c1m04i8w { height: 100dvh; } + .cjib6ds { + text-align: center; + } } @media all and (min-width: 472px) { .c1jgcte3 { diff --git a/fixtures/webstudio-features/app/routes/[duration]._index.tsx b/fixtures/webstudio-features/app/routes/[duration]._index.tsx new file mode 100644 index 000000000000..e8aa649fc1a0 --- /dev/null +++ b/fixtures/webstudio-features/app/routes/[duration]._index.tsx @@ -0,0 +1,293 @@ +import { + type MetaFunction, + type LinksFunction, + type LinkDescriptor, + type ActionFunctionArgs, + type LoaderFunctionArgs, + type HeadersFunction, + data, + redirect, + useLoaderData, +} from "react-router"; +import { + isLocalResource, + loadResource, + loadResources, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/sdk/runtime"; +import { + ReactSdkContext, + PageSettingsMeta, + PageSettingsTitle, +} from "@webstudio-is/react-sdk/runtime"; +import { + projectId, + Page, + siteName, + favIconAsset, + pageFontAssets, + pageBackgroundImageAssets, + breakpoints, +} from "../__generated__/[duration]._index"; +import { + getResources, + getPageMeta, + getRemixParams, + contactEmail, +} from "../__generated__/[duration]._index.server"; +import { assetBaseUrl, imageLoader } from "../constants.mjs"; +import css from "../__generated__/index.css?url"; +import { sitemap } from "../__generated__/$resources.sitemap.xml"; + +const customFetch: typeof fetch = (input, init) => { + if (typeof input !== "string") { + return fetch(input, init); + } + + if (isLocalResource(input, "sitemap.xml")) { + // @todo: dynamic import sitemap ??? + const response = new Response(JSON.stringify(sitemap)); + response.headers.set("content-type", "application/json; charset=utf-8"); + return Promise.resolve(response); + } + + return fetch(input, init); +}; + +export const loader = async (arg: LoaderFunctionArgs) => { + const url = new URL(arg.request.url); + const host = + arg.request.headers.get("x-forwarded-host") || + arg.request.headers.get("host") || + ""; + url.host = host; + url.protocol = "https"; + + const params = getRemixParams(arg.params); + const system = { + params, + search: Object.fromEntries(url.searchParams), + origin: url.origin, + }; + + const resources = await loadResources( + customFetch, + getResources({ system }).data + ); + const pageMeta = getPageMeta({ system, resources }); + + if (pageMeta.redirect) { + const status = + pageMeta.status === 301 || pageMeta.status === 302 + ? pageMeta.status + : 302; + throw redirect(pageMeta.redirect, status); + } + + // typecheck + arg.context.EXCLUDE_FROM_SEARCH satisfies boolean; + + if (arg.context.EXCLUDE_FROM_SEARCH) { + pageMeta.excludePageFromSearch = arg.context.EXCLUDE_FROM_SEARCH; + } + + return data( + { + host, + url: url.href, + system, + resources, + pageMeta, + }, + // No way for current information to change, so add cache for 10 minutes + // In case of CRM Data, this should be set to 0 + { + status: pageMeta.status, + headers: { + "Cache-Control": "public, max-age=600", + }, + } + ); +}; + +export const headers: HeadersFunction = () => { + return { + "Cache-Control": "public, max-age=0, must-revalidate", + }; +}; + +export const meta: MetaFunction = ({ data }) => { + const metas: ReturnType = []; + if (data === undefined) { + return metas; + } + + const origin = `https://${data.host}`; + + if (siteName) { + metas.push({ + "script:ld+json": { + "@context": "https://schema.org", + "@type": "WebSite", + name: siteName, + url: origin, + }, + }); + } + + return metas; +}; + +export const links: LinksFunction = () => { + const result: LinkDescriptor[] = []; + + result.push({ + rel: "stylesheet", + href: css, + }); + + if (favIconAsset) { + result.push({ + rel: "icon", + href: imageLoader({ + src: `${assetBaseUrl}${favIconAsset}`, + // width,height must be multiple of 48 https://developers.google.com/search/docs/appearance/favicon-in-search + width: 144, + height: 144, + fit: "pad", + quality: 100, + format: "auto", + }), + type: undefined, + }); + } + + for (const asset of pageFontAssets) { + result.push({ + rel: "preload", + href: `${assetBaseUrl}${asset}`, + as: "font", + crossOrigin: "anonymous", + }); + } + + for (const backgroundImageAsset of pageBackgroundImageAssets) { + result.push({ + rel: "preload", + href: `${assetBaseUrl}${backgroundImageAsset}`, + as: "image", + }); + } + + return result; +}; + +const getRequestHost = (request: Request): string => + request.headers.get("x-forwarded-host") || request.headers.get("host") || ""; + +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { + try { + const url = new URL(request.url); + url.host = getRequestHost(request); + + const formData = await request.formData(); + + const system = { + params: {}, + search: {}, + origin: url.origin, + }; + + const resourceName = formData.get(formIdFieldName); + let resource = + typeof resourceName === "string" + ? getResources({ system }).action.get(resourceName) + : undefined; + + const formBotValue = formData.get(formBotFieldName); + + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } + + const submitTime = parseInt(formBotValue, 16); + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + formData.delete(formIdFieldName); + formData.delete(formBotFieldName); + + if (resource) { + resource.body = Object.fromEntries(formData); + } else { + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + resource = context.getDefaultActionResource?.({ + url, + projectId, + contactEmail, + formData, + }); + } + + if (resource === undefined) { + throw Error("Resource not found"); + } + const { ok, statusText } = await loadResource(fetch, resource); + if (ok) { + return { success: true }; + } + return { success: false, errors: [statusText] }; + } catch (error) { + console.error(error); + + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; + } +}; + +const Outlet = () => { + const { system, resources, url, pageMeta, host } = + useLoaderData(); + return ( + + {/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */} + + + {pageMeta.title} + + ); +}; + +export default Outlet; diff --git a/fixtures/webstudio-features/app/routes/[text-duration]._index.tsx b/fixtures/webstudio-features/app/routes/[text-duration]._index.tsx new file mode 100644 index 000000000000..3fa76b5cd977 --- /dev/null +++ b/fixtures/webstudio-features/app/routes/[text-duration]._index.tsx @@ -0,0 +1,293 @@ +import { + type MetaFunction, + type LinksFunction, + type LinkDescriptor, + type ActionFunctionArgs, + type LoaderFunctionArgs, + type HeadersFunction, + data, + redirect, + useLoaderData, +} from "react-router"; +import { + isLocalResource, + loadResource, + loadResources, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/sdk/runtime"; +import { + ReactSdkContext, + PageSettingsMeta, + PageSettingsTitle, +} from "@webstudio-is/react-sdk/runtime"; +import { + projectId, + Page, + siteName, + favIconAsset, + pageFontAssets, + pageBackgroundImageAssets, + breakpoints, +} from "../__generated__/[text-duration]._index"; +import { + getResources, + getPageMeta, + getRemixParams, + contactEmail, +} from "../__generated__/[text-duration]._index.server"; +import { assetBaseUrl, imageLoader } from "../constants.mjs"; +import css from "../__generated__/index.css?url"; +import { sitemap } from "../__generated__/$resources.sitemap.xml"; + +const customFetch: typeof fetch = (input, init) => { + if (typeof input !== "string") { + return fetch(input, init); + } + + if (isLocalResource(input, "sitemap.xml")) { + // @todo: dynamic import sitemap ??? + const response = new Response(JSON.stringify(sitemap)); + response.headers.set("content-type", "application/json; charset=utf-8"); + return Promise.resolve(response); + } + + return fetch(input, init); +}; + +export const loader = async (arg: LoaderFunctionArgs) => { + const url = new URL(arg.request.url); + const host = + arg.request.headers.get("x-forwarded-host") || + arg.request.headers.get("host") || + ""; + url.host = host; + url.protocol = "https"; + + const params = getRemixParams(arg.params); + const system = { + params, + search: Object.fromEntries(url.searchParams), + origin: url.origin, + }; + + const resources = await loadResources( + customFetch, + getResources({ system }).data + ); + const pageMeta = getPageMeta({ system, resources }); + + if (pageMeta.redirect) { + const status = + pageMeta.status === 301 || pageMeta.status === 302 + ? pageMeta.status + : 302; + throw redirect(pageMeta.redirect, status); + } + + // typecheck + arg.context.EXCLUDE_FROM_SEARCH satisfies boolean; + + if (arg.context.EXCLUDE_FROM_SEARCH) { + pageMeta.excludePageFromSearch = arg.context.EXCLUDE_FROM_SEARCH; + } + + return data( + { + host, + url: url.href, + system, + resources, + pageMeta, + }, + // No way for current information to change, so add cache for 10 minutes + // In case of CRM Data, this should be set to 0 + { + status: pageMeta.status, + headers: { + "Cache-Control": "public, max-age=600", + }, + } + ); +}; + +export const headers: HeadersFunction = () => { + return { + "Cache-Control": "public, max-age=0, must-revalidate", + }; +}; + +export const meta: MetaFunction = ({ data }) => { + const metas: ReturnType = []; + if (data === undefined) { + return metas; + } + + const origin = `https://${data.host}`; + + if (siteName) { + metas.push({ + "script:ld+json": { + "@context": "https://schema.org", + "@type": "WebSite", + name: siteName, + url: origin, + }, + }); + } + + return metas; +}; + +export const links: LinksFunction = () => { + const result: LinkDescriptor[] = []; + + result.push({ + rel: "stylesheet", + href: css, + }); + + if (favIconAsset) { + result.push({ + rel: "icon", + href: imageLoader({ + src: `${assetBaseUrl}${favIconAsset}`, + // width,height must be multiple of 48 https://developers.google.com/search/docs/appearance/favicon-in-search + width: 144, + height: 144, + fit: "pad", + quality: 100, + format: "auto", + }), + type: undefined, + }); + } + + for (const asset of pageFontAssets) { + result.push({ + rel: "preload", + href: `${assetBaseUrl}${asset}`, + as: "font", + crossOrigin: "anonymous", + }); + } + + for (const backgroundImageAsset of pageBackgroundImageAssets) { + result.push({ + rel: "preload", + href: `${assetBaseUrl}${backgroundImageAsset}`, + as: "image", + }); + } + + return result; +}; + +const getRequestHost = (request: Request): string => + request.headers.get("x-forwarded-host") || request.headers.get("host") || ""; + +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { + try { + const url = new URL(request.url); + url.host = getRequestHost(request); + + const formData = await request.formData(); + + const system = { + params: {}, + search: {}, + origin: url.origin, + }; + + const resourceName = formData.get(formIdFieldName); + let resource = + typeof resourceName === "string" + ? getResources({ system }).action.get(resourceName) + : undefined; + + const formBotValue = formData.get(formBotFieldName); + + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } + + const submitTime = parseInt(formBotValue, 16); + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + formData.delete(formIdFieldName); + formData.delete(formBotFieldName); + + if (resource) { + resource.body = Object.fromEntries(formData); + } else { + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + resource = context.getDefaultActionResource?.({ + url, + projectId, + contactEmail, + formData, + }); + } + + if (resource === undefined) { + throw Error("Resource not found"); + } + const { ok, statusText } = await loadResource(fetch, resource); + if (ok) { + return { success: true }; + } + return { success: false, errors: [statusText] }; + } catch (error) { + console.error(error); + + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; + } +}; + +const Outlet = () => { + const { system, resources, url, pageMeta, host } = + useLoaderData(); + return ( + + {/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */} + + + {pageMeta.title} + + ); +}; + +export default Outlet; diff --git a/fixtures/webstudio-features/package.json b/fixtures/webstudio-features/package.json index dcda8067d51b..04c0048fcf50 100644 --- a/fixtures/webstudio-features/package.json +++ b/fixtures/webstudio-features/package.json @@ -6,7 +6,7 @@ "dev": "react-router dev", "cli": "NODE_OPTIONS='--conditions=webstudio --import=tsx' webstudio", "fixtures:link": "pnpm cli link --link https://p-cddc1d44-af37-4cb6-a430-d300cf6f932d-dot-${BUILDER_HOST:-main.development.webstudio.is}'?authToken=1cdc6026-dd5b-4624-b89b-9bd45e9bcc3d'", - "fixtures:sync": "pnpm cli sync --buildId 3a8c8a9c-cfd9-46b8-a8bf-30d48250bc1b && pnpm prettier --write ./.webstudio/", + "fixtures:sync": "pnpm cli sync --buildId f0dfc2e7-240a-4542-ad28-a4cb68b2d9db && pnpm prettier --write ./.webstudio/", "fixtures:build": "pnpm cli build --template react-router --template ./.template && pnpm prettier --write ./app/ ./package.json ./tsconfig.json" }, "private": true, diff --git a/packages/sdk-components-animation/private-src b/packages/sdk-components-animation/private-src index 19489d0c2dbe..0ed6ac0869eb 160000 --- a/packages/sdk-components-animation/private-src +++ b/packages/sdk-components-animation/private-src @@ -1 +1 @@ -Subproject commit 19489d0c2dbe6d8596e262d1e8e4173aff407556 +Subproject commit 0ed6ac0869eb315433c6f0f6724c9f4ab19ff43a