diff --git a/src/visualBuilder/components/FieldLocationAppList.tsx b/src/visualBuilder/components/FieldLocationAppList.tsx new file mode 100644 index 00000000..b5bdba63 --- /dev/null +++ b/src/visualBuilder/components/FieldLocationAppList.tsx @@ -0,0 +1,201 @@ +import React, { useState, useEffect, useMemo } from "preact/compat"; +import { EmptyAppIcon } from "./icons/EmptyAppIcon"; +import { VisualBuilderPostMessageEvents } from "../utils/types/postMessage.types"; +import visualBuilderPostMessage from "../utils/visualBuilderPostMessage"; +import { visualBuilderStyles } from "../visualBuilder.style"; +import classNames from "classnames"; + +interface App { + app_installation_uid: string; + app_uid: string; + contentTypeUid: string; + entryUid: string; + fieldDataType: string; + fieldDisplayName: string; + fieldPath: string; + icon?: string; + locale: string; + manifest: { + uid: string; + name: string; + description: string; + icon: string; + visibility: string; + }; + title: string; + uid: string; +} + +interface FieldLocationAppListProps { + apps: App[]; + position: "left" | "right"; + toolbarRef: React.RefObject; +} + +const normalize = (text: string) => + text + .toLowerCase() + .replace(/[^a-z0-9 ]/gi, "") + .trim(); + +export const FieldLocationAppList = ({ + apps, + position, + toolbarRef, +}: FieldLocationAppListProps) => { + const remainingApps = apps.filter((app, index) => index !== 0); + const [search, setSearch] = useState(""); + + const filteredApps = useMemo(() => { + if (!search.trim()) return remainingApps; + + const normalizedSearch = normalize(search); + return remainingApps.filter((app) => { + return ( + normalize(app.title).includes(normalizedSearch) + ); + }); + }, [search, remainingApps]); + + const handleAppClick = (app: App) => { + visualBuilderPostMessage?.send( + VisualBuilderPostMessageEvents.FIELD_LOCATION_SELECTED_APP, + { + app: app, + position: toolbarRef.current?.getBoundingClientRect(), + } + ); + }; + + return ( +
+
+ + + + + setSearch((e.target as HTMLInputElement).value) + } + placeholder="Search for Apps" + className={ + visualBuilderStyles()[ + "visual-builder__field-location-app-list__search-input" + ] + } + /> +
+ +
+ {filteredApps.length === 0 && ( +
+ + No matching results found! + +
+ )} + {filteredApps.map((app) => ( +
handleAppClick(app)} + > +
+ {app.icon ? ( + {app.title} + ) : ( + + )} +
+ + {app.title} + +
+ ))} +
+
+ ); +}; diff --git a/src/visualBuilder/components/FieldLocationIcon.tsx b/src/visualBuilder/components/FieldLocationIcon.tsx new file mode 100644 index 00000000..c8719102 --- /dev/null +++ b/src/visualBuilder/components/FieldLocationIcon.tsx @@ -0,0 +1,91 @@ +import classNames from "classnames"; +import { visualBuilderStyles } from "../visualBuilder.style"; +import { EmptyAppIcon } from "./icons/EmptyAppIcon"; +import { MoreIcon } from "./icons"; +import React, { useRef } from "preact/compat"; +import { LoadingIcon } from "./icons/loading"; +import { VisualBuilderPostMessageEvents } from "../utils/types/postMessage.types"; +import visualBuilderPostMessage from "../utils/visualBuilderPostMessage"; + +export const FieldLocationIcon = ({ + fieldLocationData, + multipleFieldToolbarButtonClasses, + handleMoreIconClick, + moreButtonRef, + toolbarRef, +}: { + fieldLocationData: any; + multipleFieldToolbarButtonClasses: any; + handleMoreIconClick: () => void; + moreButtonRef: any; + toolbarRef: any; +}) => { + + + + if (!fieldLocationData?.apps || fieldLocationData.apps.length === 0) { + return null; + } + + const handleAppClick = (app: any) => { + if(!toolbarRef.current) return + visualBuilderPostMessage?.send(VisualBuilderPostMessageEvents.FIELD_LOCATION_SELECTED_APP, { + app, + position: toolbarRef.current?.getBoundingClientRect(), + }); + }; + + return ( +
+
+ + + + { + fieldLocationData.apps.length > 1 && ( + + ) + } +
+ ); +}; diff --git a/src/visualBuilder/components/FieldToolbar.tsx b/src/visualBuilder/components/FieldToolbar.tsx index c2ab9088..1ce33de6 100644 --- a/src/visualBuilder/components/FieldToolbar.tsx +++ b/src/visualBuilder/components/FieldToolbar.tsx @@ -19,12 +19,13 @@ import { MoveLeftIcon, MoveRightIcon, ReplaceAssetIcon, + MoreIcon, } from "./icons"; import { fieldIcons } from "./icons/fields"; import classNames from "classnames"; import { visualBuilderStyles } from "../visualBuilder.style"; import CommentIcon from "./CommentIcon"; -import React, { useEffect, useState } from "preact/compat"; +import React, { useEffect, useState, useRef } from "preact/compat"; import { FieldSchemaMap } from "../utils/fieldSchemaMap"; import { isFieldDisabled } from "../utils/isFieldDisabled"; import { IReferenceContentTypeSchema } from "../../cms/types/contentTypeSchema.types"; @@ -40,6 +41,10 @@ import { } from "./FieldRevert/FieldRevertComponent"; import { LoadingIcon } from "./icons/loading"; import { EntryPermissions } from "../utils/getEntryPermissions"; +import { EmptyAppIcon } from "./icons/EmptyAppIcon"; +import { FieldLocationAppList } from "./FieldLocationAppList"; +import { FieldLocationIcon } from "./FieldLocationIcon"; + export type FieldDetails = Pick< VisualBuilderCslpEventDetails, @@ -115,6 +120,13 @@ function FieldToolbarComponent( } = props; const { fieldMetadata, editableElement: targetElement } = eventDetails; const [isFormLoading, setIsFormLoading] = useState(false); + const [fieldLocationData, setFieldLocationData] = useState(null); + const [displayAllApps, setDisplayAllApps] = useState(false); + const moreButtonRef = useRef(null); + const toolbarRef = useRef(null); + const [appListPosition, setAppListPosition] = useState<"left" | "right">( + "right" + ); const parentPath = fieldMetadata?.multipleFieldMetadata?.parentDetails?.parentCslpValue || @@ -135,6 +147,7 @@ function FieldToolbarComponent( let Icon = null; let fieldType = null; let isWholeMultipleField = false; + const APP_LIST_MIN_WIDTH = 230; let disableFieldActions = false; if (fieldSchema) { @@ -176,7 +189,7 @@ function FieldToolbarComponent( // if ( // DEFAULT_MULTIPLE_FIELDS.includes(fieldType) && // isWholeMultipleField && - // !isVariant + // !isVariant // ) { // return null; // } @@ -185,6 +198,26 @@ function FieldToolbarComponent( const invertTooltipPosition = targetElement.getBoundingClientRect().top <= TOOLTIP_TOP_EDGE_BUFFER; + const handleMoreIconClick = () => { + if (toolbarRef.current) { + const rect = toolbarRef.current.getBoundingClientRect(); + const spaceRight = window.innerWidth - rect.right; + const spaceLeft = rect.left; + let position = ""; + + if (spaceRight < APP_LIST_MIN_WIDTH) { + position = "left"; + } else if (spaceRight > APP_LIST_MIN_WIDTH) { + position = "right"; + } else { + position = spaceRight > spaceLeft ? "right" : "left"; + } + setAppListPosition(position as "left" | "right"); + } + + setDisplayAllApps(!displayAllApps); + }; + const editButton = Icon ? (