From aa9a3e14bc98091d8b8da36782f5faaf2ebf006e Mon Sep 17 00:00:00 2001 From: Ludo Mikula Date: Mon, 20 Nov 2023 15:50:51 +0100 Subject: [PATCH 01/14] new: updated docker build process to use nodejs debian/ubuntu repos --- deploy/docker/Dockerfile | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index b638e62c8..c618771a9 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -66,12 +66,17 @@ CMD [ "sh" , "/lowcoder/api-service/entrypoint.sh" ] ## FROM ubuntu:jammy as build-node-service -RUN apt update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y curl ca-certificates build-essential +RUN apt update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y curl ca-certificates build-essential gnupg + +# Add nodejs repo and keys +RUN mkdir -p /etc/apt/keyrings \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list # Download nodejs and install yarn -RUN curl -sL https://deb.nodesource.com/setup_19.x | bash - \ -&& apt-get install --no-install-recommends -y nodejs \ -&& npm install -g yarn +RUN apt-get update \ + && apt-get install --no-install-recommends -y nodejs \ + && npm install -g yarn # Copy and build the node-service app COPY server/node-service/ /lowcoder/node-service/app/ @@ -93,9 +98,16 @@ RUN chmod +x /lowcoder/node-service/*.sh FROM ubuntu:jammy as lowcoder-ce-node-service LABEL maintainer="lowcoder" -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y curl ca-certificates \ - && curl -sL https://deb.nodesource.com/setup_19.x | bash - \ - && apt-get install --no-install-recommends -y nodejs gosu \ +RUN apt update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y curl ca-certificates gnupg + +# Add nodejs repo and keys +RUN mkdir -p /etc/apt/keyrings \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list + +# Download nodejs and install yarn +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y nodejs gosu \ && npm install -g yarn \ && rm -rf /var/cache/apt/lists \ && addgroup --system --gid 9001 lowcoder \ @@ -167,13 +179,20 @@ EXPOSE 3443 FROM lowcoder-ce-frontend LABEL maintainer="lowcoder" +RUN apt update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y curl ca-certificates gnupg + +# Add nodejs repo and keys +RUN mkdir -p /etc/apt/keyrings \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list + + # Install required packages RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y bash gnupg curl lsb-release \ && curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg \ && echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb bullseye main" | tee /etc/apt/sources.list.d/redis.list \ && curl -fsSL https://www.mongodb.org/static/pgp/server-4.4.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-archive-keyring.gpg \ && echo "deb [signed-by=/usr/share/keyrings/mongodb-archive-keyring.gpg arch=amd64,arm64] http://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list \ - && curl -sL https://deb.nodesource.com/setup_19.x | bash - \ && if [ "$(dpkg --print-architecture)" = "amd64" ] || [ "$(dpkg --print-architecture)" = "i386" ]; then \ curl -sL http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_$(dpkg --print-architecture).deb --output libssl1.1_1.1.1f-1ubuntu2_$(dpkg --print-architecture).deb; \ else \ From 6e1bc053d706e0968fc0972e656a418d0b384fb2 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Tue, 14 Nov 2023 14:24:09 +0500 Subject: [PATCH 02/14] fix: handle empty itemKeys for sub-menus --- .../comps/comps/layout/layoutMenuItemComp.tsx | 12 +++++++---- .../navComp/components/DroppableMenuItem.tsx | 20 ++++++++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx b/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx index 62b55a7da..e7db2f29f 100644 --- a/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx @@ -100,10 +100,14 @@ export class LayoutMenuItemListComp extends list(LayoutMenuItemCompMigrate) { const data = this.getView(); this.dispatch( this.pushAction( - value || { - label: trans("menuItem") + " " + (data.length + 1), - itemKey: genRandomKey(), - } + value + ? { + ...value, + itemKey: value.itemKey || genRandomKey(), + } : { + label: trans("menuItem") + " " + (data.length + 1), + itemKey: genRandomKey(), + } ) ); } diff --git a/client/packages/lowcoder/src/comps/comps/navComp/components/DroppableMenuItem.tsx b/client/packages/lowcoder/src/comps/comps/navComp/components/DroppableMenuItem.tsx index 44c342101..4ba18b3dc 100644 --- a/client/packages/lowcoder/src/comps/comps/navComp/components/DroppableMenuItem.tsx +++ b/client/packages/lowcoder/src/comps/comps/navComp/components/DroppableMenuItem.tsx @@ -1,10 +1,12 @@ import { useDraggable, useDroppable } from "@dnd-kit/core"; import { trans } from "i18n"; -import { Fragment } from "react"; +import { Fragment, useEffect } from "react"; import styled from "styled-components"; import DroppablePlaceholder from "./DroppablePlaceHolder"; import MenuItem, { ICommonItemProps } from "./MenuItem"; import { IDragData, IDropData } from "./types"; +import { LayoutMenuItemComp } from "comps/comps/layout/layoutMenuItemComp"; +import { genRandomKey } from "comps/utils/idGenerator"; const DraggableMenuItemWrapper = styled.div` position: relative; @@ -63,6 +65,22 @@ export default function DraggableMenuItem(props: IDraggableMenuItemProps) { disabled: isDragging || disabled || disableDropIn, data: dropData, }); + + // TODO: Remove this later. + // Set ItemKey for previously added sub-menus + useEffect(() => { + if(!items.length) return; + if(!(items[0] instanceof LayoutMenuItemComp)) return; + + return items.forEach(item => { + const subItem = item as LayoutMenuItemComp; + const itemKey = subItem.children.itemKey.getView(); + if(itemKey === '') { + subItem.children.itemKey.dispatchChangeValueAction(genRandomKey()) + } + }) + }, [items]) + return ( <> From 48e4c713aa8863f365da611e8f3100c623312f0b Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Tue, 14 Nov 2023 18:26:41 +0500 Subject: [PATCH 03/14] feat: allow more than 2 levels nested nav --- .../src/comps/comps/layout/navLayout.tsx | 18 +++++++++++------- .../navComp/components/DroppableMenuItem.tsx | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx index 5e8d47320..f65125efe 100644 --- a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx +++ b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx @@ -133,21 +133,25 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { }, [filterItem] ); - - const itemKeyRecord = useMemo(() => { + + const generateItemKeyRecord = useCallback((items: LayoutMenuItemComp[]) => { + console.log('generateItemKeyRecord', items) const result: Record = {}; items.forEach((item) => { const subItems = item.children.items.getView(); if (subItems.length > 0) { - item.children.items - .getView() - .forEach((subItem) => (result[subItem.getItemKey()] = subItem)); - } else { + Object.assign(result, generateItemKeyRecord(subItems)) + } + else { result[item.getItemKey()] = item; } }); return result; - }, [items]); + }, [items]) + + const itemKeyRecord = useMemo(() => { + return generateItemKeyRecord(items) + }, [generateItemKeyRecord, items]); const defaultOpenKeys = useMemo(() => { let itemPath: string[]; diff --git a/client/packages/lowcoder/src/comps/comps/navComp/components/DroppableMenuItem.tsx b/client/packages/lowcoder/src/comps/comps/navComp/components/DroppableMenuItem.tsx index 4ba18b3dc..c4f22191a 100644 --- a/client/packages/lowcoder/src/comps/comps/navComp/components/DroppableMenuItem.tsx +++ b/client/packages/lowcoder/src/comps/comps/navComp/components/DroppableMenuItem.tsx @@ -117,7 +117,7 @@ export default function DraggableMenuItem(props: IDraggableMenuItemProps) { item={subItem} level={0} disabled={disabled || isDragging || disableDropIn} - // onAddSubMenu={onAddSubMenu} + onAddSubMenu={onAddSubMenu} onDelete={onDelete} parentDragging={isDragging} /> From ee148f5babfba3d3c4a63349660131868a1e7a23 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Tue, 14 Nov 2023 18:55:29 +0500 Subject: [PATCH 04/14] feat: make parent menu items clickable --- .../comps/comps/layout/layoutMenuItemComp.tsx | 12 ++-- .../src/comps/comps/layout/navLayout.tsx | 58 +++++++++---------- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx b/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx index e7db2f29f..a833229a9 100644 --- a/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx @@ -38,15 +38,13 @@ export class LayoutMenuItemComp extends MultiBaseComp { } override getPropertyView(): ReactNode { - const isLeaf = this.children.items.getView().length === 0; return ( <> - {isLeaf && - this.children.action.propertyView({ - onAppChange: (label) => { - label && this.children.label.dispatchChangeValueAction(label); - }, - })} + {this.children.action.propertyView({ + onAppChange: (label) => { + label && this.children.label.dispatchChangeValueAction(label); + }, + })} {this.children.label.propertyView({ label: trans("label") })} {this.children.icon.propertyView({ label: trans("icon"), diff --git a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx index f65125efe..03920da75 100644 --- a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx +++ b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx @@ -71,6 +71,33 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { return !item.children.hidden.getView(); }, []); + const generateItemKeyRecord = useCallback((items: LayoutMenuItemComp[]) => { + const result: Record = {}; + items.forEach((item) => { + const subItems = item.children.items.getView(); + if (subItems.length > 0) { + Object.assign(result, generateItemKeyRecord(subItems)) + } + result[item.getItemKey()] = item; + }); + return result; + }, [items]) + + const itemKeyRecord = useMemo(() => { + return generateItemKeyRecord(items) + }, [generateItemKeyRecord, items]); + + const onMenuItemClick = ({key}: {key: string}) => { + const itemComp = itemKeyRecord[key]; + const url = [ + ALL_APPLICATIONS_URL, + pathParam.applicationId, + pathParam.viewMode, + itemComp.getItemKey(), + ].join("/"); + itemComp.children.action.act(url); + } + const getMenuItem = useCallback( (itemComps: LayoutMenuItemComp[]): MenuProps["items"] => { return itemComps.filter(filterItem).map((item) => { @@ -81,6 +108,8 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { title: label, key: item.getItemKey(), icon: {item.children.icon.getView()}, + onTitleClick: onMenuItemClick, + onClick: onMenuItemClick, ...(subItems.length > 0 && { children: getMenuItem(subItems) }), }; }); @@ -133,25 +162,6 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { }, [filterItem] ); - - const generateItemKeyRecord = useCallback((items: LayoutMenuItemComp[]) => { - console.log('generateItemKeyRecord', items) - const result: Record = {}; - items.forEach((item) => { - const subItems = item.children.items.getView(); - if (subItems.length > 0) { - Object.assign(result, generateItemKeyRecord(subItems)) - } - else { - result[item.getItemKey()] = item; - } - }); - return result; - }, [items]) - - const itemKeyRecord = useMemo(() => { - return generateItemKeyRecord(items) - }, [generateItemKeyRecord, items]); const defaultOpenKeys = useMemo(() => { let itemPath: string[]; @@ -192,16 +202,6 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { style={{ height: "100%" }} defaultOpenKeys={defaultOpenKeys} selectedKeys={[selectedKey]} - onClick={(e) => { - const itemComp = itemKeyRecord[e.key]; - const url = [ - ALL_APPLICATIONS_URL, - pathParam.applicationId, - pathParam.viewMode, - itemComp.getItemKey(), - ].join("/"); - itemComp.children.action.act(url); - }} /> {pageView} From 8e39c185b5a75c8ebfc672eba385b7b49caf4fb9 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Tue, 14 Nov 2023 22:34:23 +0500 Subject: [PATCH 05/14] feat: added width and background option for nav bar --- .../src/comps/comps/layout/navLayout.tsx | 21 +++++++++++++++++-- .../comps/controls/styleControlConstants.tsx | 2 ++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx index 03920da75..2a07f491e 100644 --- a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx +++ b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx @@ -8,12 +8,17 @@ import { withDispatchHook } from "comps/generators/withDispatchHook"; import { NameAndExposingInfo } from "comps/utils/exposingTypes"; import { ALL_APPLICATIONS_URL } from "constants/routesURL"; import { TopHeaderHeight } from "constants/style"; -import { Section } from "lowcoder-design"; +import { Section, sectionNames } from "lowcoder-design"; import { trans } from "i18n"; import { EditorContainer, EmptyContent } from "pages/common/styledComponent"; import { useCallback, useEffect, useMemo, useState } from "react"; import styled from "styled-components"; import { isUserViewMode, useAppPathParam } from "util/hooks"; +import { StringControl } from "comps/controls/codeControl"; +import { styleControl } from "comps/controls/styleControl"; +import { NavLayoutStyle } from "comps/controls/styleControlConstants"; + +const DEFAULT_WIDTH = 240; const StyledSide = styled(Layout.Sider)` max-height: calc(100vh - ${TopHeaderHeight}); @@ -46,6 +51,8 @@ let NavTmpLayout = (function () { label: trans("menuItem") + " 1", }, ]), + width: StringControl, + style: styleControl(NavLayoutStyle), }; return new MultiCompBuilder(childrenMap, (props) => { return null; @@ -54,6 +61,16 @@ let NavTmpLayout = (function () { return ( <>
{menuPropertyView(children.items)}
+
+ { children.width.propertyView({ + label: trans("drawer.width"), + tooltip: trans("drawer.widthTooltip"), + placeholder: DEFAULT_WIDTH + "", + })} +
+
+ { children.style.getPropertyView() } +
); }) @@ -195,7 +212,7 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { let content = ( - + Date: Thu, 16 Nov 2023 21:22:21 +0500 Subject: [PATCH 06/14] feat: styling option for nav layout --- .../src/comps/comps/layout/navLayout.tsx | 220 ++++++++++++++++-- .../comps/controls/styleControlConstants.tsx | 61 ++++- .../packages/lowcoder/src/i18n/locales/en.ts | 9 + .../packages/lowcoder/src/i18n/locales/zh.ts | 9 + 4 files changed, 272 insertions(+), 27 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx index 2a07f491e..afbdba8b8 100644 --- a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx +++ b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx @@ -1,4 +1,4 @@ -import { Layout, Menu as AntdMenu, MenuProps } from "antd"; +import { Layout, Menu as AntdMenu, MenuProps, Segmented } from "antd"; import MainContent from "components/layout/MainContent"; import { LayoutMenuItemComp, LayoutMenuItemListComp } from "comps/comps/layout/layoutMenuItemComp"; import { menuPropertyView } from "comps/comps/navComp/components/MenuItemList"; @@ -8,17 +8,47 @@ import { withDispatchHook } from "comps/generators/withDispatchHook"; import { NameAndExposingInfo } from "comps/utils/exposingTypes"; import { ALL_APPLICATIONS_URL } from "constants/routesURL"; import { TopHeaderHeight } from "constants/style"; -import { Section, sectionNames } from "lowcoder-design"; +import { Section, controlItem, sectionNames } from "lowcoder-design"; import { trans } from "i18n"; import { EditorContainer, EmptyContent } from "pages/common/styledComponent"; import { useCallback, useEffect, useMemo, useState } from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; import { isUserViewMode, useAppPathParam } from "util/hooks"; import { StringControl } from "comps/controls/codeControl"; import { styleControl } from "comps/controls/styleControl"; -import { NavLayoutStyle } from "comps/controls/styleControlConstants"; +import { + NavLayoutStyle, + NavLayoutItemStyle, + NavLayoutItemStyleType, + NavLayoutItemHoverStyle, + NavLayoutItemHoverStyleType, + NavLayoutItemActiveStyle, + NavLayoutItemActiveStyleType, +} from "comps/controls/styleControlConstants"; +import { dropdownControl } from "comps/controls/dropdownControl"; const DEFAULT_WIDTH = 240; +const ModeOptions = [ + { label: trans("navLayout.modeInline"), value: "inline" }, + { label: trans("navLayout.modeVertical"), value: "vertical" }, +] as const; + +type MenuItemStyleOptionValue = "normal" | "hover" | "active"; + +const menuItemStyleOptions = [ + { + value: "normal", + label: "Normal", + }, + { + value: "hover", + label: "Hover", + }, + { + value: "active", + label: "Active", + } +] const StyledSide = styled(Layout.Sider)` max-height: calc(100vh - ${TopHeaderHeight}); @@ -44,6 +74,80 @@ const ContentWrapper = styled.div` } `; +const StyledMenu = styled(AntdMenu)<{ + $navItemStyle?: NavLayoutItemStyleType & { width: string}, + $navItemHoverStyle?: NavLayoutItemHoverStyleType, + $navItemActiveStyle?: NavLayoutItemActiveStyleType, +}>` + .ant-menu-item { + height: auto; + width: ${(props) => props.$navItemStyle?.width}; + background-color: ${(props) => props.$navItemStyle?.background}; + color: ${(props) => props.$navItemStyle?.text}; + border-radius: ${(props) => props.$navItemStyle?.radius} !important; + border: ${(props) => `1px solid ${props.$navItemStyle?.border}`}; + margin: ${(props) => props.$navItemStyle?.margin}; + padding: ${(props) => props.$navItemStyle?.padding}; + + } + .ant-menu-item-active { + background-color: ${(props) => props.$navItemHoverStyle?.background} !important; + color: ${(props) => props.$navItemHoverStyle?.text} !important; + border: ${(props) => `1px solid ${props.$navItemHoverStyle?.border}`}; + } + + .ant-menu-item-selected { + background-color: ${(props) => props.$navItemActiveStyle?.background} !important; + color: ${(props) => props.$navItemActiveStyle?.text} !important; + border: ${(props) => `1px solid ${props.$navItemActiveStyle?.border}`}; + } + + .ant-menu-submenu { + margin: ${(props) => props.$navItemStyle?.margin}; + width: ${(props) => props.$navItemStyle?.width}; + + .ant-menu-submenu-title { + width: 100%; + height: auto !important; + background-color: ${(props) => props.$navItemStyle?.background}; + color: ${(props) => props.$navItemStyle?.text}; + border-radius: ${(props) => props.$navItemStyle?.radius} !important; + border: ${(props) => `1px solid ${props.$navItemStyle?.border}`}; + margin: 0; + padding: ${(props) => props.$navItemStyle?.padding}; + + } + + .ant-menu-item { + width: 100%; + } + + &.ant-menu-submenu-active { + >.ant-menu-submenu-title { + width: 100%; + background-color: ${(props) => props.$navItemHoverStyle?.background} !important; + color: ${(props) => props.$navItemHoverStyle?.text} !important; + border: ${(props) => `1px solid ${props.$navItemHoverStyle?.border}`}; + } + } + &.ant-menu-submenu-selected { + >.ant-menu-submenu-title { + width: 100%; + background-color: ${(props) => props.$navItemActiveStyle?.background} !important; + color: ${(props) => props.$navItemActiveStyle?.text} !important; + border: ${(props) => `1px solid ${props.$navItemActiveStyle?.border}`}; + } + } + } + +`; + +const defaultStyle = { + radius: '0px', + margin: '0px', + padding: '0px', +} + let NavTmpLayout = (function () { const childrenMap = { items: withDefault(LayoutMenuItemListComp, [ @@ -51,27 +155,57 @@ let NavTmpLayout = (function () { label: trans("menuItem") + " 1", }, ]), - width: StringControl, - style: styleControl(NavLayoutStyle), + width: withDefault(StringControl, DEFAULT_WIDTH), + mode: dropdownControl(ModeOptions, "inline"), + navStyle: withDefault(styleControl(NavLayoutStyle), defaultStyle), + navItemStyle: withDefault(styleControl(NavLayoutItemStyle), defaultStyle), + navItemHoverStyle: withDefault(styleControl(NavLayoutItemHoverStyle), {}), + navItemActiveStyle: withDefault(styleControl(NavLayoutItemActiveStyle), {}), }; return new MultiCompBuilder(childrenMap, (props) => { return null; }) .setPropertyViewFn((children) => { + const [styleSegment, setStyleSegment] = useState('normal') + return ( - <> +
{menuPropertyView(children.items)}
{ children.width.propertyView({ - label: trans("drawer.width"), - tooltip: trans("drawer.widthTooltip"), + label: trans("navLayout.width"), + tooltip: trans("navLayout.widthTooltip"), placeholder: DEFAULT_WIDTH + "", - })} + })} + { children.mode.propertyView({ + label: trans("labelProp.position"), + radioButton: true + })}
-
- { children.style.getPropertyView() } +
+ { children.navStyle.getPropertyView() }
- +
+ {controlItem({}, ( + setStyleSegment(k as MenuItemStyleOptionValue)} + /> + ))} + {styleSegment === 'normal' && ( + children.navItemStyle.getPropertyView() + )} + {styleSegment === 'hover' && ( + children.navItemHoverStyle.getPropertyView() + )} + {styleSegment === 'active' && ( + children.navItemActiveStyle.getPropertyView() + )} +
+
); }) .build(); @@ -82,13 +216,19 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { const isViewMode = isUserViewMode(pathParam); const [selectedKey, setSelectedKey] = useState(""); const items = useMemo(() => comp.children.items.getView(), [comp.children.items]); - + const navWidth = useMemo(() => comp.children.width.getView(), [comp.children.width]); + const navMode = useMemo(() => comp.children.mode.getView(), [comp.children.mode]); + const navStyle = useMemo(() => comp.children.navStyle.getView(), [comp.children.navStyle]); + const navItemStyle = useMemo(() => comp.children.navItemStyle.getView(), [comp.children.navItemStyle]); + const navItemHoverStyle = useMemo(() => comp.children.navItemHoverStyle.getView(), [comp.children.navItemHoverStyle]); + const navItemActiveStyle = useMemo(() => comp.children.navItemActiveStyle.getView(), [comp.children.navItemActiveStyle]); + console.log(navItemActiveStyle); // filter out hidden. unauthorised items filtered by server const filterItem = useCallback((item: LayoutMenuItemComp): boolean => { return !item.children.hidden.getView(); }, []); - const generateItemKeyRecord = useCallback((items: LayoutMenuItemComp[]) => { + const generateItemKeyRecord = (items: LayoutMenuItemComp[]) => { const result: Record = {}; items.forEach((item) => { const subItems = item.children.items.getView(); @@ -98,13 +238,13 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { result[item.getItemKey()] = item; }); return result; - }, [items]) + } const itemKeyRecord = useMemo(() => { return generateItemKeyRecord(items) - }, [generateItemKeyRecord, items]); + }, [items]); - const onMenuItemClick = ({key}: {key: string}) => { + const onMenuItemClick = useCallback(({key}: {key: string}) => { const itemComp = itemKeyRecord[key]; const url = [ ALL_APPLICATIONS_URL, @@ -113,7 +253,7 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { itemComp.getItemKey(), ].join("/"); itemComp.children.action.act(url); - } + }, [pathParam.applicationId, pathParam.viewMode, itemKeyRecord]) const getMenuItem = useCallback( (itemComps: LayoutMenuItemComp[]): MenuProps["items"] => { @@ -131,7 +271,7 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { }; }); }, - [filterItem] + [onMenuItemClick, filterItem] ); const menuItems = useMemo(() => getMenuItem(items), [items, getMenuItem]); @@ -210,15 +350,47 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { } } + const getVerticalMargin = (margin: string[]) => { + if(margin.length === 1) return `${margin[0]}`; + if(margin.length === 2) return `(${margin[0]} + ${margin[0]})`; + if(margin.length === 3 || margin.length === 4) + return `(${margin[0]} + ${margin[2]})`; + + return '0px'; + } + const getHorizontalMargin = (margin: string[]) => { + if(margin.length === 1) return `(${margin[0]} + ${margin[0]})`; + if(margin.length === 2) return `(${margin[1]} + ${margin[1]})`; + if(margin.length === 3 || margin.length === 4) + return `(${margin[1]} + ${margin[3]})`; + + return '0px'; + } + let content = ( - - + {pageView} diff --git a/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx b/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx index 715db926e..58b706771 100644 --- a/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx +++ b/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx @@ -821,9 +821,7 @@ export const TreeStyle = [ export const TreeSelectStyle = [...multiSelectCommon, ...ACCENT_VALIDATE] as const; -export const DrawerStyle = [getBackground()] as const; - -export const NavLayoutStyle = [getBackground()] as const; +export const DrawerStyle = [getBackground()] as const export const JsonEditorStyle = [LABEL] as const; @@ -930,6 +928,59 @@ export const ResponsiveLayoutColStyle = [ PADDING, ] as const; +export const NavLayoutStyle = [ + ...getBgBorderRadiusByBg(), + { + name: "text", + label: trans("text"), + depName: "background", + // depTheme: "primary", + depType: DEP_TYPE.CONTRAST_TEXT, + transformer: contrastText, + }, + MARGIN, + PADDING, +] as const; + +export const NavLayoutItemStyle = [ + getBackground("primarySurface"), + getStaticBorder('transparent'), + RADIUS, + { + name: "text", + label: trans("text"), + depName: "background", + depType: DEP_TYPE.CONTRAST_TEXT, + transformer: contrastText, + }, + MARGIN, + PADDING, +] as const; + +export const NavLayoutItemHoverStyle = [ + getBackground("canvas"), + getStaticBorder('transparent'), + { + name: "text", + label: trans("text"), + depName: "background", + depType: DEP_TYPE.CONTRAST_TEXT, + transformer: contrastText, + }, +] as const; + +export const NavLayoutItemActiveStyle = [ + getBackground("primary"), + getStaticBorder('transparent'), + { + name: "text", + label: trans("text"), + depName: "background", + depType: DEP_TYPE.CONTRAST_TEXT, + transformer: contrastText, + }, +] as const; + export const CarouselStyle = [getBackground("canvas")] as const; export const RichTextEditorStyle = [getStaticBorder(), RADIUS] as const; @@ -970,6 +1021,10 @@ export type CarouselStyleType = StyleConfigType; export type RichTextEditorStyleType = StyleConfigType; export type ResponsiveLayoutRowStyleType = StyleConfigType; export type ResponsiveLayoutColStyleType = StyleConfigType; +export type NavLayoutStyleType = StyleConfigType; +export type NavLayoutItemStyleType = StyleConfigType; +export type NavLayoutItemHoverStyleType = StyleConfigType; +export type NavLayoutItemActiveStyleType = StyleConfigType; export function widthCalculator(margin: string) { const marginArr = margin?.trim().replace(/\s+/g,' ').split(" ") || ""; diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 40138fb17..2ade13c21 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -2710,4 +2710,13 @@ export const en = { rowLayout: "Row Layout", columnsLayout: "Columns Layout", }, + navLayout: { + mode: "Mode", + modeInline: "Inline", + modeVertical: "Vertical", + width: "Width", + widthTooltip: "Number or percentage, e.g. 520, 60%", + navStyle: "Menu Style", + navItemStyle: "Menu Item Style", + } }; diff --git a/client/packages/lowcoder/src/i18n/locales/zh.ts b/client/packages/lowcoder/src/i18n/locales/zh.ts index 53b922a9e..aabf64e82 100644 --- a/client/packages/lowcoder/src/i18n/locales/zh.ts +++ b/client/packages/lowcoder/src/i18n/locales/zh.ts @@ -2557,6 +2557,15 @@ timeLine: { matchColumnsHeight: "匹配列高度", rowLayout: "行布局", columnsLayout: "栏目布局", + }, + navLayout: { + mode: "模式", + modeInline: "排队", + modeVertical: "垂直的", + width: "宽度", + widthTooltip: "数字或百分比,例如 520,60%", + navStyle: "菜单风格", + navItemStyle: "菜单项样式", } }; From 4be209ba6dc87856592209d1c1fa547bf88e4016 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Fri, 17 Nov 2023 00:54:00 +0500 Subject: [PATCH 07/14] feat: background-image for nav layout --- .../src/comps/comps/layout/navLayout.tsx | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx index afbdba8b8..c69c8c294 100644 --- a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx +++ b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx @@ -26,6 +26,7 @@ import { NavLayoutItemActiveStyleType, } from "comps/controls/styleControlConstants"; import { dropdownControl } from "comps/controls/dropdownControl"; +import _ from "lodash"; const DEFAULT_WIDTH = 240; const ModeOptions = [ @@ -156,6 +157,7 @@ let NavTmpLayout = (function () { }, ]), width: withDefault(StringControl, DEFAULT_WIDTH), + backgroundImage: withDefault(StringControl, ""), mode: dropdownControl(ModeOptions, "inline"), navStyle: withDefault(styleControl(NavLayoutStyle), defaultStyle), navItemStyle: withDefault(styleControl(NavLayoutItemStyle), defaultStyle), @@ -170,7 +172,9 @@ let NavTmpLayout = (function () { return (
-
{menuPropertyView(children.items)}
+
+ {menuPropertyView(children.items)} +
{ children.width.propertyView({ label: trans("navLayout.width"), @@ -181,6 +185,10 @@ let NavTmpLayout = (function () { label: trans("labelProp.position"), radioButton: true })} + {children.backgroundImage.propertyView({ + label: `Background Image`, + placeholder: 'https://temp.im/350x400', + })}
{ children.navStyle.getPropertyView() } @@ -222,7 +230,8 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { const navItemStyle = useMemo(() => comp.children.navItemStyle.getView(), [comp.children.navItemStyle]); const navItemHoverStyle = useMemo(() => comp.children.navItemHoverStyle.getView(), [comp.children.navItemHoverStyle]); const navItemActiveStyle = useMemo(() => comp.children.navItemActiveStyle.getView(), [comp.children.navItemActiveStyle]); - console.log(navItemActiveStyle); + const backgroundImage = comp.children.backgroundImage.getView(); + // filter out hidden. unauthorised items filtered by server const filterItem = useCallback((item: LayoutMenuItemComp): boolean => { return !item.children.hidden.getView(); @@ -367,6 +376,10 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { return '0px'; } + let backgroundStyle = navStyle.background; + if(!_.isEmpty(backgroundImage)) { + backgroundStyle = `center / cover url('${backgroundImage}') no-repeat, ${backgroundStyle}`; + } let content = ( @@ -376,18 +389,18 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { style={{ height: `calc(100% - ${getVerticalMargin(navStyle.margin.split(' '))})`, width: `calc(100% - ${getHorizontalMargin(navStyle.margin.split(' '))})`, + borderRight: `1px solid ${navStyle.border}`, borderRadius: navStyle.radius, color: navStyle.text, margin: navStyle.margin, padding: navStyle.padding, - background: navStyle.background, - borderRight: `1px solid ${navStyle.border}`, + background: backgroundStyle, }} defaultOpenKeys={defaultOpenKeys} selectedKeys={[selectedKey]} $navItemStyle={{ width: `calc(100% - ${getHorizontalMargin(navItemStyle.margin.split(' '))})`, - ...navItemStyle + ...navItemStyle, }} $navItemHoverStyle={navItemHoverStyle} $navItemActiveStyle={navItemActiveStyle} From b06fa7ea7b9f26782a58c87d254a04b2bef22fb6 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Mon, 20 Nov 2023 19:38:50 +0500 Subject: [PATCH 08/14] feat: allow json data for nav layout --- .../comps/comps/layout/layoutMenuItemComp.tsx | 1 + .../src/comps/comps/layout/navLayout.tsx | 274 ++++++++++++++---- .../comps/comps/layout/navLayoutConstants.ts | 77 +++++ .../packages/lowcoder/src/i18n/locales/en.ts | 1 + .../packages/lowcoder/src/i18n/locales/zh.ts | 1 + 5 files changed, 299 insertions(+), 55 deletions(-) create mode 100644 client/packages/lowcoder/src/comps/comps/layout/navLayoutConstants.ts diff --git a/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx b/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx index a833229a9..0999a4012 100644 --- a/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/layout/layoutMenuItemComp.tsx @@ -96,6 +96,7 @@ const LayoutMenuItemCompMigrate = migrateOldData(LayoutMenuItemComp, (oldData: a export class LayoutMenuItemListComp extends list(LayoutMenuItemCompMigrate) { addItem(value?: any) { const data = this.getView(); + this.dispatch( this.pushAction( value diff --git a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx index c69c8c294..368e459a9 100644 --- a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx +++ b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx @@ -12,9 +12,9 @@ import { Section, controlItem, sectionNames } from "lowcoder-design"; import { trans } from "i18n"; import { EditorContainer, EmptyContent } from "pages/common/styledComponent"; import { useCallback, useEffect, useMemo, useState } from "react"; -import styled, { css } from "styled-components"; +import styled from "styled-components"; import { isUserViewMode, useAppPathParam } from "util/hooks"; -import { StringControl } from "comps/controls/codeControl"; +import { StringControl, jsonControl } from "comps/controls/codeControl"; import { styleControl } from "comps/controls/styleControl"; import { NavLayoutStyle, @@ -27,30 +27,20 @@ import { } from "comps/controls/styleControlConstants"; import { dropdownControl } from "comps/controls/dropdownControl"; import _ from "lodash"; +import { check } from "util/convertUtils"; +import { genRandomKey } from "comps/utils/idGenerator"; +import history from "util/history"; +import { + DataOption, + DataOptionType, + ModeOptions, + jsonMenuItems, + menuItemStyleOptions +} from "./navLayoutConstants"; const DEFAULT_WIDTH = 240; -const ModeOptions = [ - { label: trans("navLayout.modeInline"), value: "inline" }, - { label: trans("navLayout.modeVertical"), value: "vertical" }, -] as const; - type MenuItemStyleOptionValue = "normal" | "hover" | "active"; -const menuItemStyleOptions = [ - { - value: "normal", - label: "Normal", - }, - { - value: "hover", - label: "Hover", - }, - { - value: "active", - label: "Active", - } -] - const StyledSide = styled(Layout.Sider)` max-height: calc(100vh - ${TopHeaderHeight}); overflow: auto; @@ -143,19 +133,57 @@ const StyledMenu = styled(AntdMenu)<{ `; +const StyledImage = styled.img` + height: 1em; + color: currentColor; +`; + const defaultStyle = { radius: '0px', margin: '0px', padding: '0px', } +type UrlActionType = { + url?: string; + newTab?: boolean; +} + +export type MenuItemNode = { + label: string; + key: string; + hidden?: boolean; + icon?: any; + action?: UrlActionType, + children?: MenuItemNode[]; +} + +function checkDataNodes(value: any, key?: string): MenuItemNode[] | undefined { + return check(value, ["array", "undefined"], key, (node, k) => { + check(node, ["object"], k); + check(node["label"], ["string"], "label"); + check(node["hidden"], ["boolean", "undefined"], "hidden"); + check(node["icon"], ["string", "undefined"], "icon"); + check(node["action"], ["object", "undefined"], "action"); + checkDataNodes(node["children"], "children"); + return node; + }); +} + +function convertTreeData(data: any) { + return data === "" ? [] : checkDataNodes(data) ?? []; +} + let NavTmpLayout = (function () { const childrenMap = { + dataOptionType: dropdownControl(DataOptionType, DataOption.Manual), items: withDefault(LayoutMenuItemListComp, [ { label: trans("menuItem") + " 1", + itemKey: genRandomKey(), }, ]), + jsonItems: jsonControl(convertTreeData, jsonMenuItems), width: withDefault(StringControl, DEFAULT_WIDTH), backgroundImage: withDefault(StringControl, ""), mode: dropdownControl(ModeOptions, "inline"), @@ -173,7 +201,17 @@ let NavTmpLayout = (function () { return (
- {menuPropertyView(children.items)} + {children.dataOptionType.propertyView({ + radioButton: true, + type: "oneline", + })} + { + children.dataOptionType.getView() === DataOption.Manual + ? menuPropertyView(children.items) + : children.jsonItems.propertyView({ + label: "Json Data", + }) + }
{ children.width.propertyView({ @@ -199,7 +237,6 @@ let NavTmpLayout = (function () { block options={menuItemStyleOptions} value={styleSegment} - // className="comp-panel-tab" onChange={(k) => setStyleSegment(k as MenuItemStyleOptionValue)} /> ))} @@ -223,46 +260,97 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { const pathParam = useAppPathParam(); const isViewMode = isUserViewMode(pathParam); const [selectedKey, setSelectedKey] = useState(""); - const items = useMemo(() => comp.children.items.getView(), [comp.children.items]); - const navWidth = useMemo(() => comp.children.width.getView(), [comp.children.width]); - const navMode = useMemo(() => comp.children.mode.getView(), [comp.children.mode]); - const navStyle = useMemo(() => comp.children.navStyle.getView(), [comp.children.navStyle]); - const navItemStyle = useMemo(() => comp.children.navItemStyle.getView(), [comp.children.navItemStyle]); - const navItemHoverStyle = useMemo(() => comp.children.navItemHoverStyle.getView(), [comp.children.navItemHoverStyle]); - const navItemActiveStyle = useMemo(() => comp.children.navItemActiveStyle.getView(), [comp.children.navItemActiveStyle]); + const items = comp.children.items.getView(); + const navWidth = comp.children.width.getView(); + const navMode = comp.children.mode.getView(); + const navStyle = comp.children.navStyle.getView(); + const navItemStyle = comp.children.navItemStyle.getView(); + const navItemHoverStyle = comp.children.navItemHoverStyle.getView(); + const navItemActiveStyle = comp.children.navItemActiveStyle.getView(); const backgroundImage = comp.children.backgroundImage.getView(); - + const jsonItems = comp.children.jsonItems.getView(); + const dataOptionType = comp.children.dataOptionType.getView(); + // filter out hidden. unauthorised items filtered by server const filterItem = useCallback((item: LayoutMenuItemComp): boolean => { return !item.children.hidden.getView(); }, []); - const generateItemKeyRecord = (items: LayoutMenuItemComp[]) => { - const result: Record = {}; - items.forEach((item) => { - const subItems = item.children.items.getView(); - if (subItems.length > 0) { - Object.assign(result, generateItemKeyRecord(subItems)) + const generateItemKeyRecord = useCallback( + (items: LayoutMenuItemComp[] | MenuItemNode[]) => { + const result: Record = {}; + if(dataOptionType === DataOption.Manual) { + (items as LayoutMenuItemComp[])?.forEach((item) => { + const subItems = item.children.items.getView(); + if (subItems.length > 0) { + Object.assign(result, generateItemKeyRecord(subItems)) + } + result[item.getItemKey()] = item; + }); } - result[item.getItemKey()] = item; - }); - return result; - } + if(dataOptionType === DataOption.Json) { + (items as MenuItemNode[])?.forEach((item) => { + if (item.children?.length) { + Object.assign(result, generateItemKeyRecord(item.children)) + } + result[item.key] = item; + }) + } + return result; + }, [dataOptionType] + ) const itemKeyRecord = useMemo(() => { + if(dataOptionType === DataOption.Json) { + return generateItemKeyRecord(jsonItems) + } return generateItemKeyRecord(items) - }, [items]); + }, [dataOptionType, jsonItems, items, generateItemKeyRecord]); const onMenuItemClick = useCallback(({key}: {key: string}) => { - const itemComp = itemKeyRecord[key]; + const itemComp = itemKeyRecord[key] + const url = [ ALL_APPLICATIONS_URL, pathParam.applicationId, pathParam.viewMode, - itemComp.getItemKey(), + key, ].join("/"); - itemComp.children.action.act(url); - }, [pathParam.applicationId, pathParam.viewMode, itemKeyRecord]) + + // handle manual menu item action + if(dataOptionType === DataOption.Manual) { + (itemComp as LayoutMenuItemComp).children.action.act(url); + return; + } + // handle json menu item action + if((itemComp as MenuItemNode).action?.newTab) { + return window.open((itemComp as MenuItemNode).action?.url, '_blank') + } + history.push(url); + }, [pathParam.applicationId, pathParam.viewMode, dataOptionType, itemKeyRecord]) + + const getJsonMenuItem = useCallback( + (items: MenuItemNode[]): MenuProps["items"] => { + return items?.map((item: MenuItemNode) => { + const { + label, + key, + hidden, + icon, + children, + } = item; + return { + label, + key, + hidden, + icon: , + onTitleClick: onMenuItemClick, + onClick: onMenuItemClick, + ...(children?.length && { children: getJsonMenuItem(children) }), + } + }) + }, [onMenuItemClick] + ) const getMenuItem = useCallback( (itemComps: LayoutMenuItemComp[]): MenuProps["items"] => { @@ -283,7 +371,11 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { [onMenuItemClick, filterItem] ); - const menuItems = useMemo(() => getMenuItem(items), [items, getMenuItem]); + const menuItems = useMemo(() => { + if(dataOptionType === DataOption.Json) return getJsonMenuItem(jsonItems) + + return getMenuItem(items) + }, [dataOptionType, jsonItems, getJsonMenuItem, items, getMenuItem]); // Find by path itemKey const findItemPathByKey = useCallback( @@ -329,7 +421,60 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { [filterItem] ); + // Find by path itemKey + const findItemPathByKeyJson = useCallback( + (itemComps: MenuItemNode[], itemKey: string): string[] => { + for (let item of itemComps) { + const subItems = item.children; + if (subItems?.length) { + // have subMenus + const childPath = findItemPathByKeyJson(subItems, itemKey); + if (childPath.length > 0) { + return [item.key, ...childPath]; + } + } else { + if (item.key === itemKey) { + return [item.key]; + } + } + } + return []; + }, + [] + ); + + // Get the first visible menu + const findFirstItemPathJson = useCallback( + (itemComps: MenuItemNode[]): string[] => { + for (let item of itemComps) { + if (!item.hidden) { + const subItems = item.children; + if (subItems?.length) { + // have subMenus + const childPath = findFirstItemPathJson(subItems); + if (childPath.length > 0) { + return [item.key, ...childPath]; + } + } else { + return [item.key]; + } + } + } + return []; + }, [] + ); + const defaultOpenKeys = useMemo(() => { + if(dataOptionType === DataOption.Json) { + let itemPath: string[]; + if (pathParam.appPageId) { + itemPath = findItemPathByKeyJson(jsonItems, pathParam.appPageId); + } else { + itemPath = findFirstItemPathJson(jsonItems); + } + return itemPath.slice(0, itemPath.length - 1); + } + let itemPath: string[]; if (pathParam.appPageId) { itemPath = findItemPathByKey(items, pathParam.appPageId); @@ -350,14 +495,32 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { setSelectedKey(selectedKey); }, [pathParam.appPageId]); - let pageView = ; - const selectedItem = itemKeyRecord[selectedKey]; - if (selectedItem && !selectedItem.children.hidden.getView()) { - const compView = selectedItem.children.action.getView(); - if (compView) { - pageView = compView; + const pageView = useMemo(() => { + let pageView = ; + + if(dataOptionType === DataOption.Manual) { + const selectedItem = (itemKeyRecord[selectedKey] as LayoutMenuItemComp); + if (selectedItem && !selectedItem.children.hidden.getView()) { + const compView = selectedItem.children.action.getView(); + if (compView) { + pageView = compView; + } + } } - } + if(dataOptionType === DataOption.Json) { + const item = (itemKeyRecord[selectedKey] as MenuItemNode) + if(item?.action?.url) { + pageView =