diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 1df791830..e9978c9f8 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -58,12 +58,6 @@ module.exports = { "testing-library/prefer-screen-queries": "off", }, }, - { - files: ["**/modules/*.tsx"], - rules: { - "@typescript-eslint/no-explicit-any": "off", - }, - }, { files: ["**/*.js"], rules: { diff --git a/.github/setup-acceptance/action.yml b/.github/setup-acceptance/action.yml index 623b48983..205e6855c 100644 --- a/.github/setup-acceptance/action.yml +++ b/.github/setup-acceptance/action.yml @@ -4,9 +4,7 @@ runs: using: composite steps: - uses: ./.github/setup-ci - - run: | - npm run build -w=packages/studio-plugin - npm run build -w=packages/studio + - run: npm run build shell: bash - uses: actions/cache@v3 with: diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index ec10faa1c..ca0d1ea7a 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -2,7 +2,7 @@ name: Run Tests on: workflow_call jobs: - studio: + studio-ui: strategy: matrix: os: [windows-latest, ubuntu-latest] @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v3 - uses: ./.github/setup-ci - run: npm run build -w=packages/studio-plugin - - run: npm test -w=packages/studio -- --coverage=false + - run: npm test -w=packages/studio-ui -- --coverage=false studio_plugin: strategy: @@ -32,4 +32,6 @@ jobs: - uses: actions/checkout@v3 - uses: ./.github/setup-ci - run: npm run build + - run: npm run build-test-site -w=apps/test-site - run: npm run typecheck-jest + - run: npm run size-limit -w=packages/studio-ui diff --git a/.prettierignore b/.prettierignore index 24659b54d..ec302daa5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,7 +1,6 @@ **/coverage **/lib -packages/studio-plugin/tests/__fixtures__/PageFile/noReturnParenthesesPage.tsx -packages/studio-plugin/tests/__fixtures__/PageFile/repeaterPage.tsx +packages/studio-plugin/tests/__fixtures__/ComponentTreeParser/noReturnParentheses.tsx packages/studio-plugin/tests/__fixtures__/StudioConfigs/malformed/studio.config.js packages/studio/src/tailwind-full.css .github diff --git a/README.md b/README.md index 5b0250c2f..ea4fee036 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Now that you have a page, you probably want to do some things with it. Specifica ![enter image description here](https://yext-studio-images.s3.amazonaws.com/Screen+Shot+2023-02-02+at+9.18.10+AM.png) -These are the various Components that can be added to your page. For now, you can ignore Layouts and Modules. Those will be described later. Once you select a Component, such as `Banner`, it will appear in the middle preview pane and on the left-hand side under `Layers`: +These are the various Components that can be added to your page. For now, you can ignore Layouts which will be described later. Once you select a Component, such as `Banner`, it will appear in the middle preview pane and on the left-hand side under `Layers`: ![enter image description here](https://yext-studio-images.s3.amazonaws.com/Screen+Shot+2023-02-03+at+9.41.51+AM.png) @@ -116,26 +116,6 @@ When the new Component is added to a page, the `Properties` tab on the left-hand We've already added a few custom Components to the starter for you. One is a Banner, to prominently display an Entity's address. We also added an Entity Result Card and CTA in case you want to add Search functionality. You can use these Components as a guide when writing your own. -## Reuse through Modules - -Users may find that they use a certain combination of Components often across pages. For example, they may often pair a Search Bar Component with a Results Component. Repeating this combination over and over, for each page is tedious. That's where Studio Modules come in. A Module represents a named combination of Components. They can be added to a Page just like a single Component. - -### Creating a Module - -Creating a Module is simple and can be done entirely within Studio. No Developer intervention is needed! To start, you will need to add a Layout. This is done by clicking the same Icon you did for adding a Component: - -![enter image description here](https://yext-studio-images.s3.amazonaws.com/Screen+Shot+2023-02-02+at+12.38.01+PM.png) - -A Layout groups a set of Studio Components. Once the Layout is in place on the page, add the Components that you want in the Layout. The Components will initially be siblings of the Layout. In the `Layers` pane, you will need to drag the Components under Layout. - -![enter image description here](https://yext-studio-images.s3.amazonaws.com/Screen+Shot+2023-02-02+at+1.07.06+PM.png) - -Once the Layout has all the necessary Components, click it and you will see a `Create Module` button appear: - -![enter image description here](https://yext-studio-images.s3.amazonaws.com/Screen+Shot+2023-02-02+at+1.00.17+PM.png) - -The button will open a modal that prompts you to name your Module. The Module is then available for use on any page! The TSX file for the Module will be under `src/modules` in the repo. - ## Site Settings Often, you will want to have attributes that are accessible to all pages within a Site. In Classic Pages, these were known as Site Attributes. In the new Sites system, we usually recommend adding these attributes to a Site Entity. Studio provides an additional mechanism for supplying these values: the `src/siteSettings.ts` file. We cannot rely exclusively on a Site Entity because Studio can be used outside of PagesJS. The `siteSettings.ts` file is initially setup by the Developer: diff --git a/apps/test-site/package.json b/apps/test-site/package.json index 62b5f10b5..66f775e1a 100644 --- a/apps/test-site/package.json +++ b/apps/test-site/package.json @@ -34,7 +34,7 @@ "dev": "studio", "localData": "yext pages generate-test-data -a", "start": "craco start", - "build": "craco build" + "build-test-site": "craco build" }, "browserslist": { "production": [ diff --git a/apps/test-site/src/layouts/LocationLayout.tsx b/apps/test-site/src/layouts/LocationLayout.tsx new file mode 100644 index 000000000..7b7a51a02 --- /dev/null +++ b/apps/test-site/src/layouts/LocationLayout.tsx @@ -0,0 +1,13 @@ +import Header from "../components/Header"; + +function LocationLayout() { + return ( +
+ ); +} + +export default LocationLayout; diff --git a/apps/test-site/src/templates/UniversalPage.tsx b/apps/test-site/src/templates/UniversalPage.tsx index d007473bf..d074d0867 100644 --- a/apps/test-site/src/templates/UniversalPage.tsx +++ b/apps/test-site/src/templates/UniversalPage.tsx @@ -24,9 +24,6 @@ export default function UniversalPage({ document }: TemplateProps) { <> - {document.services.map((item, index) => ( - - ))} + ); +} diff --git a/packages/studio/src/components/AddPageButton/AddPageButton.tsx b/packages/studio-ui/src/components/AddPageButton/AddPageButton.tsx similarity index 100% rename from packages/studio/src/components/AddPageButton/AddPageButton.tsx rename to packages/studio-ui/src/components/AddPageButton/AddPageButton.tsx diff --git a/packages/studio/src/components/AddPageButton/AddPageContext.ts b/packages/studio-ui/src/components/AddPageButton/AddPageContext.ts similarity index 100% rename from packages/studio/src/components/AddPageButton/AddPageContext.ts rename to packages/studio-ui/src/components/AddPageButton/AddPageContext.ts diff --git a/packages/studio/src/components/AddPageButton/AddPageContextProvider.tsx b/packages/studio-ui/src/components/AddPageButton/AddPageContextProvider.tsx similarity index 100% rename from packages/studio/src/components/AddPageButton/AddPageContextProvider.tsx rename to packages/studio-ui/src/components/AddPageButton/AddPageContextProvider.tsx diff --git a/packages/studio/src/components/AddPageButton/BasicPageDataCollector.tsx b/packages/studio-ui/src/components/AddPageButton/BasicPageDataCollector.tsx similarity index 100% rename from packages/studio/src/components/AddPageButton/BasicPageDataCollector.tsx rename to packages/studio-ui/src/components/AddPageButton/BasicPageDataCollector.tsx diff --git a/packages/studio/src/components/AddPageButton/EntityIdField.tsx b/packages/studio-ui/src/components/AddPageButton/EntityIdField.tsx similarity index 100% rename from packages/studio/src/components/AddPageButton/EntityIdField.tsx rename to packages/studio-ui/src/components/AddPageButton/EntityIdField.tsx diff --git a/packages/studio/src/components/AddPageButton/FlowStep.ts b/packages/studio-ui/src/components/AddPageButton/FlowStep.ts similarity index 100% rename from packages/studio/src/components/AddPageButton/FlowStep.ts rename to packages/studio-ui/src/components/AddPageButton/FlowStep.ts diff --git a/packages/studio/src/components/AddPageButton/PageTypeSelector.tsx b/packages/studio-ui/src/components/AddPageButton/PageTypeSelector.tsx similarity index 100% rename from packages/studio/src/components/AddPageButton/PageTypeSelector.tsx rename to packages/studio-ui/src/components/AddPageButton/PageTypeSelector.tsx diff --git a/packages/studio/src/components/AddPageButton/StreamScopeCollector.tsx b/packages/studio-ui/src/components/AddPageButton/StreamScopeCollector.tsx similarity index 100% rename from packages/studio/src/components/AddPageButton/StreamScopeCollector.tsx rename to packages/studio-ui/src/components/AddPageButton/StreamScopeCollector.tsx diff --git a/packages/studio/src/components/ArrayPropEditor.tsx b/packages/studio-ui/src/components/ArrayPropEditor.tsx similarity index 100% rename from packages/studio/src/components/ArrayPropEditor.tsx rename to packages/studio-ui/src/components/ArrayPropEditor.tsx diff --git a/packages/studio/src/components/ComponentKindIcon.tsx b/packages/studio-ui/src/components/ComponentKindIcon.tsx similarity index 79% rename from packages/studio/src/components/ComponentKindIcon.tsx rename to packages/studio-ui/src/components/ComponentKindIcon.tsx index 764db6329..b1f23226f 100644 --- a/packages/studio/src/components/ComponentKindIcon.tsx +++ b/packages/studio-ui/src/components/ComponentKindIcon.tsx @@ -1,4 +1,3 @@ -import { ReactComponent as Hexagon } from "../icons/hexagon.svg"; import { ReactComponent as Box } from "../icons/box.svg"; import { ReactComponent as Container } from "../icons/container.svg"; import useStudioStore from "../store/useStudioStore"; @@ -19,11 +18,7 @@ export default function ComponentKindIcon( (store) => store.fileMetadatas.getComponentMetadata ); - const { kind } = componentState; - if (kind === ComponentStateKind.Module) { - return ; - } - if (kind !== ComponentStateKind.Standard) { + if (componentState.kind !== ComponentStateKind.Standard) { return null; } diff --git a/packages/studio/src/components/ComponentNode.tsx b/packages/studio-ui/src/components/ComponentNode.tsx similarity index 92% rename from packages/studio/src/components/ComponentNode.tsx rename to packages/studio-ui/src/components/ComponentNode.tsx index 62862586e..5a7c511dc 100644 --- a/packages/studio/src/components/ComponentNode.tsx +++ b/packages/studio-ui/src/components/ComponentNode.tsx @@ -1,8 +1,4 @@ -import { - ComponentState, - ComponentStateHelpers, - ComponentStateKind, -} from "@yext/studio-plugin"; +import { ComponentState, ComponentStateKind } from "@yext/studio-plugin"; import { ReactComponent as Vector } from "../icons/vector.svg"; import classNames from "classnames"; import ComponentKindIcon from "./ComponentKindIcon"; @@ -54,9 +50,8 @@ export default function ComponentNode(props: ComponentNodeProps): JSX.Element { () => ({ paddingLeft: `${depth}em` }), [depth] ); - const extractedState = - ComponentStateHelpers.extractRepeatedState(componentState); - const isErrorState = extractedState.kind === ComponentStateKind.Error; + + const isErrorState = componentState.kind === ComponentStateKind.Error; const componentNodeClasses = classNames( "flex pr-4 items-center justify-between h-9", { @@ -94,7 +89,7 @@ export default function ComponentNode(props: ComponentNodeProps): JSX.Element { {isErrorState && ( >; } @@ -35,35 +28,13 @@ export default function ComponentPreview({ expressionSources, childElements = [], setTooltipProps, - parentItem, }: ComponentPreviewProps): JSX.Element | null { - const previewProps = usePreviewProps( - componentState, - expressionSources, - parentItem - ); + const previewProps = usePreviewProps(componentState, expressionSources); const element = useElement(componentState, (type) => createElement(type, previewProps, ...childElements) ); - if (TypeGuards.isModuleState(componentState)) { - return ( - - ); - } else if (TypeGuards.isRepeaterState(componentState)) { - return ( - - ); - } else if (componentState.kind === ComponentStateKind.Error) { + if (componentState.kind === ComponentStateKind.Error) { return ( { - if (TypeGuards.isRepeaterState(c) || TypeGuards.isModuleState(c)) { - return undefined; - } else if (c.kind === ComponentStateKind.Fragment) { + if (c.kind === ComponentStateKind.Fragment) { return Fragment; } else if (c.kind === ComponentStateKind.BuiltIn) { return c.componentName; diff --git a/packages/studio/src/components/ComponentTree.tsx b/packages/studio-ui/src/components/ComponentTree.tsx similarity index 100% rename from packages/studio/src/components/ComponentTree.tsx rename to packages/studio-ui/src/components/ComponentTree.tsx diff --git a/packages/studio/src/components/ComponentTreePreview.tsx b/packages/studio-ui/src/components/ComponentTreePreview.tsx similarity index 77% rename from packages/studio/src/components/ComponentTreePreview.tsx rename to packages/studio-ui/src/components/ComponentTreePreview.tsx index 2aa6d356a..31e8d8332 100644 --- a/packages/studio/src/components/ComponentTreePreview.tsx +++ b/packages/studio-ui/src/components/ComponentTreePreview.tsx @@ -10,7 +10,6 @@ interface ComponentTreePreviewProps { componentTree: ComponentState[]; expressionSources: ExpressionSources; setTooltipProps: Dispatch>; - renderHighlightingContainer?: boolean; } /** @@ -20,13 +19,11 @@ export default function ComponentTreePreview({ componentTree, expressionSources, setTooltipProps, - renderHighlightingContainer = true, }: ComponentTreePreviewProps): JSX.Element { const elements = useComponentTreeElements( componentTree, expressionSources, - setTooltipProps, - renderHighlightingContainer + setTooltipProps ); return <>{elements}; } @@ -38,8 +35,7 @@ export default function ComponentTreePreview({ function useComponentTreeElements( componentTree: ComponentState[], expressionSources: ExpressionSources, - setTooltipProps: Dispatch>, - renderHighlightingContainer?: boolean + setTooltipProps: Dispatch> ): (JSX.Element | null)[] | null { return useMemo(() => { return ComponentTreeHelpers.mapComponentTree( @@ -53,11 +49,6 @@ function useComponentTreeElements( setTooltipProps={setTooltipProps} /> ); - if (!renderHighlightingContainer) { - return ( - {renderedComponent} - ); - } return ( {renderedComponent} @@ -65,10 +56,5 @@ function useComponentTreeElements( ); } ); - }, [ - componentTree, - expressionSources, - renderHighlightingContainer, - setTooltipProps, - ]); + }, [componentTree, expressionSources, setTooltipProps]); } diff --git a/packages/studio/src/components/DeployButton.tsx b/packages/studio-ui/src/components/DeployButton.tsx similarity index 100% rename from packages/studio/src/components/DeployButton.tsx rename to packages/studio-ui/src/components/DeployButton.tsx diff --git a/packages/studio/src/components/EditStreamScopeButton.tsx b/packages/studio-ui/src/components/EditStreamScopeButton.tsx similarity index 100% rename from packages/studio/src/components/EditStreamScopeButton.tsx rename to packages/studio-ui/src/components/EditStreamScopeButton.tsx diff --git a/packages/studio/src/components/EditStreamScopeModal.tsx b/packages/studio-ui/src/components/EditStreamScopeModal.tsx similarity index 97% rename from packages/studio/src/components/EditStreamScopeModal.tsx rename to packages/studio-ui/src/components/EditStreamScopeModal.tsx index 811239497..1ee8f3c7a 100644 --- a/packages/studio/src/components/EditStreamScopeModal.tsx +++ b/packages/studio-ui/src/components/EditStreamScopeModal.tsx @@ -5,7 +5,8 @@ import StreamScopePicker, { } from "./StreamScopePicker"; import { ResponseType, StreamScope } from "@yext/studio-plugin"; import useStudioStore from "../store/useStudioStore"; -import { isEqual, sortBy } from "lodash"; +import isEqual from "lodash/isEqual"; +import sortBy from "lodash/sortBy"; import { toast } from "react-toastify"; export interface EditStreamScopeModalProps { diff --git a/packages/studio/src/components/EditorSidebar.tsx b/packages/studio-ui/src/components/EditorSidebar.tsx similarity index 100% rename from packages/studio/src/components/EditorSidebar.tsx rename to packages/studio-ui/src/components/EditorSidebar.tsx diff --git a/packages/studio/src/components/EntityPicker.tsx b/packages/studio-ui/src/components/EntityPicker.tsx similarity index 100% rename from packages/studio/src/components/EntityPicker.tsx rename to packages/studio-ui/src/components/EntityPicker.tsx diff --git a/packages/studio/src/components/ErrorComponentPreview.tsx b/packages/studio-ui/src/components/ErrorComponentPreview.tsx similarity index 100% rename from packages/studio/src/components/ErrorComponentPreview.tsx rename to packages/studio-ui/src/components/ErrorComponentPreview.tsx diff --git a/packages/studio/src/components/FieldPicker/FieldDropdown.tsx b/packages/studio-ui/src/components/FieldPicker/FieldDropdown.tsx similarity index 98% rename from packages/studio/src/components/FieldPicker/FieldDropdown.tsx rename to packages/studio-ui/src/components/FieldPicker/FieldDropdown.tsx index a4a97db9f..101001f36 100644 --- a/packages/studio/src/components/FieldPicker/FieldDropdown.tsx +++ b/packages/studio-ui/src/components/FieldPicker/FieldDropdown.tsx @@ -1,6 +1,6 @@ import { useCallback, MouseEvent, CSSProperties } from "react"; import { ReactComponent as VectorIcon } from "../../icons/vector.svg"; -import { startCase } from "lodash"; +import startCase from "lodash/startCase"; const listStyles: CSSProperties = { minWidth: "200px", diff --git a/packages/studio/src/components/FieldPicker/FieldPicker.tsx b/packages/studio-ui/src/components/FieldPicker/FieldPicker.tsx similarity index 100% rename from packages/studio/src/components/FieldPicker/FieldPicker.tsx rename to packages/studio-ui/src/components/FieldPicker/FieldPicker.tsx diff --git a/packages/studio/src/components/FieldPicker/FieldPickerInput.tsx b/packages/studio-ui/src/components/FieldPicker/FieldPickerInput.tsx similarity index 100% rename from packages/studio/src/components/FieldPicker/FieldPickerInput.tsx rename to packages/studio-ui/src/components/FieldPicker/FieldPickerInput.tsx diff --git a/packages/studio/src/components/Highlighter.tsx b/packages/studio-ui/src/components/Highlighter.tsx similarity index 100% rename from packages/studio/src/components/Highlighter.tsx rename to packages/studio-ui/src/components/Highlighter.tsx diff --git a/packages/studio/src/components/HighlightingContainer.tsx b/packages/studio-ui/src/components/HighlightingContainer.tsx similarity index 98% rename from packages/studio/src/components/HighlightingContainer.tsx rename to packages/studio-ui/src/components/HighlightingContainer.tsx index 2dd2a2884..024019472 100644 --- a/packages/studio/src/components/HighlightingContainer.tsx +++ b/packages/studio-ui/src/components/HighlightingContainer.tsx @@ -1,4 +1,4 @@ -import { isEqual } from "lodash"; +import isEqual from "lodash/isEqual"; import { Component, PropsWithChildren, ReactInstance } from "react"; import DOMRectProperties from "../store/models/DOMRectProperties"; import useStudioStore from "../store/useStudioStore"; diff --git a/packages/studio/src/components/IFramePortal.tsx b/packages/studio-ui/src/components/IFramePortal.tsx similarity index 100% rename from packages/studio/src/components/IFramePortal.tsx rename to packages/studio-ui/src/components/IFramePortal.tsx diff --git a/packages/studio/src/components/InfoButton.tsx b/packages/studio-ui/src/components/InfoButton.tsx similarity index 100% rename from packages/studio/src/components/InfoButton.tsx rename to packages/studio-ui/src/components/InfoButton.tsx diff --git a/packages/studio/src/components/LeftSidebar.tsx b/packages/studio-ui/src/components/LeftSidebar.tsx similarity index 91% rename from packages/studio/src/components/LeftSidebar.tsx rename to packages/studio-ui/src/components/LeftSidebar.tsx index 5ec7c8d17..774772842 100644 --- a/packages/studio/src/components/LeftSidebar.tsx +++ b/packages/studio-ui/src/components/LeftSidebar.tsx @@ -7,8 +7,8 @@ import ActivePagePanel from "./ActivePagePanel"; * Renders the left sidebar of Studio, which lists all pages, indicates which * page is active, and displays the component tree for that active page. * - * Allows the user to change which page is active and to rearrange the components and - * modules in the component tree of the active page. + * Allows the user to change which page is active and to rearrange the components + * in the component tree of the active page. */ export default function LeftSidebar(): JSX.Element { return ( diff --git a/packages/studio/src/components/NumberPropInput.tsx b/packages/studio-ui/src/components/NumberPropInput.tsx similarity index 100% rename from packages/studio/src/components/NumberPropInput.tsx rename to packages/studio-ui/src/components/NumberPropInput.tsx diff --git a/packages/studio/src/components/ObjectPropEditor.tsx b/packages/studio-ui/src/components/ObjectPropEditor.tsx similarity index 100% rename from packages/studio/src/components/ObjectPropEditor.tsx rename to packages/studio-ui/src/components/ObjectPropEditor.tsx diff --git a/packages/studio/src/components/OpenLivePreviewButton.tsx b/packages/studio-ui/src/components/OpenLivePreviewButton.tsx similarity index 100% rename from packages/studio/src/components/OpenLivePreviewButton.tsx rename to packages/studio-ui/src/components/OpenLivePreviewButton.tsx diff --git a/packages/studio/src/components/PageSettingsButton/PageSettingsButton.tsx b/packages/studio-ui/src/components/PageSettingsButton/PageSettingsButton.tsx similarity index 100% rename from packages/studio/src/components/PageSettingsButton/PageSettingsButton.tsx rename to packages/studio-ui/src/components/PageSettingsButton/PageSettingsButton.tsx diff --git a/packages/studio/src/components/PageSettingsButton/PageSettingsModal.tsx b/packages/studio-ui/src/components/PageSettingsButton/PageSettingsModal.tsx similarity index 100% rename from packages/studio/src/components/PageSettingsButton/PageSettingsModal.tsx rename to packages/studio-ui/src/components/PageSettingsButton/PageSettingsModal.tsx diff --git a/packages/studio/src/components/PillPicker/PillPicker.tsx b/packages/studio-ui/src/components/PillPicker/PillPicker.tsx similarity index 97% rename from packages/studio/src/components/PillPicker/PillPicker.tsx rename to packages/studio-ui/src/components/PillPicker/PillPicker.tsx index 8020aceae..4af1da992 100644 --- a/packages/studio/src/components/PillPicker/PillPicker.tsx +++ b/packages/studio-ui/src/components/PillPicker/PillPicker.tsx @@ -1,6 +1,6 @@ import { useCallback, useRef, useState } from "react"; import { ReactComponent as EmbedIcon } from "../../icons/embed.svg"; -import { useRootClose } from "@restart/ui"; +import useRootClose from "@restart/ui/useRootClose"; import classNames from "classnames"; interface PillPickerProps { diff --git a/packages/studio/src/components/PillPicker/PillPickerInput.tsx b/packages/studio-ui/src/components/PillPicker/PillPickerInput.tsx similarity index 100% rename from packages/studio/src/components/PillPicker/PillPickerInput.tsx rename to packages/studio-ui/src/components/PillPicker/PillPickerInput.tsx diff --git a/packages/studio-ui/src/components/PreviewPanel.tsx b/packages/studio-ui/src/components/PreviewPanel.tsx new file mode 100644 index 000000000..2d782f12d --- /dev/null +++ b/packages/studio-ui/src/components/PreviewPanel.tsx @@ -0,0 +1,43 @@ +import { Dispatch, SetStateAction, useMemo } from "react"; +import useStudioStore from "../store/useStudioStore"; +import ComponentTreePreview from "./ComponentTreePreview"; +import useRawSiteSettings from "../hooks/useRawSiteSettings"; +import { ITooltip } from "react-tooltip"; + +export default function PreviewPanel(props: { + setTooltipProps: Dispatch>; +}) { + const { setTooltipProps } = props; + const componentTree = useStudioStore((store) => + store.actions.getComponentTree() + ); + + const pageExpressionSources = usePageExpressionSources(); + if (!componentTree) { + return null; + } + + return ( + + ); +} + +function usePageExpressionSources() { + const activeEntityData = useStudioStore((store) => + store.pages.getActiveEntityData() + ); + const rawSiteSettings = useRawSiteSettings(); + const pageExpressionSources = useMemo( + () => ({ + document: activeEntityData, + siteSettings: rawSiteSettings, + }), + [activeEntityData, rawSiteSettings] + ); + + return pageExpressionSources; +} diff --git a/packages/studio/src/components/PreviewWithUseComponents.tsx b/packages/studio-ui/src/components/PreviewWithUseComponents.tsx similarity index 100% rename from packages/studio/src/components/PreviewWithUseComponents.tsx rename to packages/studio-ui/src/components/PreviewWithUseComponents.tsx diff --git a/packages/studio/src/components/PropEditor.tsx b/packages/studio-ui/src/components/PropEditor.tsx similarity index 96% rename from packages/studio/src/components/PropEditor.tsx rename to packages/studio-ui/src/components/PropEditor.tsx index 8c723f088..c2bfbebe9 100644 --- a/packages/studio/src/components/PropEditor.tsx +++ b/packages/studio-ui/src/components/PropEditor.tsx @@ -25,7 +25,7 @@ interface PropEditorProps { const tooltipStyle = { backgroundColor: "black" }; /** - * Renders an input editor for a single prop of a component or module. + * Renders an input editor for a single prop of a component. */ export default function PropEditor({ propName, diff --git a/packages/studio/src/components/PropEditors.tsx b/packages/studio-ui/src/components/PropEditors.tsx similarity index 100% rename from packages/studio/src/components/PropEditors.tsx rename to packages/studio-ui/src/components/PropEditors.tsx diff --git a/packages/studio/src/components/PropInput.tsx b/packages/studio-ui/src/components/PropInput.tsx similarity index 100% rename from packages/studio/src/components/PropInput.tsx rename to packages/studio-ui/src/components/PropInput.tsx diff --git a/packages/studio-ui/src/components/PropsPanel.tsx b/packages/studio-ui/src/components/PropsPanel.tsx new file mode 100644 index 000000000..0cfbd036b --- /dev/null +++ b/packages/studio-ui/src/components/PropsPanel.tsx @@ -0,0 +1,20 @@ +import useActiveComponentWithProps from "../hooks/useActiveComponentWithProps"; +import ActiveComponentPropEditors from "./ActiveComponentPropEditors"; + +/** + * Renders prop editors for the active component selected by the user. + */ +export default function PropsPanel(): JSX.Element | null { + const activeComponentWithProps = useActiveComponentWithProps(); + if (!activeComponentWithProps) { + return null; + } + const { activeComponentState, propShape } = activeComponentWithProps; + + return ( + + ); +} diff --git a/packages/studio/src/components/RemovableElement.tsx b/packages/studio-ui/src/components/RemovableElement.tsx similarity index 100% rename from packages/studio/src/components/RemovableElement.tsx rename to packages/studio-ui/src/components/RemovableElement.tsx diff --git a/packages/studio/src/components/RemoveElementButton.tsx b/packages/studio-ui/src/components/RemoveElementButton.tsx similarity index 100% rename from packages/studio/src/components/RemoveElementButton.tsx rename to packages/studio-ui/src/components/RemoveElementButton.tsx diff --git a/packages/studio/src/components/RemovePageButton.tsx b/packages/studio-ui/src/components/RemovePageButton.tsx similarity index 100% rename from packages/studio/src/components/RemovePageButton.tsx rename to packages/studio-ui/src/components/RemovePageButton.tsx diff --git a/packages/studio/src/components/SaveButton.tsx b/packages/studio-ui/src/components/SaveButton.tsx similarity index 100% rename from packages/studio/src/components/SaveButton.tsx rename to packages/studio-ui/src/components/SaveButton.tsx diff --git a/packages/studio/src/components/SiteSettingsPanel.tsx b/packages/studio-ui/src/components/SiteSettingsPanel.tsx similarity index 99% rename from packages/studio/src/components/SiteSettingsPanel.tsx rename to packages/studio-ui/src/components/SiteSettingsPanel.tsx index 02133c554..e2725c159 100644 --- a/packages/studio/src/components/SiteSettingsPanel.tsx +++ b/packages/studio-ui/src/components/SiteSettingsPanel.tsx @@ -11,7 +11,7 @@ import { SiteSettingsVal, } from "@yext/studio-plugin"; import React, { useCallback } from "react"; -import { startCase } from "lodash"; +import startCase from "lodash/startCase"; import useStudioStore from "../store/useStudioStore"; import PropInput from "./PropInput"; diff --git a/packages/studio/src/components/StreamScopePicker.tsx b/packages/studio-ui/src/components/StreamScopePicker.tsx similarity index 100% rename from packages/studio/src/components/StreamScopePicker.tsx rename to packages/studio-ui/src/components/StreamScopePicker.tsx diff --git a/packages/studio/src/components/StringPropInput.tsx b/packages/studio-ui/src/components/StringPropInput.tsx similarity index 100% rename from packages/studio/src/components/StringPropInput.tsx rename to packages/studio-ui/src/components/StringPropInput.tsx diff --git a/packages/studio/src/components/TailwindPropInput.tsx b/packages/studio-ui/src/components/TailwindPropInput.tsx similarity index 95% rename from packages/studio/src/components/TailwindPropInput.tsx rename to packages/studio-ui/src/components/TailwindPropInput.tsx index a4fb6075d..97a1ecae0 100644 --- a/packages/studio/src/components/TailwindPropInput.tsx +++ b/packages/studio-ui/src/components/TailwindPropInput.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useState } from "react"; -import generateTailwindSafelist from "../utils/generateTailwindSafelist"; +import { generateTailwindSafelist } from "@yext/studio-plugin"; import { StudioTailwindTheme } from "@yext/studio-plugin"; import PillPickerInput from "./PillPicker/PillPickerInput"; diff --git a/packages/studio/src/components/Toast.tsx b/packages/studio-ui/src/components/Toast.tsx similarity index 100% rename from packages/studio/src/components/Toast.tsx rename to packages/studio-ui/src/components/Toast.tsx diff --git a/packages/studio/src/components/UndefinedMenuButton.tsx b/packages/studio-ui/src/components/UndefinedMenuButton.tsx similarity index 100% rename from packages/studio/src/components/UndefinedMenuButton.tsx rename to packages/studio-ui/src/components/UndefinedMenuButton.tsx diff --git a/packages/studio/src/components/UndoRedo.tsx b/packages/studio-ui/src/components/UndoRedo.tsx similarity index 100% rename from packages/studio/src/components/UndoRedo.tsx rename to packages/studio-ui/src/components/UndoRedo.tsx diff --git a/packages/studio/src/components/UnionPropInput.tsx b/packages/studio-ui/src/components/UnionPropInput.tsx similarity index 100% rename from packages/studio/src/components/UnionPropInput.tsx rename to packages/studio-ui/src/components/UnionPropInput.tsx diff --git a/packages/studio/src/components/Viewport/ViewportButton.tsx b/packages/studio-ui/src/components/Viewport/ViewportButton.tsx similarity index 100% rename from packages/studio/src/components/Viewport/ViewportButton.tsx rename to packages/studio-ui/src/components/Viewport/ViewportButton.tsx diff --git a/packages/studio/src/components/Viewport/ViewportMenu.tsx b/packages/studio-ui/src/components/Viewport/ViewportMenu.tsx similarity index 100% rename from packages/studio/src/components/Viewport/ViewportMenu.tsx rename to packages/studio-ui/src/components/Viewport/ViewportMenu.tsx diff --git a/packages/studio/src/components/Viewport/defaults.tsx b/packages/studio-ui/src/components/Viewport/defaults.tsx similarity index 100% rename from packages/studio/src/components/Viewport/defaults.tsx rename to packages/studio-ui/src/components/Viewport/defaults.tsx diff --git a/packages/studio/src/components/common/ButtonWithModal.tsx b/packages/studio-ui/src/components/common/ButtonWithModal.tsx similarity index 100% rename from packages/studio/src/components/common/ButtonWithModal.tsx rename to packages/studio-ui/src/components/common/ButtonWithModal.tsx diff --git a/packages/studio/src/components/common/ColorPicker.tsx b/packages/studio-ui/src/components/common/ColorPicker.tsx similarity index 97% rename from packages/studio/src/components/common/ColorPicker.tsx rename to packages/studio-ui/src/components/common/ColorPicker.tsx index 09f44fbb8..0e25a6ce8 100644 --- a/packages/studio/src/components/common/ColorPicker.tsx +++ b/packages/studio-ui/src/components/common/ColorPicker.tsx @@ -1,5 +1,5 @@ import { ChangeEvent, useEffect, useMemo, useState } from "react"; -import { debounce } from "lodash"; +import debounce from "lodash/debounce"; import PropValueHelpers from "../../utils/PropValueHelpers"; import { PropValueKind, PropValueType } from "@yext/studio-plugin"; diff --git a/packages/studio/src/components/common/DialogModal.tsx b/packages/studio-ui/src/components/common/DialogModal.tsx similarity index 96% rename from packages/studio/src/components/common/DialogModal.tsx rename to packages/studio-ui/src/components/common/DialogModal.tsx index eabd54489..6beb7b50b 100644 --- a/packages/studio/src/components/common/DialogModal.tsx +++ b/packages/studio-ui/src/components/common/DialogModal.tsx @@ -37,7 +37,7 @@ export default function DialogModal({ } ); - const moduleBodyContent = useMemo(() => { + const modalBodyContent = useMemo(() => { return ( <>
@@ -83,7 +83,7 @@ export default function DialogModal({ return ( diff --git a/packages/studio/src/components/common/Divider.tsx b/packages/studio-ui/src/components/common/Divider.tsx similarity index 100% rename from packages/studio/src/components/common/Divider.tsx rename to packages/studio-ui/src/components/common/Divider.tsx diff --git a/packages/studio/src/components/common/ErrorBoundary.tsx b/packages/studio-ui/src/components/common/ErrorBoundary.tsx similarity index 100% rename from packages/studio/src/components/common/ErrorBoundary.tsx rename to packages/studio-ui/src/components/common/ErrorBoundary.tsx diff --git a/packages/studio/src/components/common/FormModal.tsx b/packages/studio-ui/src/components/common/FormModal.tsx similarity index 100% rename from packages/studio/src/components/common/FormModal.tsx rename to packages/studio-ui/src/components/common/FormModal.tsx diff --git a/packages/studio/src/components/common/MessageBubble.tsx b/packages/studio-ui/src/components/common/MessageBubble.tsx similarity index 100% rename from packages/studio/src/components/common/MessageBubble.tsx rename to packages/studio-ui/src/components/common/MessageBubble.tsx diff --git a/packages/studio/src/components/common/Modal.tsx b/packages/studio-ui/src/components/common/Modal.tsx similarity index 100% rename from packages/studio/src/components/common/Modal.tsx rename to packages/studio-ui/src/components/common/Modal.tsx diff --git a/packages/studio/src/components/common/OptionPicker.tsx b/packages/studio-ui/src/components/common/OptionPicker.tsx similarity index 100% rename from packages/studio/src/components/common/OptionPicker.tsx rename to packages/studio-ui/src/components/common/OptionPicker.tsx diff --git a/packages/studio/src/components/common/StreamScopeField.tsx b/packages/studio-ui/src/components/common/StreamScopeField.tsx similarity index 100% rename from packages/studio/src/components/common/StreamScopeField.tsx rename to packages/studio-ui/src/components/common/StreamScopeField.tsx diff --git a/packages/studio/src/components/common/StreamScopeFieldLabel.tsx b/packages/studio-ui/src/components/common/StreamScopeFieldLabel.tsx similarity index 100% rename from packages/studio/src/components/common/StreamScopeFieldLabel.tsx rename to packages/studio-ui/src/components/common/StreamScopeFieldLabel.tsx diff --git a/packages/studio/src/components/common/Toggle.tsx b/packages/studio-ui/src/components/common/Toggle.tsx similarity index 100% rename from packages/studio/src/components/common/Toggle.tsx rename to packages/studio-ui/src/components/common/Toggle.tsx diff --git a/packages/studio/src/components/common/renderIconForType.tsx b/packages/studio-ui/src/components/common/renderIconForType.tsx similarity index 81% rename from packages/studio/src/components/common/renderIconForType.tsx rename to packages/studio-ui/src/components/common/renderIconForType.tsx index 8f5c74b54..9452add93 100644 --- a/packages/studio/src/components/common/renderIconForType.tsx +++ b/packages/studio-ui/src/components/common/renderIconForType.tsx @@ -1,4 +1,3 @@ -import { ReactComponent as Hexagon } from "../../icons/hexagon.svg"; import { ReactComponent as Box } from "../../icons/box.svg"; import { ReactComponent as Container } from "../../icons/container.svg"; import { ElementType } from "../AddElementMenu/AddElementMenu"; @@ -11,9 +10,8 @@ export default function renderIconForType(type: ElementType) { case ElementType.Components: return ; case ElementType.Containers: + case ElementType.Layouts: return ; - case ElementType.Modules: - return ; default: console.error(`Could not find Icon for type ${type}`); return null; diff --git a/packages/studio/src/index.d.ts b/packages/studio-ui/src/global.d.ts similarity index 100% rename from packages/studio/src/index.d.ts rename to packages/studio-ui/src/global.d.ts diff --git a/packages/studio/src/hooks/useActiveComponent.tsx b/packages/studio-ui/src/hooks/useActiveComponent.tsx similarity index 70% rename from packages/studio/src/hooks/useActiveComponent.tsx rename to packages/studio-ui/src/hooks/useActiveComponent.tsx index 5a3dcdb0a..b25e81af8 100644 --- a/packages/studio/src/hooks/useActiveComponent.tsx +++ b/packages/studio-ui/src/hooks/useActiveComponent.tsx @@ -2,7 +2,6 @@ import { ComponentState, ComponentStateKind, FileMetadata, - TypeGuards, } from "@yext/studio-plugin"; import useStudioStore from "../store/useStudioStore"; @@ -16,13 +15,8 @@ export default function useActiveComponent(): { return useStudioStore((store) => { const activeComponentState = store.actions.getActiveComponentState(); const activeComponentMetadata = - activeComponentState && - TypeGuards.isStandardOrModuleComponentState(activeComponentState) + activeComponentState?.kind === ComponentStateKind.Standard ? store.fileMetadatas.getFileMetadata(activeComponentState.metadataUUID) - : activeComponentState?.kind === ComponentStateKind.Repeater - ? store.fileMetadatas.getFileMetadata( - activeComponentState.repeatedComponent.metadataUUID - ) : undefined; return { activeComponentMetadata, diff --git a/packages/studio/src/hooks/useActiveComponentName.tsx b/packages/studio-ui/src/hooks/useActiveComponentName.tsx similarity index 72% rename from packages/studio/src/hooks/useActiveComponentName.tsx rename to packages/studio-ui/src/hooks/useActiveComponentName.tsx index c25a30f6e..4b58d3824 100644 --- a/packages/studio/src/hooks/useActiveComponentName.tsx +++ b/packages/studio-ui/src/hooks/useActiveComponentName.tsx @@ -1,8 +1,4 @@ -import { - ComponentState, - ComponentStateKind, - TypeGuards, -} from "@yext/studio-plugin"; +import { ComponentState, ComponentStateKind } from "@yext/studio-plugin"; import useStudioStore from "../store/useStudioStore"; export default function useActiveComponentName(): string | undefined { @@ -19,8 +15,6 @@ export default function useActiveComponentName(): string | undefined { export function getComponentDisplayName(componentState: ComponentState) { if (componentState.kind === ComponentStateKind.Fragment) { return "Fragment"; - } else if (TypeGuards.isRepeaterState(componentState)) { - return `List (${componentState.repeatedComponent.componentName})`; } else { return componentState.componentName; } diff --git a/packages/studio/src/hooks/useActiveComponentWithProps.tsx b/packages/studio-ui/src/hooks/useActiveComponentWithProps.tsx similarity index 54% rename from packages/studio/src/hooks/useActiveComponentWithProps.tsx rename to packages/studio-ui/src/hooks/useActiveComponentWithProps.tsx index 3158e89b6..f1d07c945 100644 --- a/packages/studio/src/hooks/useActiveComponentWithProps.tsx +++ b/packages/studio-ui/src/hooks/useActiveComponentWithProps.tsx @@ -1,12 +1,17 @@ import { - ComponentStateHelpers, ComponentStateKind, + FileMetadata, FileMetadataKind, - TypeGuards, + PropShape, + StandardComponentState, } from "@yext/studio-plugin"; import useActiveComponent from "./useActiveComponent"; -export default function useActiveComponentWithProps() { +export default function useActiveComponentWithProps(): { + activeComponentMetadata: FileMetadata; + propShape: PropShape; + activeComponentState: StandardComponentState; +} | null { const { activeComponentMetadata, activeComponentState } = useActiveComponent(); @@ -17,17 +22,7 @@ export default function useActiveComponentWithProps() { return null; } - if ( - !activeComponentState || - !TypeGuards.isEditableComponentState(activeComponentState) - ) { - return null; - } - - const extractedComponentState = - ComponentStateHelpers.extractRepeatedState(activeComponentState); - - if (extractedComponentState.kind === ComponentStateKind.Error) { + if (activeComponentState?.kind !== ComponentStateKind.Standard) { return null; } @@ -35,6 +30,5 @@ export default function useActiveComponentWithProps() { activeComponentMetadata, propShape: activeComponentMetadata.propShape, activeComponentState, - extractedComponentState, }; } diff --git a/packages/studio/src/hooks/useComposedCssClasses.tsx b/packages/studio-ui/src/hooks/useComposedCssClasses.tsx similarity index 100% rename from packages/studio/src/hooks/useComposedCssClasses.tsx rename to packages/studio-ui/src/hooks/useComposedCssClasses.tsx diff --git a/packages/studio/src/hooks/useFuncWithZundoBatching.tsx b/packages/studio-ui/src/hooks/useFuncWithZundoBatching.tsx similarity index 95% rename from packages/studio/src/hooks/useFuncWithZundoBatching.tsx rename to packages/studio-ui/src/hooks/useFuncWithZundoBatching.tsx index f4bb997f2..40bc8e7ac 100644 --- a/packages/studio/src/hooks/useFuncWithZundoBatching.tsx +++ b/packages/studio-ui/src/hooks/useFuncWithZundoBatching.tsx @@ -1,6 +1,6 @@ import { useCallback, useMemo } from "react"; import useTemporalStore from "../store/useTemporalStore"; -import { debounce } from "lodash"; +import debounce from "lodash/debounce"; /** * Updates a function so it doesn't trigger Zundo store updates until after a diff --git a/packages/studio/src/hooks/useHasChanges.ts b/packages/studio-ui/src/hooks/useHasChanges.ts similarity index 62% rename from packages/studio/src/hooks/useHasChanges.ts rename to packages/studio-ui/src/hooks/useHasChanges.ts index 0cd7c3ac6..22811178b 100644 --- a/packages/studio/src/hooks/useHasChanges.ts +++ b/packages/studio-ui/src/hooks/useHasChanges.ts @@ -1,5 +1,5 @@ import useStudioStore from "../store/useStudioStore"; -import { isEqual } from "lodash"; +import isEqual from "lodash/isEqual"; export default function useHasChanges() { // TODO(SLAP-2556) Refactor pendingChanges to use PreviousSaveSlice @@ -7,9 +7,7 @@ export default function useHasChanges() { store.pages.pendingChanges.pagesToRemove, store.pages.pendingChanges.pagesToUpdate, ]); - const UUIDToFileMetadata = useStudioStore( - (store) => store.fileMetadatas.UUIDToFileMetadata - ); + const previousSave = useStudioStore((store) => store.previousSave); const siteSettingsValues = useStudioStore( (store) => store.siteSettings.values @@ -19,15 +17,8 @@ export default function useHasChanges() { previousSave.siteSettings.values, siteSettingsValues ); - const hasFileMetadataChanges = !isEqual( - previousSave.fileMetadatas.UUIDToFileMetadata, - UUIDToFileMetadata - ); return ( - pagesToRemove.size > 0 || - pagesToUpdate.size > 0 || - siteSettingsHaveChanged || - hasFileMetadataChanges + pagesToRemove.size > 0 || pagesToUpdate.size > 0 || siteSettingsHaveChanged ); } diff --git a/packages/studio/src/hooks/useImportedComponents.tsx b/packages/studio-ui/src/hooks/useImportedComponents.tsx similarity index 80% rename from packages/studio/src/hooks/useImportedComponents.tsx rename to packages/studio-ui/src/hooks/useImportedComponents.tsx index 03ab78467..a2d597c2f 100644 --- a/packages/studio/src/hooks/useImportedComponents.tsx +++ b/packages/studio-ui/src/hooks/useImportedComponents.tsx @@ -3,8 +3,8 @@ import useStudioStore from "../store/useStudioStore"; import { ComponentState } from "@yext/studio-plugin"; /** - * Load all functional component methods correspond to the components - * and modules use in the provided page state's component tree. + * Load all functional component methods corresponding to the components + * used in the provided page state's component tree. */ export default async function useImportedComponents( componentTree?: ComponentState[] diff --git a/packages/studio/src/hooks/useOnPropChange.tsx b/packages/studio-ui/src/hooks/useOnPropChange.tsx similarity index 100% rename from packages/studio/src/hooks/useOnPropChange.tsx rename to packages/studio-ui/src/hooks/useOnPropChange.tsx diff --git a/packages/studio/src/hooks/usePreviewProps.tsx b/packages/studio-ui/src/hooks/usePreviewProps.tsx similarity index 73% rename from packages/studio/src/hooks/usePreviewProps.tsx rename to packages/studio-ui/src/hooks/usePreviewProps.tsx index caa18f01b..855aef8e1 100644 --- a/packages/studio/src/hooks/usePreviewProps.tsx +++ b/packages/studio-ui/src/hooks/usePreviewProps.tsx @@ -1,5 +1,4 @@ import { - TypeGuards, ComponentState, FileMetadataKind, ComponentStateKind, @@ -13,12 +12,11 @@ import { /** * Gets the previewable props for the specified component, with any expressions - * hydrated from the expression sources and parent item. + * hydrated from the expression sources. */ export default function usePreviewProps( c: ComponentState | undefined, - expressionSources: ExpressionSources, - parentItem?: unknown + expressionSources: ExpressionSources ): Record { const getFileMetadata = useStudioStore( (store) => store.fileMetadatas.getFileMetadata @@ -31,12 +29,12 @@ export default function usePreviewProps( }, {}); } - if (!c || !TypeGuards.isStandardOrModuleComponentState(c)) { + if (c?.kind !== ComponentStateKind.Standard) { return {}; } const fileMetadata = getFileMetadata(c.metadataUUID); - if (!fileMetadata || fileMetadata.kind === FileMetadataKind.Error) { + if (fileMetadata?.kind === FileMetadataKind.Error) { throw new Error( `Cannot get propShape for FileMetadata of ${c.componentName}` ); @@ -44,7 +42,6 @@ export default function usePreviewProps( return getPropsForPreview(c.props, fileMetadata?.propShape ?? {}, { ...expressionSources, - ...(parentItem !== undefined && { item: parentItem }), }); - }, [c, expressionSources, parentItem, getFileMetadata]); + }, [c, expressionSources, getFileMetadata]); } diff --git a/packages/studio/src/hooks/useRawSiteSettings.tsx b/packages/studio-ui/src/hooks/useRawSiteSettings.tsx similarity index 100% rename from packages/studio/src/hooks/useRawSiteSettings.tsx rename to packages/studio-ui/src/hooks/useRawSiteSettings.tsx diff --git a/packages/studio/src/icons/addcomponent.svg b/packages/studio-ui/src/icons/addcomponent.svg similarity index 100% rename from packages/studio/src/icons/addcomponent.svg rename to packages/studio-ui/src/icons/addcomponent.svg diff --git a/packages/studio/src/icons/box.svg b/packages/studio-ui/src/icons/box.svg similarity index 100% rename from packages/studio/src/icons/box.svg rename to packages/studio-ui/src/icons/box.svg diff --git a/packages/studio/src/icons/check.svg b/packages/studio-ui/src/icons/check.svg similarity index 100% rename from packages/studio/src/icons/check.svg rename to packages/studio-ui/src/icons/check.svg diff --git a/packages/studio/src/icons/container.svg b/packages/studio-ui/src/icons/container.svg similarity index 100% rename from packages/studio/src/icons/container.svg rename to packages/studio-ui/src/icons/container.svg diff --git a/packages/studio/src/icons/content.svg b/packages/studio-ui/src/icons/content.svg similarity index 100% rename from packages/studio/src/icons/content.svg rename to packages/studio-ui/src/icons/content.svg diff --git a/packages/studio/src/icons/deletemodule.svg b/packages/studio-ui/src/icons/deletemodule.svg similarity index 100% rename from packages/studio/src/icons/deletemodule.svg rename to packages/studio-ui/src/icons/deletemodule.svg diff --git a/packages/studio/src/icons/detachmodule.svg b/packages/studio-ui/src/icons/detachmodule.svg similarity index 100% rename from packages/studio/src/icons/detachmodule.svg rename to packages/studio-ui/src/icons/detachmodule.svg diff --git a/packages/studio/src/icons/editmodule.svg b/packages/studio-ui/src/icons/editmodule.svg similarity index 100% rename from packages/studio/src/icons/editmodule.svg rename to packages/studio-ui/src/icons/editmodule.svg diff --git a/packages/studio/src/icons/ellipses.svg b/packages/studio-ui/src/icons/ellipses.svg similarity index 100% rename from packages/studio/src/icons/ellipses.svg rename to packages/studio-ui/src/icons/ellipses.svg diff --git a/packages/studio/src/icons/embed.svg b/packages/studio-ui/src/icons/embed.svg similarity index 100% rename from packages/studio/src/icons/embed.svg rename to packages/studio-ui/src/icons/embed.svg diff --git a/packages/studio/src/icons/gear.svg b/packages/studio-ui/src/icons/gear.svg similarity index 100% rename from packages/studio/src/icons/gear.svg rename to packages/studio-ui/src/icons/gear.svg diff --git a/packages/studio/src/icons/globe.svg b/packages/studio-ui/src/icons/globe.svg similarity index 100% rename from packages/studio/src/icons/globe.svg rename to packages/studio-ui/src/icons/globe.svg diff --git a/packages/studio/src/icons/info.svg b/packages/studio-ui/src/icons/info.svg similarity index 100% rename from packages/studio/src/icons/info.svg rename to packages/studio-ui/src/icons/info.svg diff --git a/packages/studio/src/icons/plus.svg b/packages/studio-ui/src/icons/plus.svg similarity index 100% rename from packages/studio/src/icons/plus.svg rename to packages/studio-ui/src/icons/plus.svg diff --git a/packages/studio/src/icons/scope.svg b/packages/studio-ui/src/icons/scope.svg similarity index 100% rename from packages/studio/src/icons/scope.svg rename to packages/studio-ui/src/icons/scope.svg diff --git a/packages/studio/src/icons/sliders.svg b/packages/studio-ui/src/icons/sliders.svg similarity index 100% rename from packages/studio/src/icons/sliders.svg rename to packages/studio-ui/src/icons/sliders.svg diff --git a/packages/studio/src/icons/undo.svg b/packages/studio-ui/src/icons/undo.svg similarity index 100% rename from packages/studio/src/icons/undo.svg rename to packages/studio-ui/src/icons/undo.svg diff --git a/packages/studio/src/icons/vector.svg b/packages/studio-ui/src/icons/vector.svg similarity index 100% rename from packages/studio/src/icons/vector.svg rename to packages/studio-ui/src/icons/vector.svg diff --git a/packages/studio/src/icons/viewport.svg b/packages/studio-ui/src/icons/viewport.svg similarity index 100% rename from packages/studio/src/icons/viewport.svg rename to packages/studio-ui/src/icons/viewport.svg diff --git a/packages/studio/src/icons/x.svg b/packages/studio-ui/src/icons/x.svg similarity index 100% rename from packages/studio/src/icons/x.svg rename to packages/studio-ui/src/icons/x.svg diff --git a/packages/studio/src/icons/yextfavicon.svg b/packages/studio-ui/src/icons/yextfavicon.svg similarity index 100% rename from packages/studio/src/icons/yextfavicon.svg rename to packages/studio-ui/src/icons/yextfavicon.svg diff --git a/packages/studio-ui/src/index.ts b/packages/studio-ui/src/index.ts new file mode 100644 index 000000000..3fe76c509 --- /dev/null +++ b/packages/studio-ui/src/index.ts @@ -0,0 +1,3 @@ +export { default as App } from "./App"; +export { default as hotReloadStore } from "./store/hotReloadStore"; +export { StudioHMRUpdateID } from "@yext/studio-plugin"; diff --git a/packages/studio/src/messaging/sendMessage.ts b/packages/studio-ui/src/messaging/sendMessage.ts similarity index 100% rename from packages/studio/src/messaging/sendMessage.ts rename to packages/studio-ui/src/messaging/sendMessage.ts diff --git a/packages/studio/src/store/StudioActions.ts b/packages/studio-ui/src/store/StudioActions.ts similarity index 74% rename from packages/studio/src/store/StudioActions.ts rename to packages/studio-ui/src/store/StudioActions.ts index 1e2623593..11f832796 100644 --- a/packages/studio/src/store/StudioActions.ts +++ b/packages/studio-ui/src/store/StudioActions.ts @@ -2,17 +2,14 @@ import { ComponentState, ComponentStateKind, ComponentTreeHelpers, + LayoutState, MessageID, - ModuleMetadata, - ModuleState, PropValues, - TypeGuards, } from "@yext/studio-plugin"; import FileMetadataSlice from "./models/slices/FileMetadataSlice"; import PageSlice from "./models/slices/PageSlice"; -import { v4 } from "uuid"; import sendMessage from "../messaging/sendMessage"; -import { cloneDeep } from "lodash"; +import cloneDeep from "lodash/cloneDeep"; import SiteSettingsSlice from "./models/slices/SiteSettingsSlice"; import PreviousSaveSlice from "./models/slices/PreviousSaveSlice"; import StudioConfigSlice from "./models/slices/StudioConfigSlice"; @@ -24,6 +21,7 @@ import GenerateTestDataAction from "./StudioActions/GenerateTestDataAction"; import CreatePageAction from "./StudioActions/CreatePageAction"; import dynamicImportFromBrowser from "../utils/dynamicImportFromBrowser"; import path from "path-browserify"; +import { v4 } from "uuid"; export default class StudioActions { public addComponent: AddComponentAction["addComponent"]; @@ -62,10 +60,7 @@ export default class StudioActions { } getComponentTree = () => { - const moduleMetadataBeingEdited = this.getModuleMetadataBeingEdited(); - return moduleMetadataBeingEdited - ? moduleMetadataBeingEdited.componentTree - : this.getPages().getActivePageState()?.componentTree; + return this.getPages().getActivePageState()?.componentTree; }; getComponentState = (componentTree?: ComponentState[], uuid?: string) => { @@ -90,18 +85,6 @@ export default class StudioActions { ); }; - getModuleMetadataBeingEdited = () => { - const { moduleUUIDBeingEdited, getActivePageState } = this.getPages(); - const state = this.getComponentState( - getActivePageState()?.componentTree, - moduleUUIDBeingEdited - ); - if (!state || !TypeGuards.isModuleState(state)) { - return undefined; - } - return this.getFileMetadatas().getModuleMetadata(state.metadataUUID); - }; - updateActiveComponentProps = (props: PropValues) => { const activeComponentState = this.getActiveComponentState(); if (!activeComponentState) { @@ -119,17 +102,7 @@ export default class StudioActions { ); } - const updatedComponentState = TypeGuards.isRepeaterState( - activeComponentState - ) - ? { - ...activeComponentState, - repeatedComponent: { - ...activeComponentState.repeatedComponent, - props, - }, - } - : { ...activeComponentState, props }; + const updatedComponentState = { ...activeComponentState, props }; this.replaceComponentState( activeComponentState.uuid, @@ -138,15 +111,8 @@ export default class StudioActions { }; updateComponentTree = (componentTree: ComponentState[]) => { - const moduleMetadataBeingEdited = this.getModuleMetadataBeingEdited(); const activePageName = this.getPages().activePageName; - - if (moduleMetadataBeingEdited) { - this.getFileMetadatas().setComponentTreeInModule( - moduleMetadataBeingEdited.metadataUUID, - componentTree - ); - } else if (activePageName) { + if (activePageName) { this.getPages().setComponentTreeInPage(activePageName, componentTree); } }; @@ -186,38 +152,6 @@ export default class StudioActions { } }; - /** - * @param moduleMetadata - the {@link ModuleMetadata} of the module to detach. - * @param instanceUUID - the instance to detach. - */ - detachModuleInstance = ( - moduleMetadata: ModuleMetadata, - moduleState: ModuleState - ) => { - const currentComponentTree = this.getComponentTree(); - if (!currentComponentTree) { - return; - } - const updatedComponentTree = currentComponentTree.flatMap( - (componentState) => { - if (componentState.uuid !== moduleState.uuid) { - return componentState; - } - return ComponentTreeHelpers.mapComponentTreeParentsFirst( - moduleMetadata.componentTree, - (child, parentValue) => { - return { - ...child, - uuid: v4(), - parentUUID: parentValue?.uuid ?? componentState.parentUUID, - }; - } - ); - } - ); - this.updateComponentTree(updatedComponentTree); - }; - deploy = async () => { await sendMessage(MessageID.Deploy, this.getSaveData()); this.updatePreviousSave(); @@ -251,16 +185,32 @@ export default class StudioActions { this.getPages().setActivePageEntities(entitiesRecord); }; + addLayout = (layoutState: LayoutState) => { + const tree = this.getComponentTree() ?? []; + const layoutTreeWithNewUUIDs: ComponentState[] = + ComponentTreeHelpers.mapComponentTreeParentsFirst( + layoutState.componentTree, + (componentState, parentState) => { + const updatedComponentState = { + ...componentState, + uuid: v4(), + }; + if (parentState) { + updatedComponentState.parentUUID = parentState.uuid; + } + return updatedComponentState; + } + ); + const updatedTree = layoutTreeWithNewUUIDs.concat(tree); + this.updateComponentTree(updatedTree); + }; + private updatePreviousSave = () => { - const { UUIDToFileMetadata } = this.getFileMetadatas(); const { values } = this.getSiteSettings(); const previousSaveState = cloneDeep({ siteSettings: { values, }, - fileMetadatas: { - UUIDToFileMetadata, - }, }); this.getPreviousSave().setPreviousSave(previousSaveState); }; @@ -268,11 +218,9 @@ export default class StudioActions { private getSaveData = () => { const { pages, pendingChanges: pendingPageChanges } = this.getPages(); const { pagesToRemove, pagesToUpdate } = pendingPageChanges; - const { UUIDToFileMetadata } = this.getFileMetadatas(); const { values } = this.getSiteSettings(); return { pageNameToPageState: pages, - UUIDToFileMetadata, pendingChanges: { pagesToRemove: [...pagesToRemove.keys()], pagesToUpdate: [...pagesToUpdate], diff --git a/packages/studio/src/store/StudioActions/AddComponentAction.ts b/packages/studio-ui/src/store/StudioActions/AddComponentAction.ts similarity index 95% rename from packages/studio/src/store/StudioActions/AddComponentAction.ts rename to packages/studio-ui/src/store/StudioActions/AddComponentAction.ts index a93ab4c61..2c23b6e80 100644 --- a/packages/studio/src/store/StudioActions/AddComponentAction.ts +++ b/packages/studio-ui/src/store/StudioActions/AddComponentAction.ts @@ -1,7 +1,7 @@ import { ComponentState, TypeGuards, - ValidFileMetadata, + ComponentMetadata, } from "@yext/studio-plugin"; import StudioActions from "../StudioActions"; @@ -11,7 +11,7 @@ export default class AddComponentAction { /** * Adds the component to the current active component tree. */ - addComponent = (metadata: ValidFileMetadata) => { + addComponent = (metadata: ComponentMetadata) => { const componentState = this.studioActions.createComponentState(metadata); return this.insertComponentState(componentState); }; diff --git a/packages/studio/src/store/StudioActions/CreateComponentStateAction.ts b/packages/studio-ui/src/store/StudioActions/CreateComponentStateAction.ts similarity index 62% rename from packages/studio/src/store/StudioActions/CreateComponentStateAction.ts rename to packages/studio-ui/src/store/StudioActions/CreateComponentStateAction.ts index 90d11de82..4b33b4b5a 100644 --- a/packages/studio/src/store/StudioActions/CreateComponentStateAction.ts +++ b/packages/studio-ui/src/store/StudioActions/CreateComponentStateAction.ts @@ -1,8 +1,7 @@ import { ComponentStateKind, - FileMetadataKind, - StandardOrModuleComponentState, - ValidFileMetadata, + StandardComponentState, + ComponentMetadata, } from "@yext/studio-plugin"; import path from "path-browserify"; import { v4 } from "uuid"; @@ -10,14 +9,11 @@ import PropValueHelpers from "../../utils/PropValueHelpers"; export default class CreateComponentStateAction { createComponentState = ( - metadata: ValidFileMetadata - ): StandardOrModuleComponentState => { + metadata: ComponentMetadata + ): StandardComponentState => { const componentName = path.basename(metadata.filepath, ".tsx"); - const componentState: StandardOrModuleComponentState = { - kind: - metadata.kind === FileMetadataKind.Module - ? ComponentStateKind.Module - : ComponentStateKind.Standard, + const componentState: StandardComponentState = { + kind: ComponentStateKind.Standard, componentName, props: { ...PropValueHelpers.getDefaultPropValues(metadata.propShape ?? {}), diff --git a/packages/studio/src/store/StudioActions/CreatePageAction.ts b/packages/studio-ui/src/store/StudioActions/CreatePageAction.ts similarity index 100% rename from packages/studio/src/store/StudioActions/CreatePageAction.ts rename to packages/studio-ui/src/store/StudioActions/CreatePageAction.ts diff --git a/packages/studio/src/store/StudioActions/GenerateTestDataAction.ts b/packages/studio-ui/src/store/StudioActions/GenerateTestDataAction.ts similarity index 98% rename from packages/studio/src/store/StudioActions/GenerateTestDataAction.ts rename to packages/studio-ui/src/store/StudioActions/GenerateTestDataAction.ts index a620de853..98dc1eeaa 100644 --- a/packages/studio/src/store/StudioActions/GenerateTestDataAction.ts +++ b/packages/studio-ui/src/store/StudioActions/GenerateTestDataAction.ts @@ -10,7 +10,7 @@ import { import { Stream } from "@yext/pages"; import PageSlice from "../models/slices/PageSlice"; import sendMessage from "../../messaging/sendMessage"; -import { isEqual } from "lodash"; +import isEqual from "lodash/isEqual"; import StudioActions from "../StudioActions"; export default class GenerateTestDataAction { diff --git a/packages/studio/src/store/StudioActions/ImportComponentAction.ts b/packages/studio-ui/src/store/StudioActions/ImportComponentAction.ts similarity index 62% rename from packages/studio/src/store/StudioActions/ImportComponentAction.ts rename to packages/studio-ui/src/store/StudioActions/ImportComponentAction.ts index e4210edc7..e16621426 100644 --- a/packages/studio/src/store/StudioActions/ImportComponentAction.ts +++ b/packages/studio-ui/src/store/StudioActions/ImportComponentAction.ts @@ -1,13 +1,9 @@ import { ComponentState, - ComponentStateHelpers, ComponentStateKind, ErrorComponentState, FileMetadata, - FileMetadataKind, - ModuleMetadata, - StandardOrModuleComponentState, - TypeGuards, + StandardComponentState, } from "@yext/studio-plugin"; import FileMetadataSlice from "../models/slices/FileMetadataSlice"; import dynamicImportFromBrowser from "../../utils/dynamicImportFromBrowser"; @@ -15,27 +11,23 @@ import getFunctionComponent from "../../utils/getFunctionComponent"; /** * Imports a component into the global store. - * - * Modules are not directly imported, but instead have all their constituents - * imported instead, similar to a Page. */ export default class ImportComponentAction { constructor(private getFileMetadataSlice: () => FileMetadataSlice) {} importComponent = async (c: ComponentState): Promise => { if ( - !TypeGuards.isEditableComponentState(c) && + c.kind !== ComponentStateKind.Standard && c.kind !== ComponentStateKind.Error ) { return; } - const componentState = ComponentStateHelpers.extractRepeatedState(c); - await this.importStandardOrModuleComponentState(componentState); + await this.importStandardOrErrorComponentState(c); }; - private importStandardOrModuleComponentState = async ( - componentState: StandardOrModuleComponentState | ErrorComponentState + private importStandardOrErrorComponentState = async ( + componentState: StandardComponentState | ErrorComponentState ) => { const { metadataUUID, componentName } = componentState; const { getFileMetadata, UUIDToImportedComponent } = @@ -49,10 +41,6 @@ export default class ImportComponentAction { return; } - if (metadata.kind === FileMetadataKind.Module) { - return this.importModule(metadata); - } - const importedValue = await dynamicImportFromBrowser(metadata.filepath); const functionComponent = getFunctionComponent( importedValue, @@ -66,10 +54,4 @@ export default class ImportComponentAction { ); } }; - - importModule = async (metadata: ModuleMetadata) => { - return Promise.all( - metadata.componentTree.map((c) => this.importComponent(c)) - ); - }; } diff --git a/packages/studio/src/store/StudioActions/UpdateActivePageAction.ts b/packages/studio-ui/src/store/StudioActions/UpdateActivePageAction.ts similarity index 93% rename from packages/studio/src/store/StudioActions/UpdateActivePageAction.ts rename to packages/studio-ui/src/store/StudioActions/UpdateActivePageAction.ts index fca2c545e..4039e4b43 100644 --- a/packages/studio/src/store/StudioActions/UpdateActivePageAction.ts +++ b/packages/studio-ui/src/store/StudioActions/UpdateActivePageAction.ts @@ -13,7 +13,6 @@ export default class UpdateActivePageAction { updateActivePage = async (activePageName?: string): Promise => { this.getPageSlice().setActivePage(activePageName); - this.getPageSlice().setModuleUUIDBeingEdited(undefined); const activePageState = this.getPageSlice().getActivePageState(); // Any file is fine so pick the first one. const anyAcceptedEntityFile = activePageState?.pagesJS?.entityFiles?.[0]; diff --git a/packages/studio/src/store/hotReloadStore.ts b/packages/studio-ui/src/store/hotReloadStore.ts similarity index 86% rename from packages/studio/src/store/hotReloadStore.ts rename to packages/studio-ui/src/store/hotReloadStore.ts index c40e668a0..806b49116 100644 --- a/packages/studio/src/store/hotReloadStore.ts +++ b/packages/studio-ui/src/store/hotReloadStore.ts @@ -13,9 +13,11 @@ export default async function hotReloadStore(payload: StudioHMRPayload) { const { updateType, studioData } = payload; switch (updateType) { case "components": - case "modules": await syncFileMetadata(studioData, payload.file); break; + case "layouts": + syncLayouts(studioData); + break; case "pages": syncPages(studioData); break; @@ -30,6 +32,7 @@ export default async function hotReloadStore(payload: StudioHMRPayload) { async function fullSync(studioData: StudioData, file: string) { syncPages(studioData); + syncLayouts(studioData); await syncFileMetadata(studioData, file); syncSiteSettings(studioData); useStudioStore.setState((store) => { @@ -38,12 +41,9 @@ async function fullSync(studioData: StudioData, file: string) { } async function syncFileMetadata(studioData: StudioData, file: string) { - const UUIDToFileMetadata = removeTopLevelFragments( - studioData.UUIDToFileMetadata - ); + const UUIDToFileMetadata = studioData.UUIDToFileMetadata; useStudioStore.setState((store) => { store.fileMetadatas.UUIDToFileMetadata = UUIDToFileMetadata; - store.previousSave.fileMetadatas.UUIDToFileMetadata = UUIDToFileMetadata; }); const fileMetadata = Object.values(UUIDToFileMetadata).find( (metadata) => metadata.filepath === file @@ -82,6 +82,15 @@ function syncPages(studioData: StudioData) { }); } +function syncLayouts(studioData: StudioData) { + const layoutNameToLayoutState = removeTopLevelFragments( + studioData.layoutNameToLayoutState + ); + useStudioStore.setState((store) => { + store.layouts.layoutNameToLayoutState = layoutNameToLayoutState; + }); +} + function syncSiteSettings(studioData: StudioData) { useStudioStore.setState((store) => { store.siteSettings.shape = studioData.siteSettings?.shape; diff --git a/packages/studio/src/store/models/DOMRectProperties.ts b/packages/studio-ui/src/store/models/DOMRectProperties.ts similarity index 100% rename from packages/studio/src/store/models/DOMRectProperties.ts rename to packages/studio-ui/src/store/models/DOMRectProperties.ts diff --git a/packages/studio/src/store/models/ImportType.ts b/packages/studio-ui/src/store/models/ImportType.ts similarity index 82% rename from packages/studio/src/store/models/ImportType.ts rename to packages/studio-ui/src/store/models/ImportType.ts index a640cca03..0d9c3c297 100644 --- a/packages/studio/src/store/models/ImportType.ts +++ b/packages/studio-ui/src/store/models/ImportType.ts @@ -2,6 +2,6 @@ import { FunctionComponent } from "react"; /** * Describe the import shape of a Studio's React source file - * (e.g. Module, Component, and Page). + * (e.g. Component or Page). */ export type ImportType = FunctionComponent>; diff --git a/packages/studio/src/store/models/StudioStore.ts b/packages/studio-ui/src/store/models/StudioStore.ts similarity index 93% rename from packages/studio/src/store/models/StudioStore.ts rename to packages/studio-ui/src/store/models/StudioStore.ts index 55a04d056..5b38fda06 100644 --- a/packages/studio/src/store/models/StudioStore.ts +++ b/packages/studio-ui/src/store/models/StudioStore.ts @@ -1,6 +1,7 @@ import StudioActions from "../StudioActions"; import AccountContentSlice from "./slices/AccountContentSlice"; import FileMetadataSlice from "./slices/FileMetadataSlice"; +import { LayoutSlice } from "./slices/LayoutSlice"; import PagePreviewSlice from "./slices/PagePreviewSlice"; import PageSlice from "./slices/PageSlice"; import PreviousSaveSlice from "./slices/PreviousSaveSlice"; @@ -16,10 +17,10 @@ import StudioEnvDataSlice from "./slices/StudioEnvDataSlice"; export type StudioStore = { fileMetadatas: FileMetadataSlice; pages: PageSlice; + layouts: LayoutSlice; siteSettings: SiteSettingSlice; pagePreview: PagePreviewSlice; previousSave: PreviousSaveSlice; - createModule: (modulePath: string) => void; actions: StudioActions; studioConfig: StudioConfigSlice; studioEnvData: StudioEnvDataSlice; diff --git a/packages/studio/src/store/models/slices/AccountContentSlice.ts b/packages/studio-ui/src/store/models/slices/AccountContentSlice.ts similarity index 100% rename from packages/studio/src/store/models/slices/AccountContentSlice.ts rename to packages/studio-ui/src/store/models/slices/AccountContentSlice.ts diff --git a/packages/studio/src/store/models/slices/FileMetadataSlice.ts b/packages/studio-ui/src/store/models/slices/FileMetadataSlice.ts similarity index 53% rename from packages/studio/src/store/models/slices/FileMetadataSlice.ts rename to packages/studio-ui/src/store/models/slices/FileMetadataSlice.ts index 48546362a..71e7e9914 100644 --- a/packages/studio/src/store/models/slices/FileMetadataSlice.ts +++ b/packages/studio-ui/src/store/models/slices/FileMetadataSlice.ts @@ -1,37 +1,21 @@ -import { - ComponentMetadata, - ComponentState, - FileMetadata, - ModuleMetadata, - ValidFileMetadata, -} from "@yext/studio-plugin"; +import { ComponentMetadata, FileMetadata } from "@yext/studio-plugin"; import { ImportType } from "../ImportType"; export interface FileMetadataSliceStates { - /** Metadata of all components and modules that can be used in Studio. */ + /** Metadata of all components that can be used in Studio. */ UUIDToFileMetadata: Record; /** Component's metadata uuid and its functional component method. */ UUIDToImportedComponent: Record; } export interface FileMetadataSliceActions { - setFileMetadata: ( - metadataUUID: string, - fileMetadata: ValidFileMetadata - ) => void; getFileMetadata: (metadataUUID: string) => FileMetadata | undefined; - getModuleMetadata: (metadataUUID: string) => ModuleMetadata; - removeFileMetadata: (metadataUUID: string) => void; getComponentMetadata: (metadataUUID: string) => ComponentMetadata; setImportedComponent: (uuid: string, importedComponent: ImportType) => void; - setComponentTreeInModule: ( - metadataUUID: string, - componentTree: ComponentState[] - ) => void; } /** - * Maintains metadata for Component and Module, available for users + * Maintains metadata for Components available for users * to import and preview in Studio. */ type FileMetadataSlice = FileMetadataSliceStates & FileMetadataSliceActions; diff --git a/packages/studio-ui/src/store/models/slices/LayoutSlice.ts b/packages/studio-ui/src/store/models/slices/LayoutSlice.ts new file mode 100644 index 000000000..0aae78f10 --- /dev/null +++ b/packages/studio-ui/src/store/models/slices/LayoutSlice.ts @@ -0,0 +1,8 @@ +import { LayoutState } from "@yext/studio-plugin"; + +export interface LayoutSlice { + /** + * A mapping of name to LayoutState for layouts that can be applied via Studio. + */ + layoutNameToLayoutState: Record; +} diff --git a/packages/studio/src/store/models/slices/PagePreviewSlice.ts b/packages/studio-ui/src/store/models/slices/PagePreviewSlice.ts similarity index 100% rename from packages/studio/src/store/models/slices/PagePreviewSlice.ts rename to packages/studio-ui/src/store/models/slices/PagePreviewSlice.ts diff --git a/packages/studio/src/store/models/slices/PageSlice.ts b/packages/studio-ui/src/store/models/slices/PageSlice.ts similarity index 90% rename from packages/studio/src/store/models/slices/PageSlice.ts rename to packages/studio-ui/src/store/models/slices/PageSlice.ts index a690f2254..9463240b6 100644 --- a/packages/studio/src/store/models/slices/PageSlice.ts +++ b/packages/studio-ui/src/store/models/slices/PageSlice.ts @@ -2,7 +2,6 @@ import { ComponentState, ErrorPageState, GetPathVal, - ModuleMetadata, PageState, StreamScope, } from "@yext/studio-plugin"; @@ -43,10 +42,6 @@ export interface PageSliceStates { */ pagesToUpdate: Set; }; - /** - * The {@link ComponentState.uuid} for the module currently being edited, if it exists. - */ - moduleUUIDBeingEdited?: string; } interface PageSliceActions { @@ -65,7 +60,6 @@ interface PageSliceActions { setActiveComponentUUID: (activeComponentUUID: string | undefined) => void; setActiveComponentRect: (rect: DOMRectProperties | undefined) => void; - setModuleUUIDBeingEdited: (moduleStateUUID: string | undefined) => void; setActiveEntityFile: (activeEntityFile?: string) => void; setActivePageEntities: ( @@ -74,7 +68,6 @@ interface PageSliceActions { getActiveEntityData: () => Record | undefined; clearPendingChanges: () => void; - detachAllModuleInstances: (metadata: ModuleMetadata) => void; } /** diff --git a/packages/studio/src/store/models/slices/PreviousSaveSlice.ts b/packages/studio-ui/src/store/models/slices/PreviousSaveSlice.ts similarity index 79% rename from packages/studio/src/store/models/slices/PreviousSaveSlice.ts rename to packages/studio-ui/src/store/models/slices/PreviousSaveSlice.ts index 8aaa80786..e8be737dd 100644 --- a/packages/studio/src/store/models/slices/PreviousSaveSlice.ts +++ b/packages/studio-ui/src/store/models/slices/PreviousSaveSlice.ts @@ -1,4 +1,3 @@ -import FileMetadataSlice from "./FileMetadataSlice"; import SiteSettingSlice from "./SiteSettingsSlice"; /** @@ -6,7 +5,6 @@ import SiteSettingSlice from "./SiteSettingsSlice"; */ export interface PreviousSaveSliceState { siteSettings: Pick; - fileMetadatas: Pick; } export interface PreviousSaveSliceActions { diff --git a/packages/studio/src/store/models/slices/SiteSettingsSlice.ts b/packages/studio-ui/src/store/models/slices/SiteSettingsSlice.ts similarity index 100% rename from packages/studio/src/store/models/slices/SiteSettingsSlice.ts rename to packages/studio-ui/src/store/models/slices/SiteSettingsSlice.ts diff --git a/packages/studio/src/store/models/slices/StudioConfigSlice.ts b/packages/studio-ui/src/store/models/slices/StudioConfigSlice.ts similarity index 100% rename from packages/studio/src/store/models/slices/StudioConfigSlice.ts rename to packages/studio-ui/src/store/models/slices/StudioConfigSlice.ts diff --git a/packages/studio/src/store/models/slices/StudioEnvDataSlice.ts b/packages/studio-ui/src/store/models/slices/StudioEnvDataSlice.ts similarity index 100% rename from packages/studio/src/store/models/slices/StudioEnvDataSlice.ts rename to packages/studio-ui/src/store/models/slices/StudioEnvDataSlice.ts diff --git a/packages/studio/src/store/models/utils.ts b/packages/studio-ui/src/store/models/utils.ts similarity index 100% rename from packages/studio/src/store/models/utils.ts rename to packages/studio-ui/src/store/models/utils.ts diff --git a/packages/studio/src/store/slices/accountContent/createAccountContentSlice.ts b/packages/studio-ui/src/store/slices/accountContent/createAccountContentSlice.ts similarity index 100% rename from packages/studio/src/store/slices/accountContent/createAccountContentSlice.ts rename to packages/studio-ui/src/store/slices/accountContent/createAccountContentSlice.ts diff --git a/packages/studio/src/store/slices/accountContent/utils.ts b/packages/studio-ui/src/store/slices/accountContent/utils.ts similarity index 100% rename from packages/studio/src/store/slices/accountContent/utils.ts rename to packages/studio-ui/src/store/slices/accountContent/utils.ts diff --git a/packages/studio-ui/src/store/slices/createFileMetadataSlice.ts b/packages/studio-ui/src/store/slices/createFileMetadataSlice.ts new file mode 100644 index 000000000..2f47bd960 --- /dev/null +++ b/packages/studio-ui/src/store/slices/createFileMetadataSlice.ts @@ -0,0 +1,44 @@ +import { + ComponentMetadata, + FileMetadata, + FileMetadataKind, +} from "@yext/studio-plugin"; +import initialStudioData from "virtual_yext-studio"; +import FileMetadataSlice from "../models/slices/FileMetadataSlice"; +import { SliceCreator } from "../models/utils"; + +const createFileMetadataSlice: SliceCreator = ( + set, + get +) => ({ + UUIDToFileMetadata: initialStudioData.UUIDToFileMetadata, + UUIDToImportedComponent: {}, + getFileMetadata: (metadataUUID: string) => + get().UUIDToFileMetadata[metadataUUID], + getComponentMetadata: (metadataUUID) => { + const fileMetadata = get().getFileMetadata(metadataUUID); + assertIsComponentMetadata(fileMetadata); + return fileMetadata; + }, + setImportedComponent(uuid, importedComponent) { + set((store) => { + store.UUIDToImportedComponent[uuid] = importedComponent; + }); + }, +}); + +function assertIsComponentMetadata( + fileMetadata?: FileMetadata +): asserts fileMetadata is ComponentMetadata { + if (fileMetadata?.kind !== FileMetadataKind.Component) { + throw new Error( + `Expected a ComponentMetadata, instead received ${JSON.stringify( + fileMetadata, + null, + 2 + )}.` + ); + } +} + +export default createFileMetadataSlice; diff --git a/packages/studio-ui/src/store/slices/createLayoutSlice.ts b/packages/studio-ui/src/store/slices/createLayoutSlice.ts new file mode 100644 index 000000000..b10ebcad1 --- /dev/null +++ b/packages/studio-ui/src/store/slices/createLayoutSlice.ts @@ -0,0 +1,12 @@ +import initialStudioData from "virtual_yext-studio"; +import { LayoutSlice } from "../models/slices/LayoutSlice"; +import { SliceCreator } from "../models/utils"; +import removeTopLevelFragments from "../../utils/removeTopLevelFragments"; + +const createLayoutSlice: SliceCreator = () => ({ + layoutNameToLayoutState: removeTopLevelFragments( + initialStudioData.layoutNameToLayoutState + ), +}); + +export default createLayoutSlice; diff --git a/packages/studio/src/store/slices/createPagePreviewSlice.ts b/packages/studio-ui/src/store/slices/createPagePreviewSlice.ts similarity index 100% rename from packages/studio/src/store/slices/createPagePreviewSlice.ts rename to packages/studio-ui/src/store/slices/createPagePreviewSlice.ts diff --git a/packages/studio/src/store/slices/pages/createPageSlice.ts b/packages/studio-ui/src/store/slices/createPageSlice.ts similarity index 88% rename from packages/studio/src/store/slices/pages/createPageSlice.ts rename to packages/studio-ui/src/store/slices/createPageSlice.ts index 2ce693c40..872a242b6 100644 --- a/packages/studio/src/store/slices/pages/createPageSlice.ts +++ b/packages/studio-ui/src/store/slices/createPageSlice.ts @@ -4,14 +4,13 @@ import { PageState, StreamScope, } from "@yext/studio-plugin"; -import { isEqual } from "lodash"; +import isEqual from "lodash/isEqual"; import initialStudioData from "virtual_yext-studio"; -import DOMRectProperties from "../../models/DOMRectProperties"; -import PageSlice, { PageSliceStates } from "../../models/slices/PageSlice"; -import { SliceCreator } from "../../models/utils"; -import createDetachAllModuleInstances from "./detachAllModuleInstances"; -import removeTopLevelFragments from "../../../utils/removeTopLevelFragments"; -import PropValueHelpers from "../../../utils/PropValueHelpers"; +import DOMRectProperties from "../models/DOMRectProperties"; +import PageSlice, { PageSliceStates } from "../models/slices/PageSlice"; +import { SliceCreator } from "../models/utils"; +import removeTopLevelFragments from "../../utils/removeTopLevelFragments"; +import PropValueHelpers from "../../utils/PropValueHelpers"; const firstPageEntry = Object.entries( initialStudioData.pageNameToPageState @@ -28,7 +27,6 @@ const initialStates: PageSliceStates = { pagesToRemove: new Set(), pagesToUpdate: new Set(), }, - moduleUUIDBeingEdited: undefined, }; export const createPageSlice: SliceCreator = (set, get) => { @@ -196,22 +194,12 @@ export const createPageSlice: SliceCreator = (set, get) => { }, }; - const moduleStateActions: Pick = { - setModuleUUIDBeingEdited(moduleStateUUID: string | undefined) { - set((store) => { - store.moduleUUIDBeingEdited = moduleStateUUID; - }); - }, - }; - return { ...initialStates, ...pageActions, ...activePageActions, ...pageComponentActions, ...activeEntityFileActions, - ...moduleStateActions, - detachAllModuleInstances: createDetachAllModuleInstances(get), }; }; diff --git a/packages/studio/src/store/slices/createPreviousSaveSlice.ts b/packages/studio-ui/src/store/slices/createPreviousSaveSlice.ts similarity index 84% rename from packages/studio/src/store/slices/createPreviousSaveSlice.ts rename to packages/studio-ui/src/store/slices/createPreviousSaveSlice.ts index 0e0c9af15..aeed09ee0 100644 --- a/packages/studio/src/store/slices/createPreviousSaveSlice.ts +++ b/packages/studio-ui/src/store/slices/createPreviousSaveSlice.ts @@ -8,9 +8,6 @@ const createPreviousSaveSlice: SliceCreator = (set) => ({ siteSettings: { values: initialStudioData.siteSettings?.values, }, - fileMetadatas: { - UUIDToFileMetadata: initialStudioData.UUIDToFileMetadata, - }, setPreviousSave(saveState: PreviousSaveSliceState) { set(saveState); }, diff --git a/packages/studio/src/store/slices/createSiteSettingsSlice.ts b/packages/studio-ui/src/store/slices/createSiteSettingsSlice.ts similarity index 100% rename from packages/studio/src/store/slices/createSiteSettingsSlice.ts rename to packages/studio-ui/src/store/slices/createSiteSettingsSlice.ts diff --git a/packages/studio/src/store/slices/createStudioConfigSlice.ts b/packages/studio-ui/src/store/slices/createStudioConfigSlice.ts similarity index 100% rename from packages/studio/src/store/slices/createStudioConfigSlice.ts rename to packages/studio-ui/src/store/slices/createStudioConfigSlice.ts diff --git a/packages/studio/src/store/slices/createStudioEnvDataSlice.ts b/packages/studio-ui/src/store/slices/createStudioEnvDataSlice.ts similarity index 100% rename from packages/studio/src/store/slices/createStudioEnvDataSlice.ts rename to packages/studio-ui/src/store/slices/createStudioEnvDataSlice.ts diff --git a/packages/studio/src/store/useStudioStore.ts b/packages/studio-ui/src/store/useStudioStore.ts similarity index 93% rename from packages/studio/src/store/useStudioStore.ts rename to packages/studio-ui/src/store/useStudioStore.ts index 664e0fd24..1f565e666 100644 --- a/packages/studio/src/store/useStudioStore.ts +++ b/packages/studio-ui/src/store/useStudioStore.ts @@ -5,16 +5,16 @@ import { enableMapSet } from "immer"; import { StudioStore } from "./models/StudioStore"; import createFileMetadataSlice from "./slices/createFileMetadataSlice"; -import createPageSlice from "./slices/pages/createPageSlice"; +import createPageSlice from "./slices/createPageSlice"; import createSiteSettingSlice from "./slices/createSiteSettingsSlice"; import createPagePreviewSlice from "./slices/createPagePreviewSlice"; -import getCreateModuleAction from "./createModuleAction"; import StudioActions from "./StudioActions"; import createStudioConfigSlice from "./slices/createStudioConfigSlice"; import createPreviousSaveSlice from "./slices/createPreviousSaveSlice"; import { addZundoMiddleware } from "./zundoMiddleware"; import createStudioEnvDataSlice from "./slices/createStudioEnvDataSlice"; import createAccountContentSlice from "./slices/accountContent/createAccountContentSlice"; +import createLayoutSlice from "./slices/createLayoutSlice"; enableMapSet(); @@ -36,9 +36,9 @@ const useStudioStore = create()( return { fileMetadatas: lens(createFileMetadataSlice), pages: lens(createPageSlice), + layouts: lens(createLayoutSlice), siteSettings: lens(createSiteSettingSlice), pagePreview: lens(createPagePreviewSlice), - createModule: getCreateModuleAction(get), previousSave: lens(createPreviousSaveSlice), actions: new StudioActions( () => get().pages, diff --git a/packages/studio/src/store/useTemporalStore.ts b/packages/studio-ui/src/store/useTemporalStore.ts similarity index 100% rename from packages/studio/src/store/useTemporalStore.ts rename to packages/studio-ui/src/store/useTemporalStore.ts diff --git a/packages/studio/src/store/zundoMiddleware.ts b/packages/studio-ui/src/store/zundoMiddleware.ts similarity index 74% rename from packages/studio/src/store/zundoMiddleware.ts rename to packages/studio-ui/src/store/zundoMiddleware.ts index 5b1f56fc1..90775fd10 100644 --- a/packages/studio/src/store/zundoMiddleware.ts +++ b/packages/studio-ui/src/store/zundoMiddleware.ts @@ -1,19 +1,14 @@ -import FileMetadataSlice from "./models/slices/FileMetadataSlice"; import PageSlice from "./models/slices/PageSlice"; import SiteSettingSlice from "./models/slices/SiteSettingsSlice"; import { StudioStore } from "./models/StudioStore"; -import { isEqual } from "lodash"; +import isEqual from "lodash/isEqual"; import { ZundoOptions, temporal } from "zundo"; import { TemporalStudioStore } from "./useTemporalStore"; import { StateCreator } from "zustand"; type UserUpdatableStore = { siteSettings: Pick; - fileMetadatas: Pick; - pages: Pick< - PageSlice, - "pages" | "activePageName" | "activeComponentUUID" | "moduleUUIDBeingEdited" - >; + pages: Pick; }; /** @@ -24,21 +19,15 @@ type UserUpdatableStore = { */ function getUserUpdatableStore(store: StudioStore): UserUpdatableStore { const { values } = store.siteSettings; - const { UUIDToFileMetadata } = store.fileMetadatas; - const { pages, activePageName, activeComponentUUID, moduleUUIDBeingEdited } = - store.pages; + const { pages, activePageName, activeComponentUUID } = store.pages; return { siteSettings: { values, }, - fileMetadatas: { - UUIDToFileMetadata, - }, pages: { pages, activePageName, activeComponentUUID, - moduleUUIDBeingEdited, }, }; } diff --git a/packages/studio/src/utils/PageDataValidator.ts b/packages/studio-ui/src/utils/PageDataValidator.ts similarity index 100% rename from packages/studio/src/utils/PageDataValidator.ts rename to packages/studio-ui/src/utils/PageDataValidator.ts diff --git a/packages/studio/src/utils/PropValueHelpers.ts b/packages/studio-ui/src/utils/PropValueHelpers.ts similarity index 100% rename from packages/studio/src/utils/PropValueHelpers.ts rename to packages/studio-ui/src/utils/PropValueHelpers.ts diff --git a/packages/studio/src/utils/StreamScopeParser.ts b/packages/studio-ui/src/utils/StreamScopeParser.ts similarity index 100% rename from packages/studio/src/utils/StreamScopeParser.ts rename to packages/studio-ui/src/utils/StreamScopeParser.ts diff --git a/packages/studio/src/utils/TemplateExpressionFormatter.ts b/packages/studio-ui/src/utils/TemplateExpressionFormatter.ts similarity index 100% rename from packages/studio/src/utils/TemplateExpressionFormatter.ts rename to packages/studio-ui/src/utils/TemplateExpressionFormatter.ts diff --git a/packages/studio/src/utils/createIsSupportedPropMetadata.ts b/packages/studio-ui/src/utils/createIsSupportedPropMetadata.ts similarity index 100% rename from packages/studio/src/utils/createIsSupportedPropMetadata.ts rename to packages/studio-ui/src/utils/createIsSupportedPropMetadata.ts diff --git a/packages/studio/src/utils/dynamicImportFromBrowser.ts b/packages/studio-ui/src/utils/dynamicImportFromBrowser.ts similarity index 71% rename from packages/studio/src/utils/dynamicImportFromBrowser.ts rename to packages/studio-ui/src/utils/dynamicImportFromBrowser.ts index 3494f3bdc..fbcad436f 100644 --- a/packages/studio/src/utils/dynamicImportFromBrowser.ts +++ b/packages/studio-ui/src/utils/dynamicImportFromBrowser.ts @@ -1,3 +1,5 @@ +import path from "path-browserify"; + /* eslint-disable @typescript-eslint/no-explicit-any */ /** * In order to support dynamic imports from the browser on Windows, @@ -8,5 +10,6 @@ export default function dynamicImportFromBrowser( absFilepath: string ): Promise { - return import(/* @vite-ignore */ "/@fs/" + absFilepath); + const importPath = path.join("/@fs", absFilepath); + return import(/* @vite-ignore */ importPath); } diff --git a/packages/studio/src/utils/filterEntityData.ts b/packages/studio-ui/src/utils/filterEntityData.ts similarity index 100% rename from packages/studio/src/utils/filterEntityData.ts rename to packages/studio-ui/src/utils/filterEntityData.ts diff --git a/packages/studio/src/utils/getFunctionComponent.ts b/packages/studio-ui/src/utils/getFunctionComponent.ts similarity index 100% rename from packages/studio/src/utils/getFunctionComponent.ts rename to packages/studio-ui/src/utils/getFunctionComponent.ts diff --git a/packages/studio/src/utils/getPropsForPreview.ts b/packages/studio-ui/src/utils/getPropsForPreview.ts similarity index 95% rename from packages/studio/src/utils/getPropsForPreview.ts rename to packages/studio-ui/src/utils/getPropsForPreview.ts index a8eabbc67..cb17535ca 100644 --- a/packages/studio/src/utils/getPropsForPreview.ts +++ b/packages/studio-ui/src/utils/getPropsForPreview.ts @@ -8,7 +8,7 @@ import { PropVal, PropType, } from "@yext/studio-plugin"; -import { get } from "lodash"; +import get from "lodash/get"; import TemplateExpressionFormatter from "./TemplateExpressionFormatter"; /** @@ -197,12 +197,6 @@ function getExpressionValue( if (TypeGuards.isStreamsDataExpression(expression)) { return getValueFromPath(expression, "document"); } - if (expression.startsWith("props.")) { - return getValueFromPath(expression, "props"); - } - if (expression.startsWith("item")) { - return getValueFromPath(expression, "item"); - } console.warn( `Invalid expression: ${expression}.` + " Expressions must reference an expression source from", @@ -230,5 +224,5 @@ function logInvalidExpressionWarning( } export type ExpressionSources = { - [key in "document" | "siteSettings" | "props"]?: Record; -} & { item?: unknown }; + [key in "document" | "siteSettings"]?: Record; +}; diff --git a/packages/studio/src/utils/getSelectCssClasses.ts b/packages/studio-ui/src/utils/getSelectCssClasses.ts similarity index 100% rename from packages/studio/src/utils/getSelectCssClasses.ts rename to packages/studio-ui/src/utils/getSelectCssClasses.ts diff --git a/packages/studio/src/utils/rectToJson.ts b/packages/studio-ui/src/utils/rectToJson.ts similarity index 100% rename from packages/studio/src/utils/rectToJson.ts rename to packages/studio-ui/src/utils/rectToJson.ts diff --git a/packages/studio/src/utils/removeTopLevelFragments.ts b/packages/studio-ui/src/utils/removeTopLevelFragments.ts similarity index 88% rename from packages/studio/src/utils/removeTopLevelFragments.ts rename to packages/studio-ui/src/utils/removeTopLevelFragments.ts index c1e674ace..975797700 100644 --- a/packages/studio/src/utils/removeTopLevelFragments.ts +++ b/packages/studio-ui/src/utils/removeTopLevelFragments.ts @@ -1,8 +1,8 @@ import { ComponentStateKind, ComponentState, - FileMetadata, PageState, + LayoutState, } from "@yext/studio-plugin"; /** @@ -10,14 +10,10 @@ import { * and removes all top level fragments. */ export default function removeTopLevelFragments< - T extends PageState | FileMetadata + T extends PageState | LayoutState >(record: Record): Record { const entries = Object.entries(record).map( ([key, componentTreeContainer]) => { - if (!("componentTree" in componentTreeContainer)) { - return [key, componentTreeContainer]; - } - const updatedContainer = { ...componentTreeContainer, componentTree: removeTopLevelFragmentsFromTree( diff --git a/packages/studio/tests/__fixtures__/componentStates.ts b/packages/studio-ui/tests/__fixtures__/componentStates.ts similarity index 100% rename from packages/studio/tests/__fixtures__/componentStates.ts rename to packages/studio-ui/tests/__fixtures__/componentStates.ts diff --git a/packages/studio/tests/__fixtures__/mockStoreNestedComponents.ts b/packages/studio-ui/tests/__fixtures__/mockStoreNestedComponents.ts similarity index 65% rename from packages/studio/tests/__fixtures__/mockStoreNestedComponents.ts rename to packages/studio-ui/tests/__fixtures__/mockStoreNestedComponents.ts index 073dde9f1..1193a4916 100644 --- a/packages/studio/tests/__fixtures__/mockStoreNestedComponents.ts +++ b/packages/studio-ui/tests/__fixtures__/mockStoreNestedComponents.ts @@ -5,7 +5,6 @@ import { FileMetadataKind, FileMetadata, ComponentMetadata, - ModuleMetadata, ComponentState, StandardComponentState, } from "@yext/studio-plugin"; @@ -56,70 +55,9 @@ const containerMetadata: ComponentMetadata = { filepath: path.resolve(__dirname, "../__mocks__/Container.tsx"), }; -const moduleMetadata: ModuleMetadata = { - kind: FileMetadataKind.Module, - metadataUUID: "panel-metadata-uuid", - propShape: { - text: { type: PropValueType.string, required: false }, - }, - filepath: path.resolve(__dirname, "../__mocks__/Panel.tsx"), - componentTree: [ - { - kind: ComponentStateKind.Fragment, - uuid: "fragment-uuid", - }, - { - ...componentState, - uuid: "internal-banner-uuid-0", - props: { - title: { - kind: PropValueKind.Expression, - valueType: PropValueType.string, - value: "props.text", - }, - }, - parentUUID: "fragment-uuid", - }, - { - ...componentState, - uuid: "internal-banner-uuid-1", - parentUUID: "fragment-uuid", - }, - ], -}; - -const moduleWithObjPropsMetadata: ModuleMetadata = { - kind: FileMetadataKind.Module, - metadataUUID: "module-obj-props-metadata-uuid", - propShape: { - obj: { - type: PropValueType.Object, - required: false, - shape: { - text: { type: PropValueType.string, required: false }, - }, - }, - }, - filepath: "unused", - componentTree: [ - { - ...componentState, - props: { - title: { - kind: PropValueKind.Expression, - valueType: PropValueType.string, - value: "`hello ${props.obj?.text}`", - }, - }, - }, - ], -}; - export const mockUUIDToFileMetadata: Record = { "banner-metadata-uuid": componentMetadata, "container-metadata-uuid": containerMetadata, - "panel-metadata-uuid": moduleMetadata, - "module-obj-props-metadata-uuid": moduleWithObjPropsMetadata, }; export const nestedComponentTree: ComponentState[] = [ diff --git a/packages/studio/tests/__mocks__/Banner.tsx b/packages/studio-ui/tests/__mocks__/Banner.tsx similarity index 100% rename from packages/studio/tests/__mocks__/Banner.tsx rename to packages/studio-ui/tests/__mocks__/Banner.tsx diff --git a/packages/studio/tests/__mocks__/Container.tsx b/packages/studio-ui/tests/__mocks__/Container.tsx similarity index 100% rename from packages/studio/tests/__mocks__/Container.tsx rename to packages/studio-ui/tests/__mocks__/Container.tsx diff --git a/packages/studio/tests/__mocks__/Panel.tsx b/packages/studio-ui/tests/__mocks__/Panel.tsx similarity index 100% rename from packages/studio/tests/__mocks__/Panel.tsx rename to packages/studio-ui/tests/__mocks__/Panel.tsx diff --git a/packages/studio/tests/__mocks__/entityFile.json b/packages/studio-ui/tests/__mocks__/entityFile.json similarity index 100% rename from packages/studio/tests/__mocks__/entityFile.json rename to packages/studio-ui/tests/__mocks__/entityFile.json diff --git a/packages/studio/tests/__mocks__/mockLocalData.json b/packages/studio-ui/tests/__mocks__/mockLocalData.json similarity index 100% rename from packages/studio/tests/__mocks__/mockLocalData.json rename to packages/studio-ui/tests/__mocks__/mockLocalData.json diff --git a/packages/studio/tests/__mocks__/siteSettings.ts b/packages/studio-ui/tests/__mocks__/siteSettings.ts similarity index 100% rename from packages/studio/tests/__mocks__/siteSettings.ts rename to packages/studio-ui/tests/__mocks__/siteSettings.ts diff --git a/packages/studio/tests/__setup__/setup-env.ts b/packages/studio-ui/tests/__setup__/setup-env.ts similarity index 100% rename from packages/studio/tests/__setup__/setup-env.ts rename to packages/studio-ui/tests/__setup__/setup-env.ts diff --git a/packages/studio/tests/__setup__/svgTransformer.cjs b/packages/studio-ui/tests/__setup__/svgTransformer.cjs similarity index 100% rename from packages/studio/tests/__setup__/svgTransformer.cjs rename to packages/studio-ui/tests/__setup__/svgTransformer.cjs diff --git a/packages/studio/tests/__utils__/helpers.ts b/packages/studio-ui/tests/__utils__/helpers.ts similarity index 100% rename from packages/studio/tests/__utils__/helpers.ts rename to packages/studio-ui/tests/__utils__/helpers.ts diff --git a/packages/studio/tests/__utils__/mockActiveComponentState.ts b/packages/studio-ui/tests/__utils__/mockActiveComponentState.ts similarity index 100% rename from packages/studio/tests/__utils__/mockActiveComponentState.ts rename to packages/studio-ui/tests/__utils__/mockActiveComponentState.ts diff --git a/packages/studio/tests/__utils__/mockActivePage.ts b/packages/studio-ui/tests/__utils__/mockActivePage.ts similarity index 100% rename from packages/studio/tests/__utils__/mockActivePage.ts rename to packages/studio-ui/tests/__utils__/mockActivePage.ts diff --git a/packages/studio/tests/__utils__/mockActivePageTree.ts b/packages/studio-ui/tests/__utils__/mockActivePageTree.ts similarity index 68% rename from packages/studio/tests/__utils__/mockActivePageTree.ts rename to packages/studio-ui/tests/__utils__/mockActivePageTree.ts index 59f67c11c..c00f2507c 100644 --- a/packages/studio/tests/__utils__/mockActivePageTree.ts +++ b/packages/studio-ui/tests/__utils__/mockActivePageTree.ts @@ -1,13 +1,9 @@ import { ComponentState } from "@yext/studio-plugin"; import mockStore from "./mockStore"; -export function mockActivePageTree( - componentTree: ComponentState[], - moduleUUIDBeingEdited?: string -) { +export function mockActivePageTree(componentTree: ComponentState[]) { mockStore({ pages: { - moduleUUIDBeingEdited, activePageName: "pagename", pages: { pagename: { diff --git a/packages/studio/tests/__utils__/mockPageSliceState.ts b/packages/studio-ui/tests/__utils__/mockPageSliceState.ts similarity index 100% rename from packages/studio/tests/__utils__/mockPageSliceState.ts rename to packages/studio-ui/tests/__utils__/mockPageSliceState.ts diff --git a/packages/studio/tests/__utils__/mockStore.ts b/packages/studio-ui/tests/__utils__/mockStore.ts similarity index 100% rename from packages/studio/tests/__utils__/mockStore.ts rename to packages/studio-ui/tests/__utils__/mockStore.ts diff --git a/packages/studio/tests/components/ActiveComponentPropEditors.test.tsx b/packages/studio-ui/tests/components/ActiveComponentPropEditors.test.tsx similarity index 93% rename from packages/studio/tests/components/ActiveComponentPropEditors.test.tsx rename to packages/studio-ui/tests/components/ActiveComponentPropEditors.test.tsx index f4b726bf3..2d568659f 100644 --- a/packages/studio/tests/components/ActiveComponentPropEditors.test.tsx +++ b/packages/studio-ui/tests/components/ActiveComponentPropEditors.test.tsx @@ -11,9 +11,6 @@ import { PropValues, PropValueType, StandardComponentState, - StandardOrModuleComponentState, - TypeGuards, - ValidFileMetadata, } from "@yext/studio-plugin"; import userEvent from "@testing-library/user-event"; import useStudioStore from "../../src/store/useStudioStore"; @@ -74,46 +71,16 @@ const getComponentProps = () => ( useStudioStore .getState() - .actions.getActiveComponentState() as StandardOrModuleComponentState + .actions.getActiveComponentState() as StandardComponentState ).props; describe("ComponentStateKind.Component", () => { - testStandardOrModuleComponentState( - activeComponentState, - activeComponentMetadata - ); -}); - -describe("ComponentStateKind.Module", () => { - const activeModuleState: ComponentState = { - kind: ComponentStateKind.Module, - componentName: "ModuleBanner", - props: {}, - uuid: "modulebanner-uuid", - metadataUUID: "modulebanner-metadata-uuid", - }; - - const activeModuleMetadata: FileMetadata = { - kind: FileMetadataKind.Module, - metadataUUID: "modulebanner-metadata-uuid", - filepath: "mock-filepath", - componentTree: [], - }; - testStandardOrModuleComponentState(activeModuleState, activeModuleMetadata); -}); - -function testStandardOrModuleComponentState( - state: StandardOrModuleComponentState, - metadata: ValidFileMetadata -) { - const componentKindLabel = - state.kind === ComponentStateKind.Standard ? "component" : "module"; - + const componentKindLabel = "component"; beforeEach(() => { mockStoreActiveComponent({ - activeComponent: state, + activeComponent: activeComponentState, activeComponentMetadata: { - ...metadata, + ...activeComponentMetadata, propShape, }, }); @@ -122,14 +89,14 @@ function testStandardOrModuleComponentState( it(`renders message when there are no editable props`, () => { render( false} /> ); screen.getByText( - `${state.componentName} has no Editable Properties in this Panel.` + `${activeComponentState.componentName} has no Editable Properties in this Panel.` ); expect(screen.queryByText("title")).toBeNull(); expect(screen.queryByText("num")).toBeNull(); @@ -138,8 +105,8 @@ function testStandardOrModuleComponentState( }); it(`renders prop editors for each of the active ${componentKindLabel}'s non string props`, () => { - const activeState: StandardOrModuleComponentState = { - ...state, + const activeState: StandardComponentState = { + ...activeComponentState, props: { bgColor: { kind: PropValueKind.Literal, @@ -163,7 +130,7 @@ function testStandardOrModuleComponentState( it(`renders tooltip for each of the active ${componentKindLabel}'s props with docs`, async () => { render( ); @@ -181,7 +148,7 @@ function testStandardOrModuleComponentState( activeComponentMetadata?.kind === FileMetadataKind.Error || !activeComponentMetadata?.propShape || !activeComponentState || - !TypeGuards.isStandardOrModuleComponentState(activeComponentState) + activeComponentState?.kind !== ComponentStateKind.Standard ) { return null; } @@ -192,8 +159,8 @@ function testStandardOrModuleComponentState( /> ); } - const activeComponent: StandardOrModuleComponentState = { - ...state, + const activeComponent: StandardComponentState = { + ...activeComponentState, props: { title: { kind: PropValueKind.Literal, @@ -222,7 +189,7 @@ function testStandardOrModuleComponentState( mockStoreActiveComponent({ activeComponent: activeComponent, activeComponentMetadata: { - ...metadata, + ...activeComponentMetadata, propShape, }, }); @@ -285,7 +252,7 @@ function testStandardOrModuleComponentState( jest.useRealTimers(); }); }); -} +}); it("converts string literals to string expressions when propKind = Expression", async () => { const props: PropValues = { @@ -781,7 +748,7 @@ function ActiveComponentPropEditorsWrapper(props: { propShape: PropShape }) { const state = useStudioStore().pages.pages["index"].componentTree[0]; return ( ); diff --git a/packages/studio/tests/components/ActivePagePanel.test.tsx b/packages/studio-ui/tests/components/ActivePagePanel.test.tsx similarity index 100% rename from packages/studio/tests/components/ActivePagePanel.test.tsx rename to packages/studio-ui/tests/components/ActivePagePanel.test.tsx diff --git a/packages/studio/tests/components/AddElementButton.test.tsx b/packages/studio-ui/tests/components/AddElementButton.test.tsx similarity index 54% rename from packages/studio/tests/components/AddElementButton.test.tsx rename to packages/studio-ui/tests/components/AddElementButton.test.tsx index aee84a0f9..f79cfd743 100644 --- a/packages/studio/tests/components/AddElementButton.test.tsx +++ b/packages/studio-ui/tests/components/AddElementButton.test.tsx @@ -2,8 +2,6 @@ import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import AddElementButton from "../../src/components/AddElementButton"; import mockActivePage from "../__utils__/mockActivePage"; -import mockStore from "../__utils__/mockStore"; -import { FileMetadataKind } from "@yext/studio-plugin"; it("renders the button when there is an active page state (but no menu)", () => { mockActivePage({ @@ -16,7 +14,6 @@ it("renders the button when there is an active page state (but no menu)", () => expect(screen.getByRole("button")).toBeDefined(); expect(screen.queryByText("Components")).toBeNull(); expect(screen.queryByText("Layouts")).toBeNull(); - expect(screen.queryByText("Modules")).toBeNull(); }); it("does not render when there is no active page state", () => { @@ -35,31 +32,4 @@ it("clicking the button opens the menu", async () => { await userEvent.click(screen.getByRole("button")); expect(screen.getByText("Components")).toBeDefined(); expect(screen.getByText("Layouts")).toBeDefined(); - expect(screen.queryByText("Modules")).toBeNull(); -}); - -it("the menu will render modules only if there are available modules", async () => { - mockActivePage({ - componentTree: [], - filepath: "", - cssImports: [], - }); - mockStore({ - fileMetadatas: { - UUIDToFileMetadata: { - "module-metadata-uuid": { - kind: FileMetadataKind.Module, - componentTree: [], - metadataUUID: "module-metadata-uuid", - filepath: "-filepath-", - }, - }, - }, - }); - - render(); - await userEvent.click(screen.getByRole("button")); - expect(screen.getByText("Components")).toBeDefined(); - expect(screen.getByText("Layouts")).toBeDefined(); - expect(screen.getByText("Modules")).toBeDefined(); }); diff --git a/packages/studio/tests/components/AddElementMenu.test.tsx b/packages/studio-ui/tests/components/AddElementMenu.test.tsx similarity index 56% rename from packages/studio/tests/components/AddElementMenu.test.tsx rename to packages/studio-ui/tests/components/AddElementMenu.test.tsx index fafd1f3ee..a562912d2 100644 --- a/packages/studio/tests/components/AddElementMenu.test.tsx +++ b/packages/studio-ui/tests/components/AddElementMenu.test.tsx @@ -20,19 +20,30 @@ beforeEach(() => { "uuid-component": { kind: FileMetadataKind.Component, metadataUUID: "comp", - filepath: "blah/Mock-Component.tsx", + filepath: "blah/MockComponent.tsx", }, "uuid-container": { kind: FileMetadataKind.Component, metadataUUID: "cont", acceptsChildren: true, - filepath: "blah/Mock-Container.tsx", + filepath: "blah/MockContainer.tsx", }, - "uuid-module": { - kind: FileMetadataKind.Module, - metadataUUID: "modu", - componentTree: [], - filepath: "blah/Mock-Module.tsx", + }, + }, + layouts: { + layoutNameToLayoutState: { + MyMockLayout: { + componentTree: [ + { + kind: ComponentStateKind.Standard, + metadataUUID: "comp", + componentName: "MockComponent", + props: {}, + uuid: "component-inside-layout-uuid", + }, + ], + cssImports: [], + filepath: "/filepath/to/MyMockLayout.tsx", }, }, }, @@ -41,18 +52,17 @@ beforeEach(() => { it("renders Components on load", () => { render(); - expect(screen.getByText("Mock-Component")).toBeDefined(); - expect(screen.queryByText("Mock-Container")).toBeNull(); - expect(screen.queryByText("Mock-Module")).toBeNull(); + expect(screen.getByText("MockComponent")).toBeDefined(); + expect(screen.queryByText("MockContainer")).toBeNull(); expect(closeMenu).not.toBeCalled(); }); it("can add a component to the tree", async () => { render(); - await userEvent.click(screen.getByText("Mock-Component")); + await userEvent.click(screen.getByText("MockComponent")); expect(useStudioStore.getState().actions.getComponentTree()).toEqual([ { - componentName: "Mock-Component", + componentName: "MockComponent", kind: ComponentStateKind.Standard, metadataUUID: "comp", props: {}, @@ -62,22 +72,13 @@ it("can add a component to the tree", async () => { expect(closeMenu).toBeCalledTimes(1); }); -it("can switch to Layouts", async () => { - render(); - await userEvent.click(screen.getByText("Layouts")); - expect(screen.queryByText("Mock-Component")).toBeNull(); - expect(screen.getByText("Mock-Container")).toBeDefined(); - expect(screen.queryByText("Mock-Module")).toBeNull(); - expect(closeMenu).not.toBeCalled(); -}); - it("can add a container to the tree", async () => { render(); - await userEvent.click(screen.getByText("Layouts")); - await userEvent.click(screen.getByText("Mock-Container")); + await userEvent.click(screen.getByText("Containers")); + await userEvent.click(screen.getByText("MockContainer")); expect(useStudioStore.getState().actions.getComponentTree()).toEqual([ { - componentName: "Mock-Container", + componentName: "MockContainer", kind: ComponentStateKind.Standard, metadataUUID: "cont", props: {}, @@ -87,24 +88,15 @@ it("can add a container to the tree", async () => { expect(closeMenu).toBeCalledTimes(1); }); -it("can switch to Modules", async () => { +it("can add layouts to the tree", async () => { render(); - await userEvent.click(screen.getByText("Modules")); - expect(screen.queryByText("Mock-Component")).toBeNull(); - expect(screen.queryByText("Mock-Container")).toBeNull(); - expect(screen.getByText("Mock-Module")).toBeDefined(); - expect(closeMenu).not.toBeCalled(); -}); - -it("can add a module to the tree", async () => { - render(); - await userEvent.click(screen.getByText("Modules")); - await userEvent.click(screen.getByText("Mock-Module")); + await userEvent.click(screen.getByText("Layouts")); + await userEvent.click(screen.getByText("MyMockLayout")); expect(useStudioStore.getState().actions.getComponentTree()).toEqual([ { - componentName: "Mock-Module", - kind: ComponentStateKind.Module, - metadataUUID: "modu", + componentName: "MockComponent", + kind: ComponentStateKind.Standard, + metadataUUID: "comp", props: {}, uuid: expect.any(String), }, diff --git a/packages/studio/tests/components/AddPageFlow/AddPageButton.test.tsx b/packages/studio-ui/tests/components/AddPageFlow/AddPageButton.test.tsx similarity index 100% rename from packages/studio/tests/components/AddPageFlow/AddPageButton.test.tsx rename to packages/studio-ui/tests/components/AddPageFlow/AddPageButton.test.tsx diff --git a/packages/studio/tests/components/ArrayPropEditor.test.tsx b/packages/studio-ui/tests/components/ArrayPropEditor.test.tsx similarity index 100% rename from packages/studio/tests/components/ArrayPropEditor.test.tsx rename to packages/studio-ui/tests/components/ArrayPropEditor.test.tsx diff --git a/packages/studio/tests/components/ComponentTree.test.tsx b/packages/studio-ui/tests/components/ComponentTree.test.tsx similarity index 100% rename from packages/studio/tests/components/ComponentTree.test.tsx rename to packages/studio-ui/tests/components/ComponentTree.test.tsx diff --git a/packages/studio/tests/components/EditStreamScopeButton.test.tsx b/packages/studio-ui/tests/components/EditStreamScopeButton.test.tsx similarity index 100% rename from packages/studio/tests/components/EditStreamScopeButton.test.tsx rename to packages/studio-ui/tests/components/EditStreamScopeButton.test.tsx diff --git a/packages/studio/tests/components/EntityPicker.test.tsx b/packages/studio-ui/tests/components/EntityPicker.test.tsx similarity index 100% rename from packages/studio/tests/components/EntityPicker.test.tsx rename to packages/studio-ui/tests/components/EntityPicker.test.tsx diff --git a/packages/studio/tests/components/ErrorBoundary.test.tsx b/packages/studio-ui/tests/components/ErrorBoundary.test.tsx similarity index 100% rename from packages/studio/tests/components/ErrorBoundary.test.tsx rename to packages/studio-ui/tests/components/ErrorBoundary.test.tsx diff --git a/packages/studio/tests/components/FieldPicker/FieldPicker.test.tsx b/packages/studio-ui/tests/components/FieldPicker/FieldPicker.test.tsx similarity index 100% rename from packages/studio/tests/components/FieldPicker/FieldPicker.test.tsx rename to packages/studio-ui/tests/components/FieldPicker/FieldPicker.test.tsx diff --git a/packages/studio/tests/components/Highlighter.test.tsx b/packages/studio-ui/tests/components/Highlighter.test.tsx similarity index 100% rename from packages/studio/tests/components/Highlighter.test.tsx rename to packages/studio-ui/tests/components/Highlighter.test.tsx diff --git a/packages/studio/tests/components/InfoButton.test.tsx b/packages/studio-ui/tests/components/InfoButton.test.tsx similarity index 100% rename from packages/studio/tests/components/InfoButton.test.tsx rename to packages/studio-ui/tests/components/InfoButton.test.tsx diff --git a/packages/studio/tests/components/OpenLivePreviewButton.test.tsx b/packages/studio-ui/tests/components/OpenLivePreviewButton.test.tsx similarity index 100% rename from packages/studio/tests/components/OpenLivePreviewButton.test.tsx rename to packages/studio-ui/tests/components/OpenLivePreviewButton.test.tsx diff --git a/packages/studio/tests/components/PageSettingsButton.test.tsx b/packages/studio-ui/tests/components/PageSettingsButton.test.tsx similarity index 100% rename from packages/studio/tests/components/PageSettingsButton.test.tsx rename to packages/studio-ui/tests/components/PageSettingsButton.test.tsx diff --git a/packages/studio/tests/components/PreviewPanel.test.tsx b/packages/studio-ui/tests/components/PreviewPanel.test.tsx similarity index 59% rename from packages/studio/tests/components/PreviewPanel.test.tsx rename to packages/studio-ui/tests/components/PreviewPanel.test.tsx index ffdd86d5f..780da0ba6 100644 --- a/packages/studio/tests/components/PreviewPanel.test.tsx +++ b/packages/studio-ui/tests/components/PreviewPanel.test.tsx @@ -10,35 +10,11 @@ import { import mockStore from "../__utils__/mockStore"; import { ComponentState, - ComponentStateKind, - ModuleState, PropValueKind, PropValueType, - RepeaterState, } from "@yext/studio-plugin"; import useImportedComponents from "../../src/hooks/useImportedComponents"; -const moduleState: ModuleState = { - kind: ComponentStateKind.Module, - componentName: "Panel", - props: { - text: { - kind: PropValueKind.Literal, - value: "This is Panel module", - valueType: PropValueType.string, - }, - }, - uuid: "panel-uuid", - metadataUUID: "panel-metadata-uuid", -}; - -const repeaterState: RepeaterState = { - kind: ComponentStateKind.Repeater, - uuid: "panel-uuid", - listExpression: "document.favs", - repeatedComponent: moduleState, -}; - const mockSetState = jest.fn(); beforeEach(() => { @@ -59,36 +35,6 @@ describe("renders preview", () => { expect(banner2).toBeDefined(); }); - it("renders component tree with Module component type", async () => { - const tree = [moduleState]; - await mockPreviewState(tree); - render(); - const panel = await screen.findByText(/This is Panel module/); - const banner = await screen.findByText(/This is Banner/); - expect(panel).toBeDefined(); - expect(banner).toBeDefined(); - }); - - it("renders component tree with Repeater component over a module", async () => { - const tree = [repeaterState]; - await mockPreviewState(tree); - render(); - const panels = await screen.findAllByText(/This is Panel module/); - const banners = await screen.findAllByText(/This is Banner/); - expect(panels).toHaveLength(3); - expect(banners).toHaveLength(3); - }); - - it("does not render Repeater if list expression is not found", async () => { - const tree = [{ ...repeaterState, listExpression: "document.services" }]; - await mockPreviewState(tree); - render(); - const panels = screen.queryByText(/This is Panel module/); - const banners = screen.queryByText(/This is Banner/); - expect(panels).toBeNull(); - expect(banners).toBeNull(); - }); - it("renders component with transformed props", async () => { const tree: ComponentState[] = [ { @@ -106,16 +52,6 @@ describe("renders preview", () => { }, }, }, - { - ...moduleState, - props: { - text: { - kind: PropValueKind.Expression, - value: "siteSettings.someText", - valueType: PropValueType.string, - }, - }, - }, ]; await mockPreviewState(tree); render(); @@ -123,8 +59,6 @@ describe("renders preview", () => { expect(siteSettingsExpressionProp).toBeDefined(); const documentExpressionProp = await screen.findByText(/123/); expect(documentExpressionProp).toBeDefined(); - const moduleExpressionProp = await screen.findByText(/mock-text/); - expect(moduleExpressionProp).toBeDefined(); }); it("can render component using nested siteSettings expression", async () => { @@ -170,32 +104,6 @@ describe("renders preview", () => { const nestedPropUsage = await screen.findByText(/eggyweggy/); expect(nestedPropUsage).toBeDefined(); }); - - it("can render repeated module using item expression", async () => { - const tree: ComponentState[] = [ - { - ...repeaterState, - repeatedComponent: { - ...moduleState, - props: { - text: { - kind: PropValueKind.Expression, - value: "item", - valueType: PropValueType.string, - }, - }, - }, - }, - ]; - await mockPreviewState(tree); - render(); - const catItemProp = await screen.findByText(/cat/); - expect(catItemProp).toBeDefined(); - const dogItemProp = await screen.findByText(/dog/); - expect(dogItemProp).toBeDefined(); - const sleepItemProp = await screen.findByText(/sleep/); - expect(sleepItemProp).toBeDefined(); - }); }); it("clicking a component in the preview updates the activeComponentUUID", async () => { @@ -216,33 +124,6 @@ it("clicking a component in the preview updates the activeComponentUUID", async ); }); -it("can preview a module with object props", async () => { - const componentTree: ComponentState[] = [ - { - kind: ComponentStateKind.Module, - componentName: "ModuleWithObjProps", - uuid: "module-obj-props-uuid", - metadataUUID: "module-obj-props-metadata-uuid", - props: { - obj: { - kind: PropValueKind.Literal, - valueType: PropValueType.Object, - value: { - text: { - kind: PropValueKind.Expression, - valueType: PropValueType.string, - value: "document.name", - }, - }, - }, - }, - }, - ]; - await mockPreviewState(componentTree); - render(); - expect(screen.getByText("hello bob")).toBeDefined(); -}); - async function mockPreviewState(componentTree: ComponentState[]) { mockStore({ pages: { diff --git a/packages/studio/tests/components/PropEditor.test.tsx b/packages/studio-ui/tests/components/PropEditor.test.tsx similarity index 100% rename from packages/studio/tests/components/PropEditor.test.tsx rename to packages/studio-ui/tests/components/PropEditor.test.tsx diff --git a/packages/studio/tests/components/PropInput.test.tsx b/packages/studio-ui/tests/components/PropInput.test.tsx similarity index 100% rename from packages/studio/tests/components/PropInput.test.tsx rename to packages/studio-ui/tests/components/PropInput.test.tsx diff --git a/packages/studio-ui/tests/components/PropsPanel.test.tsx b/packages/studio-ui/tests/components/PropsPanel.test.tsx new file mode 100644 index 000000000..8a3fa3068 --- /dev/null +++ b/packages/studio-ui/tests/components/PropsPanel.test.tsx @@ -0,0 +1,21 @@ +import { ComponentStateKind } from "@yext/studio-plugin"; +import mockStoreActiveComponent from "../__utils__/mockActiveComponentState"; +import PropsPanel from "../../src/components/PropsPanel"; +import { render } from "@testing-library/react"; + +it("does not render prop editor(s) for fragment component", () => { + mockStoreActiveComponent({ + activeComponent: { + kind: ComponentStateKind.Fragment, + uuid: "fragment-uuid", + }, + }); + const { container } = render(); + expect(container).toBeEmptyDOMElement(); +}); + +it("does not render prop editor(s) when there's no selected active component", () => { + mockStoreActiveComponent({}); + const { container } = render(); + expect(container).toBeEmptyDOMElement(); +}); diff --git a/packages/studio/tests/components/RemoveElementButton.test.tsx b/packages/studio-ui/tests/components/RemoveElementButton.test.tsx similarity index 100% rename from packages/studio/tests/components/RemoveElementButton.test.tsx rename to packages/studio-ui/tests/components/RemoveElementButton.test.tsx diff --git a/packages/studio/tests/components/RemovePageButton.test.tsx b/packages/studio-ui/tests/components/RemovePageButton.test.tsx similarity index 100% rename from packages/studio/tests/components/RemovePageButton.test.tsx rename to packages/studio-ui/tests/components/RemovePageButton.test.tsx diff --git a/packages/studio/tests/components/SaveButton.test.tsx b/packages/studio-ui/tests/components/SaveButton.test.tsx similarity index 96% rename from packages/studio/tests/components/SaveButton.test.tsx rename to packages/studio-ui/tests/components/SaveButton.test.tsx index a68a50f92..dbbcd73b4 100644 --- a/packages/studio/tests/components/SaveButton.test.tsx +++ b/packages/studio-ui/tests/components/SaveButton.test.tsx @@ -36,9 +36,6 @@ it("enables the button when there are pending SiteSettingsValues changes", () => siteSettings: { values: undefined, }, - fileMetadatas: { - UUIDToFileMetadata: {}, - }, }, siteSettings: { values: { @@ -72,9 +69,6 @@ it("disables the button when there are no pending changes", () => { }, }, }, - fileMetadatas: { - UUIDToFileMetadata: {}, - }, }, siteSettings: { values: { diff --git a/packages/studio/tests/components/SiteSettingsPanel.test.tsx b/packages/studio-ui/tests/components/SiteSettingsPanel.test.tsx similarity index 100% rename from packages/studio/tests/components/SiteSettingsPanel.test.tsx rename to packages/studio-ui/tests/components/SiteSettingsPanel.test.tsx diff --git a/packages/studio/tests/components/TailwindPropInput.test.tsx b/packages/studio-ui/tests/components/TailwindPropInput.test.tsx similarity index 100% rename from packages/studio/tests/components/TailwindPropInput.test.tsx rename to packages/studio-ui/tests/components/TailwindPropInput.test.tsx diff --git a/packages/studio/tests/components/UndefinedMenuButton.test.tsx b/packages/studio-ui/tests/components/UndefinedMenuButton.test.tsx similarity index 100% rename from packages/studio/tests/components/UndefinedMenuButton.test.tsx rename to packages/studio-ui/tests/components/UndefinedMenuButton.test.tsx diff --git a/packages/studio/tests/components/UndoRedo.test.tsx b/packages/studio-ui/tests/components/UndoRedo.test.tsx similarity index 100% rename from packages/studio/tests/components/UndoRedo.test.tsx rename to packages/studio-ui/tests/components/UndoRedo.test.tsx diff --git a/packages/studio/tests/components/ViewportButton.test.tsx b/packages/studio-ui/tests/components/ViewportButton.test.tsx similarity index 100% rename from packages/studio/tests/components/ViewportButton.test.tsx rename to packages/studio-ui/tests/components/ViewportButton.test.tsx diff --git a/packages/studio/tests/hooks/useImportedComponents.test.tsx b/packages/studio-ui/tests/hooks/useImportedComponents.test.tsx similarity index 100% rename from packages/studio/tests/hooks/useImportedComponents.test.tsx rename to packages/studio-ui/tests/hooks/useImportedComponents.test.tsx diff --git a/packages/studio/tests/store/StudioActions/AddComponentAction.test.ts b/packages/studio-ui/tests/store/StudioActions/AddComponentAction.test.ts similarity index 71% rename from packages/studio/tests/store/StudioActions/AddComponentAction.test.ts rename to packages/studio-ui/tests/store/StudioActions/AddComponentAction.test.ts index 4cdd97bf9..ea54be75c 100644 --- a/packages/studio/tests/store/StudioActions/AddComponentAction.test.ts +++ b/packages/studio-ui/tests/store/StudioActions/AddComponentAction.test.ts @@ -26,14 +26,6 @@ const initialTree: ComponentState[] = [ uuid: "mock-component-uuid", parentUUID: "mock-container-uuid", }, - { - componentName: "Mock-Module", - kind: ComponentStateKind.Module, - metadataUUID: "uuid-module", - props: {}, - uuid: "mock-module-uuid", - parentUUID: "mock-container-uuid", - }, { kind: ComponentStateKind.Fragment, uuid: "mock-fragment-uuid", @@ -62,47 +54,12 @@ beforeEach(() => { acceptsChildren: true, filepath: "blah/Mock-Container.tsx", }, - "uuid-module": { - kind: FileMetadataKind.Module, - metadataUUID: "uuid-module", - componentTree: [], - filepath: "blah/Mock-Module.tsx", - }, - StarModuleMetadataUUID: { - kind: FileMetadataKind.Module, - componentTree: initialTree, - metadataUUID: "StarModuleMetadataUUID", - filepath: "unused", - }, }, }, }); }); -describe("adds components to ModuleMetadata when a module is being edited", () => { - beforeEach(() => { - mockActivePageTree( - [ - { - kind: ComponentStateKind.Module, - uuid: "ModuleState.uuid", - metadataUUID: "StarModuleMetadataUUID", - componentName: "StarModule", - props: {}, - }, - ], - "ModuleState.uuid" - ); - }); - - insertionOrderTestSuite(() => { - return useStudioStore.getState().fileMetadatas.UUIDToFileMetadata[ - "StarModuleMetadataUUID" - ]; - }); -}); - -describe("adds components to the active PageState when no module is being edited", () => { +describe("adds components to the active PageState", () => { beforeEach(() => { mockActivePageTree(initialTree); }); @@ -169,20 +126,6 @@ function insertionOrderTestSuite( ); }); - it("puts new component directly after module if it is active component", () => { - useStudioStore.getState().pages.setActiveComponentUUID("mock-module-uuid"); - useStudioStore.getState().actions.addComponent(componentMetadata); - expect(getExpectedObject()).toEqual( - expect.objectContaining({ - componentTree: [ - ...initialTree.slice(0, 3), - { ...newComponentState, parentUUID: "mock-container-uuid" }, - ...initialTree.slice(3), - ], - }) - ); - }); - it("puts new component at start if fragment is active component", () => { useStudioStore .getState() diff --git a/packages/studio/tests/store/StudioActions/CreateComponentStateAction.test.ts b/packages/studio-ui/tests/store/StudioActions/CreateComponentStateAction.test.ts similarity index 89% rename from packages/studio/tests/store/StudioActions/CreateComponentStateAction.test.ts rename to packages/studio-ui/tests/store/StudioActions/CreateComponentStateAction.test.ts index 05151ffea..9c57787e5 100644 --- a/packages/studio/tests/store/StudioActions/CreateComponentStateAction.test.ts +++ b/packages/studio-ui/tests/store/StudioActions/CreateComponentStateAction.test.ts @@ -2,7 +2,6 @@ import { ComponentMetadata, ComponentStateKind, FileMetadataKind, - ModuleMetadata, PropValueKind, PropValueType, } from "@yext/studio-plugin"; @@ -30,10 +29,9 @@ it("creates the expected component state", () => { }); it("adds default values for required props", () => { - const moduleMetadata: ModuleMetadata = { - kind: FileMetadataKind.Module, - filepath: "./ModuleLol.tsx", - componentTree: [], + const componentMetadata: ComponentMetadata = { + kind: FileMetadataKind.Component, + filepath: "./Component.tsx", metadataUUID: "unused", propShape: { document: { @@ -56,7 +54,7 @@ it("adds default values for required props", () => { const actualState = useStudioStore .getState() - .actions.createComponentState(moduleMetadata); + .actions.createComponentState(componentMetadata); expect(actualState).toEqual( expect.objectContaining({ diff --git a/packages/studio/tests/store/StudioActions/GenerateTestDataAction.test.ts b/packages/studio-ui/tests/store/StudioActions/GenerateTestDataAction.test.ts similarity index 100% rename from packages/studio/tests/store/StudioActions/GenerateTestDataAction.test.ts rename to packages/studio-ui/tests/store/StudioActions/GenerateTestDataAction.test.ts diff --git a/packages/studio/tests/store/StudioActions/UpdateActivePageAction.test.ts b/packages/studio-ui/tests/store/StudioActions/UpdateActivePageAction.test.ts similarity index 100% rename from packages/studio/tests/store/StudioActions/UpdateActivePageAction.test.ts rename to packages/studio-ui/tests/store/StudioActions/UpdateActivePageAction.test.ts diff --git a/packages/studio-ui/tests/store/StudioActions/activeComponentActions.test.ts b/packages/studio-ui/tests/store/StudioActions/activeComponentActions.test.ts new file mode 100644 index 000000000..0367b40d7 --- /dev/null +++ b/packages/studio-ui/tests/store/StudioActions/activeComponentActions.test.ts @@ -0,0 +1,109 @@ +import { + ComponentState, + ComponentStateKind, + FileMetadataKind, + PropValueKind, + PropValueType, + PropValues, +} from "@yext/studio-plugin"; +import useStudioStore from "../../../src/store/useStudioStore"; +import mockStore from "../../__utils__/mockStore"; + +describe("getActiveComponentState", () => { + it("can get the current active component within a page", () => { + mockInitialStore(); + const componentState = useStudioStore + .getState() + .actions.getActiveComponentState(); + expect(componentState).toEqual( + expect.objectContaining({ + componentName: "MyBanner", + }) + ); + }); +}); + +describe("getComponentTree", () => { + it("can get the component tree when a page is being edited", () => { + mockInitialStore(); + const tree = useStudioStore.getState().actions.getComponentTree(); + expect(tree).toEqual([ + expect.objectContaining({ + componentName: "MyBanner", + }), + ]); + }); +}); + +describe("updateComponentTree", () => { + it("can rearrange the component tree when a page is being edited", () => { + mockInitialStore(); + const updatedTree: ComponentState[] = [ + { + kind: ComponentStateKind.BuiltIn, + props: {}, + componentName: "div", + uuid: "div-0", + }, + ]; + useStudioStore.getState().actions.updateComponentTree(updatedTree); + const tree = useStudioStore.getState().actions.getComponentTree(); + expect(tree).toEqual(updatedTree); + }); +}); + +describe("updateActiveComponentProps", () => { + it("updates the active component props in the current active page", () => { + mockInitialStore(); + const updatedProps: PropValues = { + hi: { + kind: PropValueKind.Literal, + valueType: PropValueType.string, + value: "bye bye bocchi", + }, + }; + useStudioStore.getState().actions.updateActiveComponentProps(updatedProps); + const componentStateAfterUpdate = useStudioStore + .getState() + .actions.getActiveComponentState(); + expect(componentStateAfterUpdate).toEqual( + expect.objectContaining({ + componentName: "MyBanner", + props: updatedProps, + }) + ); + }); +}); + +function mockInitialStore() { + mockStore({ + pages: { + activePageName: "testpage", + activeComponentUUID: "banner-0", + pages: { + testpage: { + componentTree: [ + { + kind: ComponentStateKind.Standard, + componentName: "MyBanner", + props: {}, + uuid: "banner-0", + metadataUUID: "bannerMeta", + }, + ], + cssImports: [], + filepath: "page-filepath", + }, + }, + }, + fileMetadatas: { + UUIDToFileMetadata: { + bannerMeta: { + metadataUUID: "bannerMeta", + kind: FileMetadataKind.Component, + filepath: "component-filepath", + }, + }, + }, + }); +} diff --git a/packages/studio/tests/store/StudioActions/createPage.test.ts b/packages/studio-ui/tests/store/StudioActions/createPage.test.ts similarity index 100% rename from packages/studio/tests/store/StudioActions/createPage.test.ts rename to packages/studio-ui/tests/store/StudioActions/createPage.test.ts diff --git a/packages/studio/tests/store/StudioActions/refreshActivePageEntities.test.ts b/packages/studio-ui/tests/store/StudioActions/refreshActivePageEntities.test.ts similarity index 100% rename from packages/studio/tests/store/StudioActions/refreshActivePageEntities.test.ts rename to packages/studio-ui/tests/store/StudioActions/refreshActivePageEntities.test.ts diff --git a/packages/studio-ui/tests/store/StudioActions/removeComponent.test.ts b/packages/studio-ui/tests/store/StudioActions/removeComponent.test.ts new file mode 100644 index 000000000..ae4c3a877 --- /dev/null +++ b/packages/studio-ui/tests/store/StudioActions/removeComponent.test.ts @@ -0,0 +1,46 @@ +import { ComponentState, ComponentStateKind } from "@yext/studio-plugin"; +import useStudioStore from "../../../src/store/useStudioStore"; +import { searchBarComponent } from "../../__fixtures__/componentStates"; +import mockStore from "../../__utils__/mockStore"; + +const initialTree: ComponentState[] = [ + { + kind: ComponentStateKind.Fragment, + uuid: "mock-uuid-0", + }, + { + ...searchBarComponent, + uuid: "mock-uuid-1", + parentUUID: "mock-uuid-0", + }, + { + ...searchBarComponent, + uuid: "mock-uuid-2", + parentUUID: "mock-uuid-1", + }, + { + ...searchBarComponent, + uuid: "mock-uuid-3", + parentUUID: "mock-uuid-0", + }, +]; + +it("removes component and its children from the active PageState", () => { + mockStore({ + pages: { + activePageName: "pagename", + pages: { + pagename: { + componentTree: initialTree, + cssImports: [], + filepath: "unused", + }, + }, + }, + }); + + useStudioStore.getState().actions.removeComponent("mock-uuid-1"); + const updatedTree = + useStudioStore.getState().pages.pages["pagename"].componentTree; + expect(updatedTree).toEqual([initialTree[0], initialTree[3]]); +}); diff --git a/packages/studio/tests/store/StudioActions/saveChanges.test.ts b/packages/studio-ui/tests/store/StudioActions/saveChanges.test.ts similarity index 76% rename from packages/studio/tests/store/StudioActions/saveChanges.test.ts rename to packages/studio-ui/tests/store/StudioActions/saveChanges.test.ts index ffb57b4dc..4a2579775 100644 --- a/packages/studio/tests/store/StudioActions/saveChanges.test.ts +++ b/packages/studio-ui/tests/store/StudioActions/saveChanges.test.ts @@ -1,7 +1,7 @@ import useStudioStore from "../../../src/store/useStudioStore"; import * as sendMessageModule from "../../../src/messaging/sendMessage"; import mockStore from "../../__utils__/mockStore"; -import { FileMetadata, FileMetadataKind, MessageID } from "@yext/studio-plugin"; +import { MessageID } from "@yext/studio-plugin"; import { PagesRecord } from "../../../src/store/models/slices/PageSlice"; const mockPages: PagesRecord = { @@ -12,15 +12,6 @@ const mockPages: PagesRecord = { }, }; -const mockUUIDToFileMetadata: Record = { - "module-uuid": { - kind: FileMetadataKind.Module, - componentTree: [], - metadataUUID: "module-uuid", - filepath: "mock-filepath", - }, -}; - beforeEach(() => { mockStore({ pages: { @@ -30,9 +21,6 @@ beforeEach(() => { pagesToUpdate: new Set(["UpdateMe"]), }, }, - fileMetadatas: { - UUIDToFileMetadata: mockUUIDToFileMetadata, - }, }); }); @@ -42,7 +30,6 @@ it("sends pending changes to server to update files", async () => { expect(sendMessageSpy).toBeCalledTimes(1); expect(sendMessageSpy).toBeCalledWith(MessageID.SaveChanges, { pageNameToPageState: mockPages, - UUIDToFileMetadata: mockUUIDToFileMetadata, pendingChanges: { pagesToRemove: ["RemoveMe"], pagesToUpdate: ["UpdateMe"], diff --git a/packages/studio/tests/store/StudioActions/updateActiveComponentProps.test.ts b/packages/studio-ui/tests/store/StudioActions/updateActiveComponentProps.test.ts similarity index 100% rename from packages/studio/tests/store/StudioActions/updateActiveComponentProps.test.ts rename to packages/studio-ui/tests/store/StudioActions/updateActiveComponentProps.test.ts diff --git a/packages/studio/tests/store/createAccountContentSlice/createAccountContentSlice.test.tsx b/packages/studio-ui/tests/store/createAccountContentSlice/createAccountContentSlice.test.tsx similarity index 100% rename from packages/studio/tests/store/createAccountContentSlice/createAccountContentSlice.test.tsx rename to packages/studio-ui/tests/store/createAccountContentSlice/createAccountContentSlice.test.tsx diff --git a/packages/studio/tests/store/createAccountContentSlice/utils.test.tsx b/packages/studio-ui/tests/store/createAccountContentSlice/utils.test.tsx similarity index 100% rename from packages/studio/tests/store/createAccountContentSlice/utils.test.tsx rename to packages/studio-ui/tests/store/createAccountContentSlice/utils.test.tsx diff --git a/packages/studio-ui/tests/store/createFileMetadataSlice/createFileMetadataSlice.test.tsx b/packages/studio-ui/tests/store/createFileMetadataSlice/createFileMetadataSlice.test.tsx new file mode 100644 index 000000000..c0c9b3b84 --- /dev/null +++ b/packages/studio-ui/tests/store/createFileMetadataSlice/createFileMetadataSlice.test.tsx @@ -0,0 +1,54 @@ +import { + ComponentMetadata, + FileMetadataKind, + PropValueType, +} from "@yext/studio-plugin"; +import useStudioStore from "../../../src/store/useStudioStore"; +import { FileMetadataSliceStates } from "../../../src/store/models/slices/FileMetadataSlice"; +import mockStore from "../../__utils__/mockStore"; + +const componentMetadata: ComponentMetadata = { + kind: FileMetadataKind.Component, + metadataUUID: "mock-metadata-uuid", + filepath: "mock-filepath", + propShape: { + myText: { + type: PropValueType.string, + tooltip: "a random string", + required: false, + }, + }, +}; + +it("returns a FileMetadata using getFileMetadata", () => { + setInitialState({ + UUIDToFileMetadata: { + "uuid-1": componentMetadata, + }, + }); + const fileMetadata = useStudioStore + .getState() + .fileMetadatas.getFileMetadata("uuid-1"); + expect(fileMetadata).toEqual(componentMetadata); +}); + +it("updates UUIDToImportedComponent using setImportedComponent", () => { + const importedComponent = () =>
hello world
; + const newImportedComponents = { + Banner: importedComponent, + }; + useStudioStore + .getState() + .fileMetadatas.setImportedComponent("Banner", importedComponent); + const UUIDToImportedComponent = + useStudioStore.getState().fileMetadatas.UUIDToImportedComponent; + expect(UUIDToImportedComponent).toEqual(newImportedComponents); +}); + +function setInitialState(initialState: Partial): void { + const baseState: FileMetadataSliceStates = { + UUIDToFileMetadata: {}, + UUIDToImportedComponent: {}, + }; + mockStore({ fileMetadatas: { ...baseState, ...initialState } }); +} diff --git a/packages/studio/tests/store/createPageSlice/activeEntityActions.test.ts b/packages/studio-ui/tests/store/createPageSlice/activeEntityActions.test.ts similarity index 100% rename from packages/studio/tests/store/createPageSlice/activeEntityActions.test.ts rename to packages/studio-ui/tests/store/createPageSlice/activeEntityActions.test.ts diff --git a/packages/studio/tests/store/createPageSlice/activePageActions.test.ts b/packages/studio-ui/tests/store/createPageSlice/activePageActions.test.ts similarity index 100% rename from packages/studio/tests/store/createPageSlice/activePageActions.test.ts rename to packages/studio-ui/tests/store/createPageSlice/activePageActions.test.ts diff --git a/packages/studio/tests/store/createPageSlice/pageActions.test.ts b/packages/studio-ui/tests/store/createPageSlice/pageActions.test.ts similarity index 100% rename from packages/studio/tests/store/createPageSlice/pageActions.test.ts rename to packages/studio-ui/tests/store/createPageSlice/pageActions.test.ts diff --git a/packages/studio/tests/store/createPageSlice/pageComponentActions.test.ts b/packages/studio-ui/tests/store/createPageSlice/pageComponentActions.test.ts similarity index 100% rename from packages/studio/tests/store/createPageSlice/pageComponentActions.test.ts rename to packages/studio-ui/tests/store/createPageSlice/pageComponentActions.test.ts diff --git a/packages/studio/tests/store/createSiteSettings.test.ts b/packages/studio-ui/tests/store/createSiteSettings.test.ts similarity index 100% rename from packages/studio/tests/store/createSiteSettings.test.ts rename to packages/studio-ui/tests/store/createSiteSettings.test.ts diff --git a/packages/studio/tests/tsconfig.json b/packages/studio-ui/tests/tsconfig.json similarity index 85% rename from packages/studio/tests/tsconfig.json rename to packages/studio-ui/tests/tsconfig.json index 8e3f1c089..79aeb2055 100644 --- a/packages/studio/tests/tsconfig.json +++ b/packages/studio-ui/tests/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "../tsconfig.json", - "include": ["**/*", "../src/index.d.ts"], + "include": ["**/*", "../src/global.d.ts"], "compilerOptions": { "noEmit": true, "types": [ diff --git a/packages/studio/tests/utils/PageDataValidator.test.ts b/packages/studio-ui/tests/utils/PageDataValidator.test.ts similarity index 100% rename from packages/studio/tests/utils/PageDataValidator.test.ts rename to packages/studio-ui/tests/utils/PageDataValidator.test.ts diff --git a/packages/studio/tests/utils/PropValueHelpers.test.ts b/packages/studio-ui/tests/utils/PropValueHelpers.test.ts similarity index 100% rename from packages/studio/tests/utils/PropValueHelpers.test.ts rename to packages/studio-ui/tests/utils/PropValueHelpers.test.ts diff --git a/packages/studio/tests/utils/TemplateExpressionFormatter.test.ts b/packages/studio-ui/tests/utils/TemplateExpressionFormatter.test.ts similarity index 100% rename from packages/studio/tests/utils/TemplateExpressionFormatter.test.ts rename to packages/studio-ui/tests/utils/TemplateExpressionFormatter.test.ts diff --git a/packages/studio/tests/utils/filterEntityData.test.tsx b/packages/studio-ui/tests/utils/filterEntityData.test.tsx similarity index 100% rename from packages/studio/tests/utils/filterEntityData.test.tsx rename to packages/studio-ui/tests/utils/filterEntityData.test.tsx diff --git a/packages/studio/tests/utils/getPropsForPreview.test.ts b/packages/studio-ui/tests/utils/getPropsForPreview.test.ts similarity index 85% rename from packages/studio/tests/utils/getPropsForPreview.test.ts rename to packages/studio-ui/tests/utils/getPropsForPreview.test.ts index 535409de2..61e26fd2a 100644 --- a/packages/studio/tests/utils/getPropsForPreview.test.ts +++ b/packages/studio-ui/tests/utils/getPropsForPreview.test.ts @@ -70,18 +70,6 @@ const arrayPropMetadata: PropMetadata = { }, required: false, }; - -const parentPropShape: PropShape = { - title: { - type: PropValueType.string, - required: false, - }, - parentExpression: { - type: PropValueType.string, - required: false, - }, -}; - it("returns value as is for primitive prop of type Literal", () => { const transformedProps = getPropsForPreview( { @@ -176,32 +164,6 @@ describe("expression value handling", () => { }); }); - it("can handle expressions that reference literal props", () => { - const transformedProps = transformFooProp("props.title", { - title: { - kind: PropValueKind.Literal, - valueType: PropValueType.string, - value: "the title prop", - }, - }); - expect(transformedProps).toEqual({ - foo: "the title prop", - }); - }); - - it("can handle expressions that reference expression props", () => { - const transformedProps = transformFooProp("props.title", { - title: { - kind: PropValueKind.Expression, - valueType: PropValueType.string, - value: "document.name", - }, - }); - expect(transformedProps).toEqual({ - foo: "office space", - }); - }); - it("can handle expression that references an array of objects", () => { const transformedProps = getPropsForPreview( { @@ -274,22 +236,6 @@ describe("template string literal value handling", () => { foo: "1 ${unknownSource.city} 2", }); }); - - it("can handle an expression prop that references a parent expression prop", () => { - const transformedProps = transformFooProp( - "`childProp - ${props.parentExpression}`", - { - parentExpression: { - kind: PropValueKind.Expression, - valueType: PropValueType.string, - value: "`parentProp - ${document.name}`", - }, - } - ); - expect(transformedProps).toEqual({ - foo: "childProp - parentProp - office space", - }); - }); }); it("converts expressions using streams data into bracket syntax", () => { @@ -313,7 +259,7 @@ it("applies expression sources for streams data", () => { }); }); -function transformFooProp(value: string, parentProps: PropValues = {}) { +function transformFooProp(value: string) { return getPropsForPreview( { foo: { @@ -325,11 +271,6 @@ function transformFooProp(value: string, parentProps: PropValues = {}) { propShape, { ...expressionSources, - props: getPropsForPreview( - parentProps, - parentPropShape, - expressionSources - ), } ); } diff --git a/packages/studio/tests/utils/removeTopLevelFragments.test.ts b/packages/studio-ui/tests/utils/removeTopLevelFragments.test.ts similarity index 52% rename from packages/studio/tests/utils/removeTopLevelFragments.test.ts rename to packages/studio-ui/tests/utils/removeTopLevelFragments.test.ts index 1eb93dee8..fa964ca30 100644 --- a/packages/studio/tests/utils/removeTopLevelFragments.test.ts +++ b/packages/studio-ui/tests/utils/removeTopLevelFragments.test.ts @@ -1,8 +1,6 @@ import { ComponentState, ComponentStateKind, - FileMetadata, - FileMetadataKind, PageState, StandardComponentState, } from "@yext/studio-plugin"; @@ -50,41 +48,3 @@ it("removes top level fragments from a PageState record", () => { }, ]); }); - -it("removes top level fragments from a FileMetadata record", () => { - const componentTree: ComponentState[] = [ - { - kind: ComponentStateKind.Fragment, - uuid: "fragment-uuid", - }, - childComponent, - ]; - const fileMetadataRecord: Record = { - "module-metadata-uuid": { - kind: FileMetadataKind.Module, - componentTree, - metadataUUID: "module-metadata-uuid", - filepath: "/unused", - }, - "component-metadata-uuid": { - kind: FileMetadataKind.Component, - filepath: "/unused", - metadataUUID: "component-metadata-uuid", - }, - }; - - const updatedRecord = removeTopLevelFragments(fileMetadataRecord); - - expect(updatedRecord["component-metadata-uuid"]).toEqual( - fileMetadataRecord["component-metadata-uuid"] - ); - expect(updatedRecord["module-metadata-uuid"]).toEqual({ - ...fileMetadataRecord["module-metadata-uuid"], - componentTree: [ - { - ...childComponent, - parentUUID: undefined, - }, - ], - }); -}); diff --git a/packages/studio-ui/tsconfig.json b/packages/studio-ui/tsconfig.json new file mode 100644 index 000000000..70d77dd47 --- /dev/null +++ b/packages/studio-ui/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "lib", + "types": ["@yext/studio-plugin/virtual-module", "vite-plugin-svgr/client"] + }, + "include": ["src"] +} diff --git a/packages/studio-ui/vite.config.ts b/packages/studio-ui/vite.config.ts new file mode 100644 index 000000000..6a068f8e5 --- /dev/null +++ b/packages/studio-ui/vite.config.ts @@ -0,0 +1,35 @@ +// vite.config.js +import { resolve } from "path"; +import { defineConfig, PluginOption } from "vite"; +import svgr from "vite-plugin-svgr"; +import { visualizer } from "rollup-plugin-visualizer"; +import dts from "vite-plugin-dts"; +import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js"; + +export default defineConfig({ + plugins: [ + svgr(), + dts(), + cssInjectedByJsPlugin(), + visualizer() as PluginOption, + ], + build: { + outDir: "lib", + sourcemap: true, + lib: { + entry: resolve(__dirname, "src/index.ts"), + formats: ["es"], + fileName: "src/index", + }, + rollupOptions: { + external: [ + "virtual_yext-studio-git-data", + "virtual_yext-studio", + "@pathToUserProjectRoot/tailwind.config", + "react", + "react-dom", + "react/jsx-runtime", + ], + }, + }, +}); diff --git a/packages/studio/.prettierignore b/packages/studio/.prettierignore deleted file mode 100644 index 0b9be3b37..000000000 --- a/packages/studio/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -dist/ -lib/ -coverage/ -src/tailwind-full.css \ No newline at end of file diff --git a/packages/studio/package.json b/packages/studio/package.json index f51349439..71522c355 100644 --- a/packages/studio/package.json +++ b/packages/studio/package.json @@ -1,6 +1,6 @@ { "name": "@yext/studio", - "version": "0.20.0", + "version": "0.22.0", "types": "./lib/types.d.ts", "type": "module", "bin": { @@ -10,55 +10,23 @@ "dev": "tsc --watch --preserveWatchOutput -p tsconfig.node.json & tsc --watch --preserveWatchOutput -p tsconfig.json", "build": "run-script-os", "build:default": "npm run build:windows && chmod 755 lib/bin/studio.js", - "build:windows": "rimraf lib && tsc -p tsconfig.node.json && tsc -p tsconfig.json", - "preview": "vite preview", - "test": "jest", - "typecheck-jest": "npx tsc -p tests/tsconfig.json" + "build:windows": "rimraf lib && tsc -p tsconfig.node.json && tsc -p tsconfig.json" }, "dependencies": { - "@dhmk/zustand-lens": "^2.0.5", - "@minoru/react-dnd-treeview": "^3.4.1", - "@restart/ui": "^1.5.2", "@vitejs/plugin-react": "^4.0.4", - "@yext/studio-plugin": "0.20.0", + "@yext/studio-plugin": "0.22.0", + "@yext/studio-ui": "0.22.0", "autoprefixer": "^10.4.14", "cac": "^6.7.14", - "classnames": "^2.3.2", "cross-env": "^7.0.3", - "immer": "^9.0.21", - "lodash": "^4.17.21", - "path-browserify": "^1.0.1", "postcss": "^8.4.27", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-modal": "3.16.1", - "react-select": "^5.7.4", - "react-toastify": "^9.1.1", - "react-tooltip": "^5.18.0", - "tailwind-merge": "^1.8.1", - "tailwindcss": "^3.3.3", "vite": "^4.4.7", - "vite-plugin-svgr": "^2.4.0", - "zundo": "2.0.0-beta.12", - "zustand": "^4.3.2" + "vite-plugin-svgr": "^2.4.0" }, "devDependencies": { - "@babel/core": "^7.20.5", - "@babel/plugin-syntax-flow": "^7.18.6", - "@babel/plugin-transform-react-jsx": "^7.19.0", - "@rollup/plugin-typescript": "^10.0.1", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.4.0", - "@testing-library/user-event": "^14.4.3", - "@types/jest": "^29.2.4", - "@types/lodash": "^4.14.191", "@types/node": "^18.11.15", - "@types/path-browserify": "^1.0.0", - "@types/react": "^18.0.26", - "@types/react-dom": "^18.0.10", - "@types/react-modal": "3.13.1", - "jest": "^29.5.0", - "jest-environment-jsdom": "^29.3.1", - "resize-observer-polyfill": "^1.5.1" + "@types/react": "^18.0.26" } } diff --git a/packages/studio/src/components/AddElementMenu/ElementSelector.tsx b/packages/studio/src/components/AddElementMenu/ElementSelector.tsx deleted file mode 100644 index a9ced1fbf..000000000 --- a/packages/studio/src/components/AddElementMenu/ElementSelector.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { FileMetadataKind, ValidFileMetadata } from "@yext/studio-plugin"; -import { useCallback } from "react"; -import useStudioStore from "../../store/useStudioStore"; -import path from "path-browserify"; -import { ElementType } from "./AddElementMenu"; -import renderIconForType from "../common/renderIconForType"; - -interface ElementSelectorProps { - activeType: ElementType; - afterSelect?: () => void; -} - -/** - * The list of available, addable elements for the current activeType. - */ -export default function ElementSelector({ - activeType, - afterSelect, -}: ElementSelectorProps) { - const UUIDToFileMetadata = useStudioStore((store) => { - return store.fileMetadatas.UUIDToFileMetadata; - }); - - const addableElements = Object.values(UUIDToFileMetadata).filter( - (metadata): metadata is ValidFileMetadata => { - if (activeType === ElementType.Components) { - return ( - metadata.kind === FileMetadataKind.Component && - !metadata.acceptsChildren - ); - } else if (activeType === ElementType.Containers) { - return !!( - metadata.kind === FileMetadataKind.Component && - metadata.acceptsChildren - ); - } else { - return metadata.kind === FileMetadataKind.Module; - } - } - ); - - if (addableElements.length === 0) { - return ( -
- Nothing to see here! -
- ); - } - - return ( -
- {addableElements.map((metadata) => { - return ( -
- ); -} - -function Option({ - metadata, - activeType, - afterSelect, -}: { - metadata: ValidFileMetadata; -} & ElementSelectorProps) { - const componentName = path.basename(metadata.filepath, ".tsx"); - const moduleMetadataBeingEdited = useStudioStore((store) => - store.actions.getModuleMetadataBeingEdited() - ); - - const addComponent = useStudioStore((store) => { - return store.actions.addComponent; - }); - - const handleSelect = useCallback(() => { - addComponent(metadata); - afterSelect?.(); - }, [afterSelect, addComponent, metadata]); - - // Prevent users from adding infinite looping modules. - const isSameAsActiveModule = - moduleMetadataBeingEdited?.metadataUUID === metadata.metadataUUID; - - return ( - - ); -} diff --git a/packages/studio/src/components/ModuleActions.tsx b/packages/studio/src/components/ModuleActions.tsx deleted file mode 100644 index e210cbe0c..000000000 --- a/packages/studio/src/components/ModuleActions.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { - FileMetadata, - FileMetadataKind, - TypeGuards, - ComponentState, -} from "@yext/studio-plugin"; -import ModuleEditActions from "./ModuleActions/ModuleEditActions"; -import CreateModuleButton from "./ModuleActions/CreateModuleButton"; - -/** - * Renders either a {@link CreateModuleButton} or the {@link ModuleEditActions}, depending - * on if the active Component is already a Module or not. - * - * @param metadata - The {@link FileMetadata} of the active Component. - * @param state - The {@link ComponentState} of the active Component. - */ -export default function ModuleActions(props: { - metadata: FileMetadata; - state: ComponentState; -}) { - const { metadata, state } = props; - const isModule = - metadata.kind === FileMetadataKind.Module && - (TypeGuards.isModuleState(state) || TypeGuards.isRepeaterState(state)); - - return ( -
- Module Actions -
- {isModule ? ( - - ) : ( - - )} -
-
- ); -} diff --git a/packages/studio/src/components/ModuleActions/ActionIconWrapper.tsx b/packages/studio/src/components/ModuleActions/ActionIconWrapper.tsx deleted file mode 100644 index 5b762fc7e..000000000 --- a/packages/studio/src/components/ModuleActions/ActionIconWrapper.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import classNames from "classnames"; -import { PropsWithChildren, useMemo } from "react"; -import { Tooltip } from "react-tooltip"; - -import { v4 } from "uuid"; - -export default function ActionIconWrapper( - props: PropsWithChildren<{ - tooltip: string; - disabled?: boolean; - }> -) { - const anchorId = useMemo(() => v4(), []); - const className = classNames("rounded p-1", { - "text-gray-400": props.disabled, - "text-violet-600 hover:bg-slate-200": !props.disabled, - }); - return ( -
- {props.children} - -
- ); -} diff --git a/packages/studio/src/components/ModuleActions/CreateModuleButton.tsx b/packages/studio/src/components/ModuleActions/CreateModuleButton.tsx deleted file mode 100644 index ba66b1b40..000000000 --- a/packages/studio/src/components/ModuleActions/CreateModuleButton.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import FormModal, { FormData } from "../common/FormModal"; -import ButtonWithModal, { - renderModalFunction, -} from "../common/ButtonWithModal"; -import useStudioStore from "../../store/useStudioStore"; -import { useCallback, useState } from "react"; -import { ComponentStateKind } from "@yext/studio-plugin"; - -type CreateModuleForm = { modulePath: string }; - -const formData: FormData = { - modulePath: { description: "Give the module a name:" }, -}; - -/** - * Renders a button for creating a module. - */ -export default function CreateModuleButton(): JSX.Element | null { - const [getActiveComponentState, createModule] = useStudioStore((store) => [ - store.actions.getActiveComponentState, - store.createModule, - ]); - const [errorMessage, setErrorMessage] = useState(""); - - const handleModalSave = useCallback( - (form: CreateModuleForm) => { - try { - createModule(form.modulePath); - return true; - } catch (err: unknown) { - if (err instanceof Error) { - setErrorMessage(err.message); - return false; - } else { - throw err; - } - } - }, - [setErrorMessage, createModule] - ); - - const renderModal: renderModalFunction = useCallback( - (isOpen, handleClose) => { - return ( - - ); - }, - [errorMessage, handleModalSave] - ); - - const activeComponentState = getActiveComponentState(); - if ( - !activeComponentState || - activeComponentState.kind === ComponentStateKind.Module - ) { - return null; - } - - return ( - - ); -} diff --git a/packages/studio/src/components/ModuleActions/DeleteModuleButton.tsx b/packages/studio/src/components/ModuleActions/DeleteModuleButton.tsx deleted file mode 100644 index 1d3b87bc3..000000000 --- a/packages/studio/src/components/ModuleActions/DeleteModuleButton.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { ReactComponent as DeleteModuleIcon } from "../../icons/deletemodule.svg"; -import ButtonWithModal, { - renderModalFunction, -} from "../common/ButtonWithModal"; -import { useCallback, useMemo } from "react"; -import { - ComponentStateHelpers, - ComponentStateKind, - ModuleMetadata, - TypeGuards, -} from "@yext/studio-plugin"; -import DialogModal from "../common/DialogModal"; -import path from "path-browserify"; -import useStudioStore from "../../store/useStudioStore"; -import ActionIconWrapper from "./ActionIconWrapper"; - -/** - * When clicked, stages a module for deletion. - * When the changes are committed, the module file will itself be deleted - */ -export default function DeleteModuleButton({ - metadata, -}: { - metadata: ModuleMetadata; -}) { - const moduleName = path.basename(metadata.filepath, ".tsx"); - const detachAllModuleInstances = useStudioStore( - (store) => store.pages.detachAllModuleInstances - ); - const setActiveComponentUUID = useStudioStore( - (store) => store.pages.setActiveComponentUUID - ); - const removeFileMetadata = useStudioStore( - (store) => store.fileMetadatas.removeFileMetadata - ); - const pagesRecord = useStudioStore((store) => store.pages.pages); - - const isUsedInRepeater = Object.values(pagesRecord).some((pageState) => - pageState.componentTree.some( - (c) => - TypeGuards.isRepeaterState(c) && - c.repeatedComponent.metadataUUID === metadata.metadataUUID - ) - ); - - const moduleUsages = Object.keys(pagesRecord).reduce( - (usageList, pageName) => { - const usageCount = pagesRecord[pageName].componentTree - .filter(TypeGuards.isEditableComponentState) - .map(ComponentStateHelpers.extractRepeatedState) - .filter( - (c) => - c.kind === ComponentStateKind.Module && - c.metadataUUID === metadata.metadataUUID - ).length; - usageList.push({ - pageName, - usageCount, - }); - return usageList; - }, - [] as { pageName: string; usageCount: number }[] - ); - - const handleDelete = useCallback(() => { - setActiveComponentUUID(undefined); - detachAllModuleInstances(metadata); - removeFileMetadata(metadata.metadataUUID); - }, [ - setActiveComponentUUID, - detachAllModuleInstances, - metadata, - removeFileMetadata, - ]); - - const modalBody = useMemo(() => { - return ( -
-
- Deleting this module will remove its status as a module, removing it - from the Insert panel, and detach all other instances of it across - your site. This will not delete the page elements themselves. - {moduleUsages.length > 0 && " This module is found on:"} -
- {moduleUsages.length > 0 && renderModuleUsages(moduleUsages)} -
Press 'Delete' to confirm this."
-
- ); - }, [moduleUsages]); - - const renderModal: renderModalFunction = useCallback( - (isOpen, handleClose) => { - return ( - - ); - }, - [handleDelete, modalBody] - ); - - const tooltipText = isUsedInRepeater - ? "Unable to delete module while it is used in a list anywhere in the site" - : "Delete"; - - return ( - <> - - - - } - /> - - ); -} - -function renderModuleUsages( - moduleUsages: { pageName: string; usageCount: number }[] -) { - const usages = moduleUsages.filter(({ usageCount }) => usageCount > 0); - if (usages.length === 0) { - return null; - } - - const usageCountText = (usageCount: number) => { - if (usageCount > 1) { - return `${usageCount} instances`; - } - return `1 instance`; - }; - - return ( -
    - {usages.map(({ pageName, usageCount }) => { - return ( -
  • - {pageName}: {usageCountText(usageCount)} -
  • - ); - })} -
- ); -} diff --git a/packages/studio/src/components/ModuleActions/DetachModuleButton.tsx b/packages/studio/src/components/ModuleActions/DetachModuleButton.tsx deleted file mode 100644 index c312b1480..000000000 --- a/packages/studio/src/components/ModuleActions/DetachModuleButton.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import ActionIconWrapper from "./ActionIconWrapper"; -import { ReactComponent as DetachModuleIcon } from "../../icons/detachmodule.svg"; -import { useCallback } from "react"; -import { - ComponentStateHelpers, - ModuleMetadata, - ModuleState, - RepeaterState, - TypeGuards, -} from "@yext/studio-plugin"; -import useStudioStore from "../../store/useStudioStore"; - -export default function DetachModuleButton(props: { - metadata: ModuleMetadata; - state: ModuleState | RepeaterState; -}) { - const { metadata, state } = props; - const detachModuleInstance = useStudioStore( - (store) => store.actions.detachModuleInstance - ); - - const isRepeater = TypeGuards.isRepeaterState(state); - - const handleClick = useCallback(() => { - !isRepeater && detachModuleInstance(metadata, state); - }, [detachModuleInstance, metadata, state, isRepeater]); - - const moduleName = - ComponentStateHelpers.extractRepeatedState(state).componentName; - const tooltipText = isRepeater - ? "Unable to detach module instance since it is in a list" - : "Detach"; - - return ( - - ); -} diff --git a/packages/studio/src/components/ModuleActions/EditModuleButton.tsx b/packages/studio/src/components/ModuleActions/EditModuleButton.tsx deleted file mode 100644 index 50010c9cc..000000000 --- a/packages/studio/src/components/ModuleActions/EditModuleButton.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { - ComponentStateHelpers, - ModuleState, - RepeaterState, -} from "@yext/studio-plugin"; -import { useCallback } from "react"; -import { ReactComponent as EditModuleIcon } from "../../icons/editmodule.svg"; -import useStudioStore from "../../store/useStudioStore"; -import ActionIconWrapper from "./ActionIconWrapper"; - -export default function EditModuleButton({ - state, -}: { - state: ModuleState | RepeaterState; -}) { - const [setModuleUUIDBeingEdited, setActiveComponentUUID] = useStudioStore( - (store) => [ - store.pages.setModuleUUIDBeingEdited, - store.pages.setActiveComponentUUID, - ] - ); - - const handleClick = useCallback(() => { - setActiveComponentUUID(undefined); - setModuleUUIDBeingEdited(state.uuid); - }, [state.uuid, setModuleUUIDBeingEdited, setActiveComponentUUID]); - - const moduleName = - ComponentStateHelpers.extractRepeatedState(state).componentName; - - return ( - - ); -} diff --git a/packages/studio/src/components/ModuleActions/ModuleEditActions.tsx b/packages/studio/src/components/ModuleActions/ModuleEditActions.tsx deleted file mode 100644 index 5011ed645..000000000 --- a/packages/studio/src/components/ModuleActions/ModuleEditActions.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { - ModuleState, - ModuleMetadata, - RepeaterState, -} from "@yext/studio-plugin"; -import DeleteModuleButton from "./DeleteModuleButton"; -import DetachModuleButton from "./DetachModuleButton"; -import EditModuleButton from "./EditModuleButton"; - -/** - * Displays a list of available actions for manipulating a Module. - */ -export default function ModuleEditActions({ - metadata, - state, -}: { - metadata: ModuleMetadata; - state: ModuleState | RepeaterState; -}) { - return ( - <> - - - - - ); -} diff --git a/packages/studio/src/components/ModulePreview.tsx b/packages/studio/src/components/ModulePreview.tsx deleted file mode 100644 index 068a24ece..000000000 --- a/packages/studio/src/components/ModulePreview.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Dispatch, SetStateAction, useMemo } from "react"; -import ComponentTreePreview from "./ComponentTreePreview"; -import { ModuleState } from "@yext/studio-plugin"; -import useStudioStore from "../store/useStudioStore"; -import { ExpressionSources } from "../utils/getPropsForPreview"; -import { ITooltip } from "react-tooltip"; - -export default function ModulePreview(props: { - expressionSources: ExpressionSources; - previewProps: Record; - moduleState: ModuleState; - setTooltipProps: Dispatch>; -}) { - const { expressionSources, previewProps, moduleState, setTooltipProps } = - props; - - const getModuleMetadata = useStudioStore( - (store) => store.fileMetadatas.getModuleMetadata - ); - const componentTree = getModuleMetadata( - moduleState.metadataUUID - ).componentTree; - - const moduleExpressionSources = useMemo( - () => ({ - ...expressionSources, - props: previewProps, - }), - [expressionSources, previewProps] - ); - - return ( - - ); -} diff --git a/packages/studio/src/components/PreviewPanel.tsx b/packages/studio/src/components/PreviewPanel.tsx deleted file mode 100644 index 94b288546..000000000 --- a/packages/studio/src/components/PreviewPanel.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { Dispatch, SetStateAction, useMemo } from "react"; -import useStudioStore from "../store/useStudioStore"; -import usePreviewProps from "../hooks/usePreviewProps"; -import ComponentTreePreview from "./ComponentTreePreview"; -import useRawSiteSettings from "../hooks/useRawSiteSettings"; -import { ComponentStateHelpers, TypeGuards } from "@yext/studio-plugin"; -import { get } from "lodash"; -import { ITooltip } from "react-tooltip"; - -export default function PreviewPanel(props: { - setTooltipProps: Dispatch>; -}) { - const { setTooltipProps } = props; - const [componentTree, moduleUUIDBeingEdited, getComponentState] = - useStudioStore((store) => [ - store.actions.getComponentTree(), - store.pages.moduleUUIDBeingEdited, - store.actions.getComponentState, - ]); - - const pageExpressionSources = usePageExpressionSources(); - - const state = moduleUUIDBeingEdited - ? getComponentState(componentTree, moduleUUIDBeingEdited) - : undefined; - const list = - state && TypeGuards.isRepeaterState(state) - ? get(pageExpressionSources, state.listExpression) - : undefined; - const item = Array.isArray(list) ? list[0] : undefined; - - const extractedState = - state && TypeGuards.isEditableComponentState(state) - ? ComponentStateHelpers.extractRepeatedState(state) - : undefined; - const parentPreviewProps = usePreviewProps( - extractedState, - pageExpressionSources, - item - ); - - const expressionSources = useMemo( - () => ({ - ...pageExpressionSources, - ...(moduleUUIDBeingEdited && { props: parentPreviewProps }), - }), - [pageExpressionSources, moduleUUIDBeingEdited, parentPreviewProps] - ); - - if (!componentTree) { - return null; - } - - return ( - - ); -} - -function usePageExpressionSources() { - const activeEntityData = useStudioStore((store) => - store.pages.getActiveEntityData() - ); - const rawSiteSettings = useRawSiteSettings(); - const pageExpressionSources = useMemo( - () => ({ - document: activeEntityData, - siteSettings: rawSiteSettings, - }), - [activeEntityData, rawSiteSettings] - ); - - return pageExpressionSources; -} diff --git a/packages/studio/src/components/PropsPanel.tsx b/packages/studio/src/components/PropsPanel.tsx deleted file mode 100644 index 56b56fdba..000000000 --- a/packages/studio/src/components/PropsPanel.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import useActiveComponentWithProps from "../hooks/useActiveComponentWithProps"; -import ActiveComponentPropEditors from "./ActiveComponentPropEditors"; -import { ComponentStateKind } from "@yext/studio-plugin"; -import ModuleActions from "./ModuleActions"; -import RepeaterPanel from "./RepeaterPanel"; -import Divider from "./common/Divider"; - -/** - * Renders prop editors for the active component selected by the user. - */ -export default function PropsPanel(): JSX.Element | null { - const activeComponentWithProps = useActiveComponentWithProps(); - if (!activeComponentWithProps) { - return null; - } - const { - activeComponentMetadata, - activeComponentState, - extractedComponentState, - propShape, - } = activeComponentWithProps; - - const isModule = extractedComponentState.kind === ComponentStateKind.Module; - - return ( - <> - {isModule && ( - <> - - - - )} - - - - ); -} diff --git a/packages/studio/src/components/RepeaterPanel.tsx b/packages/studio/src/components/RepeaterPanel.tsx deleted file mode 100644 index c1833b46a..000000000 --- a/packages/studio/src/components/RepeaterPanel.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { TypeGuards } from "@yext/studio-plugin"; -import Divider from "./common/Divider"; -import useActiveComponent from "../hooks/useActiveComponent"; -import MessageBubble from "./common/MessageBubble"; - -export default function RepeaterPanel() { - const { activeComponentState } = useActiveComponent(); - if ( - !activeComponentState || - !TypeGuards.isRepeaterState(activeComponentState) - ) { - return null; - } - - return ( -
- - -
- ); -} diff --git a/packages/studio/src/components/RepeaterPreview.tsx b/packages/studio/src/components/RepeaterPreview.tsx deleted file mode 100644 index 526f04d24..000000000 --- a/packages/studio/src/components/RepeaterPreview.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { RepeaterState } from "@yext/studio-plugin"; -import { get } from "lodash"; -import { Dispatch, SetStateAction, useCallback, useMemo } from "react"; -import { ExpressionSources } from "../utils/getPropsForPreview"; -import ComponentPreview from "./ComponentPreview"; -import { ITooltip } from "react-tooltip"; - -interface RepeaterPreviewProps { - repeaterState: RepeaterState; - expressionSources: ExpressionSources; - setTooltipProps: Dispatch>; -} - -/** - * Renders the preview for a Repeater component. - */ -export default function RepeaterPreview({ - repeaterState, - expressionSources, - setTooltipProps, -}: RepeaterPreviewProps): JSX.Element | null { - const { repeatedComponent, listExpression } = repeaterState; - const repeatedElementState = useMemo( - () => ({ - ...repeatedComponent, - uuid: repeaterState.uuid, - parentUUID: repeaterState.parentUUID, - }), - [repeatedComponent, repeaterState] - ); - - const renderRepeatedElement = useCallback( - (item: unknown, key: number | string) => ( - - ), - [repeatedElementState, expressionSources, setTooltipProps] - ); - - const list = get(expressionSources, listExpression) as unknown; - if (!Array.isArray(list)) { - console.warn( - `Unable to render list repeater. Expected "${listExpression}" to reference an array in `, - expressionSources - ); - return null; - } - return <>{list.map(renderRepeatedElement)}; -} diff --git a/packages/studio/src/icons/hexagon.svg b/packages/studio/src/icons/hexagon.svg deleted file mode 100644 index 2eec703a5..000000000 --- a/packages/studio/src/icons/hexagon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/studio/src/main.tsx b/packages/studio/src/main.tsx index 9c5588d5f..14484cffb 100644 --- a/packages/studio/src/main.tsx +++ b/packages/studio/src/main.tsx @@ -1,10 +1,8 @@ import React from "react"; import ReactDOM from "react-dom/client"; -import App from "./App"; +import { App, hotReloadStore, StudioHMRUpdateID } from "@yext/studio-ui"; +import type { StudioHMRPayload } from "@yext/studio-plugin"; import "./tailwind-directives.css"; -import "react-tooltip/dist/react-tooltip.css"; -import { StudioHMRPayload, StudioHMRUpdateID } from "@yext/studio-plugin"; -import hotReloadStore from "./store/hotReloadStore"; if (import.meta.hot) { import.meta.hot.on(StudioHMRUpdateID, (hmrPayload: StudioHMRPayload) => { diff --git a/packages/studio/src/store/createModuleAction.ts b/packages/studio/src/store/createModuleAction.ts deleted file mode 100644 index b50f6b790..000000000 --- a/packages/studio/src/store/createModuleAction.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { StudioStore } from "./models/StudioStore"; -import path from "path-browserify"; -import { - ComponentState, - ComponentTreeHelpers, - FileMetadataKind, - ModuleMetadata, - PropValueType, -} from "@yext/studio-plugin"; -import { differenceWith, isEqual } from "lodash"; -import { v4 } from "uuid"; - -export default function getCreateModuleAction( - get: () => StudioStore -): StudioStore["createModule"] { - function throwIfInvalidFilepath(filepath: string) { - const modulesFolder = get().studioConfig.paths.modules; - const moduleName = path.basename(filepath, ".tsx"); - if (!filepath.startsWith(modulesFolder)) { - throw new Error( - `Error creating module: modulePath is invalid: "${path.relative( - modulesFolder, - filepath - )}".` - ); - } else if (moduleName.charAt(0) !== moduleName.charAt(0).toUpperCase()) { - throw new Error( - "Error creating module: Module names must start with an uppercase letter." - ); - } else if ( - Object.values(get().fileMetadatas.UUIDToFileMetadata).some( - (fileMetadata) => - path.basename(fileMetadata.filepath, ".tsx") === moduleName - ) - ) { - throw new Error( - `Error creating module: module name "${moduleName}" is already used.` - ); - } - } - - function createModuleMetadata( - filepath: string, - descendants: ComponentState[], - activeComponentState: ComponentState - ): ModuleMetadata { - const moduleMetadata: ModuleMetadata = { - kind: FileMetadataKind.Module, - componentTree: [ - { ...activeComponentState, parentUUID: undefined }, - ...descendants, - ], - metadataUUID: v4(), - filepath, - propShape: {}, - }; - if (get().studioConfig.isPagesJSRepo) { - moduleMetadata.propShape = { - document: { - type: PropValueType.Record, - recordKey: "string", - recordValue: "any", - required: true, - }, - }; - } - return moduleMetadata; - } - - function createModule( - filepath: string, - componentTree: ComponentState[], - activeComponentState: ComponentState - ) { - const descendants = ComponentTreeHelpers.getDescendants( - activeComponentState, - componentTree - ); - const moduleMetadata: ModuleMetadata = createModuleMetadata( - filepath, - descendants, - activeComponentState - ); - get().fileMetadatas.setFileMetadata( - moduleMetadata.metadataUUID, - moduleMetadata - ); - const moduleState = get().actions.createComponentState(moduleMetadata); - const updatedPageComponentTree: ComponentState[] = differenceWith( - componentTree, - descendants, - isEqual - ).map((c) => { - if (c.uuid === activeComponentState.uuid) { - return { - ...moduleState, - parentUUID: c.parentUUID, - }; - } - return c; - }); - get().actions.updateComponentTree(updatedPageComponentTree); - get().pages.setActiveComponentUUID(moduleState.uuid); - } - - return (modulePath: string) => { - if (!modulePath) { - throw new Error("Error creating module: a modulePath is required."); - } - const modulesFolder = get().studioConfig.paths.modules; - const filepath = path.join(modulesFolder, modulePath + ".tsx"); - throwIfInvalidFilepath(filepath); - - const componentTree = get().actions.getComponentTree() ?? []; - const activeComponentState = get().actions.getActiveComponentState(); - if (!activeComponentState) { - throw new Error("Tried to create module without active component."); - } - createModule(filepath, componentTree, activeComponentState); - }; -} diff --git a/packages/studio/src/store/slices/createFileMetadataSlice.ts b/packages/studio/src/store/slices/createFileMetadataSlice.ts deleted file mode 100644 index 540ac7b2f..000000000 --- a/packages/studio/src/store/slices/createFileMetadataSlice.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { - ComponentMetadata, - ComponentState, - FileMetadata, - FileMetadataKind, - ModuleMetadata, -} from "@yext/studio-plugin"; -import initialStudioData from "virtual_yext-studio"; -import FileMetadataSlice from "../models/slices/FileMetadataSlice"; -import { SliceCreator } from "../models/utils"; -import removeTopLevelFragments from "../../utils/removeTopLevelFragments"; - -const createFileMetadataSlice: SliceCreator = ( - set, - get -) => ({ - UUIDToFileMetadata: removeTopLevelFragments( - initialStudioData.UUIDToFileMetadata - ), - UUIDToImportedComponent: {}, - setFileMetadata: (metadataUUID: string, metadata: FileMetadata) => - set((store) => { - store.UUIDToFileMetadata[metadataUUID] = metadata; - }), - getFileMetadata: (metadataUUID: string) => - get().UUIDToFileMetadata[metadataUUID], - removeFileMetadata: (metadataUUID: string) => - set((store) => { - const metadata = store.UUIDToFileMetadata[metadataUUID]; - if (metadata.kind === FileMetadataKind.Module) { - delete store.UUIDToFileMetadata[metadataUUID]; - } else { - console.error( - "removeFileMetadata is only allowed for modules, not:", - metadata.kind - ); - } - }), - getComponentMetadata: (metadataUUID) => { - const fileMetadata = get().getFileMetadata(metadataUUID); - assertIsComponentMetadata(fileMetadata); - return fileMetadata; - }, - getModuleMetadata: (metadataUUID) => { - const fileMetadata = get().getFileMetadata(metadataUUID); - assertIsModuleMetadata(fileMetadata); - return fileMetadata; - }, - setImportedComponent(uuid, importedComponent) { - set((store) => { - store.UUIDToImportedComponent[uuid] = importedComponent; - }); - }, - setComponentTreeInModule( - metadataUUID: string, - componentTree: ComponentState[] - ) { - set((store) => { - const fileMetadata = store.UUIDToFileMetadata[metadataUUID]; - assertIsModuleMetadata(fileMetadata); - fileMetadata.componentTree = componentTree; - }); - }, -}); - -function assertIsModuleMetadata( - fileMetadata?: FileMetadata -): asserts fileMetadata is ModuleMetadata { - if (fileMetadata?.kind !== FileMetadataKind.Module) { - throw new Error( - `Expected a ModuleMetadata, instead received ${JSON.stringify( - fileMetadata, - null, - 2 - )}.` - ); - } -} - -function assertIsComponentMetadata( - fileMetadata?: FileMetadata -): asserts fileMetadata is ComponentMetadata { - if (fileMetadata?.kind !== FileMetadataKind.Component) { - throw new Error( - `Expected a ComponentMetadata, instead received ${JSON.stringify( - fileMetadata, - null, - 2 - )}.` - ); - } -} - -export default createFileMetadataSlice; diff --git a/packages/studio/src/store/slices/pages/detachAllModuleInstances.ts b/packages/studio/src/store/slices/pages/detachAllModuleInstances.ts deleted file mode 100644 index 7339be374..000000000 --- a/packages/studio/src/store/slices/pages/detachAllModuleInstances.ts +++ /dev/null @@ -1,57 +0,0 @@ -import PageSlice from "../../models/slices/PageSlice"; -import { - ComponentState, - ComponentStateKind, - ComponentTreeHelpers, - ModuleMetadata, -} from "@yext/studio-plugin"; -import { v4 } from "uuid"; - -/** - * Creates an action that loops through all pages, and detaches all instances of the given module. - */ -export default function createDetachAllModuleInstances(get: () => PageSlice) { - return (metadata: ModuleMetadata) => { - const pagesSlice = get(); - pagesSlice.setActiveComponentUUID(undefined); - Object.keys(pagesSlice.pages).forEach((pageName) => { - const originalPage = pagesSlice.pages[pageName]; - const updatedComponentTree = detachModuleInstances( - metadata, - originalPage.componentTree - ); - get().setComponentTreeInPage(pageName, updatedComponentTree); - }); - }; -} - -/** - * Loops through the given componentTree, and detaches all modules that are instances - * of the passed in ModuleMetadata. - */ -function detachModuleInstances( - metadata: ModuleMetadata, - componentTree: ComponentState[] -) { - return componentTree.flatMap((componentStateToDetach) => { - const isModule = componentStateToDetach.kind === ComponentStateKind.Module; - if ( - !isModule || - componentStateToDetach.metadataUUID !== metadata.metadataUUID - ) { - return componentStateToDetach; - } - const detachedTree = - ComponentTreeHelpers.mapComponentTreeParentsFirst( - metadata.componentTree, - (child, parentValue) => { - return { - ...child, - uuid: v4(), - parentUUID: parentValue?.uuid ?? componentStateToDetach.parentUUID, - }; - } - ); - return detachedTree; - }); -} diff --git a/packages/studio/types.ts b/packages/studio/src/types.ts similarity index 100% rename from packages/studio/types.ts rename to packages/studio/src/types.ts diff --git a/packages/studio/tailwind.config.ts b/packages/studio/tailwind.config.ts index 510862865..509ef7c38 100644 --- a/packages/studio/tailwind.config.ts +++ b/packages/studio/tailwind.config.ts @@ -7,7 +7,7 @@ import { } from "@yext/studio-plugin"; import path from "path"; import fs from "fs"; -import generateTailwindSafelist from "./src/utils/generateTailwindSafelist"; +import { generateTailwindSafelist } from "@yext/studio-plugin"; const getRootDir = (): string => { const cliArgs: CliArgs = JSON.parse( @@ -50,6 +50,7 @@ export default { content: [ path.resolve(__dirname, "src/**/*.{ts,tsx}"), path.resolve(__dirname, "index.html"), + path.join(path.dirname(require.resolve("@yext/studio-ui")), "**/*.js"), ...transformedUserContent, ], safelist: generateTailwindSafelist(userTailwindTheme), diff --git a/packages/studio/tests/__utils__/mockRepeaterActiveComponent.ts b/packages/studio/tests/__utils__/mockRepeaterActiveComponent.ts deleted file mode 100644 index beaed0bf5..000000000 --- a/packages/studio/tests/__utils__/mockRepeaterActiveComponent.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { - RepeaterState, - ComponentStateKind, - PropValueKind, - PropValueType, - ComponentMetadata, - FileMetadataKind, - ModuleMetadata, -} from "@yext/studio-plugin"; -import mockStoreActiveComponent from "./mockActiveComponentState"; - -const repeaterComponentState: RepeaterState = { - kind: ComponentStateKind.Repeater, - uuid: "1234", - listExpression: "someList", - repeatedComponent: { - kind: ComponentStateKind.Standard, - componentName: "Standard", - props: { - text: { - kind: PropValueKind.Literal, - valueType: PropValueType.string, - value: "test", - }, - num: { - kind: PropValueKind.Literal, - valueType: PropValueType.number, - value: 5, - }, - }, - metadataUUID: "5678", - }, -}; -const componentMetadata: ComponentMetadata = { - kind: FileMetadataKind.Component, - filepath: "/some/file", - metadataUUID: "5678", - propShape: { - text: { - type: PropValueType.string, - required: false, - }, - num: { - type: PropValueType.number, - required: false, - }, - }, -}; - -const repeaterModuleState: RepeaterState = { - kind: ComponentStateKind.Repeater, - uuid: "1234", - listExpression: "someList", - repeatedComponent: { - kind: ComponentStateKind.Module, - componentName: "Mod", - props: {}, - metadataUUID: "5678", - }, -}; -const moduleMetadata: ModuleMetadata = { - kind: FileMetadataKind.Module, - filepath: "/some/file", - metadataUUID: "5678", - propShape: {}, - componentTree: [], -}; - -export function mockRepeaterActiveComponent(isRepeatedModule = false) { - if (isRepeatedModule) { - return mockStoreActiveComponent({ - activeComponent: repeaterModuleState, - activeComponentMetadata: moduleMetadata, - }); - } - return mockStoreActiveComponent({ - activeComponent: repeaterComponentState, - activeComponentMetadata: componentMetadata, - }); -} diff --git a/packages/studio/tests/components/CreateModuleButton.test.tsx b/packages/studio/tests/components/CreateModuleButton.test.tsx deleted file mode 100644 index 9bc5187ec..000000000 --- a/packages/studio/tests/components/CreateModuleButton.test.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import CreateModuleButton from "../../src/components/ModuleActions/CreateModuleButton"; -import { ComponentStateKind, FileMetadataKind } from "@yext/studio-plugin"; -import useStudioStore from "../../src/store/useStudioStore"; -import userEvent from "@testing-library/user-event"; -import { searchBarComponent } from "../__fixtures__/componentStates"; -import mockStore from "../__utils__/mockStore"; - -beforeEach(() => { - mockStore({ - pages: { - pages: { - universal: { - componentTree: [ - { - kind: ComponentStateKind.Fragment, - uuid: "mock-uuid-0", - }, - { - ...searchBarComponent, - uuid: "mock-uuid-1", - parentUUID: "mock-uuid-0", - }, - { - kind: ComponentStateKind.Module, - uuid: "mock-uuid-2", - parentUUID: "mock-uuid-1", - componentName: "module", - props: {}, - metadataUUID: "mock-metadata", - }, - { - ...searchBarComponent, - uuid: "mock-uuid-3", - parentUUID: "mock-uuid-0", - }, - ], - cssImports: [], - filepath: "mock-filepath", - }, - }, - activePageName: "universal", - activeComponentUUID: "mock-uuid-1", - }, - fileMetadatas: { - UUIDToFileMetadata: { - "Testy-metadata-uuid": { - kind: FileMetadataKind.Module, - componentTree: [], - metadataUUID: "Testy-metadata-uuid", - filepath: "src/modules/Testy.tsx", - }, - }, - }, - }); -}); - -it("does not render when there is no active page state", async () => { - await useStudioStore.getState().actions.updateActivePage(undefined); - render(); - expect(screen.queryByRole("button")).toBeNull(); -}); - -it("does not render when there is no active component state", () => { - useStudioStore.getState().pages.setActiveComponentUUID(undefined); - render(); - expect(screen.queryByRole("button")).toBeNull(); -}); - -it("does not render when the active component is a module", () => { - useStudioStore.getState().pages.setActiveComponentUUID("mock-uuid-2"); - render(); - expect(screen.queryByRole("button")).toBeNull(); -}); - -it("gives an error if the module name is already used", async () => { - render(); - await userEvent.click(screen.getByRole("button")); - const textbox = screen.getByRole("textbox"); - await userEvent.type(textbox, "Testy"); - const saveButton = screen.getByRole("button", { name: "Save" }); - await userEvent.click(saveButton); - expect( - screen.getByText( - 'Error creating module: module name "Testy" is already used.' - ) - ).toBeDefined(); -}); - -it("gives an error if the module path is invalid", async () => { - render(); - await userEvent.click(screen.getByRole("button")); - const textbox = screen.getByRole("textbox"); - await userEvent.type(textbox, "../Test"); - const saveButton = screen.getByRole("button", { name: "Save" }); - await userEvent.click(saveButton); - expect( - screen.getByText( - 'Error creating module: modulePath is invalid: "../Test.tsx".' - ) - ).toBeDefined(); -}); - -it("closes the modal when a module is successfully created", async () => { - const createModuleSpy = jest.spyOn(useStudioStore.getState(), "createModule"); - render(); - await userEvent.click(screen.getByRole("button")); - const textbox = screen.getByRole("textbox"); - await userEvent.type(textbox, "Module"); - const saveButton = screen.getByRole("button", { name: "Save" }); - await userEvent.click(saveButton); - expect(createModuleSpy).toBeCalledWith("Module"); - expect(screen.queryByText("Save")).toBeNull(); -}); diff --git a/packages/studio/tests/components/ModuleActions.test.tsx/DeleteModuleButton.test.tsx b/packages/studio/tests/components/ModuleActions.test.tsx/DeleteModuleButton.test.tsx deleted file mode 100644 index f6ab5df45..000000000 --- a/packages/studio/tests/components/ModuleActions.test.tsx/DeleteModuleButton.test.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import { - ComponentStateKind, - FileMetadataKind, - ModuleMetadata, - PageState, -} from "@yext/studio-plugin"; -import DeleteModuleButton from "../../../src/components/ModuleActions/DeleteModuleButton"; -import useStudioStore from "../../../src/store/useStudioStore"; -import mockStore from "../../__utils__/mockStore"; - -it("can open modal and delete modules", async () => { - const basicPageState: PageState = { - componentTree: [ - { - kind: ComponentStateKind.Module, - componentName: "Star", - props: {}, - uuid: "first-comp-state", - metadataUUID: "star-metadata-uuid", - }, - ], - cssImports: [], - filepath: "unused", - }; - - const testModuleMetadata: ModuleMetadata = { - kind: FileMetadataKind.Module, - metadataUUID: "star-metadata-uuid", - componentTree: [ - { - kind: ComponentStateKind.Standard, - componentName: "Banner", - props: {}, - uuid: "will-be-replaced", - metadataUUID: "banner-metadata-uuid", - }, - ], - filepath: "unused/Star.tsx", - }; - - mockStore({ - pages: { - activeComponentUUID: "will be unset after", - pages: { - firstPage: basicPageState, - }, - }, - fileMetadatas: { - UUIDToFileMetadata: { - "star-metadata-uuid": testModuleMetadata, - }, - }, - }); - - render(); - const openModalButton = await screen.findByRole("button", { - name: "Delete Module Star", - }); - await userEvent.click(openModalButton); - const deleteModuleConfirmation = await screen.findByRole("button", { - name: "Delete", - }); - await userEvent.click(deleteModuleConfirmation); - expect(useStudioStore.getState().pages.activeComponentUUID).toBeUndefined(); - expect(useStudioStore.getState().pages.pages).toEqual({ - firstPage: expect.objectContaining({ - componentTree: [ - expect.objectContaining({ kind: ComponentStateKind.Standard }), - ], - }), - }); - expect(useStudioStore.getState().fileMetadatas.UUIDToFileMetadata).toEqual( - {} - ); -}); diff --git a/packages/studio/tests/components/ModuleActions.test.tsx/EditModuleButton.test.tsx b/packages/studio/tests/components/ModuleActions.test.tsx/EditModuleButton.test.tsx deleted file mode 100644 index 1b80106bf..000000000 --- a/packages/studio/tests/components/ModuleActions.test.tsx/EditModuleButton.test.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import { - ComponentStateKind, - FileMetadataKind, - ModuleMetadata, - ModuleState, -} from "@yext/studio-plugin"; -import EditModuleButton from "../../../src/components/ModuleActions/EditModuleButton"; -import useStudioStore from "../../../src/store/useStudioStore"; -import mockStore from "../../__utils__/mockStore"; - -it("sets module UUID being edited and resets active component when clicked", async () => { - const moduleState: ModuleState = { - kind: ComponentStateKind.Module, - componentName: "Star", - props: {}, - uuid: "first-comp-state", - metadataUUID: "star-metadata-uuid", - }; - const testModuleMetadata: ModuleMetadata = { - kind: FileMetadataKind.Module, - metadataUUID: "star-metadata-uuid", - componentTree: [ - { - kind: ComponentStateKind.Standard, - componentName: "Banner", - props: {}, - uuid: "will-be-replaced", - metadataUUID: "banner-metadata-uuid", - }, - ], - filepath: "unused/Star.tsx", - }; - - mockStore({ - pages: { - activeComponentUUID: "first-comp-state", - pages: { - firstPage: { - componentTree: [moduleState], - cssImports: [], - filepath: "unused", - }, - }, - }, - fileMetadatas: { - UUIDToFileMetadata: { - "star-metadata-uuid": testModuleMetadata, - }, - }, - }); - - render(); - const editButton = await screen.findByRole("button", { - name: "Edit Module Star", - }); - await userEvent.click(editButton); - expect(useStudioStore.getState().pages.moduleUUIDBeingEdited).toEqual( - "first-comp-state" - ); - expect(useStudioStore.getState().pages.activeComponentUUID).toBeUndefined(); -}); diff --git a/packages/studio/tests/components/PropsPanel.test.tsx b/packages/studio/tests/components/PropsPanel.test.tsx deleted file mode 100644 index 24e62c1f8..000000000 --- a/packages/studio/tests/components/PropsPanel.test.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { - ComponentMetadata, - ComponentStateKind, - FileMetadataKind, - ModuleMetadata, - ModuleState, - StandardComponentState, -} from "@yext/studio-plugin"; -import mockStoreActiveComponent from "../__utils__/mockActiveComponentState"; -import PropsPanel from "../../src/components/PropsPanel"; -import { render, screen } from "@testing-library/react"; -import { mockRepeaterActiveComponent } from "../__utils__/mockRepeaterActiveComponent"; - -it("does not render prop editor(s) for fragment component", () => { - mockStoreActiveComponent({ - activeComponent: { - kind: ComponentStateKind.Fragment, - uuid: "fragment-uuid", - }, - }); - const { container } = render(); - expect(container).toBeEmptyDOMElement(); -}); - -it("does not render prop editor(s) when there's no selected active component", () => { - mockStoreActiveComponent({}); - const { container } = render(); - expect(container).toBeEmptyDOMElement(); -}); - -it("does not render 'Create Module' button for Standard Component", () => { - const state: StandardComponentState = { - kind: ComponentStateKind.Standard, - componentName: "Standard", - props: {}, - uuid: "1234", - metadataUUID: "5678", - }; - const metadata: ComponentMetadata = { - kind: FileMetadataKind.Component, - filepath: "/some/file", - metadataUUID: "5678", - propShape: {}, - }; - - mockStoreActiveComponent({ - activeComponent: state, - activeComponentMetadata: metadata, - }); - - render(); - expect(screen.queryByText("Create Module")).toBeNull(); -}); - -it("renders Module Actions for Active Module", () => { - const state: ModuleState = { - kind: ComponentStateKind.Module, - componentName: "Test", - props: {}, - uuid: "1234", - metadataUUID: "5678", - }; - const metadata: ModuleMetadata = { - kind: FileMetadataKind.Module, - filepath: "/some/file", - metadataUUID: "5678", - propShape: {}, - componentTree: [], - }; - - mockStoreActiveComponent({ - activeComponent: state, - activeComponentMetadata: metadata, - }); - - render(); - expect(screen.getAllByRole("button")).toHaveLength(3); - screen.getByRole("button", { name: "Edit Module Test" }); - screen.getByRole("button", { name: "Detach Module Test" }); - screen.getByRole("button", { name: "Delete Module file" }); -}); - -describe("Repeaters", () => { - it("renders repeated component's props", () => { - mockRepeaterActiveComponent(); - render(); - screen.getByText("text"); - expect(screen.getAllByRole("textbox")[0]).toHaveValue("test"); - screen.getByText("num"); - expect(screen.getByRole("textbox", { name: "num" })).toHaveValue("5"); - }); - - it("does not render Create Module button for a repeated component", () => { - mockRepeaterActiveComponent(); - render(); - expect(screen.queryByText("Create Module")).toBeNull(); - }); - - it("renders Module Actions for a repeated module", () => { - mockRepeaterActiveComponent(true); - render(); - expect(screen.getAllByRole("button")).toHaveLength(3); - const editButton = screen.getByRole("button", { name: "Edit Module Mod" }); - expect(editButton).toBeEnabled(); - const detachButton = screen.getByRole("button", { - name: "Detach Module Mod", - }); - expect(detachButton).toBeDisabled(); - const deleteButton = screen.getByRole("button", { - name: "Delete Module file", - }); - expect(deleteButton).toBeDisabled(); - }); -}); diff --git a/packages/studio/tests/components/RepeaterPanel.test.tsx b/packages/studio/tests/components/RepeaterPanel.test.tsx deleted file mode 100644 index 3ba995070..000000000 --- a/packages/studio/tests/components/RepeaterPanel.test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { ComponentStateKind, FileMetadataKind } from "@yext/studio-plugin"; -import mockStoreActiveComponent from "../__utils__/mockActiveComponentState"; -import { render, screen } from "@testing-library/react"; -import { mockRepeaterActiveComponent } from "../__utils__/mockRepeaterActiveComponent"; -import RepeaterPanel from "../../src/components/RepeaterPanel"; - -it("does not render panel for non-repeater component", () => { - mockStoreActiveComponent({ - activeComponent: { - kind: ComponentStateKind.Standard, - uuid: "banner-uuid", - metadataUUID: "metadata-uuid", - componentName: "Banner", - props: {}, - }, - activeComponentMetadata: { - kind: FileMetadataKind.Component, - filepath: "/some/file", - metadataUUID: "metadata-uuid", - propShape: {}, - }, - }); - const { container } = render(); - expect(container).toBeEmptyDOMElement(); -}); - -it("only renders message for repeated component", () => { - mockRepeaterActiveComponent(); - render(); - expect( - screen.getByText( - "This component is repeated in a list. Please contact a developer to edit the list settings." - ) - ).toBeDefined(); - expect(screen.queryByText("List")).toBeFalsy(); - expect(screen.queryByRole("checkbox")).toBeFalsy(); - expect(screen.queryByText("List Field")).toBeFalsy(); - expect(screen.queryByRole("textbox")).toBeFalsy(); -}); diff --git a/packages/studio/tests/store/StudioActions/activeComponentActions.test.ts b/packages/studio/tests/store/StudioActions/activeComponentActions.test.ts deleted file mode 100644 index ca6b02782..000000000 --- a/packages/studio/tests/store/StudioActions/activeComponentActions.test.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { - ComponentState, - ComponentStateKind, - FileMetadataKind, - PropValueKind, - PropValues, - PropValueType, -} from "@yext/studio-plugin"; -import useStudioStore from "../../../src/store/useStudioStore"; -import mockStore from "../../__utils__/mockStore"; - -describe("getActiveComponentState", () => { - it("can get the current active component within a module", () => { - mockInitialStore(true); - const componentState = useStudioStore - .getState() - .actions.getActiveComponentState(); - expect(componentState).toEqual( - expect.objectContaining({ - componentName: "Banner", - }) - ); - }); - - it("can get the current active component within a page", () => { - mockInitialStore(false); - const componentState = useStudioStore - .getState() - .actions.getActiveComponentState(); - expect(componentState).toEqual( - expect.objectContaining({ - componentName: "MyModule", - }) - ); - }); -}); - -describe("updateActiveComponentProps", () => { - it("when a module is being edited, updates the props inside the module's tree", () => { - mockInitialStore(true); - const updatedProps: PropValues = { - hi: { - kind: PropValueKind.Literal, - valueType: PropValueType.string, - value: "bye bye bocchi", - }, - }; - useStudioStore.getState().actions.updateActiveComponentProps(updatedProps); - const componentStateAfterUpdate = useStudioStore - .getState() - .actions.getActiveComponentState(); - expect(componentStateAfterUpdate).toEqual( - expect.objectContaining({ - componentName: "Banner", - props: updatedProps, - }) - ); - }); - - it("when a module is not being edited, updates the props in the current active page", () => { - mockInitialStore(false); - const updatedProps: PropValues = { - hi: { - kind: PropValueKind.Literal, - valueType: PropValueType.string, - value: "bye bye bocchi", - }, - }; - useStudioStore.getState().actions.updateActiveComponentProps(updatedProps); - const componentStateAfterUpdate = useStudioStore - .getState() - .actions.getActiveComponentState(); - expect(componentStateAfterUpdate).toEqual( - expect.objectContaining({ - componentName: "MyModule", - props: updatedProps, - }) - ); - }); -}); - -describe("getComponentTree", () => { - it("can get the component tree when a module is being edited", () => { - mockInitialStore(true); - const tree = useStudioStore.getState().actions.getComponentTree(); - expect(tree).toEqual([ - expect.objectContaining({ - componentName: "Banner", - }), - ]); - }); - it("can get the component tree when a page is being edited", () => { - mockInitialStore(false); - const tree = useStudioStore.getState().actions.getComponentTree(); - expect(tree).toEqual([ - expect.objectContaining({ - componentName: "MyModule", - }), - ]); - }); -}); - -describe("updateComponentTree", () => { - it("can rearrange the component tree when a module is being edited", () => { - mockInitialStore(true); - const updatedTree: ComponentState[] = [ - { - kind: ComponentStateKind.BuiltIn, - props: {}, - componentName: "div", - uuid: "div-0", - }, - ]; - useStudioStore.getState().actions.updateComponentTree(updatedTree); - const tree = useStudioStore.getState().actions.getComponentTree(); - expect(tree).toEqual(updatedTree); - }); - - it("can rearrange the component tree when a page is being edited", () => { - mockInitialStore(false); - const updatedTree: ComponentState[] = [ - { - kind: ComponentStateKind.BuiltIn, - props: {}, - componentName: "div", - uuid: "div-0", - }, - ]; - useStudioStore.getState().actions.updateComponentTree(updatedTree); - const tree = useStudioStore.getState().actions.getComponentTree(); - expect(tree).toEqual(updatedTree); - }); -}); - -function mockInitialStore(isEditingModule: boolean) { - mockStore({ - pages: { - activePageName: "testpage", - moduleUUIDBeingEdited: isEditingModule ? "module-0" : undefined, - activeComponentUUID: isEditingModule ? "banner-0" : "module-0", - pages: { - testpage: { - componentTree: [ - { - kind: ComponentStateKind.Module, - componentName: "MyModule", - props: {}, - uuid: "module-0", - metadataUUID: "moduleMeta", - }, - ], - cssImports: [], - filepath: "page-filepath", - }, - }, - }, - fileMetadatas: { - UUIDToFileMetadata: { - moduleMeta: { - metadataUUID: "moduleMeta", - kind: FileMetadataKind.Module, - filepath: "module-filepath", - componentTree: [ - { - kind: ComponentStateKind.Standard, - props: {}, - componentName: "Banner", - uuid: "banner-0", - metadataUUID: "bannerMeta", - }, - ], - }, - }, - }, - }); -} diff --git a/packages/studio/tests/store/StudioActions/detachModuleInstance.test.ts b/packages/studio/tests/store/StudioActions/detachModuleInstance.test.ts deleted file mode 100644 index 89b4d53c1..000000000 --- a/packages/studio/tests/store/StudioActions/detachModuleInstance.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { - ComponentState, - ComponentStateKind, - FileMetadataKind, - ModuleMetadata, - ModuleState, -} from "@yext/studio-plugin"; -import useStudioStore from "../../../src/store/useStudioStore"; -import mockStore from "../../__utils__/mockStore"; - -it("can detach module instances", () => { - const moduleMetadata: ModuleMetadata = { - kind: FileMetadataKind.Module, - metadataUUID: "my-module", - filepath: "unuesd", - componentTree: [ - { - kind: ComponentStateKind.Standard, - componentName: "Container", - uuid: "button-0", - props: {}, - metadataUUID: "container-metadata", - }, - { - kind: ComponentStateKind.Standard, - componentName: "Banner", - uuid: "banner-0", - props: {}, - metadataUUID: "banner-metadata", - parentUUID: "button-0", - }, - ], - }; - const tree: ComponentState[] = [ - { - kind: ComponentStateKind.BuiltIn, - componentName: "div", - uuid: "div-0", - props: {}, - }, - { - kind: ComponentStateKind.Module, - componentName: "MyModule", - props: {}, - uuid: "module-0", - metadataUUID: "my-module", - parentUUID: "div-0", - }, - { - kind: ComponentStateKind.BuiltIn, - componentName: "div", - uuid: "div-1", - props: {}, - parentUUID: "div-0", - }, - ]; - mockStore({ - pages: { - activePageName: "testpage", - pages: { - testpage: { - componentTree: tree, - cssImports: [], - filepath: "unused", - }, - }, - }, - fileMetadatas: { - UUIDToFileMetadata: { - "my-module": moduleMetadata, - }, - }, - }); - useStudioStore - .getState() - .actions.detachModuleInstance(moduleMetadata, tree[1] as ModuleState); - const actualTree = useStudioStore.getState().actions.getComponentTree(); - expect(actualTree).toEqual([ - tree[0], - { - ...moduleMetadata.componentTree[0], - uuid: expect.any(String), - parentUUID: "div-0", - }, - { - ...moduleMetadata.componentTree[1], - uuid: expect.any(String), - parentUUID: actualTree?.[1]?.uuid, - }, - tree[2], - ]); -}); diff --git a/packages/studio/tests/store/StudioActions/removeComponent.test.ts b/packages/studio/tests/store/StudioActions/removeComponent.test.ts deleted file mode 100644 index 0967886bc..000000000 --- a/packages/studio/tests/store/StudioActions/removeComponent.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { - ComponentState, - ComponentStateKind, - FileMetadataKind, -} from "@yext/studio-plugin"; -import useStudioStore from "../../../src/store/useStudioStore"; -import { searchBarComponent } from "../../__fixtures__/componentStates"; -import mockStore from "../../__utils__/mockStore"; - -const initialTree: ComponentState[] = [ - { - kind: ComponentStateKind.Fragment, - uuid: "mock-uuid-0", - }, - { - ...searchBarComponent, - uuid: "mock-uuid-1", - parentUUID: "mock-uuid-0", - }, - { - ...searchBarComponent, - uuid: "mock-uuid-2", - parentUUID: "mock-uuid-1", - }, - { - ...searchBarComponent, - uuid: "mock-uuid-3", - parentUUID: "mock-uuid-0", - }, -]; - -it("removes component and its children from ModuleMetadata when a module is being edited", () => { - mockStore({ - fileMetadatas: { - UUIDToFileMetadata: { - StarModuleMetadataUUID: { - kind: FileMetadataKind.Module, - componentTree: initialTree, - metadataUUID: "StarModuleMetadataUUID", - filepath: "unused", - }, - }, - }, - pages: { - moduleUUIDBeingEdited: "ModuleState.uuid", - activePageName: "pagename", - pages: { - pagename: { - componentTree: [ - { - kind: ComponentStateKind.Module, - uuid: "ModuleState.uuid", - metadataUUID: "StarModuleMetadataUUID", - componentName: "StarModule", - props: {}, - }, - ], - cssImports: [], - filepath: "unused", - }, - }, - }, - }); - useStudioStore.getState().actions.removeComponent("mock-uuid-1"); - expect( - useStudioStore.getState().fileMetadatas.UUIDToFileMetadata[ - "StarModuleMetadataUUID" - ] - ).toEqual( - expect.objectContaining({ - componentTree: [initialTree[0], initialTree[3]], - }) - ); -}); - -it("removes component and its children from the active PageState when no module is being edited", () => { - mockStore({ - pages: { - activePageName: "pagename", - pages: { - pagename: { - componentTree: initialTree, - cssImports: [], - filepath: "unused", - }, - }, - }, - }); - - useStudioStore.getState().actions.removeComponent("mock-uuid-1"); - const updatedTree = - useStudioStore.getState().pages.pages["pagename"].componentTree; - expect(updatedTree).toEqual([initialTree[0], initialTree[3]]); -}); diff --git a/packages/studio/tests/store/createFileMetadataSlice/createFileMetadataSlice.test.tsx b/packages/studio/tests/store/createFileMetadataSlice/createFileMetadataSlice.test.tsx deleted file mode 100644 index 72295a56f..000000000 --- a/packages/studio/tests/store/createFileMetadataSlice/createFileMetadataSlice.test.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { - ComponentMetadata, - FileMetadataKind, - ModuleMetadata, - PropValueType, -} from "@yext/studio-plugin"; -import useStudioStore from "../../../src/store/useStudioStore"; -import { FileMetadataSliceStates } from "../../../src/store/models/slices/FileMetadataSlice"; -import mockStore from "../../__utils__/mockStore"; - -const componentMetadata: ComponentMetadata = { - kind: FileMetadataKind.Component, - metadataUUID: "mock-metadata-uuid", - filepath: "mock-filepath", - propShape: { - myText: { - type: PropValueType.string, - tooltip: "a random string", - required: false, - }, - }, -}; -const moduleMetadata: ModuleMetadata = { - kind: FileMetadataKind.Module, - metadataUUID: "mock-metadataUUID", - filepath: "mock-filepath", - componentTree: [], -}; - -it("updates UUIDToFileMetadata using setFileMetadata", () => { - useStudioStore - .getState() - .fileMetadatas.setFileMetadata("some-uuid", componentMetadata); - const UUIDToFileMetadata = - useStudioStore.getState().fileMetadatas.UUIDToFileMetadata; - expect(UUIDToFileMetadata).toEqual({ - "some-uuid": componentMetadata, - }); -}); - -it("returns a FileMetadata using getFileMetadata", () => { - setInitialState({ - UUIDToFileMetadata: { - "uuid-1": componentMetadata, - }, - }); - const fileMetadata = useStudioStore - .getState() - .fileMetadatas.getFileMetadata("uuid-1"); - expect(fileMetadata).toEqual(componentMetadata); -}); - -it("errors when removeFileMetadata is called on a non-module", () => { - const initialState = { - UUIDToFileMetadata: { - "uuid-1": componentMetadata, - }, - }; - setInitialState(initialState); - const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(); - useStudioStore.getState().fileMetadatas.removeFileMetadata("uuid-1"); - expect(consoleErrorSpy).toHaveBeenCalledTimes(1); - expect(consoleErrorSpy).toHaveBeenCalledWith( - "removeFileMetadata is only allowed for modules, not:", - FileMetadataKind.Component - ); - - const UUIDToFileMetadata = - useStudioStore.getState().fileMetadatas.UUIDToFileMetadata; - expect(UUIDToFileMetadata).toEqual(initialState.UUIDToFileMetadata); -}); - -it("removeFileMetadata can remove a module metadata", () => { - const initialState = { - UUIDToFileMetadata: { - "uuid-1": moduleMetadata, - }, - }; - setInitialState(initialState); - useStudioStore.getState().fileMetadatas.removeFileMetadata("uuid-1"); - const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(); - expect(consoleErrorSpy).toHaveBeenCalledTimes(0); - - const UUIDToFileMetadata = - useStudioStore.getState().fileMetadatas.UUIDToFileMetadata; - expect(UUIDToFileMetadata).toEqual({}); -}); - -it("updates UUIDToImportedComponent using setImportedComponent", () => { - const importedComponent = () =>
hello world
; - const newImportedComponents = { - Banner: importedComponent, - }; - useStudioStore - .getState() - .fileMetadatas.setImportedComponent("Banner", importedComponent); - const UUIDToImportedComponent = - useStudioStore.getState().fileMetadatas.UUIDToImportedComponent; - expect(UUIDToImportedComponent).toEqual(newImportedComponents); -}); - -function setInitialState(initialState: Partial): void { - const baseState: FileMetadataSliceStates = { - UUIDToFileMetadata: {}, - UUIDToImportedComponent: {}, - }; - mockStore({ fileMetadatas: { ...baseState, ...initialState } }); -} diff --git a/packages/studio/tests/store/createModule.test.ts b/packages/studio/tests/store/createModule.test.ts deleted file mode 100644 index b5d7b759f..000000000 --- a/packages/studio/tests/store/createModule.test.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { - ComponentStateKind, - FileMetadata, - FileMetadataKind, - PropValueType, -} from "@yext/studio-plugin"; -import useStudioStore from "../../src/store/useStudioStore"; -import { searchBarComponent } from "../__fixtures__/componentStates"; -import mockStore from "../__utils__/mockStore"; -import path from "path-browserify"; - -const UUIDToFileMetadata: Record = { - [path.resolve(__dirname, "../__mocks__", "./test.tsx")]: { - kind: FileMetadataKind.Module, - componentTree: [], - metadataUUID: "test", - filepath: path.resolve(__dirname, "../__mocks__", "./test.tsx"), - }, -}; - -beforeEach(() => { - mockStore({ - pages: { - pages: { - universal: { - componentTree: [ - { - kind: ComponentStateKind.Fragment, - uuid: "mock-uuid-0", - }, - { - ...searchBarComponent, - uuid: "mock-uuid-1", - parentUUID: "mock-uuid-0", - }, - { - kind: ComponentStateKind.Module, - uuid: "mock-uuid-2", - parentUUID: "mock-uuid-1", - componentName: "module", - props: {}, - metadataUUID: "mock-metadata", - }, - { - ...searchBarComponent, - uuid: "mock-uuid-3", - parentUUID: "mock-uuid-0", - }, - ], - cssImports: [], - filepath: "mock-filepath", - }, - }, - activePageName: "universal", - activeComponentUUID: "mock-uuid-1", - }, - fileMetadatas: { - UUIDToFileMetadata, - }, - }); -}); - -it("adds module metadata to UUIDToFileMetadata", () => { - const moduleName = "Module"; - useStudioStore.getState().createModule(moduleName); - const UUIDToFileMetadata = - useStudioStore.getState().fileMetadatas.UUIDToFileMetadata; - expect(Object.values(UUIDToFileMetadata).at(-1)).toEqual({ - kind: FileMetadataKind.Module, - componentTree: [ - { - ...searchBarComponent, - uuid: "mock-uuid-1", - }, - { - kind: ComponentStateKind.Module, - uuid: "mock-uuid-2", - parentUUID: "mock-uuid-1", - componentName: "module", - props: {}, - metadataUUID: "mock-metadata", - }, - ], - filepath: expect.stringContaining(moduleName + ".tsx"), - metadataUUID: expect.any(String), - propShape: {}, - }); -}); - -it("adds document to prop interface when isPagesJSRepo = true", () => { - useStudioStore.setState((state) => { - state.studioConfig.isPagesJSRepo = true; - }); - const moduleName = "Module"; - useStudioStore.getState().createModule(moduleName); - const UUIDToFileMetadata = - useStudioStore.getState().fileMetadatas.UUIDToFileMetadata; - expect(Object.values(UUIDToFileMetadata).at(-1)).toEqual( - expect.objectContaining({ - propShape: { - document: { - type: PropValueType.Record, - recordKey: "string", - recordValue: "any", - required: true, - }, - }, - }) - ); -}); - -it("updates active page state to include new module component", () => { - useStudioStore.getState().createModule("Module"); - const activeComponentState = useStudioStore - .getState() - .pages.getActivePageState(); - expect(activeComponentState).toEqual({ - componentTree: [ - { - kind: ComponentStateKind.Fragment, - uuid: "mock-uuid-0", - }, - { - kind: ComponentStateKind.Module, - uuid: expect.anything(), - parentUUID: "mock-uuid-0", - componentName: "Module", - metadataUUID: expect.any(String), - props: {}, - }, - { - ...searchBarComponent, - uuid: "mock-uuid-3", - parentUUID: "mock-uuid-0", - }, - ], - cssImports: [], - filepath: "mock-filepath", - }); -}); - -it("sets active component to the new module component", () => { - useStudioStore.getState().createModule("Module"); - const activeComponentState = useStudioStore - .getState() - .actions.getActiveComponentState(); - expect(activeComponentState).toEqual({ - kind: ComponentStateKind.Module, - uuid: expect.any(String), - parentUUID: "mock-uuid-0", - componentName: "Module", - metadataUUID: expect.any(String), - props: {}, - }); -}); - -describe("errors", () => { - it("throws an error when there is no active component state", () => { - useStudioStore.getState().pages.setActiveComponentUUID(undefined); - const action = () => useStudioStore.getState().createModule("Any"); - expect(action).toThrow("Tried to create module without active component."); - }); - - it("throws an error for an empty moduleName", () => { - const action = () => useStudioStore.getState().createModule(""); - expect(action).toThrow("Error creating module: a modulePath is required."); - }); - - it("throws an error for a modulePath outside the allowed path for modules", () => { - const action = () => useStudioStore.getState().createModule("../Module"); - expect(action).toThrow( - `Error creating module: modulePath is invalid: "../Module.tsx".` - ); - }); - - it("throws an error when module name starts with a lowercase letter", () => { - const action = () => useStudioStore.getState().createModule("bob/test"); - expect(action).toThrow("Module names must start with an uppercase letter."); - }); -}); diff --git a/packages/studio/tests/store/createPageSlice/detachAllModuleInstances.test.ts b/packages/studio/tests/store/createPageSlice/detachAllModuleInstances.test.ts deleted file mode 100644 index c5055576d..000000000 --- a/packages/studio/tests/store/createPageSlice/detachAllModuleInstances.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - ComponentStateKind, - FileMetadataKind, - PageState, -} from "@yext/studio-plugin"; -import useStudioStore from "../../../src/store/useStudioStore"; -import mockStore from "../../__utils__/mockStore"; - -it("can delete a module and detach all of its instances across multiple pages", () => { - const basicPageState: PageState = { - componentTree: [ - { - kind: ComponentStateKind.Module, - componentName: "Star", - props: {}, - uuid: "first-comp-state", - metadataUUID: "star-metadata-uuid", - }, - ], - cssImports: [], - filepath: "unused", - }; - mockStore({ - pages: { - pages: { - firstPage: basicPageState, - secondPage: basicPageState, - }, - }, - }); - - useStudioStore.getState().pages.detachAllModuleInstances({ - kind: FileMetadataKind.Module, - metadataUUID: "star-metadata-uuid", - componentTree: [ - { - kind: ComponentStateKind.Standard, - componentName: "Banner", - props: {}, - uuid: "will-be-replaced", - metadataUUID: "banner-metadata-uuid", - }, - ], - filepath: "unused/Star.tsx", - }); - - const expectedUpdatedTree = [ - { - kind: ComponentStateKind.Standard, - componentName: "Banner", - props: {}, - uuid: expect.any(String), - metadataUUID: "banner-metadata-uuid", - }, - ]; - - expect(useStudioStore.getState().pages.pages).toEqual({ - firstPage: { - ...basicPageState, - componentTree: expectedUpdatedTree, - }, - secondPage: { - ...basicPageState, - componentTree: expectedUpdatedTree, - }, - }); -}); diff --git a/packages/studio/tsconfig.json b/packages/studio/tsconfig.json index b43cff8f0..6635747b7 100644 --- a/packages/studio/tsconfig.json +++ b/packages/studio/tsconfig.json @@ -1,18 +1,6 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { - "module": "ESNext", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "noImplicitAny": false, - "moduleResolution": "node", - "resolveJsonModule": true, - "forceConsistentCasingInFileNames": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "jsx": "react-jsx", - "target": "ESNext", "outDir": "lib", "types": [ "vite/client", @@ -20,5 +8,5 @@ "vite-plugin-svgr/client" ] }, - "include": ["src", "types.ts"] + "include": ["src"] } diff --git a/tsconfig.json b/tsconfig.json index 8f75020fc..1bc767abe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,7 @@ "**/*.ts", "**/*.tsx", "**/*.js", + "**/*.mjs", "packages/studio/postcss.config.cjs" ] } diff --git a/turbo.json b/turbo.json index 42e89e0e2..69a14bc3e 100644 --- a/turbo.json +++ b/turbo.json @@ -3,7 +3,7 @@ "pipeline": { "build": { "dependsOn": ["^build"], - "outputs": ["dist/**", "lib/**"] + "outputs": ["lib/**"] }, "dev": { "cache": false diff --git a/update-minor-versions.mjs b/update-minor-versions.mjs new file mode 100644 index 000000000..4f838e1b8 --- /dev/null +++ b/update-minor-versions.mjs @@ -0,0 +1,49 @@ +import { execSync } from "child_process"; +import fs from "fs"; + +function bumpStudioPlugin() { + console.log("... bumping studio-plugin"); + execSync("npm version minor -w=packages/studio-plugin"); + return readVersion("./packages/studio-plugin/package.json"); +} + +function bumpStudio(pluginVersion, uiVersion) { + console.log("... bumping studio"); + const packageJsonPath = "./packages/studio/package.json"; + const packageJson = readJson(packageJsonPath); + // `npm i @yext/studio-plugin@${newVersion} --save-exact` does not update the package json, + // likely because the new version does not exist yet. + packageJson.dependencies["@yext/studio-plugin"] = pluginVersion; + packageJson.dependencies["@yext/studio-ui"] = uiVersion; + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); + execSync("npm version minor -w=packages/studio"); +} + +function bumpStudioUI() { + console.log("... bumping studio-ui"); + execSync("npm version minor -w=packages/studio-ui"); + return readVersion("./packages/studio-ui/package.json"); +} + +function readJson(filepath) { + return JSON.parse(fs.readFileSync(filepath, "utf-8")); +} + +function readVersion(packageJsonPath) { + const packageJson = readJson(packageJsonPath); + const newVersion = packageJson?.version; + if (!newVersion) { + throw new Error( + `Could not parse version from package.json at ${packageJsonPath}` + ); + } + return newVersion; +} + +function main() { + const pluginVersion = bumpStudioPlugin(); + const uiVersion = bumpStudioUI(); + bumpStudio(pluginVersion, uiVersion); +} + +main();