@@ -3,109 +3,155 @@ import { Splitter } from "antd";
3
3
import styled from "styled-components" ;
4
4
import { DispatchType , RecordConstructorToView , wrapDispatch } from "lowcoder-core" ;
5
5
import { CompAction , CompActionTypes , deleteCompAction , wrapChildAction } from "lowcoder-core" ;
6
- import { ColumnOptionControl } from "comps/controls/optionsControl" ;
7
- import { NumberControl } from "comps/controls/codeControl" ;
6
+ import { SplitColumnOptionControl } from "comps/controls/optionsControl" ;
7
+ import { NumberControl , StringControl } from "comps/controls/codeControl" ;
8
8
import { BoolControl } from "comps/controls/boolControl" ;
9
+ import { dropdownControl } from "comps/controls/dropdownControl" ;
9
10
import { styleControl } from "comps/controls/styleControl" ;
10
- import { SplitLayoutColStyle , SplitLayoutColStyleType , AnimationStyle } from "comps/controls/styleControlConstants" ;
11
+ import { SplitLayoutColStyle , SplitLayoutRowStyle , SplitLayoutRowStyleType , SplitLayoutColStyleType , AnimationStyle , heightCalculator } from "comps/controls/styleControlConstants" ;
11
12
import { sameTypeMap , UICompBuilder , withDefault } from "comps/generators" ;
12
13
import { addMapChildAction } from "comps/generators/sameTypeMap" ;
13
14
import { BackgroundColorContext } from "comps/utils/backgroundColorContext" ;
14
- import { Section , sectionNames } from "lowcoder-design" ;
15
+ import { Section , sectionNames } from "lowcoder-design" ;
15
16
import { trans } from "i18n" ;
16
- import { SimpleContainerComp } from "../containerBase/simpleContainerComp" ;
17
17
import { ContainerBaseProps , gridItemCompToGridItems , InnerGrid } from "../containerComp/containerView" ;
18
+ import { useContext } from "react" ;
19
+ import { EditorContext } from "comps/editorState" ;
20
+
21
+ import { disabledPropertyView , hiddenPropertyView } from "comps/utils/propertyUtils" ;
18
22
import { DisabledContext } from "comps/generators/uiCompBuilder" ;
19
- import { useScreenInfo } from "../../hooks/screenInfoComp" ;
23
+ import { JSONObject , JSONValue } from "util/jsonTypes" ;
24
+ import { IContainer } from "../containerBase/iContainer" ;
25
+ import { SimpleContainerComp } from "../containerBase/simpleContainerComp" ;
26
+ import { CompTree , mergeCompTrees } from "../containerBase/utils" ;
27
+ import { NameGenerator } from "comps/utils" ;
28
+ import { AutoHeightControl } from "comps/controls/autoHeightControl" ;
29
+ import { messageInstance } from "lowcoder-design/src/components/GlobalInstances" ;
30
+ import { NameConfigHidden , withExposingConfigs } from "comps/generators/withExposing" ;
31
+ import SliderControl from "@lowcoder-ee/comps/controls/sliderControl" ;
32
+ import { getBackgroundStyle } from "@lowcoder-ee/util/styleUtils" ;
33
+ import _ from "lodash" ;
20
34
21
- const SplitPanelWrapper = styled ( Splitter . Panel ) < { $collapsible : boolean } > `
22
- flex-grow: 1;
23
- ${ ( props ) => props . $collapsible && `min-width: 50px;` }
35
+ import {
36
+ HorizontalIcon ,
37
+ VerticalIcon ,
38
+ } from "lowcoder-design/src/icons" ;
39
+ import { BackgroundColor } from "@lowcoder-ee/constants/style" ;
40
+
41
+ const SplitPanelWrapper = styled ( Splitter . Panel ) < { } > `
24
42
` ;
25
43
26
- export interface SplitterLayoutTypes {
27
- orientation : "horizontal" | "vertical" ;
28
- }
44
+ const SplitterWrapper = styled . div < { $style : SplitLayoutRowStyleType } > `
45
+ border-radius: ${ ( props ) => props . $style ?. radius || "0px" } ;
46
+ border-width: ${ ( props ) => props . $style ?. borderWidth || "0px" } ;
47
+ border-color: ${ ( props ) => props . $style ?. border || "transparent" } ;
48
+ border-style: ${ ( props ) => props . $style ?. borderStyle || "solid" } ;
49
+ margin: ${ ( props ) => props . $style ?. margin || "0px" } ;
50
+ padding: ${ ( props ) => props . $style ?. padding || "0px" } ;
51
+ ${ ( props ) => ( props . $style ? getBackgroundStyle ( props . $style ) : "" ) }
52
+ ` ;
29
53
30
- /*
54
+ const OrientationOptions = [
55
+ {
56
+ label : < HorizontalIcon /> ,
57
+ value : "horizontal" ,
58
+ } ,
59
+ {
60
+ label : < VerticalIcon /> ,
61
+ value : "vertical" ,
62
+ } ,
63
+ ] as const ;
31
64
32
65
const childrenMap = {
33
66
disabled : BoolControl ,
34
- columns: ColumnOptionControl ,
67
+ columns : SplitColumnOptionControl ,
35
68
containers : withDefault ( sameTypeMap ( SimpleContainerComp ) , {
36
69
0 : { view : { } , layout : { } } ,
37
70
1 : { view : { } , layout : { } } ,
38
71
} ) ,
39
- collapsiblePanels: BoolControl,
40
- orientation: withDefault(ColumnOptionControl, "horizontal"),
41
- panelCount: withDefault(NumberControl, 2),
72
+ autoHeight : AutoHeightControl ,
73
+ horizontalGridCells : SliderControl ,
74
+ verticalGridCells : SliderControl ,
75
+ orientation : dropdownControl ( OrientationOptions , "horizontal" ) ,
76
+ matchColumnsHeight : withDefault ( BoolControl , true ) ,
42
77
columnStyle : styleControl ( SplitLayoutColStyle , "columnStyle" ) ,
78
+ bodyStyle : styleControl ( SplitLayoutRowStyle , 'bodyStyle' ) ,
43
79
animationStyle : styleControl ( AnimationStyle , "animationStyle" ) ,
80
+ mainScrollbar : withDefault ( BoolControl , false ) ,
44
81
} ;
45
82
46
83
type ViewProps = RecordConstructorToView < typeof childrenMap > ;
47
84
type SplitLayoutProps = ViewProps & { dispatch : DispatchType } ;
85
+
48
86
type ColumnContainerProps = Omit < ContainerBaseProps , "style" > & {
49
87
style : SplitLayoutColStyleType ;
88
+ matchColumnsHeight : boolean ;
89
+ backgroundColor : string ;
90
+ backgroundImage : string ;
91
+ padding : string ;
92
+ orientation : string ;
93
+ margin : string ;
50
94
} ;
51
95
52
96
const ColumnContainer = ( props : ColumnContainerProps ) => {
53
97
return (
54
98
< InnerGrid
55
99
{ ...props }
56
- emptyRows={15}
57
100
radius = { props . style . radius }
58
- style={props.style}
101
+ bgColor = { props . backgroundColor }
102
+ style = { {
103
+ ...props . style ,
104
+ height : props . orientation === "horizontal"
105
+ ? ( props . matchColumnsHeight ? heightCalculator ( props . margin ) : "auto" )
106
+ : ( props . autoHeight ? "100%" : "auto" ) ,
107
+ } }
59
108
/>
60
109
) ;
61
110
} ;
62
111
63
112
const SplitLayout = ( props : SplitLayoutProps ) => {
64
- const screenInfo = useScreenInfo();
65
- const containerRef = useRef<HTMLDivElement | null>(null);
66
- const [componentWidth, setComponentWidth] = useState<number | null>(null);
67
-
68
- let { columns, containers, dispatch, collapsiblePanels, orientation, panelCount, columnStyle } = props;
69
-
70
- useEffect(() => {
71
- if (!containerRef.current) return;
72
- const resizeObserver = new ResizeObserver((entries) => {
73
- for (let entry of entries) {
74
- setComponentWidth(entry.contentRect.width);
75
- }
76
- });
77
-
78
- resizeObserver.observe(containerRef.current);
79
- return () => resizeObserver.disconnect();
80
- }, []);
81
113
82
114
return (
83
- <BackgroundColorContext.Provider value={props.style? .background}>
115
+ < BackgroundColorContext . Provider value = { props . columnStyle . background } >
84
116
< DisabledContext . Provider value = { props . disabled } >
85
- <div ref={containerRef} style={{ height: "100%" }}>
86
- <Splitter layout={orientation}>
87
- {Array.from({ length: panelCount }, (_, index) => {
88
- const id = String(index);
89
- const childDispatch = wrapDispatch(wrapDispatch(dispatch, "containers"), id);
90
- if (!containers[id]) return null;
91
- const containerProps = containers[id].children;
92
-
117
+ < SplitterWrapper $style = { props . bodyStyle } >
118
+ < Splitter style = { { overflow : props . mainScrollbar ? "auto" : "hidden" } } layout = { props . orientation } >
119
+ { props . columns . map ( ( col , index ) => {
120
+ const id = String ( col . id ) ;
121
+ const childDispatch = wrapDispatch ( wrapDispatch ( props . dispatch , "containers" ) , id ) ;
122
+ const containerProps = props . containers [ id ] ?. children ;
93
123
return (
94
- <SplitPanelWrapper key={id} $collapsible={collapsiblePanels}>
124
+ < SplitPanelWrapper
125
+ key = { id }
126
+ collapsible = { col . collapsible }
127
+ { ...( col . minWidth !== undefined ? { min : col . minWidth } : { } ) }
128
+ { ...( col . maxWidth !== undefined ? { max : col . maxWidth } : { } ) }
129
+ { ...( col . width !== undefined ? { defaultSize : col . width } : { } ) }
130
+ >
95
131
< ColumnContainer
96
132
layout = { containerProps . layout . getView ( ) }
97
133
items = { gridItemCompToGridItems ( containerProps . items . getView ( ) ) }
98
134
positionParams = { containerProps . positionParams . getView ( ) }
99
135
dispatch = { childDispatch }
100
- style={columnStyle}
136
+ style = { props . columnStyle }
137
+ backgroundColor = { col . backgroundColor }
138
+ backgroundImage = { col . backgroundImage }
139
+ padding = { col . padding }
140
+ autoHeight = { props . autoHeight }
141
+ horizontalGridCells = { props . horizontalGridCells }
142
+ emptyRows = { props . verticalGridCells }
143
+ matchColumnsHeight = { props . matchColumnsHeight }
144
+ orientation = { props . orientation }
145
+ margin = { props . columnStyle . margin }
101
146
/>
102
147
</ SplitPanelWrapper >
103
148
) ;
104
149
} ) }
105
150
</ Splitter >
106
- </div >
151
+ </ SplitterWrapper >
107
152
</ DisabledContext . Provider >
108
153
</ BackgroundColorContext . Provider >
154
+
109
155
) ;
110
156
} ;
111
157
@@ -116,27 +162,151 @@ export const SplitLayoutBaseComp = (function () {
116
162
< Section name = { sectionNames . basic } >
117
163
{ children . columns . propertyView ( { title : trans ( "splitLayout.column" ) } ) }
118
164
</ Section >
119
- <Section name={sectionNames.layout}>
120
- {children.panelCount.propertyView({ label: trans("splitLayout.panelCount") }) }
121
- {children.collapsiblePanels.propertyView({ label: trans("splitLayout.collapsiblePanels") }) }
122
- {children.orientation.propertyView({ label: trans("splitLayout.orientation") }) }
123
- </Section>
124
- <Section name={sectionNames.style}>
125
- {children.columnStyle.getPropertyView()}
126
- {children.animationStyle.getPropertyView()}
127
- </Section>
165
+
166
+ { ( useContext ( EditorContext ) . editorModeStatus === "logic" || useContext ( EditorContext ) . editorModeStatus === "both" ) && (
167
+ < Section name = { sectionNames . interaction } >
168
+ { disabledPropertyView ( children ) }
169
+ { hiddenPropertyView ( children ) }
170
+ </ Section >
171
+ ) }
172
+ { [ "layout" , "both" ] . includes ( useContext ( EditorContext ) . editorModeStatus ) && (
173
+ < >
174
+ < Section name = { sectionNames . layout } >
175
+ { children . orientation . propertyView ( {
176
+ label : trans ( "splitLayout.orientation" ) ,
177
+ radioButton : true ,
178
+ tooltip : trans ( "splitLayout.orientationTooltip" ) ,
179
+ } ) }
180
+ { children . autoHeight . getPropertyView ( ) }
181
+ { ( ! children . autoHeight . getView ( ) ) && children . mainScrollbar . propertyView ( {
182
+ label : trans ( "prop.mainScrollbar" )
183
+ } ) }
184
+ { ( children . orientation . getView ( ) == "horizontal" ) &&
185
+ children . matchColumnsHeight . propertyView ( { label : trans ( "splitLayout.matchColumnsHeight" ) }
186
+ ) }
187
+ { children . horizontalGridCells . propertyView ( {
188
+ label : trans ( 'prop.horizontalGridCells' ) ,
189
+ } ) }
190
+ { children . verticalGridCells . propertyView ( {
191
+ label : trans ( 'prop.verticalGridCells' ) ,
192
+ } ) }
193
+ </ Section >
194
+ < Section name = { sectionNames . bodyStyle } >
195
+ { children . bodyStyle . getPropertyView ( ) }
196
+ </ Section >
197
+ < Section name = { sectionNames . detailStyle } >
198
+ { children . columnStyle . getPropertyView ( ) }
199
+ </ Section >
200
+ < Section name = { sectionNames . animationStyle } hasTooltip = { true } >
201
+ { children . animationStyle . getPropertyView ( ) }
202
+ </ Section >
203
+ </ >
204
+ ) }
128
205
</ >
129
206
) )
130
207
. build ( ) ;
131
208
} ) ( ) ;
132
209
133
- class SplitLayoutImplComp extends SplitLayoutBaseComp {
210
+ class SplitLayoutImplComp extends SplitLayoutBaseComp implements IContainer {
211
+ private syncContainers ( ) : this {
212
+ const columns = this . children . columns . getView ( ) ;
213
+ const ids : Set < string > = new Set ( columns . map ( ( column ) => String ( column . id ) ) ) ;
214
+ let containers = this . children . containers . getView ( ) ;
215
+ // delete
216
+ const actions : CompAction [ ] = [ ] ;
217
+ Object . keys ( containers ) . forEach ( ( id ) => {
218
+ if ( ! ids . has ( id ) ) {
219
+ // log.debug("syncContainers delete. ids=", ids, " id=", id);
220
+ actions . push ( wrapChildAction ( "containers" , wrapChildAction ( id , deleteCompAction ( ) ) ) ) ;
221
+ }
222
+ } ) ;
223
+ // new
224
+ ids . forEach ( ( id ) => {
225
+ if ( ! containers . hasOwnProperty ( id ) ) {
226
+ // log.debug("syncContainers new containers: ", containers, " id: ", id);
227
+ actions . push (
228
+ wrapChildAction ( "containers" , addMapChildAction ( id , { layout : { } , items : { } } ) )
229
+ ) ;
230
+ }
231
+ } ) ;
232
+ // log.debug("syncContainers. actions: ", actions);
233
+ let instance = this ;
234
+ actions . forEach ( ( action ) => {
235
+ instance = instance . reduce ( action ) ;
236
+ } ) ;
237
+ return instance ;
238
+ }
239
+
134
240
override reduce ( action : CompAction ) : this {
241
+ const columns = this . children . columns . getView ( ) ;
242
+ if ( action . type === CompActionTypes . CUSTOM ) {
243
+ const value = action . value as JSONObject ;
244
+ if ( value . type === "push" ) {
245
+ const itemValue = value . value as JSONObject ;
246
+ if ( _ . isEmpty ( itemValue . key ) ) itemValue . key = itemValue . label ;
247
+ action = {
248
+ ...action ,
249
+ value : {
250
+ ...value ,
251
+ value : { ...itemValue } ,
252
+ } ,
253
+ } as CompAction ;
254
+ }
255
+ const { path } = action ;
256
+ if ( value . type === "delete" && path [ 0 ] === 'columns' && columns . length <= 1 ) {
257
+ messageInstance . warning ( trans ( "responsiveLayout.atLeastOneColumnError" ) ) ;
258
+ // at least one column
259
+ return this ;
260
+ }
261
+ }
262
+ // log.debug("before super reduce. action: ", action);
135
263
let newInstance = super . reduce ( action ) ;
264
+ if ( action . type === CompActionTypes . UPDATE_NODES_V2 ) {
265
+ // Need eval to get the value in StringControl
266
+ newInstance = newInstance . syncContainers ( ) ;
267
+ }
268
+ // log.debug("reduce. instance: ", this, " newInstance: ", newInstance);
136
269
return newInstance ;
137
270
}
138
- }
139
271
140
- */
272
+ realSimpleContainer ( key ?: string ) : SimpleContainerComp | undefined {
273
+ return Object . values ( this . children . containers . children ) . find ( ( container ) =>
274
+ container . realSimpleContainer ( key )
275
+ ) ;
276
+ }
277
+
278
+ getCompTree ( ) : CompTree {
279
+ const containerMap = this . children . containers . getView ( ) ;
280
+ const compTrees = Object . values ( containerMap ) . map ( ( container ) => container . getCompTree ( ) ) ;
281
+ return mergeCompTrees ( compTrees ) ;
282
+ }
283
+
284
+ findContainer ( key : string ) : IContainer | undefined {
285
+ const containerMap = this . children . containers . getView ( ) ;
286
+ for ( const container of Object . values ( containerMap ) ) {
287
+ const foundContainer = container . findContainer ( key ) ;
288
+ if ( foundContainer ) {
289
+ return foundContainer === container ? this : foundContainer ;
290
+ }
291
+ }
292
+ return undefined ;
293
+ }
294
+
295
+ getPasteValue ( nameGenerator : NameGenerator ) : JSONValue {
296
+ const containerMap = this . children . containers . getView ( ) ;
297
+ const containerPasteValueMap = _ . mapValues ( containerMap , ( container ) =>
298
+ container . getPasteValue ( nameGenerator )
299
+ ) ;
300
+
301
+ return { ...this . toJsonValue ( ) , containers : containerPasteValueMap } ;
302
+ }
303
+
304
+ override autoHeight ( ) : boolean {
305
+ return this . children . autoHeight . getView ( ) ;
306
+ }
307
+ }
141
308
142
- export const SplitLayoutComp = null ;
309
+ export const SplitLayoutComp = withExposingConfigs (
310
+ SplitLayoutImplComp ,
311
+ [ NameConfigHidden ]
312
+ ) ;
0 commit comments