Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 0d47fe8

Browse files
Merge pull request #829 from newwork-software/addColumnLayout
WIP: Add column based layout
2 parents b4852b6 + f9e9008 commit 0d47fe8

File tree

5 files changed

+332
-2
lines changed

5 files changed

+332
-2
lines changed
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
import { default as Row } from "antd/es/row";
2+
import { default as Col } from "antd/es/col";
3+
import { JSONObject, JSONValue } from "util/jsonTypes";
4+
import { CompAction, CompActionTypes, deleteCompAction, wrapChildAction } from "lowcoder-core";
5+
import { DispatchType, RecordConstructorToView, wrapDispatch } from "lowcoder-core";
6+
import { AutoHeightControl } from "comps/controls/autoHeightControl";
7+
import { ColumnOptionControl } from "comps/controls/optionsControl";
8+
import { styleControl } from "comps/controls/styleControl";
9+
import {
10+
ResponsiveLayoutRowStyle,
11+
ResponsiveLayoutRowStyleType,
12+
ResponsiveLayoutColStyleType,
13+
ResponsiveLayoutColStyle
14+
} from "comps/controls/styleControlConstants";
15+
import { sameTypeMap, UICompBuilder, withDefault } from "comps/generators";
16+
import { addMapChildAction } from "comps/generators/sameTypeMap";
17+
import { NameConfigHidden, withExposingConfigs } from "comps/generators/withExposing";
18+
import { NameGenerator } from "comps/utils";
19+
import { Section, controlItem, sectionNames } from "lowcoder-design";
20+
import { HintPlaceHolder } from "lowcoder-design";
21+
import _ from "lodash";
22+
import styled from "styled-components";
23+
import { IContainer } from "../containerBase/iContainer";
24+
import { SimpleContainerComp } from "../containerBase/simpleContainerComp";
25+
import { CompTree, mergeCompTrees } from "../containerBase/utils";
26+
import {
27+
ContainerBaseProps,
28+
gridItemCompToGridItems,
29+
InnerGrid,
30+
} from "../containerComp/containerView";
31+
import { BackgroundColorContext } from "comps/utils/backgroundColorContext";
32+
import { trans } from "i18n";
33+
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
34+
import { BoolControl } from "comps/controls/boolControl";
35+
import { BoolCodeControl, NumberControl, StringControl } from "comps/controls/codeControl";
36+
37+
import { useContext } from "react";
38+
import { EditorContext } from "comps/editorState";
39+
40+
import { disabledPropertyView, hiddenPropertyView } from "comps/utils/propertyUtils";
41+
import { DisabledContext } from "comps/generators/uiCompBuilder";
42+
43+
44+
const ColWrapper = styled(Col)<{
45+
$style: ResponsiveLayoutColStyleType,
46+
$minWidth?: string,
47+
$matchColumnsHeight: boolean,
48+
}>`
49+
> div {
50+
height: ${(props) => props.$matchColumnsHeight ? '100%' : 'auto'};
51+
}
52+
`;
53+
54+
const childrenMap = {
55+
disabled: BoolCodeControl,
56+
columns: ColumnOptionControl,
57+
containers: withDefault(sameTypeMap(SimpleContainerComp), {
58+
0: { view: {}, layout: {} },
59+
1: { view: {}, layout: {} },
60+
}),
61+
autoHeight: AutoHeightControl,
62+
matchColumnsHeight: withDefault(BoolControl, true),
63+
templateRows: withDefault(StringControl, "1fr"),
64+
rowGap: withDefault(StringControl, "20px"),
65+
templateColumns: withDefault(StringControl, "1fr 1fr"),
66+
columnGap: withDefault(StringControl, "20px"),
67+
columnStyle: withDefault(styleControl(ResponsiveLayoutColStyle), {})
68+
};
69+
70+
type ViewProps = RecordConstructorToView<typeof childrenMap>;
71+
type ColumnLayoutProps = ViewProps & { dispatch: DispatchType };
72+
type ColumnContainerProps = Omit<ContainerBaseProps, 'style'> & {
73+
style: ResponsiveLayoutColStyleType,
74+
}
75+
76+
const ColumnContainer = (props: ColumnContainerProps) => {
77+
return (
78+
<InnerGrid
79+
{...props}
80+
emptyRows={15}
81+
hintPlaceholder={HintPlaceHolder}
82+
radius={"0"}
83+
style={props.style}
84+
enableGridLines={false}
85+
/>
86+
);
87+
};
88+
89+
90+
const ColumnLayout = (props: ColumnLayoutProps) => {
91+
let {
92+
columns,
93+
containers,
94+
dispatch,
95+
matchColumnsHeight,
96+
templateRows,
97+
rowGap,
98+
templateColumns,
99+
columnGap,
100+
columnStyle,
101+
} = props;
102+
103+
return (
104+
<BackgroundColorContext.Provider value={"none"}>
105+
<DisabledContext.Provider value={props.disabled}>
106+
<div style={{height: '100%', backgroundColor: "pink"}}>
107+
<div style={{display: "grid", gridTemplateColumns: templateColumns, columnGap, gridTemplateRows: templateRows, rowGap}}>
108+
{columns.map(column => {
109+
const id = String(column.id);
110+
const childDispatch = wrapDispatch(wrapDispatch(dispatch, "containers"), id);
111+
if(!containers[id]) return null
112+
const containerProps = containers[id].children;
113+
114+
const columnCustomStyle = {
115+
margin: "0",
116+
padding: !_.isEmpty(column.padding) ? column.padding : "0",
117+
radius: "0",
118+
border: "1px dashed pink", // `${!_.isEmpty(column.border) ? column.border : columnStyle.border}`,
119+
background: !_.isEmpty(column.background) ? column.background : columnStyle.background,
120+
overflow: "hidden",
121+
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%)",
122+
backgroundClip: "content-box, padding-box",
123+
124+
}
125+
const noOfColumns = columns.length;
126+
let backgroundStyle = columnCustomStyle.background;
127+
if(!_.isEmpty(column.backgroundImage)) {
128+
backgroundStyle = `center / cover url('${column.backgroundImage}') no-repeat, ${backgroundStyle}`;
129+
}
130+
return (
131+
<ColWrapper
132+
key={id}
133+
$style={columnCustomStyle}
134+
$minWidth={column.minWidth}
135+
$matchColumnsHeight={matchColumnsHeight}
136+
>
137+
<ColumnContainer
138+
layout={containerProps.layout.getView()}
139+
items={gridItemCompToGridItems(containerProps.items.getView())}
140+
positionParams={containerProps.positionParams.getView()}
141+
dispatch={childDispatch}
142+
autoHeight={props.autoHeight}
143+
style={{
144+
...columnCustomStyle,
145+
background: backgroundStyle,
146+
}}
147+
/>
148+
</ColWrapper>
149+
)
150+
})
151+
}
152+
</div>
153+
</div>
154+
</DisabledContext.Provider>
155+
</BackgroundColorContext.Provider>
156+
);
157+
};
158+
159+
export const ResponsiveLayoutBaseComp = (function () {
160+
return new UICompBuilder(childrenMap, (props, dispatch) => {
161+
return (
162+
<ColumnLayout {...props} dispatch={dispatch} />
163+
);
164+
})
165+
.setPropertyViewFn((children) => {
166+
return (
167+
<>
168+
<Section name={sectionNames.basic}>
169+
{children.columns.propertyView({
170+
title: trans("responsiveLayout.column"),
171+
newOptionLabel: "Column",
172+
})}
173+
{children.templateColumns.propertyView({label: "Column Definition"})}
174+
{children.templateRows.propertyView({label: "Row Definition"})}
175+
</Section>
176+
177+
{(useContext(EditorContext).editorModeStatus === "logic" || useContext(EditorContext).editorModeStatus === "both") && (
178+
<Section name={sectionNames.interaction}>
179+
{disabledPropertyView(children)}
180+
{hiddenPropertyView(children)}
181+
</Section>
182+
)}
183+
184+
{["layout", "both"].includes(useContext(EditorContext).editorModeStatus) && (
185+
<>
186+
<Section name={sectionNames.layout}>
187+
{children.autoHeight.getPropertyView()}
188+
</Section>
189+
<Section name={trans("responsiveLayout.columnsLayout")}>
190+
{children.matchColumnsHeight.propertyView({
191+
label: trans("responsiveLayout.matchColumnsHeight")
192+
})}
193+
{controlItem({}, (
194+
<div style={{ marginTop: '8px' }}>
195+
{trans("responsiveLayout.columnsSpacing")}
196+
</div>
197+
))}
198+
{children.columnGap.propertyView({label: "Column Gap"})}
199+
{children.rowGap.propertyView({label: "Row Gap"})}
200+
</Section>
201+
</>
202+
)}
203+
</>
204+
);
205+
})
206+
.build();
207+
})();
208+
209+
class ColumnLayoutImplComp extends ResponsiveLayoutBaseComp implements IContainer {
210+
private syncContainers(): this {
211+
const columns = this.children.columns.getView();
212+
const ids: Set<string> = new Set(columns.map((column) => String(column.id)));
213+
let containers = this.children.containers.getView();
214+
// delete
215+
const actions: CompAction[] = [];
216+
Object.keys(containers).forEach((id) => {
217+
if (!ids.has(id)) {
218+
// log.debug("syncContainers delete. ids=", ids, " id=", id);
219+
actions.push(wrapChildAction("containers", wrapChildAction(id, deleteCompAction())));
220+
}
221+
});
222+
// new
223+
ids.forEach((id) => {
224+
if (!containers.hasOwnProperty(id)) {
225+
// log.debug("syncContainers new containers: ", containers, " id: ", id);
226+
actions.push(
227+
wrapChildAction("containers", addMapChildAction(id, { layout: {}, items: {} }))
228+
);
229+
}
230+
});
231+
// log.debug("syncContainers. actions: ", actions);
232+
let instance = this;
233+
actions.forEach((action) => {
234+
instance = instance.reduce(action);
235+
});
236+
return instance;
237+
}
238+
239+
override reduce(action: CompAction): this {
240+
const columns = this.children.columns.getView();
241+
if (action.type === CompActionTypes.CUSTOM) {
242+
const value = action.value as JSONObject;
243+
if (value.type === "push") {
244+
const itemValue = value.value as JSONObject;
245+
if (_.isEmpty(itemValue.key)) itemValue.key = itemValue.label;
246+
action = {
247+
...action,
248+
value: {
249+
...value,
250+
value: { ...itemValue },
251+
},
252+
} as CompAction;
253+
}
254+
const { path } = action;
255+
if (value.type === "delete" && path[0] === 'columns' && columns.length <= 1) {
256+
messageInstance.warning(trans("responsiveLayout.atLeastOneColumnError"));
257+
// at least one column
258+
return this;
259+
}
260+
}
261+
// log.debug("before super reduce. action: ", action);
262+
let newInstance = super.reduce(action);
263+
if (action.type === CompActionTypes.UPDATE_NODES_V2) {
264+
// Need eval to get the value in StringControl
265+
newInstance = newInstance.syncContainers();
266+
}
267+
// log.debug("reduce. instance: ", this, " newInstance: ", newInstance);
268+
return newInstance;
269+
}
270+
271+
realSimpleContainer(key?: string): SimpleContainerComp | undefined {
272+
return Object.values(this.children.containers.children).find((container) =>
273+
container.realSimpleContainer(key)
274+
);
275+
}
276+
277+
getCompTree(): CompTree {
278+
const containerMap = this.children.containers.getView();
279+
const compTrees = Object.values(containerMap).map((container) => container.getCompTree());
280+
return mergeCompTrees(compTrees);
281+
}
282+
283+
findContainer(key: string): IContainer | undefined {
284+
const containerMap = this.children.containers.getView();
285+
for (const container of Object.values(containerMap)) {
286+
const foundContainer = container.findContainer(key);
287+
if (foundContainer) {
288+
return foundContainer === container ? this : foundContainer;
289+
}
290+
}
291+
return undefined;
292+
}
293+
294+
getPasteValue(nameGenerator: NameGenerator): JSONValue {
295+
const containerMap = this.children.containers.getView();
296+
const containerPasteValueMap = _.mapValues(containerMap, (container) =>
297+
container.getPasteValue(nameGenerator)
298+
);
299+
300+
return { ...this.toJsonValue(), containers: containerPasteValueMap };
301+
}
302+
303+
override autoHeight(): boolean {
304+
return this.children.autoHeight.getView();
305+
}
306+
}
307+
308+
export const ColumnLayoutComp = withExposingConfigs(
309+
ColumnLayoutImplComp,
310+
[ NameConfigHidden]
311+
);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { ColumnLayoutComp } from "./columnLayout";

‎client/packages/lowcoder/src/comps/index.tsx‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,23 @@ export var uiCompMap: Registry = {
429429
defaultDataFnName: "defaultPageLayoutData",
430430
defaultDataFnPath: "comps/tableComp/mockTableComp",
431431
},
432+
columnLayout: {
433+
name: "Column Layout",
434+
enName: "Column Layout",
435+
description: trans("uiComp.responsiveLayoutCompDesc"),
436+
categories: ["layout"],
437+
icon: ResponsiveLayoutCompIcon,
438+
keywords: trans("uiComp.responsiveLayoutCompKeywords"),
439+
lazyLoad: true,
440+
compName: 'ColumnLayoutComp',
441+
compPath: 'comps/columnLayout/index',
442+
withoutLoading: true,
443+
layoutInfo: {
444+
w: 24,
445+
h: 25,
446+
delayCollision: true,
447+
},
448+
},
432449
floatTextContainer: {
433450
name: trans("uiComp.floatTextContainerCompName"),
434451
enName: "Container",

‎client/packages/lowcoder/src/comps/uiCompRegistry.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ export type UICompType =
160160
| "sunburstChart"
161161
| "themeriverChart"
162162
| "basicChart"
163+
| "columnLayout"
163164
;
164165

165166

‎client/packages/lowcoder/src/pages/editor/editorConstants.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,7 @@ import {
9999
HillchartCompIconSmall,
100100
TurnstileCaptchaCompIconSmall,
101101
PivotTableCompIconSmall,
102-
GraphChartCompIconSmall
103-
102+
GraphChartCompIconSmall,
104103
} from "lowcoder-design";
105104

106105
export const CompStateIcon: {
@@ -115,6 +114,7 @@ export const CompStateIcon: {
115114
chart: <ChartCompIconSmall />,
116115
checkbox: <CheckboxCompIconSmall />,
117116
collapsibleContainer: <CollapsibleContainerCompIconSmall />,
117+
columnLayout: <IconCompIconSmall />,
118118
comment: <CommentCompIconSmall />,
119119
container: <ContainerCompIconSmall />,
120120
controlButton: <IconButtonCompIconSmall />,

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /