diff --git a/platform/wab/src/wab/client/components/insert-panel/InsertPanel.tsx b/platform/wab/src/wab/client/components/insert-panel/InsertPanel.tsx index bc404c3d32a..4c6d34eb064 100644 --- a/platform/wab/src/wab/client/components/insert-panel/InsertPanel.tsx +++ b/platform/wab/src/wab/client/components/insert-panel/InsertPanel.tsx @@ -28,6 +28,7 @@ import { createAddHostLessComponent, createAddInsertableTemplate, createAddTemplateComponent, + createAddTplCodeComponents, createAddTplComponent, createAddTplImage, createFakeHostLessComponent, @@ -77,9 +78,11 @@ import { } from "@/wab/common"; import { HighlightBlinker } from "@/wab/commons/components/HighlightBlinker"; import { + CodeComponent, getSubComponents, getSuperComponents, isCodeComponent, + isCodeComponentWithSection, isComponentHiddenFromContentEditor, isContextCodeComponent, isDefaultComponentKind, @@ -275,10 +278,13 @@ const AddDrawerContent = observer(function AddDrawerContent(props: { let itemIndex = 0; const isAtomicSection = atomicHostlessSections.includes(section); + const virtualItems: VirtualItem[] = groupedItems .filter((group) => query || (group.sectionKey ?? group.key) === section) .flatMap((group, index) => [ - ...(!isAtomicSection ? [{ type: "header", group } as const] : []), + ...(!isAtomicSection && !group.isHeaderLess + ? [{ type: "header", group } as const] + : []), ...group.items.map( (item) => ({ type: "item", item, group, itemIndex: itemIndex++ } as const) @@ -936,6 +942,49 @@ function getLeafProjectIdForHostLessPackageMeta(pkg: HostLessPackageInfo) { return last(ensureArray(pkg.projectId)); } +function getCodeComponentsGroups(studioCtx: StudioCtx): AddItemGroup[] { + // All code components with studio UI will receive a dedicated section + // in the AddDrawer. + const components: CodeComponent[] = studioCtx.site.components.filter( + (c): c is CodeComponent => isCodeComponentWithSection(c) + ); + const groups = groupBy(components, (c) => c.codeComponentMeta.section); + return sortBy( + Object.entries(groups) + .map(([section, sectionComponents]) => { + const subGroups = groupBy(sectionComponents, (c) => { + const meta = c.codeComponentMeta; + if (!meta.displayName) { + return ""; + } + const parts = meta.displayName.split("/"); + // Remove the last part to get the sub-section + return parts.length > 1 ? parts.slice(0, -1).join("/") : ""; + }); + + return Object.entries(subGroups).map( + ([subSection, subSectionComponents]) => { + return { + key: `code-components-${section}${ + subSection ? `-${subSection}` : "" + }`, + isHeaderLess: !subSection, + sectionKey: section, + sectionLabel: section, + label: subSection, + items: sortBy( + createAddTplCodeComponents(subSectionComponents), + (item) => item.label + ), + }; + } + ); + }) + .flat(), + (itemGroup) => itemGroup.key + ); +} + export function buildAddItemGroups({ studioCtx, includeFrames = true, @@ -1155,6 +1204,9 @@ export function buildAddItemGroups({ } ), + // Code components groups + ...getCodeComponentsGroups(studioCtx), + includeFrames && canInsertAlias(uiConfig, "frame", canInsertContext) && { key: "frames", @@ -1166,6 +1218,7 @@ export function buildAddItemGroups({ ], }, + // Custom components includes all the components from the project { key: "components", label: "Custom components", @@ -1174,6 +1227,7 @@ export function buildAddItemGroups({ (c) => isReusableComponent(c) && !isContextCodeComponent(c) && + !isCodeComponentWithSection(c) && !( contentEditorMode && isComponentHiddenFromContentEditor(c, studioCtx) diff --git a/platform/wab/src/wab/client/components/studio/add-drawer/AddDrawer.module.css b/platform/wab/src/wab/client/components/studio/add-drawer/AddDrawer.module.css new file mode 100644 index 00000000000..9230c97bdc4 --- /dev/null +++ b/platform/wab/src/wab/client/components/studio/add-drawer/AddDrawer.module.css @@ -0,0 +1,8 @@ +.preview-image { + position: relative; + width: 100%; + height: 100%; + object-fit: contain; + min-width: 0; + min-height: 0; +} diff --git a/platform/wab/src/wab/client/components/studio/add-drawer/AddDrawer.tsx b/platform/wab/src/wab/client/components/studio/add-drawer/AddDrawer.tsx index 8dac94b3bb7..292a408f922 100644 --- a/platform/wab/src/wab/client/components/studio/add-drawer/AddDrawer.tsx +++ b/platform/wab/src/wab/client/components/studio/add-drawer/AddDrawer.tsx @@ -36,6 +36,7 @@ import { getPlumeImage, } from "@/wab/client/components/plume/plume-display-utils"; import { PlumyIcon } from "@/wab/client/components/plume/plume-markers"; +import sty from "@/wab/client/components/studio/add-drawer/AddDrawer.module.css"; import AddDrawerItem from "@/wab/client/components/studio/add-drawer/AddDrawerItem"; import { AddItemGroup } from "@/wab/client/components/studio/add-drawer/AddDrawerSection"; import { DraggableInsertable } from "@/wab/client/components/studio/add-drawer/DraggableInsertable"; @@ -58,6 +59,7 @@ import { getScreenVariantToInsertableTemplate, postInsertableTemplate, } from "@/wab/client/insertable-templates"; +import ComponentIcon from "@/wab/client/plasmic/plasmic_kit/PlasmicIcon__Component"; import PlumeMarkIcon from "@/wab/client/plasmic/plasmic_kit_design_system/icons/PlasmicIcon__PlumeMark"; import { PlasmicAddDrawer } from "@/wab/client/plasmic/plasmic_kit_left_pane/PlasmicAddDrawer"; import { StudioCtx } from "@/wab/client/studio-ctx/StudioCtx"; @@ -65,6 +67,7 @@ import { ViewCtx } from "@/wab/client/studio-ctx/view-ctx"; import { trackEvent } from "@/wab/client/tracking"; import { assert, + cx, ensure, ensureArray, filterFalsy, @@ -109,6 +112,7 @@ import { } from "@/wab/shared/code-components/code-components"; import { isRenderableType } from "@/wab/shared/core/model-util"; import { isTagListContainer } from "@/wab/shared/core/rich-text-util"; +import { CSSProperties } from "@/wab/shared/element-repr/element-repr-v2"; import { InsertableTemplateExtraInfo } from "@/wab/shared/insertable-templates/types"; import { FRAMES_CAP } from "@/wab/shared/Labels"; import { @@ -448,6 +452,51 @@ export function createAddTplComponent(component: Component): AddTplItem { }; } +function getAddTplItemPreviewImage( + url: string, + objectPosition: CSSProperties["objectPosition"] +) { + return ( + + ); +} + +export function createAddTplCodeComponent( + component: CodeComponent +): AddTplItem { + const thumbUrl = component.codeComponentMeta.thumbnailUrl; + return { + ...createAddTplComponent(component), + previewImage: thumbUrl ? ( + getAddTplItemPreviewImage(thumbUrl, "center center") + ) : ( + + ), + isCompact: true, + }; +} + +export function createAddTplCodeComponents( + components: CodeComponent[] +): AddTplItem[] { + return components.flatMap((component) => { + // It shouldn't be possible to have a sub component that is not a code component + // but we'll be safe and filter them out + const subComponents = getSubComponents(component).filter(isCodeComponent); + return [ + createAddTplCodeComponent(component), + ...subComponents.map(createAddTplCodeComponent), + ]; + }); +} + export function createAddComponentPreset( studioCtx: StudioCtx, component: CodeComponent, @@ -481,22 +530,9 @@ export function createAddInsertableTemplate( label: meta.componentName, canWrap: false, icon: COMBINATION_ICON, - previewImage: meta.imageUrl ? ( - - ) : null, + previewImage: meta.imageUrl + ? getAddTplItemPreviewImage(meta.imageUrl, "center top") + : null, factory: ( vc: ViewCtx, extraInfo: InsertableTemplateExtraInfo, @@ -547,22 +583,9 @@ export function createAddTemplateComponent( label: meta.displayName ?? meta.componentName, canWrap: false, icon: COMBINATION_ICON, - previewImage: meta.imageUrl ? ( - - ) : null, + previewImage: meta.imageUrl + ? getAddTplItemPreviewImage(meta.imageUrl, "center top") + : null, factory: ( vc: ViewCtx, extraInfo: InsertableTemplateComponentExtraInfo, @@ -639,21 +662,7 @@ export function createAddHostLessComponent( icon: COMBINATION_ICON, gray: meta.gray, previewImage: meta.imageUrl ? ( - + getAddTplItemPreviewImage(meta.imageUrl, "center center") ) : meta.videoUrl ? (