Skip to content

Commit

Permalink
Add Layouts via AddElementMenu (#375)
Browse files Browse the repository at this point in the history
This PR implements adding layouts via the AddElementMenu/Button.

J=SLAP-2924
TEST=manual,auto

I can add layouts multiple times, set the newly added components as
active, and save to file
updated AddElementMenu tests

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
oshi97 and github-actions[bot] authored Sep 19, 2023
1 parent dcc9aec commit 522a18f
Show file tree
Hide file tree
Showing 22 changed files with 192 additions and 114 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion e2e-tests/tests/deploy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ studioTest.use({
studioTest("can deploy changes", async ({ studioPage }) => {
const gitOps = studioPage.gitOps;
const startingRef = await gitOps.getCurrentRef();
await studioPage.addElement("Container", "Layouts");
await studioPage.addElement("Container", "Containers");
await studioPage.takePageScreenshotAfterImgRender();

const numCommitsBeforeDeploy = await gitOps.getNumCommitsFromRef(startingRef);
Expand Down
4 changes: 2 additions & 2 deletions e2e-tests/tests/infra/StudioPlaywrightPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,15 @@ export default class StudioPlaywrightPage {

async addElement(
elementName: string,
category: "Components" | "Layouts",
category: "Components" | "Containers" | "Layouts",
shouldTakeScreenshots = true
) {
await this.openAddElementMenu(category, shouldTakeScreenshots);
await this.getAddElementLocator(elementName).click();
}

async openAddElementMenu(
category: "Components" | "Layouts" = "Components",
category: "Components" | "Containers" | "Layouts" = "Components",
shouldTakeScreenshots = false
) {
const takeScreenshot = () =>
Expand Down
97 changes: 97 additions & 0 deletions packages/studio/src/components/AddElementMenu/AddElementList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { FileMetadataKind, ComponentMetadata } from "@yext/studio-plugin";
import useStudioStore from "../../store/useStudioStore";
import { ElementType } from "./AddElementMenu";
import AddElementOption from "./AddElementOption";
import path from "path-browserify";
import renderIconForType from "../common/renderIconForType";
import { useMemo } from "react";

interface AddElementListProps {
activeType: ElementType;
afterSelect?: () => void;
}

/**
* The list of available, addable elements for the current activeType.
*/
export default function AddElementList({
activeType,
afterSelect,
}: AddElementListProps) {
const options = useOptions(activeType, afterSelect);

if (options.length === 0) {
return (
<div className="flex flex-col items-start py-3 pl-6 opacity-50">
Nothing to see here!
</div>
);
}

return (
<div className="flex flex-col items-start py-1">
{options.map((props) => {
return (
<AddElementOption {...props} icon={renderIconForType(activeType)} />
);
})}
</div>
);
}

function useOptions(activeType: ElementType, afterSelect?: () => void) {
const [UUIDToFileMetadata, layoutNameToLayoutState, addComponent, addLayout] =
useStudioStore((store) => {
return [
store.fileMetadatas.UUIDToFileMetadata,
store.layouts.layoutNameToLayoutState,
store.actions.addComponent,
store.actions.addLayout,
];
});

return useMemo(() => {
if (activeType === ElementType.Layouts) {
return Object.values(layoutNameToLayoutState).map((layoutState) => {
return {
displayName: path.basename(layoutState.filepath, ".tsx"),
key: layoutState.filepath,
handleSelect: () => {
addLayout(layoutState);
afterSelect?.();
},
};
});
}

return Object.values(UUIDToFileMetadata)
.filter((metadata): metadata is ComponentMetadata => {
if (metadata.kind !== FileMetadataKind.Component) {
return false;
}
if (activeType === ElementType.Components) {
return !metadata.acceptsChildren;
} else if (activeType === ElementType.Containers) {
return !!metadata.acceptsChildren;
}
return false;
})
.map((componentMetadata) => {
return {
displayName: path.basename(componentMetadata.filepath, ".tsx"),
key: componentMetadata.filepath,
handleSelect: () => {
addComponent(componentMetadata);
afterSelect?.();
},
};
});
}, [
UUIDToFileMetadata,
activeType,
addComponent,
addLayout,
afterSelect,
layoutNameToLayoutState,
]);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useState, useCallback } from "react";
import ElementSelector from "./ElementSelector";
import AddElementList from "./AddElementList";
import renderIconForType from "../common/renderIconForType";

export enum ElementType {
Components = "Components",
Containers = "Layouts",
Containers = "Containers",
Layouts = "Layouts",
}
/**
* A menu for adding elements to the page.
Expand All @@ -19,7 +20,7 @@ export default function AddElementMenu({
return (
<div className="absolute z-20 rounded bg-white text-sm text-gray-700 shadow-lg">
<ElementTypeSwitcher activeType={activeType} setType={setType} />
<ElementSelector activeType={activeType} afterSelect={closeMenu} />
<AddElementList activeType={activeType} afterSelect={closeMenu} />
</div>
);
}
Expand Down
20 changes: 20 additions & 0 deletions packages/studio/src/components/AddElementMenu/AddElementOption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export default function AddElementOption({
displayName,
handleSelect,
icon,
}: {
displayName: string;
handleSelect?: () => void;
icon: JSX.Element | null;
}) {
return (
<button
className="flex items-center gap-x-2 px-6 py-2 cursor-pointer hover:bg-gray-100 w-full text-left"
onClick={handleSelect}
aria-label={`Add ${displayName} Element`}
>
{icon}
{displayName}
</button>
);
}
90 changes: 0 additions & 90 deletions packages/studio/src/components/AddElementMenu/ElementSelector.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default function renderIconForType(type: ElementType) {
case ElementType.Components:
return <Box />;
case ElementType.Containers:
case ElementType.Layouts:
return <Container />;
default:
console.error(`Could not find Icon for type ${type}`);
Expand Down
22 changes: 22 additions & 0 deletions packages/studio/src/store/StudioActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
ComponentState,
ComponentStateKind,
ComponentTreeHelpers,
LayoutState,
MessageID,
PropValues,
} from "@yext/studio-plugin";
Expand All @@ -20,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"];
Expand Down Expand Up @@ -183,6 +185,26 @@ 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 { values } = this.getSiteSettings();
const previousSaveState = cloneDeep({
Expand Down
2 changes: 1 addition & 1 deletion packages/studio/src/store/hotReloadStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function syncLayouts(studioData: StudioData) {
studioData.layoutNameToLayoutState
);
useStudioStore.setState((store) => {
store.layouts.layouts = layoutNameToLayoutState;
store.layouts.layoutNameToLayoutState = layoutNameToLayoutState;
});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/studio/src/store/models/slices/LayoutSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ export interface LayoutSlice {
/**
* A mapping of name to LayoutState for layouts that can be applied via Studio.
*/
layouts: Record<string, LayoutState>;
layoutNameToLayoutState: Record<string, LayoutState>;
}
4 changes: 3 additions & 1 deletion packages/studio/src/store/slices/createLayoutSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { SliceCreator } from "../models/utils";
import removeTopLevelFragments from "../../utils/removeTopLevelFragments";

const createLayoutSlice: SliceCreator<LayoutSlice> = () => ({
layouts: removeTopLevelFragments(initialStudioData.layoutNameToLayoutState),
layoutNameToLayoutState: removeTopLevelFragments(
initialStudioData.layoutNameToLayoutState
),
});

export default createLayoutSlice;
Loading

0 comments on commit 522a18f

Please sign in to comment.