diff --git a/client/package.json b/client/package.json index 2f49baf8c..fccf0fff1 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "lowcoder-root", - "version": "2.2.0", + "version": "2.3.0", "type": "module", "private": true, "workspaces": [ diff --git a/client/packages/lowcoder-core/lib/index.d.ts b/client/packages/lowcoder-core/lib/index.d.ts index d0db17798..b29a693f4 100644 --- a/client/packages/lowcoder-core/lib/index.d.ts +++ b/client/packages/lowcoder-core/lib/index.d.ts @@ -454,7 +454,7 @@ declare enum CompActionTypes { * broadcast other actions in comp tree structure. * used for encapsulate MultiBaseComp */ - BROADCAST = "BROADCAST" + BROADCAST = "BROADCAST", } type ExtraActionType = "layout" | "delete" | "add" | "modify" | "rename" | "recover" | "upgrade"; type ActionExtraInfo = { diff --git a/client/packages/lowcoder-design/src/icons/index.ts b/client/packages/lowcoder-design/src/icons/index.ts index 2afd2a739..938cd1fc4 100644 --- a/client/packages/lowcoder-design/src/icons/index.ts +++ b/client/packages/lowcoder-design/src/icons/index.ts @@ -214,8 +214,9 @@ export { ReactComponent as ClickHouseIcon } from "./icon-query-ClickHouse.svg"; export { ReactComponent as ResetIcon } from "./icon-style-reset.svg"; export { ReactComponent as EditIcon } from "./icon-edit.svg"; export { ReactComponent as EditableIcon } from "./icon-editable.svg"; -export { ReactComponent as LeftStateIcon } from "./icon-left-state.svg"; -export { ReactComponent as LeftSettingIcon } from "./icon-left-setting.svg"; +export { ReactComponent as LeftStateIcon } from "./remix/node-tree.svg"; +export { ReactComponent as LeftSettingIcon } from "./remix/tools-fill.svg"; +export { ReactComponent as LeftLayersIcon } from "./remix/stack-line.svg"; export { ReactComponent as LeftHelpIcon } from "./icon-left-help.svg"; export { ReactComponent as LeftPreloadIcon } from "./icon-left-preload.svg"; export { ReactComponent as CollapsibleContainerCompIcon } from "./icon-collapsible-container.svg"; @@ -277,31 +278,37 @@ export { ReactComponent as SignatureIcon } from "./icon-signature.svg"; export { ReactComponent as ManualIcon } from "./icon-manual.svg"; export { ReactComponent as WarnIcon } from "./icon-warn.svg"; export { ReactComponent as SyncManualIcon } from "./icon-sync-manual.svg"; -export { ReactComponent as DangerIcon } from "icons/icon-danger.svg"; -export { ReactComponent as TableMinusIcon } from "icons/icon-table-minus.svg"; -export { ReactComponent as TablePlusIcon } from "icons/icon-table-plus.svg"; -export { ReactComponent as MobileAppIcon } from "icons/icon-mobile-app.svg"; -export { ReactComponent as MobileNavIcon } from "icons/icon-navigation-mobile.svg"; -export { ReactComponent as PcNavIcon } from "icons/icon-navigation-pc.svg"; -export { ReactComponent as UnLockIcon } from "icons/icon-unlock.svg"; -export { ReactComponent as CalendarDeleteIcon } from "icons/icon-calendar-delete.svg"; -export { ReactComponent as TableCheckedIcon } from "icons/icon-table-checked.svg"; -export { ReactComponent as TableUnCheckedIcon } from "icons/icon-table-boolean-false.svg"; -export { ReactComponent as FileFolderIcon } from "icons/icon-editor-folder.svg"; -export { ReactComponent as ExpandIcon } from "icons/icon-expand.svg"; -export { ReactComponent as CompressIcon } from "icons/icon-compress.svg"; -export { ReactComponent as TableCellsIcon } from "icons/icon-table-cells.svg"; // Added By Aqib Mirza -export { ReactComponent as TimeLineIcon } from "icons/icon-timeline-comp.svg" -export { ReactComponent as LottieIcon } from "icons/icon-lottie.svg"; -export { ReactComponent as CommentIcon } from "icons/icon-comment-comp.svg"; -export { ReactComponent as MentionIcon } from "icons/icon-mention-comp.svg"; -export { ReactComponent as AutoCompleteCompIcon } from "icons/icon-autocomplete-comp.svg"; -export { ReactComponent as WidthIcon } from "icons/icon-width.svg"; -export { ReactComponent as ResponsiveLayoutCompIcon } from "icons/icon-responsive-layout-comp.svg"; +export { ReactComponent as DangerIcon } from "./icon-danger.svg"; +export { ReactComponent as TableMinusIcon } from "./icon-table-minus.svg"; +export { ReactComponent as TablePlusIcon } from "./icon-table-plus.svg"; +export { ReactComponent as MobileAppIcon } from "./icon-mobile-app.svg"; +export { ReactComponent as MobileNavIcon } from "./icon-navigation-mobile.svg"; +export { ReactComponent as PcNavIcon } from "./icon-navigation-pc.svg"; +export { ReactComponent as UnLockIcon } from "./icon-unlock.svg"; +export { ReactComponent as CalendarDeleteIcon } from "./icon-calendar-delete.svg"; +export { ReactComponent as TableCheckedIcon } from "./icon-table-checked.svg"; +export { ReactComponent as TableUnCheckedIcon } from "./icon-table-boolean-false.svg"; +export { ReactComponent as FileFolderIcon } from "./icon-editor-folder.svg"; +export { ReactComponent as ExpandIcon } from "./icon-expand.svg"; +export { ReactComponent as CompressIcon } from "./icon-compress.svg"; +export { ReactComponent as TableCellsIcon } from "./icon-table-cells.svg"; // Added By Aqib Mirza +export { ReactComponent as TimeLineIcon } from "./icon-timeline-comp.svg" +export { ReactComponent as LottieIcon } from "./icon-lottie.svg"; +export { ReactComponent as CommentIcon } from "./icon-comment-comp.svg"; +export { ReactComponent as MentionIcon } from "./icon-mention-comp.svg"; +export { ReactComponent as AutoCompleteCompIcon } from "./icon-autocomplete-comp.svg"; +export { ReactComponent as WidthIcon } from "./icon-width.svg"; +export { ReactComponent as ResponsiveLayoutCompIcon } from "./icon-responsive-layout-comp.svg"; export { ReactComponent as TextSizeIcon } from "./remix/font-size-2.svg"; export { ReactComponent as FontFamilyIcon } from "./remix/font-sans-serif.svg"; export { ReactComponent as TextWeigthIcon } from "./remix/bold.svg"; export { ReactComponent as BorderWidthIcon } from "./remix/expand-width-line.svg"; +export { ReactComponent as LeftInfoLine } from "./remix/information-line.svg"; +export { ReactComponent as LeftInfoFill } from "./remix/information-fill.svg"; +export { ReactComponent as LeftShow } from "./remix/eye-off-line.svg"; +export { ReactComponent as LeftHide } from "./remix/eye-line.svg"; +export { ReactComponent as LeftLock } from "./remix/lock-line.svg"; +export { ReactComponent as LeftUnlock } from "./remix/lock-unlock-line.svg"; // new diff --git a/client/packages/lowcoder/src/comps/comps/containerComp/containerView.tsx b/client/packages/lowcoder/src/comps/comps/containerComp/containerView.tsx index 26ac5561e..45d8f7d26 100644 --- a/client/packages/lowcoder/src/comps/comps/containerComp/containerView.tsx +++ b/client/packages/lowcoder/src/comps/comps/containerComp/containerView.tsx @@ -230,6 +230,14 @@ const onDrop = ( }; const key = genRandomKey(); const layoutItem = Object.values(items)[0]; + // calculate postion of newly added comp + // should have last position in the comps list + let itemPos = 0; + if (!Object.keys(layout).length) { + itemPos = 0; + } else { + itemPos = Math.max(...Object.values(layout).map(l => l.pos || 0)) + 1; + } // log.debug("layout: onDrop. widgetValue: ", widgetValue, " layoutItem: ", layoutItem); dispatch( wrapActionExtraInfo( @@ -237,7 +245,12 @@ const onDrop = ( layout: changeValueAction( { ...layout, - [key]: { ...layoutItem, i: key, placeholder: undefined }, + [key]: { + ...layoutItem, + i: key, + placeholder: undefined, + pos: itemPos, + }, }, true ), @@ -463,6 +476,7 @@ export function InnerGrid(props: ViewPropsWithSelect) { layout={props.layout} extraLayout={extraLayout} onDropDragOver={(e) => { + const compType = draggingUtils.getData("compType"); const compLayout = draggingUtils.getData("compLayout"); if (compType) { diff --git a/client/packages/lowcoder/src/comps/comps/textComp.tsx b/client/packages/lowcoder/src/comps/comps/textComp.tsx index 671e67354..aec1f8af1 100644 --- a/client/packages/lowcoder/src/comps/comps/textComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/textComp.tsx @@ -37,7 +37,7 @@ const getStyle = (style: TextStyleType) => { margin: ${style.margin} !important; padding: ${style.padding}; width: ${widthCalculator(style.margin)}; - height: ${heightCalculator(style.margin)}; + // height: ${heightCalculator(style.margin)}; h1 { line-height: 1.5; } diff --git a/client/packages/lowcoder/src/comps/editorState.tsx b/client/packages/lowcoder/src/comps/editorState.tsx index 35732c075..ee9db5475 100644 --- a/client/packages/lowcoder/src/comps/editorState.tsx +++ b/client/packages/lowcoder/src/comps/editorState.tsx @@ -3,7 +3,11 @@ import { RootComp as RootCompTmp } from "comps/comps/rootComp"; import { PositionParams } from "layout"; import _ from "lodash"; import React, { ReactNode } from "react"; -import { BottomResComp, BottomResListComp, BottomResTypeEnum } from "types/bottomRes"; +import { + BottomResComp, + BottomResListComp, + BottomResTypeEnum, +} from "types/bottomRes"; import { setFields } from "util/objectUtils"; import { OptionalComp, renameAction } from "lowcoder-core"; import { GridItemComp } from "./comps/gridItemComp"; @@ -13,7 +17,7 @@ import { NameAndExposingInfo } from "./utils/exposingTypes"; import { checkName } from "./utils/rename"; import { trans } from "i18n"; import { UiLayoutType } from "./comps/uiComp"; -import { getEditorModeStatus } from "util/localStorageUtil"; +import { getCollisionStatus, getEditorModeStatus } from "util/localStorageUtil"; type RootComp = InstanceType; @@ -43,6 +47,7 @@ export class EditorState { readonly showPropertyPane: boolean = false; readonly selectedCompNames: Set = new Set(); readonly editorModeStatus: string = ""; + readonly collisionStatus: string = ""; readonly isDragging: boolean = false; readonly draggingCompType: string = "button"; readonly forceShowGrid: boolean = false; // show grid lines @@ -52,16 +57,20 @@ export class EditorState { readonly showResultCompName: string = ""; readonly selectSource?: SelectSourceType; // the source of select type - private readonly setEditorState: (fn: (editorState: EditorState) => EditorState) => void; + private readonly setEditorState: ( + fn: (editorState: EditorState) => EditorState + ) => void; constructor( rootComp: RootComp, setEditorState: (fn: (editorState: EditorState) => EditorState) => void, - initialEditorModeStatus: string = getEditorModeStatus() + initialEditorModeStatus: string = getEditorModeStatus(), + initialCollisionStatus: string = getCollisionStatus() ) { this.rootComp = rootComp; this.setEditorState = setEditorState; this.editorModeStatus = initialEditorModeStatus; + this.collisionStatus = initialCollisionStatus; } /** @@ -79,7 +88,10 @@ export class EditorState { } getAllCompMap() { - return { ...this.getAllHooksCompMap(), ...this.getUIComp().getAllCompItems() }; + return { + ...this.getAllHooksCompMap(), + ...this.getUIComp().getAllCompItems(), + }; } getAllUICompMap() { @@ -104,7 +116,9 @@ export class EditorState { */ getUICompByName(name: string) { const compMap = this.getAllUICompMap(); - return Object.values(compMap).find((item) => item.children.name.getView() === name); + return Object.values(compMap).find( + (item) => item.children.name.getView() === name + ); } getNameGenerator() { @@ -120,17 +134,22 @@ export class EditorState { uiCompInfoList(): Array { const compMap = this.getAllUICompMap(); - return Object.values(compMap).map((item) => { + return Object.entries(compMap).map(([key, item]) => { return { name: item.children.name.getView(), type: item.children.compType.getView(), data: item.children.comp.exposingValues, dataDesc: item.children.comp.exposingInfo().propertyDesc, + key: key, }; }); } - getCompInfo(nameAndExposingInfo: NameAndExposingInfo, name: string, type: string): CompInfo { + getCompInfo( + nameAndExposingInfo: NameAndExposingInfo, + name: string, + type: string + ): CompInfo { return { name, type, @@ -157,7 +176,11 @@ export class EditorState { const exposingInfo = listComp.nameAndExposingInfo(); return listComp.getView().map((item) => { const name = item.children.name.getView(); - return this.getCompInfo(exposingInfo, name, BottomResTypeEnum.DateResponder); + return this.getCompInfo( + exposingInfo, + name, + BottomResTypeEnum.DateResponder + ); }); } @@ -175,7 +198,11 @@ export class EditorState { const exposingInfo = listComp.nameAndExposingInfo(); return listComp.getView().map((item) => { const name = item.children.name.getView(); - return this.getCompInfo(exposingInfo, name, BottomResTypeEnum.Transformer); + return this.getCompInfo( + exposingInfo, + name, + BottomResTypeEnum.Transformer + ); }); } @@ -222,7 +249,10 @@ export class EditorState { } selectedQueryComp() { - if (this.selectedBottomResType !== BottomResTypeEnum.Query || !this.selectedBottomResName) { + if ( + this.selectedBottomResType !== BottomResTypeEnum.Query || + !this.selectedBottomResName + ) { return undefined; } return this.getQueriesComp() @@ -233,7 +263,9 @@ export class EditorState { } showResultComp(): BottomResComp | undefined { - const bottomResComps = Object.values(BottomResTypeEnum).reduce((a, b) => { + const bottomResComps = Object.values(BottomResTypeEnum).reduce< + BottomResComp[] + >((a, b) => { const items = this.getBottomResListComp(b).items(); return a.concat(items); }, []); @@ -279,7 +311,10 @@ export class EditorState { return this.getUIComp().getComp(); } const [key, comp] = _.toPairs(selectedComps)[0]; - if (_.size(selectedComps) === 1 && isContainer((comp as GridItemComp)?.children?.comp)) { + if ( + _.size(selectedComps) === 1 && + isContainer((comp as GridItemComp)?.children?.comp) + ) { return comp.children.comp; } @@ -307,7 +342,9 @@ export class EditorState { isCompSelected(compName: string): OptionalComp { const compMap = this.getAllCompMap(); return Object.values(compMap).find( - (item) => item.children.name.getView() === compName && this.selectedCompNames.has(compName) + (item) => + item.children.name.getView() === compName && + this.selectedCompNames.has(compName) ); } @@ -319,6 +356,10 @@ export class EditorState { this.changeState({ editorModeStatus: newEditorModeStatus }); } + setCollisionStatus(newCollisionStatus: string) { + this.changeState({ collisionStatus: newCollisionStatus }); + } + setDragging(dragging: boolean) { if (this.isDragging === dragging) { return; @@ -356,7 +397,10 @@ export class EditorState { }); } - setSelectedCompNames(selectedCompNames: Set, selectSource?: SelectSourceType) { + setSelectedCompNames( + selectedCompNames: Set, + selectSource?: SelectSourceType + ) { if (selectedCompNames.size === 0 && this.selectedCompNames.size === 0) { return; } @@ -406,7 +450,9 @@ export class EditorState { } getBottomResComp(name: string): BottomResComp | undefined { - const bottomResComps = Object.values(BottomResTypeEnum).reduce((a, b) => { + const bottomResComps = Object.values(BottomResTypeEnum).reduce< + BottomResComp[] + >((a, b) => { const items = this.getBottomResListComp(b).items(); return a.concat(items); }, []); @@ -467,8 +513,11 @@ export class EditorState { getAppType(): UiLayoutType { return this.getUIComp().children.compType.getView(); } + getCollisionStatus(): string { + return this.collisionStatus; + } + } - export const EditorContext = React.createContext(undefined as any); // current comp name diff --git a/client/packages/lowcoder/src/constants/Layers.ts b/client/packages/lowcoder/src/constants/Layers.ts index fcd2a361b..358f52dcc 100644 --- a/client/packages/lowcoder/src/constants/Layers.ts +++ b/client/packages/lowcoder/src/constants/Layers.ts @@ -30,7 +30,7 @@ export const Layers = { // comp selection wrapper dragSelectBox: 399, // - compHover: 300, + compHover: 100, // compSelected: 200, // diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index b5ec23f09..cd57c9165 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -104,7 +104,12 @@ export const en = { "components": "Active Components", "modals": "in-App Modals", "expandTip": "Click to Expand {component}'s Data", - "collapseTip": "Click to Collapse {component}'s Data" + "collapseTip": "Click to Collapse {component}'s Data", + "layers": "Layers", + "activatelayers": "Use Layers in this App", + "selectedComponents": "Selected Components...", + "displayComponents": "control Display", + "lockComponents": "control Position", }, // second part diff --git a/client/packages/lowcoder/src/i18n/locales/zh.ts b/client/packages/lowcoder/src/i18n/locales/zh.ts index 7ff3d6825..ac6020fd4 100644 --- a/client/packages/lowcoder/src/i18n/locales/zh.ts +++ b/client/packages/lowcoder/src/i18n/locales/zh.ts @@ -106,7 +106,10 @@ leftPanel: { components: "组件", modals: "对话框", expandTip: "点击展开 {component} 的数据", - collapseTip: "点击折叠 {component} 的数据" + collapseTip: "点击折叠 {component} 的数据", + layers: "图层", + activatelayers: "激活图层", + selectedComponents: "已选组件", }, bottomPanel: { title: "查询", diff --git a/client/packages/lowcoder/src/layout/gridItem.tsx b/client/packages/lowcoder/src/layout/gridItem.tsx index ba80c46f5..ad1f8ae06 100644 --- a/client/packages/lowcoder/src/layout/gridItem.tsx +++ b/client/packages/lowcoder/src/layout/gridItem.tsx @@ -34,6 +34,7 @@ type GridItemCallback = ( arg3: Data ) => void; export type GridItemProps = { + zIndex: number; children: ReactElement; cols: number; containerWidth: number; @@ -81,10 +82,18 @@ export type GridItemProps = { export const IsDroppable = React.createContext(true); -const ResizableStyled = styled(Resizable)` - &:hover { - z-index: 1; - } +/* const ResizableStyled = styled(Resizable)<{ $zIndex: number, isDroppable : boolean}>` + z-index: ${props => props.$zIndex * 10}; + ${props => props.isDroppable && ` + &:hover { + z-index: 1; + } + `} +`; */ + +// changed to remove &:hover { z-index: 1; as it lead into flickering +const ResizableStyled = styled(Resizable)<{ $zIndex: number, isDroppable : boolean}>` + z-index: ${props => props.$zIndex * 10}; `; /** @@ -151,7 +160,8 @@ export function GridItem(props: GridItemProps) { const mixinResizable = ( child: ReactElement, position: Position, - isResizable: boolean + isResizable: boolean, + zIndex: number, ): ReactElement => { const { cols, x, minW, minH, maxW, maxH, resizeHandles } = props; // This is the max possible width - doesn't go to infinity because of the width of the window @@ -179,6 +189,8 @@ export function GridItem(props: GridItemProps) { onResizeStop={onResizeStop} resizeHandles={resizeHandles} handle={Handle} + $zIndex={zIndex} + isDroppable={draggingUtils.getData("i") !== props.i} > {child} @@ -397,7 +409,7 @@ export function GridItem(props: GridItemProps) { return { width, height, top, left }; }, [dragging, position.height, position.left, position.top, position.width, resizing]); - const { isDraggable, isResizable, layoutHide, children, isSelected, clickItem } = props; + const { isDraggable, isResizable, layoutHide, children, isSelected, clickItem, zIndex } = props; const pos = calcPosition(); const render = () => { let child = React.Children.only(children); @@ -433,7 +445,7 @@ export function GridItem(props: GridItemProps) { }, }); // Resizable support. This is usually on but the user can toggle it off. - newChild = mixinResizable(newChild, pos, isResizable); + newChild = mixinResizable(newChild, pos, isResizable, zIndex); // Draggable support. This is always on, except for with placeholders. newChild = mixinDraggable(newChild, isDraggable); return newChild; diff --git a/client/packages/lowcoder/src/layout/gridLayout.tsx b/client/packages/lowcoder/src/layout/gridLayout.tsx index 328ff1ab6..0058ff71f 100644 --- a/client/packages/lowcoder/src/layout/gridLayout.tsx +++ b/client/packages/lowcoder/src/layout/gridLayout.tsx @@ -393,6 +393,7 @@ class GridLayout extends React.Component { }; processGridItem( + zIndex: number, item: LayoutItem, childrenMap: _.Dictionary ): React.ReactElement | undefined { @@ -464,6 +465,7 @@ class GridLayout extends React.Component { top: showName?.top ?? 0, bottom: (showName?.bottom ?? 0) + (this.ref.current?.scrollHeight ?? 0), }} + zIndex={zIndex} > {child} @@ -863,7 +865,6 @@ class GridLayout extends React.Component { // move the logic to onDragEnd function when dragging from the canvas return; } - let layout = this.getUILayout(); const ops = layoutOpUtils.push(this.state.ops, deleteItemOp(droppingKey)); const items = _.pick(layout, droppingKey); @@ -1001,6 +1002,7 @@ class GridLayout extends React.Component { this.ref = this.props.innerRef ?? this.innerRef; // log.debug("GridLayout render. layout: ", layout, " oriLayout: ", this.state.layout, " extraLayout: ", this.props.extraLayout); + const layouts = Object.values(layout); return ( {
{showGridLines && this.gridLines()} {mounted && - Object.values(layout).map((item) => this.processGridItem(item, childrenMap))} + layouts.map((item) => { + const zIndex = item.pos !== undefined + ? layouts.length - item.pos + : 1; + return this.processGridItem(zIndex, item, childrenMap) + }) + } {this.hintPlaceholder()}
diff --git a/client/packages/lowcoder/src/layout/utils.ts b/client/packages/lowcoder/src/layout/utils.ts index c55c44dc2..40ee0ed72 100644 --- a/client/packages/lowcoder/src/layout/utils.ts +++ b/client/packages/lowcoder/src/layout/utils.ts @@ -1,17 +1,20 @@ import { UICompType } from "comps/uiCompRegistry"; import _ from "lodash"; -import React, { ReactElement, SyntheticEvent } from "react"; +import React, { ReactElement, SyntheticEvent, useContext } from "react"; import { DraggableEvent } from "react-draggable"; import { PositionParams } from "./calculateUtils"; import { draggingUtils } from "./draggingUtils"; import { GridLayoutProps, ResizeHandleAxis } from "./gridLayoutPropTypes"; +import { getCollisionStatus } from "util/localStorageUtil"; + export type LayoutItem = { w: number; h: number; x: number; y: number; i: string; + pos?: number; minW?: number; minH?: number; maxW?: number; @@ -169,7 +172,12 @@ export function collides(l1: LayoutItem, l2: LayoutItem): boolean { if (l1.y + l1.h <= l2.y) return false; // l1 is above l2 if (l1.y >= l2.y + l2.h) return false; // l1 is below l2 - return true; // boxes overlap + if (getCollisionStatus() === "true") { + return false; + } + else { + return true; // boxes overlap + } } /** diff --git a/client/packages/lowcoder/src/pages/common/header.tsx b/client/packages/lowcoder/src/pages/common/header.tsx index 65663b68d..06a0067ec 100644 --- a/client/packages/lowcoder/src/pages/common/header.tsx +++ b/client/packages/lowcoder/src/pages/common/header.tsx @@ -4,7 +4,11 @@ import { default as Radio, RadioChangeEvent } from "antd/es/radio"; import LayoutHeader from "components/layout/Header"; import { SHARE_TITLE } from "constants/apiConstants"; import { AppTypeEnum } from "constants/applicationConstants"; -import { ALL_APPLICATIONS_URL, AUTH_LOGIN_URL, preview } from "constants/routesURL"; +import { + ALL_APPLICATIONS_URL, + AUTH_LOGIN_URL, + preview, +} from "constants/routesURL"; import { User } from "constants/userConstants"; import { CommonTextLabel, @@ -23,8 +27,14 @@ import { trans } from "i18n"; import dayjs from "dayjs"; import { useContext, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { publishApplication, updateAppMetaAction } from "redux/reduxActions/applicationActions"; -import { recoverSnapshotAction, setShowAppSnapshot } from "redux/reduxActions/appSnapshotActions"; +import { + publishApplication, + updateAppMetaAction, +} from "redux/reduxActions/applicationActions"; +import { + recoverSnapshotAction, + setShowAppSnapshot, +} from "redux/reduxActions/appSnapshotActions"; import { currentApplication } from "redux/selectors/applicationSelector"; import { getSelectedAppSnapshot, @@ -42,8 +52,7 @@ import { HeaderStartDropdown } from "./headerStartDropdown"; import { AppPermissionDialog } from "../../components/PermissionDialog/AppPermissionDialog"; import { getBrandingConfig } from "../../redux/selectors/configSelectors"; import { messageInstance } from "lowcoder-design"; -import { EditorContext } from "../../comps/editorState"; - +import { EditorContext } from "../../comps/editorState"; const StyledLink = styled.a` display: flex; @@ -159,13 +168,13 @@ const GrayBtn = styled(TacoButton)` margin-right: 8px; cursor: pointer; --antd-wave-shadow-color: #8b8fa34c; - + &:hover { background: #666666; color: #ffffff; border: none; } - + &:focus { background: #666666; color: #ffffff; @@ -261,7 +270,10 @@ function HeaderProfile(props: { user: User }) { return (
{user.isAnonymous ? ( - history.push(AUTH_LOGIN_URL)}> + history.push(AUTH_LOGIN_URL)} + > {trans("userAuth.login")} ) : ( @@ -273,8 +285,11 @@ function HeaderProfile(props: { user: User }) { export type PanelStatus = { left: boolean; bottom: boolean; right: boolean }; export type TogglePanel = (panel?: keyof PanelStatus) => void; + export type EditorModeStatus = "layout" | "logic" | "both"; -export type ToggleEditorModeStatus = (editorModeStatus?: EditorModeStatus) => void; +export type ToggleEditorModeStatus = ( + editorModeStatus?: EditorModeStatus +) => void; type HeaderProps = { panelStatus: PanelStatus; @@ -303,16 +318,31 @@ export default function Header(props: HeaderProps) { const isModule = appType === AppTypeEnum.Module; const editorModeOptions = [ - { label: trans("header.editorMode_layout"), key: "editorModeSelector_layout", value: "layout" }, - { label: trans("header.editorMode_logic"), key: "editorModeSelector_logic", value: "logic" }, - { label: trans("header.editorMode_both"), key: "editorModeSelector_both", value: "both" }, + { + label: trans("header.editorMode_layout"), + key: "editorModeSelector_layout", + value: "layout", + }, + { + label: trans("header.editorMode_logic"), + key: "editorModeSelector_logic", + value: "logic", + }, + { + label: trans("header.editorMode_both"), + key: "editorModeSelector_both", + value: "both", + }, ]; - - const onEditorStateValueChange = ({ target: { value } }: RadioChangeEvent) => { + + const onEditorStateValueChange = ({ + target: { value }, + }: RadioChangeEvent) => { toggleEditorModeStatus(value); editorState.setEditorModeStatus(value); }; - + + const headerStart = ( <> history.push(ALL_APPLICATIONS_URL)}> @@ -332,7 +362,12 @@ export default function Header(props: HeaderProps) { messageInstance.warning(trans("header.nameCheckMessage")); return; } - dispatch(updateAppMetaAction({ applicationId: applicationId, name: value })); + dispatch( + updateAppMetaAction({ + applicationId: applicationId, + name: value, + }) + ); setEditName(false); }} /> @@ -345,15 +380,23 @@ export default function Header(props: HeaderProps) { }} /> )} - {showAppSnapshot && {trans("header.viewOnly")}} + {showAppSnapshot && ( + {trans("header.viewOnly")} + )} ); - // key={option.key} - const headerMiddle = ( - <> - + <> + <> + + {editorModeOptions.map((option) => ( {option.label} @@ -379,7 +422,9 @@ export default function Header(props: HeaderProps) { CustomModal.confirm({ title: trans("header.recoverAppSnapshotTitle"), content: trans("header.recoverAppSnapshotContent", { - time: dayjs(selectedSnapshot.createTime).format("YYYY-MM-DD HH:mm"), + time: dayjs(selectedSnapshot.createTime).format( + "YYYY-MM-DD HH:mm" + ), }), onConfirm: () => { dispatch( @@ -411,11 +456,15 @@ export default function Header(props: HeaderProps) { !visible && setPermissionDialogVisible(false)} + onVisibleChange={(visible) => + !visible && setPermissionDialogVisible(false) + } /> )} {canManageApp(user, application) && ( - setPermissionDialogVisible(true)}>{SHARE_TITLE} + setPermissionDialogVisible(true)}> + {SHARE_TITLE} + )} preview(applicationId)}> {trans("header.preview")} @@ -438,11 +487,15 @@ export default function Header(props: HeaderProps) { items={[ { key: "deploy", - label: {trans("header.deploy")}, + label: ( + {trans("header.deploy")} + ), }, { key: "snapshot", - label: {trans("header.snapshot")}, + label: ( + {trans("header.snapshot")} + ), }, ]} /> @@ -458,7 +511,11 @@ export default function Header(props: HeaderProps) { ); return ( - + ); } @@ -477,7 +534,9 @@ export function AppHeader() { ); } diff --git a/client/packages/lowcoder/src/pages/editor/LeftLayersContent.tsx b/client/packages/lowcoder/src/pages/editor/LeftLayersContent.tsx new file mode 100644 index 000000000..9fd7e6804 --- /dev/null +++ b/client/packages/lowcoder/src/pages/editor/LeftLayersContent.tsx @@ -0,0 +1,547 @@ +import { CompInfo, EditorContext } from "comps/editorState"; +import { + BaseSection, + CollapseLabel as Label, + CollapseTitle as Title, + FoldedIcon, + LeftCommon, + ScrollBar, + UnfoldIcon, + LeftShow, +} from "lowcoder-design"; +import React, { useCallback, useContext, useMemo, useState, useEffect, useRef } from "react"; +import _, { get } from "lodash"; +import styled from "styled-components"; +import { leftCompListClassName } from "pages/tutorials/tutorialsConstant"; +import UIComp from "comps/comps/uiComp"; +import { getTreeNodeByKey } from "util/objectUtils"; +import { TopHeaderHeight } from "constants/style"; +import { trans } from "i18n"; +import { CompTree } from "comps/comps/containerBase"; +import { CompStateIcon } from "./editorConstants"; +import { UICompType } from "comps/uiCompRegistry"; +import { DirectoryTreeStyle, Node } from "./styledComponents"; +import { isAggregationApp } from "util/appUtils"; +import cloneDeep from 'lodash/cloneDeep'; +import { useDispatch } from "react-redux"; +import { useApplicationId } from "util/hooks"; +import { Button, Divider, Dropdown, Flex, Input, Menu, MenuProps, Space } from "antd"; +import { Switch } from "antd"; +import { + saveCollisionStatus, + getCollisionStatus, +} from "util/localStorageUtil"; +import { check, withViewFn } from "@lowcoder-ee/index.sdk"; +import { DownOutlined } from "@ant-design/icons"; +import { ItemType } from "antd/es/menu/hooks/useItems"; +import ColorPicker, { configChangeParams } from "components/ColorPicker"; + + +export type DisabledCollisionStatus = "true" | "false"; // "true" means collision is not enabled - Layering works, "false" means collision is enabled - Layering does not work +export type ToggleCollisionStatus = (collisionStatus?: DisabledCollisionStatus) => void; + +interface LeftLayersContentProps { + uiComp: InstanceType; +} + +const DropdownLeftShow = () => ( + // Setting custom viewBox +); + +type NodeItem = { + key: string; + title: string; + type?: UICompType; + children: NodeItem[]; + pos?: number; + disabled?: boolean; + fixed?: boolean; +}; + +const LeftLayersContentWrapper = styled.div` + height: calc(100vh - ${TopHeaderHeight}); +`; + +const CustomDropdown = styled(Dropdown)` + .ant-dropdown-menu-item-icon { + width: 14px !important; + height: 14px !important; + max-width: 14px !important; + } +`; + +export const LeftLayersContent = (props: LeftLayersContentProps) => { + const { uiComp } = props; + const editorState = useContext(EditorContext); + const [expandedKeys, setExpandedKeys] = useState>([]); + const dispatch = useDispatch(); + const applicationId = useApplicationId(); + + // added by Falk Wolsky to support a Layers in Lowcoder + const [collisionStatus, setCollisionStatus] = useState(() => { + return getCollisionStatus(); + }); + + const toggleCollisionStatus: ToggleCollisionStatus = useCallback( + (value) => { + setCollisionStatus(value ? value : ("false" as DisabledCollisionStatus)); + saveCollisionStatus(value ? value : ("false" as DisabledCollisionStatus)); + }, + [collisionStatus] + ); + + const getTree = (tree: CompTree, result: NodeItem[], key?: string) => { + const { items, children } = tree; + if (Object.keys(items).length) { + for (const i in items) { + const info: NodeItem = { + title: items[i].children.name.getView(), + type: items[i].children.compType.getView() as UICompType, + key: i, + children: [], + }; + if (key) { + const parent = getTreeNodeByKey(result, key); + info.disabled = true; + parent?.children.push(info); + } else { + result.push(info); + } + } + // result = _.sortBy(result, [(x) => x.title]); + } + if (Object.keys(children).length) { + for (const i in children) { + getTree(children[i], result, i); + } + } + return result; + }; + + const uiCollapseClick = useCallback( + (compName: string) => { + editorState.setSelectedCompNames(new Set([compName]), "leftPanel"); + }, + [editorState] + ); + + const getTreeNode = (node: NodeItem, uiCompInfos: CompInfo[]) => { + const data = uiCompInfos.find((item) => item.name === node.title); + return ( + + + {node.title} + + + ); + }; + + const [componentTreeData, setComponentTreeData] = useState([]); + + // update component tree data when editor state changes + useEffect(() => { + const compData = getTreeUIData(); + setComponentTreeData(compData); + }, [editorState]); + + + const getTreeUIData = () => { + const tree = editorState.getUIComp().getTree(); + const explorerData: NodeItem[] = getTree(tree, []); + const dsl = editorState.rootComp.toJsonValue(); + + if (dsl.ui.compType === "module") { + explorerData.forEach(data => { + data['pos'] = dsl.ui.comp.container.layout[data.key].pos; + }) + } + else { + explorerData.forEach(data => { + data['pos'] = dsl.ui.layout[data.key].pos; + }) + } + + explorerData.sort((a, b) => { + const aPos = a?.pos || 0; + const bPos = b?.pos || 0; + if (aPos < bPos) return -1; + if (aPos > bPos) return 1; + return 0; + }); + + return explorerData; + } + + interface DropInfo { + node: { key: string; pos: string }; + dragNode: { key: string; pos: string }; + } + + const handleDragEnter = (info: { node?: any; expandedKeys?: any; }) => { + // Assuming 'info' has a property 'expandedKeys' which is an array of keys + const { expandedKeys } = info; + if (!expandedKeys.includes(info.node.key)) { + setExpandedKeys(expandedKeys); + } + }; + + const handleDrop = (info: { node: { key: any; pos: string; }; dragNode: { key: any; pos: string; }; }) => { + const dropPos = info.node.pos.split('-'); + const dragPos = info.dragNode.pos.split('-'); + + if (dropPos.length === dragPos.length) { + setComponentTreeData(prevData => { + let newTreeData = cloneDeep(prevData); + const dropIndex = Number(dropPos[dropPos.length - 1]); + const dragIndex = Number(dragPos[dragPos.length - 1]); + const parentNodePos = dropPos.slice(0, -1).join('-'); + + // TODO: handle drag and drop for childen of root (container components for example) + // findNodeByPos does not work yet + const parentNode = parentNodePos === "0" ? { children: newTreeData } : findNodeByPos(newTreeData, parentNodePos); + + if (parentNode && parentNode.children) { + const draggedNodeIndex = parentNode.children.findIndex(node => node.key === info.dragNode.key); + if (draggedNodeIndex !== -1) { + const [draggedNode] = parentNode.children.splice(draggedNodeIndex, 1); + parentNode.children.splice(dropIndex > dragIndex ? dropIndex - 1 : dropIndex, 0, draggedNode); + } + } + + const dsl = editorState.rootComp.toJsonValue(); + let layout: any = {}; + parentNode.children.forEach((data, index) => { + layout[data.key] = { + ...dsl.ui.layout[data.key], + pos: index, + }; + }) + + editorState.rootComp.children.ui.dispatchChangeValueAction({ + ...dsl.ui, + layout, + }) + return newTreeData; + }); + } + }; + + const findNodeByPos = (nodes: NodeItem[], pos: string): { children: NodeItem[] } => { + const posArr = pos.split('-').map(p => Number(p)); + let currentNode = { children: nodes }; + for (let i = 0; i < posArr.length; i++) { + currentNode = currentNode.children[posArr[i]]; + } + return currentNode; + }; + + // here we handle the checked keys of the component tree + + const [checkedKeys, setCheckedKeys] = useState([]); + const [actionValue, setActionValue] = useState(""); + const [selectedActionKey, setSelectedActionKey] = useState(null); + const [placeholderText, setPlaceholderText] = useState(""); + const [color, setColor] = useState(""); + + const handleColorChange = (color: string | undefined, actionType: string) => { + const newColor = color || '#ffffff'; + + for (let key of getCheckedKeys()) { + const node = getTreeNodeByKey(componentTreeData, key); + const comp = editorState.getUICompByName(node.title); + if(comp) { + const { children } = comp.children.comp; + const compType = comp.children.compType.getView(); + const types = actionType.split('.'); + if(types.length === 1) { // e.g hidden, disabled + children[types[0]]?.dispatchChangeValueAction(color); + } + else if(types.length === 2) { // nested object e.g. style.background + console.log(children[types[0]]); + if (!children[types[0]]) { + if (children[compType].children[types[0]]?.children[types[1]]) { + children[compType].children[types[0]].children[types[1]]?.dispatchChangeValueAction(color); + } + } + else { + if (children[types[0]].children[types[1]]) { + children[types[0]].children[types[1]]?.dispatchChangeValueAction(color); + } + else { + children[types[0]][types[1]]?.dispatchChangeValueAction(color); + } + } + } + } + } + }; + + + /* const handleActionValueChange = (e: any) => { + setActionValue(e.target.value); + } */ + + // sync selected components with checked keys + useEffect(() => { + setCheckedKeys([]); + const selectedComponentsOnCanvas: string[] = []; + const compTree = editorState.getUIComp().getTree(); + const explorerData: NodeItem[] = getTree(compTree, []); + for (let value of editorState.selectedCompNames) { + for (let key of explorerData) { + if (key.title === value) { + selectedComponentsOnCanvas.push(key.key); + } + } + } + setCheckedKeys(selectedComponentsOnCanvas); + }, [editorState]); + + // make sure to handle the selectedActionKey for the changed input fields + useEffect(() => { + setActionValue(''); + setColor('#ffffff'); + }, [selectedActionKey, placeholderText]); + + const onCheck = (checkedKeys: any, e: any) => { + setCheckedKeys(checkedKeys); + const checkedComponents = new Set(); + for (let key of e.checkedNodes){ + checkedComponents.add(key.title); + } + editorState.setSelectedCompNames(checkedComponents, "leftPanel"); + } + + const getCheckedKeys = () => { + return checkedKeys; + } + + const getActionValue = () => { + return actionValue; + } + + const handleComponentsActions = useCallback((actionType: string) => { + const value = getActionValue(); + for (let key of getCheckedKeys()) { + const node = getTreeNodeByKey(componentTreeData, key); + const comp = editorState.getUICompByName(node.title); + if(comp) { + const { children } = comp.children.comp; + const compType = comp.children.compType.getView(); + const types = actionType.split('.'); + if(types.length === 1) { // e.g hidden, disabled + children[types[0]]?.dispatchChangeValueAction(value); + } + else if(types.length === 2) { // nested object e.g. style.background + console.log(children[types[0]]); + if (!children[types[0]]) { + if (children[compType].children[types[0]]?.children[types[1]]) { + children[compType].children[types[0]].children[types[1]]?.dispatchChangeValueAction(value); + } + } + else { + if (children[types[0]].children[types[1]]) { + children[types[0]].children[types[1]]?.dispatchChangeValueAction(value); + } + else { + children[types[0]][types[1]]?.dispatchChangeValueAction(value); + } + } + } + } + } + }, [getActionValue, getCheckedKeys]); + + const handleActionSelection = useCallback((key: string) => { + setSelectedActionKey(key); + setPlaceholderText(getPlaceholderText(key)); + }, [handleComponentsActions]); + + const layerActions: ItemType[] = [ + { + label: 'Hide Component', + key: 'hidden', + }, + { + label: 'Disable Component', + key: 'disable', + }, + { + label: 'Margin', + key: 'style.margin', + }, + { + label: 'Padding', + key: 'style.padding', + }, + { + label: 'Border Radius', + key: 'style.radius', + }, + { + label: 'Border Width', + key: 'style.borderWidth', + }, + { + label: 'Font Size', + key: 'style.textSize', + }, + { + label: 'Font Weight', + key: 'style.textWeight', + }, + { + label: 'Font Family', + key: 'style.fontFamily', + } + ]; + + + const getPlaceholderText = (key: string) => { + switch (key) { + case 'hidden': + case 'disable': + return 'true | false'; + case 'style.background': + case 'style.text': + case 'style.border': + return 'e.g., #ffffff'; // Indicate example format + case 'style.radius': + return 'e.g., 4px'; // Indicate example format + case 'style.borderWidth': + return 'e.g., 2px'; // Indicate example format + case 'style.textSize': + return 'e.g., 16px'; // Indicate example format + case 'style.textWeight': + return 'bold | 900'; + case 'style.fontFamily': + return 'Arial, sans-serif'; + case 'style.margin': + case 'style.padding': + return 'e.g., 4px 8px 16px 32px'; // Indicate example format + default: + return 'Action Value'; + } + }; + + const getTreeUI = () => { + // here the components get sorted by name + // TODO: sort by category + // TODO: sort by Types etc. + const uiCompInfos = _.sortBy(editorState.uiCompInfoList(), [(x) => x.name]); + const isDraggable = editorState.collisionStatus === "true" ? true : false; + + return ( + <> +
+
+ {trans("leftPanel.activatelayers")} + { + toggleCollisionStatus(value == true ? "true" : "false"); + editorState.setCollisionStatus(value == true ? "true" : "false"); + }} + /> +
+ + handleDrop(info)} + treeData={componentTreeData} + icon={(props: any) => props.type && ( +
{/* Adjust the margin as needed */} + {CompStateIcon[props.type as UICompType] || } +
+ )} + switcherIcon={(props: any) => props.expanded ? : } + expandedKeys={expandedKeys} + onExpand={(keys) => setExpandedKeys(keys)} + titleRender={(nodeData) => getTreeNode(nodeData as NodeItem, uiCompInfos)} + /> + +
+ + ( + handleActionSelection(key)} + /> + )} + > + + + setActionValue(e.target.value)} // Handle changes to update actionValue + placeholder={placeholderText} + /> + +
+ handleColorChange(params.color, "style.background")} + /> + handleColorChange(params.color, "style.border")} + /> + handleColorChange(params.color, "style.text")} + /> + +
+
+ + + + ); + }; + + const uiCollapse = useMemo(() => { + if (isAggregationApp(editorState.getAppType())) { + return; + } + return getTreeUI(); + }, [editorState, uiCollapseClick, expandedKeys, componentTreeData, actionValue]); + + const layerControlContent = ( + +
+ +
{uiCollapse}
+
+
+
+ ); + + return {layerControlContent}; + +}; \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/editor/editorSkeletonView.tsx b/client/packages/lowcoder/src/pages/editor/editorSkeletonView.tsx index 9c6e5f599..01ba97c43 100644 --- a/client/packages/lowcoder/src/pages/editor/editorSkeletonView.tsx +++ b/client/packages/lowcoder/src/pages/editor/editorSkeletonView.tsx @@ -7,7 +7,7 @@ import { LeftPanel, MiddlePanel, } from "pages/common/styledComponent"; -import { getPanelStatus, getEditorModeStatus, getPanelStyle } from "util/localStorageUtil"; +import { getPanelStatus, getEditorModeStatus, getPanelStyle, getCollisionStatus } from "util/localStorageUtil"; import { BottomSkeleton } from "pages/editor/bottom/BottomContent"; import RightPanel from "pages/editor/right/RightPanel"; import _ from "lodash"; @@ -48,6 +48,7 @@ export const EditorLoadingSpin = (props: { height?: string | number }) => { export default function EditorSkeletonView() { const panelStatus = getPanelStatus(); const editorModeStatus = getEditorModeStatus(); + const collisionStatus = getCollisionStatus(); const panelStyle = getPanelStyle(); const isUserViewMode = useUserViewMode(); const isTemplate = useTemplateViewMode(); @@ -59,11 +60,19 @@ export default function EditorSkeletonView() { return ( <> -
+
{panelStatus.left && ( - + )} @@ -78,7 +87,11 @@ export default function EditorSkeletonView() { )} {panelStatus.right && ( - + )} diff --git a/client/packages/lowcoder/src/pages/editor/editorView.tsx b/client/packages/lowcoder/src/pages/editor/editorView.tsx index 8fafb8647..af1981c37 100644 --- a/client/packages/lowcoder/src/pages/editor/editorView.tsx +++ b/client/packages/lowcoder/src/pages/editor/editorView.tsx @@ -9,9 +9,20 @@ import { Layers } from "constants/Layers"; import { TopHeaderHeight } from "constants/style"; import { trans } from "i18n"; import { draggingUtils } from "layout"; -import { LeftPreloadIcon, LeftSettingIcon, LeftStateIcon, ScrollBar } from "lowcoder-design"; +import { + LeftPreloadIcon, + LeftSettingIcon, + LeftStateIcon, + LeftLayersIcon, + ScrollBar, +} from "lowcoder-design"; import { useTemplateViewMode } from "util/hooks"; -import Header, { PanelStatus, TogglePanel, EditorModeStatus, ToggleEditorModeStatus } from "pages/common/header"; +import Header, { + PanelStatus, + TogglePanel, + EditorModeStatus, + ToggleEditorModeStatus +} from "pages/common/header"; import { HelpDropdown } from "pages/common/help"; import { PreviewHeader } from "pages/common/previewHeader"; import { @@ -29,8 +40,18 @@ import { } from "pages/editor/editorHotKeys"; import RightPanel from "pages/editor/right/RightPanel"; import EditorTutorials from "pages/tutorials/editorTutorials"; -import { editorContentClassName, UserGuideLocationState } from "pages/tutorials/tutorialsConstant"; -import React, { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useState } from "react"; +import { + editorContentClassName, + UserGuideLocationState, +} from "pages/tutorials/tutorialsConstant"; +import React, { + useCallback, + useContext, + useEffect, + useLayoutEffect, + useMemo, + useState, +} from "react"; import { Helmet } from "react-helmet"; import { useDispatch, useSelector } from "react-redux"; import { useLocation } from "react-router-dom"; @@ -39,11 +60,20 @@ import { currentApplication } from "redux/selectors/applicationSelector"; import { showAppSnapshotSelector } from "redux/selectors/appSnapshotSelector"; import styled from "styled-components"; import { ExternalEditorContext } from "util/context/ExternalEditorContext"; -import { DefaultPanelStatus, getPanelStatus, savePanelStatus, DefaultEditorModeStatus, getEditorModeStatus, saveEditorModeStatus } from "util/localStorageUtil"; +import { + DefaultPanelStatus, + getPanelStatus, + savePanelStatus, + DefaultEditorModeStatus, + getEditorModeStatus, + saveEditorModeStatus, +} from "util/localStorageUtil"; import Bottom from "./bottom/BottomPanel"; import { LeftContent } from "./LeftContent"; +import { LeftLayersContent } from "./LeftLayersContent"; import { isAggregationApp } from "util/appUtils"; + const HookCompContainer = styled.div` pointer-events: none; position: absolute; @@ -108,6 +138,16 @@ const HelpDiv = styled.div` } } `; + +const LayoutMenuDiv = styled.div` + > div { + left: 6px; + right: auto; + height: 28px; + top: 15px; + } +`; + const SettingsDiv = styled.div` display: flex; flex-direction: column; @@ -167,9 +207,10 @@ interface EditorViewProps { enum SiderKey { State = "state", Setting = "setting", + Layout = "layout", } -const items = [ +const standardSiderItems = [ { key: SiderKey.State, icon: , @@ -178,8 +219,22 @@ const items = [ key: SiderKey.Setting, icon: , }, + { + key: SiderKey.Layout, + icon: , + }, ]; +const aggregationSiderItems = [ + { + key: SiderKey.State, + icon: , + }, + { + key: SiderKey.Setting, + icon: , + } +]; function EditorView(props: EditorViewProps) { const { uiComp } = props; @@ -202,8 +257,9 @@ function EditorView(props: EditorViewProps) { return showNewUserGuide ? DefaultPanelStatus : getPanelStatus(); }); - const [prePanelStatus, setPrePanelStatus] = useState(DefaultPanelStatus); - + const [prePanelStatus, setPrePanelStatus] = + useState(DefaultPanelStatus); + const togglePanel: TogglePanel = useCallback( (key) => { let newPanelStatus; @@ -224,23 +280,19 @@ function EditorView(props: EditorViewProps) { [panelStatus, prePanelStatus] ); - // added by Falk Wolsky to support a Layout and Logic Mode in Lowcoder const [editorModeStatus, setEditorModeStatus] = useState(() => { - return showNewUserGuide ? DefaultEditorModeStatus : getEditorModeStatus(); + return getEditorModeStatus(); }); - - const toggleEditorModeStatus: ToggleEditorModeStatus = useCallback( (value) => { - setEditorModeStatus(value ? value : "both" as EditorModeStatus); - saveEditorModeStatus(value ? value : "both" as EditorModeStatus); - + const toggleEditorModeStatus: ToggleEditorModeStatus = useCallback( + (value) => { + setEditorModeStatus(value ? value : ("both" as EditorModeStatus)); + saveEditorModeStatus(value ? value : ("both" as EditorModeStatus)); }, [editorModeStatus] ); - - const onCompDrag = useCallback( (dragCompKey: string) => { editorState.setDraggingCompType(dragCompKey); @@ -268,7 +320,8 @@ function EditorView(props: EditorViewProps) { setHeight(window.innerHeight); } - const eventType = "orientationchange" in window ? "orientationchange" : "resize"; + const eventType = + "orientationchange" in window ? "orientationchange" : "resize"; window.addEventListener(eventType, updateSize); updateSize(); return () => window.removeEventListener(eventType, updateSize); @@ -294,7 +347,9 @@ function EditorView(props: EditorViewProps) { {uiComp.getView()} -
{hookCompViews}
+
+ {hookCompViews} +
); @@ -322,6 +377,7 @@ function EditorView(props: EditorViewProps) { setMenuKey(params.key); }; const appSettingsComp = editorState.getAppSettingsComp(); + return ( { @@ -330,7 +386,12 @@ function EditorView(props: EditorViewProps) { draggingUtils.clearData(); }} > -
+
{application && {application.name}} {showNewUserGuide && } - + clickMenu(params)} - /> + > + + {!showAppSnapshot && ( - {menuKey === SiderKey.State && } + {menuKey === SiderKey.State && } {menuKey === SiderKey.Setting && ( {application && - !isAggregationApp(AppUILayoutType[application.applicationType]) && ( + !isAggregationApp( + AppUILayoutType[application.applicationType] + ) && ( <> {appSettingsComp.getPropertyView()} @@ -380,7 +448,11 @@ function EditorView(props: EditorViewProps) { {props.preloadComp.getPropertyView()} - dispatch(setEditorExternalStateAction({ showScriptsAndStyleModal: true })) + dispatch( + setEditorExternalStateAction({ + showScriptsAndStyleModal: true, + }) + ) } > @@ -391,6 +463,11 @@ function EditorView(props: EditorViewProps) { {props.preloadComp.getJSLibraryPropertyView()} )} + + {menuKey === SiderKey.Layout && ( + + )} + )} diff --git a/client/packages/lowcoder/src/pages/editor/styledComponents.tsx b/client/packages/lowcoder/src/pages/editor/styledComponents.tsx index c0d68b2f9..b1125d4c9 100644 --- a/client/packages/lowcoder/src/pages/editor/styledComponents.tsx +++ b/client/packages/lowcoder/src/pages/editor/styledComponents.tsx @@ -25,11 +25,20 @@ export const DirectoryTreeStyle = styled(DirectoryTree)` position: unset; .ant-tree-iconEle { width: 16px; - height: 26px; - margin-right: 4px; + height: 16px; + margin: 0px 0px 0px 4px; display: flex; align-items: center; + svg { + width: 16px; + height: 16px; + stroke: #000; + } } + + } + .ant-tree-checkbox+span { + padding-left: 0; } .ant-tree-treenode { padding: 0; @@ -58,6 +67,11 @@ export const DirectoryTreeStyle = styled(DirectoryTree)` .ant-tree-node-content-wrapper.ant-tree-node-selected { color: #333; } + .ant-tree-treenode-disabled { + .ant-tree-node-content-wrapper { + color: inherit; + } + } } `; @@ -105,14 +119,17 @@ export const Node = styled.span` } `; +// margin: 4px -16px 4px ${(props) => props.$clientX && `calc(-${props.$clientX}px + 16px)`}; +// width: 256px; export const CollapseWrapper = styled.div<{ $clientX?: number }>` - width: 256px; + width: 100%; border: 1px solid #E1E3EB; border-radius: 4px; overflow: hidden; background: #fff; - padding: 4px 0; - margin: 4px -16px 4px ${(props) => props.$clientX && `calc(-${props.$clientX}px + 16px)`}; + padding: 0px; + position: relative; + margin: 4px 0px 4px 0}; .simplebar-content > div { > .ant-collapse > .ant-collapse-item { > .ant-collapse-header { diff --git a/client/packages/lowcoder/src/util/localStorageUtil.ts b/client/packages/lowcoder/src/util/localStorageUtil.ts index 2d06ddd20..1c468be88 100644 --- a/client/packages/lowcoder/src/util/localStorageUtil.ts +++ b/client/packages/lowcoder/src/util/localStorageUtil.ts @@ -1,4 +1,5 @@ import { PanelStatus } from "pages/common/header"; +import { DisabledCollisionStatus as DisabledCollisionStatus } from "pages/editor/LeftLayersContent"; import { EditorModeStatus } from "pages/common/header"; import log from "loglevel"; import { JSONValue } from "util/jsonTypes"; @@ -19,7 +20,6 @@ export const DefaultPanelStatus: PanelStatus = { right: true, }; - const DefaultPanelStyle: PanelStyle = { bottom: { h: 285, @@ -42,12 +42,26 @@ export function getPanelStatus(): PanelStatus { return { ...DefaultPanelStatus, ...JSON.parse(str) }; } - export function saveEditorModeStatus(editorModeStatus: EditorModeStatus) { localStorage.setItem("editor_mode_status", editorModeStatus); } +//ADDED BY FRED TO SAVE enabledCollision +export function saveCollisionStatus( + collisionStatus: DisabledCollisionStatus +) { + localStorage.setItem("disable_collision", collisionStatus); +} + +export const DefaultCollisionStatus: DisabledCollisionStatus = "true"; +export function getCollisionStatus(): DisabledCollisionStatus { + const str = localStorage.getItem("disable_collision"); + if (!str) { + return DefaultCollisionStatus; + } + return str as DisabledCollisionStatus; +} -export const DefaultEditorModeStatus: EditorModeStatus = "both"; +export const DefaultEditorModeStatus: EditorModeStatus = "both"; export function getEditorModeStatus(): EditorModeStatus { const str = localStorage.getItem("editor_mode_status"); if (!str) {