From debee09ebabe3ed0aa75b6559540e16c14b9bdaa Mon Sep 17 00:00:00 2001 From: hulutter <> Date: Fri, 26 Apr 2024 16:17:44 +0200 Subject: [PATCH] wip --- .../comps/comps/columnLayout/columnLayout.tsx | 311 ++++++++++++++++++ .../src/comps/comps/columnLayout/index.tsx | 1 + client/packages/lowcoder/src/comps/index.tsx | 17 + .../lowcoder/src/comps/uiCompRegistry.ts | 1 + .../src/pages/editor/editorConstants.tsx | 3 +- 5 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 client/packages/lowcoder/src/comps/comps/columnLayout/columnLayout.tsx create mode 100644 client/packages/lowcoder/src/comps/comps/columnLayout/index.tsx diff --git a/client/packages/lowcoder/src/comps/comps/columnLayout/columnLayout.tsx b/client/packages/lowcoder/src/comps/comps/columnLayout/columnLayout.tsx new file mode 100644 index 000000000..1270015ce --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/columnLayout/columnLayout.tsx @@ -0,0 +1,311 @@ +import { default as Row } from "antd/es/row"; +import { default as Col } from "antd/es/col"; +import { JSONObject, JSONValue } from "util/jsonTypes"; +import { CompAction, CompActionTypes, deleteCompAction, wrapChildAction } from "lowcoder-core"; +import { DispatchType, RecordConstructorToView, wrapDispatch } from "lowcoder-core"; +import { AutoHeightControl } from "comps/controls/autoHeightControl"; +import { ColumnOptionControl } from "comps/controls/optionsControl"; +import { styleControl } from "comps/controls/styleControl"; +import { + ResponsiveLayoutRowStyle, + ResponsiveLayoutRowStyleType, + ResponsiveLayoutColStyleType, + ResponsiveLayoutColStyle +} from "comps/controls/styleControlConstants"; +import { sameTypeMap, UICompBuilder, withDefault } from "comps/generators"; +import { addMapChildAction } from "comps/generators/sameTypeMap"; +import { NameConfigHidden, withExposingConfigs } from "comps/generators/withExposing"; +import { NameGenerator } from "comps/utils"; +import { Section, controlItem, sectionNames } from "lowcoder-design"; +import { HintPlaceHolder } from "lowcoder-design"; +import _ from "lodash"; +import styled from "styled-components"; +import { IContainer } from "../containerBase/iContainer"; +import { SimpleContainerComp } from "../containerBase/simpleContainerComp"; +import { CompTree, mergeCompTrees } from "../containerBase/utils"; +import { + ContainerBaseProps, + gridItemCompToGridItems, + InnerGrid, +} from "../containerComp/containerView"; +import { BackgroundColorContext } from "comps/utils/backgroundColorContext"; +import { trans } from "i18n"; +import { messageInstance } from "lowcoder-design/src/components/GlobalInstances"; +import { BoolControl } from "comps/controls/boolControl"; +import { BoolCodeControl, NumberControl, StringControl } from "comps/controls/codeControl"; + +import { useContext } from "react"; +import { EditorContext } from "comps/editorState"; + +import { disabledPropertyView, hiddenPropertyView } from "comps/utils/propertyUtils"; +import { DisabledContext } from "comps/generators/uiCompBuilder"; + + +const ColWrapper = styled(Col)<{ + $style: ResponsiveLayoutColStyleType, + $minWidth?: string, + $matchColumnsHeight: boolean, +}>` + > div { + height: ${(props) => props.$matchColumnsHeight ? '100%' : 'auto'}; + } +`; + +const childrenMap = { + disabled: BoolCodeControl, + columns: ColumnOptionControl, + containers: withDefault(sameTypeMap(SimpleContainerComp), { + 0: { view: {}, layout: {} }, + 1: { view: {}, layout: {} }, + }), + autoHeight: AutoHeightControl, + matchColumnsHeight: withDefault(BoolControl, true), + templateRows: withDefault(StringControl, "1fr"), + rowGap: withDefault(StringControl, "20px"), + templateColumns: withDefault(StringControl, "1fr 1fr"), + columnGap: withDefault(StringControl, "20px"), + columnStyle: withDefault(styleControl(ResponsiveLayoutColStyle), {}) +}; + +type ViewProps = RecordConstructorToView; +type ColumnLayoutProps = ViewProps & { dispatch: DispatchType }; +type ColumnContainerProps = Omit & { + style: ResponsiveLayoutColStyleType, +} + +const ColumnContainer = (props: ColumnContainerProps) => { + return ( + + ); +}; + + +const ColumnLayout = (props: ColumnLayoutProps) => { + let { + columns, + containers, + dispatch, + matchColumnsHeight, + templateRows, + rowGap, + templateColumns, + columnGap, + columnStyle, + } = props; + + return ( + + +
+
+ {columns.map(column => { + const id = String(column.id); + const childDispatch = wrapDispatch(wrapDispatch(dispatch, "containers"), id); + if(!containers[id]) return null + const containerProps = containers[id].children; + + const columnCustomStyle = { + margin: "0", + padding: !_.isEmpty(column.padding) ? column.padding : "0", + radius: "0", + border: "1px dashed pink", // `${!_.isEmpty(column.border) ? column.border : columnStyle.border}`, + background: !_.isEmpty(column.background) ? column.background : columnStyle.background, + overflow: "hidden", + backgroundImage: "linear-gradient(to bottom, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 1) 100%), linear-gradient(to bottom, rgba(253, 246, 199, 1) 0%, rgba(253, 246, 199, 1) 100%)", + backgroundClip: "content-box, padding-box", + + } + const noOfColumns = columns.length; + let backgroundStyle = columnCustomStyle.background; + if(!_.isEmpty(column.backgroundImage)) { + backgroundStyle = `center / cover url('${column.backgroundImage}') no-repeat, ${backgroundStyle}`; + } + return ( + + + + ) + }) + } +
+
+
+
+ ); +}; + +export const ResponsiveLayoutBaseComp = (function () { + return new UICompBuilder(childrenMap, (props, dispatch) => { + return ( + + ); + }) + .setPropertyViewFn((children) => { + return ( + <> +
+ {children.columns.propertyView({ + title: trans("responsiveLayout.column"), + newOptionLabel: "Column", + })} + {children.templateColumns.propertyView({label: "Column Definition"})} + {children.templateRows.propertyView({label: "Row Definition"})} +
+ + {(useContext(EditorContext).editorModeStatus === "logic" || useContext(EditorContext).editorModeStatus === "both") && ( +
+ {disabledPropertyView(children)} + {hiddenPropertyView(children)} +
+ )} + + {["layout", "both"].includes(useContext(EditorContext).editorModeStatus) && ( + <> +
+ {children.autoHeight.getPropertyView()} +
+
+ {children.matchColumnsHeight.propertyView({ + label: trans("responsiveLayout.matchColumnsHeight") + })} + {controlItem({}, ( +
+ {trans("responsiveLayout.columnsSpacing")} +
+ ))} + {children.columnGap.propertyView({label: "Column Gap"})} + {children.rowGap.propertyView({label: "Row Gap"})} +
+ + )} + + ); + }) + .build(); +})(); + +class ColumnLayoutImplComp extends ResponsiveLayoutBaseComp implements IContainer { + private syncContainers(): this { + const columns = this.children.columns.getView(); + const ids: Set = new Set(columns.map((column) => String(column.id))); + let containers = this.children.containers.getView(); + // delete + const actions: CompAction[] = []; + Object.keys(containers).forEach((id) => { + if (!ids.has(id)) { + // log.debug("syncContainers delete. ids=", ids, " id=", id); + actions.push(wrapChildAction("containers", wrapChildAction(id, deleteCompAction()))); + } + }); + // new + ids.forEach((id) => { + if (!containers.hasOwnProperty(id)) { + // log.debug("syncContainers new containers: ", containers, " id: ", id); + actions.push( + wrapChildAction("containers", addMapChildAction(id, { layout: {}, items: {} })) + ); + } + }); + // log.debug("syncContainers. actions: ", actions); + let instance = this; + actions.forEach((action) => { + instance = instance.reduce(action); + }); + return instance; + } + + override reduce(action: CompAction): this { + const columns = this.children.columns.getView(); + if (action.type === CompActionTypes.CUSTOM) { + const value = action.value as JSONObject; + if (value.type === "push") { + const itemValue = value.value as JSONObject; + if (_.isEmpty(itemValue.key)) itemValue.key = itemValue.label; + action = { + ...action, + value: { + ...value, + value: { ...itemValue }, + }, + } as CompAction; + } + const { path } = action; + if (value.type === "delete" && path[0] === 'columns' && columns.length <= 1) { + messageInstance.warning(trans("responsiveLayout.atLeastOneColumnError")); + // at least one column + return this; + } + } + // log.debug("before super reduce. action: ", action); + let newInstance = super.reduce(action); + if (action.type === CompActionTypes.UPDATE_NODES_V2) { + // Need eval to get the value in StringControl + newInstance = newInstance.syncContainers(); + } + // log.debug("reduce. instance: ", this, " newInstance: ", newInstance); + return newInstance; + } + + realSimpleContainer(key?: string): SimpleContainerComp | undefined { + return Object.values(this.children.containers.children).find((container) => + container.realSimpleContainer(key) + ); + } + + getCompTree(): CompTree { + const containerMap = this.children.containers.getView(); + const compTrees = Object.values(containerMap).map((container) => container.getCompTree()); + return mergeCompTrees(compTrees); + } + + findContainer(key: string): IContainer | undefined { + const containerMap = this.children.containers.getView(); + for (const container of Object.values(containerMap)) { + const foundContainer = container.findContainer(key); + if (foundContainer) { + return foundContainer === container ? this : foundContainer; + } + } + return undefined; + } + + getPasteValue(nameGenerator: NameGenerator): JSONValue { + const containerMap = this.children.containers.getView(); + const containerPasteValueMap = _.mapValues(containerMap, (container) => + container.getPasteValue(nameGenerator) + ); + + return { ...this.toJsonValue(), containers: containerPasteValueMap }; + } + + override autoHeight(): boolean { + return this.children.autoHeight.getView(); + } +} + +export const ColumnLayoutComp = withExposingConfigs( + ColumnLayoutImplComp, + [ NameConfigHidden] +); diff --git a/client/packages/lowcoder/src/comps/comps/columnLayout/index.tsx b/client/packages/lowcoder/src/comps/comps/columnLayout/index.tsx new file mode 100644 index 000000000..bd529a048 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/columnLayout/index.tsx @@ -0,0 +1 @@ +export { ColumnLayoutComp } from "./columnLayout"; diff --git a/client/packages/lowcoder/src/comps/index.tsx b/client/packages/lowcoder/src/comps/index.tsx index 103096be1..9280f1f8d 100644 --- a/client/packages/lowcoder/src/comps/index.tsx +++ b/client/packages/lowcoder/src/comps/index.tsx @@ -268,6 +268,23 @@ export var uiCompMap: Registry = { delayCollision: true, }, }, + columnLayout: { + name: "Column Layout", + enName: "Column Layout", + description: trans("uiComp.responsiveLayoutCompDesc"), + categories: ["layout"], + icon: ResponsiveLayoutCompIcon, + keywords: trans("uiComp.responsiveLayoutCompKeywords"), + lazyLoad: true, + compName: 'ColumnLayoutComp', + compPath: 'comps/columnLayout/index', + withoutLoading: true, + layoutInfo: { + w: 24, + h: 25, + delayCollision: true, + }, + }, container: { name: trans("uiComp.containerCompName"), enName: "Container", diff --git a/client/packages/lowcoder/src/comps/uiCompRegistry.ts b/client/packages/lowcoder/src/comps/uiCompRegistry.ts index 40136d464..65626ef08 100644 --- a/client/packages/lowcoder/src/comps/uiCompRegistry.ts +++ b/client/packages/lowcoder/src/comps/uiCompRegistry.ts @@ -133,6 +133,7 @@ export type UICompType = | "mention" //Added By Mousheng | "autocomplete" //Added By Mousheng | "responsiveLayout" + | "columnLayout" | "tour"; diff --git a/client/packages/lowcoder/src/pages/editor/editorConstants.tsx b/client/packages/lowcoder/src/pages/editor/editorConstants.tsx index 950fc4dab..04c81acc8 100644 --- a/client/packages/lowcoder/src/pages/editor/editorConstants.tsx +++ b/client/packages/lowcoder/src/pages/editor/editorConstants.tsx @@ -42,7 +42,7 @@ import { MentionIcon, AutoCompleteCompIcon, IconCompIcon, - ResponsiveLayoutCompIcon, + ResponsiveLayoutCompIcon } from "lowcoder-design"; export const CompStateIcon: { @@ -122,5 +122,6 @@ export const CompStateIcon: { autocomplete: , icon: , responsiveLayout: , + columnLayout: , tour: , };