Skip to content

Commit

Permalink
Merge branch 'main' into dev/separate-ui-package
Browse files Browse the repository at this point in the history
  • Loading branch information
oshi97 committed Sep 19, 2023
2 parents b2433b8 + 522a18f commit 238b504
Show file tree
Hide file tree
Showing 22 changed files with 192 additions and 113 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
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
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>
);
}
Original file line number Diff line number Diff line change
@@ -1,90 +1 @@
import { FileMetadataKind, ComponentMetadata } 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 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;
}
);

if (addableElements.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">
{addableElements.map((metadata) => {
return (
<Option
metadata={metadata}
activeType={activeType}
key={metadata.metadataUUID}
afterSelect={afterSelect}
/>
);
})}
</div>
);
}

function Option({
metadata,
activeType,
afterSelect,
}: {
metadata: ComponentMetadata;
} & ElementSelectorProps) {
const componentName = path.basename(metadata.filepath, ".tsx");

const addComponent = useStudioStore((store) => {
return store.actions.addComponent;
});

const handleSelect = useCallback(() => {
addComponent(metadata);
afterSelect?.();
}, [afterSelect, addComponent, metadata]);

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 ${componentName} Element`}
>
{renderIconForType(activeType)}
{componentName}
</button>
);
}
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-ui/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-ui/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-ui/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-ui/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 238b504

Please sign in to comment.