Skip to content

Commit 1c8fd39

Browse files
added sorting option in list view comp
1 parent 873e468 commit 1c8fd39

File tree

2 files changed

+104
-36
lines changed

2 files changed

+104
-36
lines changed

client/packages/lowcoder/src/comps/comps/listViewComp/listView.tsx

+101-34
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { default as Pagination } from "antd/es/pagination";
22
import { EditorContext } from "comps/editorState";
33
import { BackgroundColorContext } from "comps/utils/backgroundColorContext";
4-
import _ from "lodash";
4+
import _, { findIndex } from "lodash";
55
import { ConstructorToView, deferAction } from "lowcoder-core";
6-
import { HintPlaceHolder, ScrollBar, pageItemRender } from "lowcoder-design";
6+
import { DragIcon, HintPlaceHolder, ScrollBar, pageItemRender } from "lowcoder-design";
77
import { RefObject, useContext, createContext, useMemo, useRef, useEffect } from "react";
88
import ReactResizeDetector from "react-resize-detector";
99
import styled from "styled-components";
@@ -22,6 +22,11 @@ import { useMergeCompStyles } from "@lowcoder-ee/util/hooks";
2222
import { childrenToProps } from "@lowcoder-ee/comps/generators/multi";
2323
import { AnimationStyleType } from "@lowcoder-ee/comps/controls/styleControlConstants";
2424
import { getBackgroundStyle } from "@lowcoder-ee/util/styleUtils";
25+
import { DndContext } from "@dnd-kit/core";
26+
import { SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
27+
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
28+
import { CSS } from "@dnd-kit/utilities";
29+
import { JSONObject } from "@lowcoder-ee/index.sdk";
2530

2631
const ListViewWrapper = styled.div<{ $style: any; $paddingWidth: string,$animationStyle:AnimationStyleType }>`
2732
height: 100%;
@@ -63,6 +68,22 @@ const ListOrientationWrapper = styled.div<{
6368
flex-direction: ${(props) => (props.$isHorizontal ? "row" : "column")};
6469
`;
6570

71+
const StyledDragIcon = styled(DragIcon)`
72+
height: 16px;
73+
width: 16px;
74+
color: #8b8fa3;
75+
76+
&:hover {
77+
cursor: grab;
78+
outline: none;
79+
}
80+
81+
&:focus {
82+
cursor: grab;
83+
outline: none;
84+
}
85+
`;
86+
6687
type MinHorizontalWidthContextType = {
6788
horizontalWidth: string,
6889
minHorizontalWidth?: string,
@@ -73,19 +94,30 @@ const MinHorizontalWidthContext = createContext<MinHorizontalWidthContextType>({
7394
minHorizontalWidth: '100px',
7495
});
7596

76-
const ContainerInListView = (props: ContainerBaseProps ) => {
97+
const ContainerInListView = (props: ContainerBaseProps & {itemIdx: number} ) => {
7798
const {
7899
horizontalWidth,
79100
minHorizontalWidth
80101
} = useContext(MinHorizontalWidthContext);
81102

103+
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
104+
id: String(props.itemIdx),
105+
});
106+
82107
return (
83108
<div
109+
ref={setNodeRef}
84110
style={{
85111
width: horizontalWidth,
86112
minWidth: minHorizontalWidth || '0px',
113+
transform: CSS.Transform.toString(transform),
114+
transition,
115+
display: 'flex',
116+
flexWrap: 'nowrap',
117+
alignItems: 'center',
87118
}}
88119
>
120+
{<StyledDragIcon {...attributes} {...listeners} />}
89121
<InnerGrid
90122
{...props}
91123
emptyRows={15}
@@ -133,35 +165,36 @@ function ListItem({
133165
// }, []);
134166

135167
return (
136-
<MinHorizontalWidthContext.Provider
137-
value={{
138-
horizontalWidth,
139-
minHorizontalWidth
168+
<MinHorizontalWidthContext.Provider
169+
value={{
170+
horizontalWidth,
171+
minHorizontalWidth
172+
}}
173+
>
174+
<ContainerInListView
175+
itemIdx={itemIdx}
176+
layout={containerProps.layout}
177+
items={gridItemCompToGridItems(containerProps.items)}
178+
horizontalGridCells={horizontalGridCells}
179+
positionParams={containerProps.positionParams}
180+
// all layout changes should only reflect on the commonContainer
181+
dispatch={itemIdx === offset ? containerProps.dispatch : _.noop}
182+
style={{
183+
height: "100%",
184+
// in case of horizontal mode, minHorizontalWidth is 0px
185+
width: minHorizontalWidth || '100%',
186+
backgroundColor: "transparent",
187+
// flex: "auto",
140188
}}
141-
>
142-
<ContainerInListView
143-
layout={containerProps.layout}
144-
items={gridItemCompToGridItems(containerProps.items)}
145-
horizontalGridCells={horizontalGridCells}
146-
positionParams={containerProps.positionParams}
147-
// all layout changes should only reflect on the commonContainer
148-
dispatch={itemIdx === offset ? containerProps.dispatch : _.noop}
149-
style={{
150-
height: "100%",
151-
// in case of horizontal mode, minHorizontalWidth is 0px
152-
width: minHorizontalWidth || '100%',
153-
backgroundColor: "transparent",
154-
// flex: "auto",
155-
}}
156-
autoHeight={autoHeight}
157-
isDroppable={itemIdx === offset}
158-
isDraggable={itemIdx === offset}
159-
isResizable={itemIdx === offset}
160-
isSelectable={itemIdx === offset}
161-
scrollContainerRef={scrollContainerRef}
162-
overflow={"hidden"}
163-
minHeight={minHeight}
164-
enableGridLines={true}
189+
autoHeight={autoHeight}
190+
isDroppable={itemIdx === offset}
191+
isDraggable={itemIdx === offset}
192+
isResizable={itemIdx === offset}
193+
isSelectable={itemIdx === offset}
194+
scrollContainerRef={scrollContainerRef}
195+
overflow={"hidden"}
196+
minHeight={minHeight}
197+
enableGridLines={true}
165198
/>
166199
</MinHorizontalWidthContext.Provider>
167200
);
@@ -190,6 +223,7 @@ export function ListView(props: Props) {
190223
() => getData(children.noOfRows.getView()),
191224
[children.noOfRows]
192225
);
226+
const listData = useMemo(() => children.listData.getView(), [children.listData]);
193227
const horizontalGridCells = useMemo(() => children.horizontalGridCells.getView(), [children.horizontalGridCells]);
194228
const autoHeight = useMemo(() => children.autoHeight.getView(), [children.autoHeight]);
195229
const showHorizontalScrollbar = useMemo(() => children.showHorizontalScrollbar.getView(), [children.showHorizontalScrollbar]);
@@ -213,6 +247,11 @@ export function ListView(props: Props) {
213247
total,
214248
};
215249
}, [children.pagination, totalCount]);
250+
251+
useEffect(() => {
252+
children.listData.dispatchChangeValueAction(data);
253+
}, [JSON.stringify(data)]);
254+
216255
const style = children.style.getView();
217256
const animationStyle = children.animationStyle.getView();
218257

@@ -229,6 +268,7 @@ export function ListView(props: Props) {
229268
// log.log("List. listHeight: ", listHeight, " minHeight: ", minHeight);
230269
const renders = _.range(0, noOfRows).map((rowIdx) => {
231270
// log.log("renders. i: ", i, "containerProps: ", containerProps, " text: ", Object.values(containerProps.items as Record<string, any>)[0].children.comp.children.text);
271+
const items = _.range(0, noOfColumns);
232272
const render = (
233273
<div
234274
key={rowIdx}
@@ -238,7 +278,7 @@ export function ListView(props: Props) {
238278
}}
239279
>
240280
<FlexWrapper>
241-
{_.range(0, noOfColumns).map((colIdx) => {
281+
{items.map((colIdx) => {
242282
const itemIdx = rowIdx * noOfColumns + colIdx + pageInfo.offset;
243283
if (
244284
itemIdx >= pageInfo.total ||
@@ -250,7 +290,7 @@ export function ListView(props: Props) {
250290
const containerProps = containerFn(
251291
{
252292
[itemIndexName]: itemIdx,
253-
[itemDataName]: getCurrentItemParams(data, itemIdx)
293+
[itemDataName]: getCurrentItemParams(listData as JSONObject[], itemIdx)
254294
},
255295
String(itemIdx)
256296
).getView();
@@ -259,6 +299,7 @@ export function ListView(props: Props) {
259299
deferAction(ContextContainerComp.batchDeleteAction([String(itemIdx)]))
260300
);
261301
};
302+
262303
return (
263304
<ListItem
264305
key={itemIdx}
@@ -278,6 +319,7 @@ export function ListView(props: Props) {
278319
</FlexWrapper>
279320
</div>
280321
);
322+
281323
return render;
282324
});
283325

@@ -289,6 +331,23 @@ export function ListView(props: Props) {
289331

290332
useMergeCompStyles(childrenProps, comp.dispatch);
291333

334+
const handleDragEnd = (e: { active: { id: string }; over: { id: string } | null }) => {
335+
if (!e.over) {
336+
return;
337+
}
338+
const fromIndex = Number(e.active.id);
339+
const toIndex = Number(e.over.id);
340+
if (fromIndex < 0 || toIndex < 0 || fromIndex === toIndex) {
341+
return;
342+
}
343+
344+
const newData = [...listData];
345+
const [movedItem] = newData.splice(fromIndex, 1);
346+
newData.splice(toIndex, 0, movedItem);
347+
348+
children.listData.dispatchChangeValueAction(newData);
349+
};
350+
292351
// log.debug("renders: ", renders);
293352
return (
294353
<BackgroundColorContext.Provider value={style.background}>
@@ -306,7 +365,15 @@ export function ListView(props: Props) {
306365
$isGrid={noOfColumns > 1}
307366
$autoHeight={autoHeight}
308367
>
309-
{renders}
368+
<DndContext onDragEnd={handleDragEnd}>
369+
<SortableContext
370+
items={
371+
_.range(0, totalCount).map((colIdx) => String(colIdx))
372+
}
373+
>
374+
{renders}
375+
</SortableContext>
376+
</DndContext>
310377
</ListOrientationWrapper>
311378
)}
312379
>

client/packages/lowcoder/src/comps/comps/listViewComp/listViewComp.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
withFunction,
3030
WrapContextNodeV2,
3131
} from "lowcoder-core";
32-
import { JSONValue } from "util/jsonTypes";
32+
import { JSONArray, JSONValue } from "util/jsonTypes";
3333
import { depthEqual, lastValueIfEqual, shallowEqual } from "util/objectUtils";
3434
import { CompTree, getAllCompItems, IContainer } from "../containerBase";
3535
import { SimpleContainerComp, toSimpleContainerData } from "../containerBase/simpleContainerComp";
@@ -43,6 +43,7 @@ import { SliderControl } from "@lowcoder-ee/comps/controls/sliderControl";
4343

4444
const childrenMap = {
4545
noOfRows: withIsLoadingMethod(NumberOrJSONObjectArrayControl), // FIXME: migrate "noOfRows" to "data"
46+
listData: stateComp<JSONArray>([]),
4647
noOfColumns: withDefault(NumberControl, 1),
4748
itemIndexName: withDefault(StringControl, "i"),
4849
itemDataName: withDefault(StringControl, "currentItem"),
@@ -116,7 +117,7 @@ export class ListViewImplComp extends ListViewTmpComp implements IContainer {
116117
const { itemCount } = getData(this.children.noOfRows.getView());
117118
const itemIndexName = this.children.itemIndexName.getView();
118119
const itemDataName = this.children.itemDataName.getView();
119-
const dataExposingNode = this.children.noOfRows.exposingNode();
120+
const dataExposingNode = this.children.listData.exposingNode();
120121
const containerComp = this.children.container;
121122
// for each container expose each comps with params
122123
const exposingRecord = _(_.range(0, itemCount))

0 commit comments

Comments
 (0)