From 1c8fd39b5202f20dd3c10a824503b8b76cdbcd30 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 16 Apr 2025 01:09:01 +0500 Subject: [PATCH 1/2] added sorting option in list view comp --- .../src/comps/comps/listViewComp/listView.tsx | 135 +++++++++++++----- .../comps/comps/listViewComp/listViewComp.tsx | 5 +- 2 files changed, 104 insertions(+), 36 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/listViewComp/listView.tsx b/client/packages/lowcoder/src/comps/comps/listViewComp/listView.tsx index 71503d0c2..a51175b04 100644 --- a/client/packages/lowcoder/src/comps/comps/listViewComp/listView.tsx +++ b/client/packages/lowcoder/src/comps/comps/listViewComp/listView.tsx @@ -1,9 +1,9 @@ import { default as Pagination } from "antd/es/pagination"; import { EditorContext } from "comps/editorState"; import { BackgroundColorContext } from "comps/utils/backgroundColorContext"; -import _ from "lodash"; +import _, { findIndex } from "lodash"; import { ConstructorToView, deferAction } from "lowcoder-core"; -import { HintPlaceHolder, ScrollBar, pageItemRender } from "lowcoder-design"; +import { DragIcon, HintPlaceHolder, ScrollBar, pageItemRender } from "lowcoder-design"; import { RefObject, useContext, createContext, useMemo, useRef, useEffect } from "react"; import ReactResizeDetector from "react-resize-detector"; import styled from "styled-components"; @@ -22,6 +22,11 @@ import { useMergeCompStyles } from "@lowcoder-ee/util/hooks"; import { childrenToProps } from "@lowcoder-ee/comps/generators/multi"; import { AnimationStyleType } from "@lowcoder-ee/comps/controls/styleControlConstants"; import { getBackgroundStyle } from "@lowcoder-ee/util/styleUtils"; +import { DndContext } from "@dnd-kit/core"; +import { SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable"; +import { restrictToVerticalAxis } from "@dnd-kit/modifiers"; +import { CSS } from "@dnd-kit/utilities"; +import { JSONObject } from "@lowcoder-ee/index.sdk"; const ListViewWrapper = styled.div<{ $style: any; $paddingWidth: string,$animationStyle:AnimationStyleType }>` height: 100%; @@ -63,6 +68,22 @@ const ListOrientationWrapper = styled.div<{ flex-direction: ${(props) => (props.$isHorizontal ? "row" : "column")}; `; +const StyledDragIcon = styled(DragIcon)` + height: 16px; + width: 16px; + color: #8b8fa3; + + &:hover { + cursor: grab; + outline: none; + } + + &:focus { + cursor: grab; + outline: none; + } +`; + type MinHorizontalWidthContextType = { horizontalWidth: string, minHorizontalWidth?: string, @@ -73,19 +94,30 @@ const MinHorizontalWidthContext = createContext({ minHorizontalWidth: '100px', }); -const ContainerInListView = (props: ContainerBaseProps ) => { +const ContainerInListView = (props: ContainerBaseProps & {itemIdx: number} ) => { const { horizontalWidth, minHorizontalWidth } = useContext(MinHorizontalWidthContext); + const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ + id: String(props.itemIdx), + }); + return (
+ {} + - ); @@ -190,6 +223,7 @@ export function ListView(props: Props) { () => getData(children.noOfRows.getView()), [children.noOfRows] ); + const listData = useMemo(() => children.listData.getView(), [children.listData]); const horizontalGridCells = useMemo(() => children.horizontalGridCells.getView(), [children.horizontalGridCells]); const autoHeight = useMemo(() => children.autoHeight.getView(), [children.autoHeight]); const showHorizontalScrollbar = useMemo(() => children.showHorizontalScrollbar.getView(), [children.showHorizontalScrollbar]); @@ -213,6 +247,11 @@ export function ListView(props: Props) { total, }; }, [children.pagination, totalCount]); + + useEffect(() => { + children.listData.dispatchChangeValueAction(data); + }, [JSON.stringify(data)]); + const style = children.style.getView(); const animationStyle = children.animationStyle.getView(); @@ -229,6 +268,7 @@ export function ListView(props: Props) { // log.log("List. listHeight: ", listHeight, " minHeight: ", minHeight); const renders = _.range(0, noOfRows).map((rowIdx) => { // log.log("renders. i: ", i, "containerProps: ", containerProps, " text: ", Object.values(containerProps.items as Record)[0].children.comp.children.text); + const items = _.range(0, noOfColumns); const render = (
- {_.range(0, noOfColumns).map((colIdx) => { + {items.map((colIdx) => { const itemIdx = rowIdx * noOfColumns + colIdx + pageInfo.offset; if ( itemIdx >= pageInfo.total || @@ -250,7 +290,7 @@ export function ListView(props: Props) { const containerProps = containerFn( { [itemIndexName]: itemIdx, - [itemDataName]: getCurrentItemParams(data, itemIdx) + [itemDataName]: getCurrentItemParams(listData as JSONObject[], itemIdx) }, String(itemIdx) ).getView(); @@ -259,6 +299,7 @@ export function ListView(props: Props) { deferAction(ContextContainerComp.batchDeleteAction([String(itemIdx)])) ); }; + return (
); + return render; }); @@ -289,6 +331,23 @@ export function ListView(props: Props) { useMergeCompStyles(childrenProps, comp.dispatch); + const handleDragEnd = (e: { active: { id: string }; over: { id: string } | null }) => { + if (!e.over) { + return; + } + const fromIndex = Number(e.active.id); + const toIndex = Number(e.over.id); + if (fromIndex < 0 || toIndex < 0 || fromIndex === toIndex) { + return; + } + + const newData = [...listData]; + const [movedItem] = newData.splice(fromIndex, 1); + newData.splice(toIndex, 0, movedItem); + + children.listData.dispatchChangeValueAction(newData); + }; + // log.debug("renders: ", renders); return ( @@ -306,7 +365,15 @@ export function ListView(props: Props) { $isGrid={noOfColumns > 1} $autoHeight={autoHeight} > - {renders} + + String(colIdx)) + } + > + {renders} + + )} > diff --git a/client/packages/lowcoder/src/comps/comps/listViewComp/listViewComp.tsx b/client/packages/lowcoder/src/comps/comps/listViewComp/listViewComp.tsx index 47da5c6f3..f23edafe4 100644 --- a/client/packages/lowcoder/src/comps/comps/listViewComp/listViewComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/listViewComp/listViewComp.tsx @@ -29,7 +29,7 @@ import { withFunction, WrapContextNodeV2, } from "lowcoder-core"; -import { JSONValue } from "util/jsonTypes"; +import { JSONArray, JSONValue } from "util/jsonTypes"; import { depthEqual, lastValueIfEqual, shallowEqual } from "util/objectUtils"; import { CompTree, getAllCompItems, IContainer } from "../containerBase"; import { SimpleContainerComp, toSimpleContainerData } from "../containerBase/simpleContainerComp"; @@ -43,6 +43,7 @@ import { SliderControl } from "@lowcoder-ee/comps/controls/sliderControl"; const childrenMap = { noOfRows: withIsLoadingMethod(NumberOrJSONObjectArrayControl), // FIXME: migrate "noOfRows" to "data" + listData: stateComp([]), noOfColumns: withDefault(NumberControl, 1), itemIndexName: withDefault(StringControl, "i"), itemDataName: withDefault(StringControl, "currentItem"), @@ -116,7 +117,7 @@ export class ListViewImplComp extends ListViewTmpComp implements IContainer { const { itemCount } = getData(this.children.noOfRows.getView()); const itemIndexName = this.children.itemIndexName.getView(); const itemDataName = this.children.itemDataName.getView(); - const dataExposingNode = this.children.noOfRows.exposingNode(); + const dataExposingNode = this.children.listData.exposingNode(); const containerComp = this.children.container; // for each container expose each comps with params const exposingRecord = _(_.range(0, itemCount)) From d6e478119edc4615609ab3a85c845ecb621c160c Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 16 Apr 2025 01:21:29 +0500 Subject: [PATCH 2/2] added switch to enable/disable sorting --- .../src/comps/comps/listViewComp/listView.tsx | 52 ++++++++++++++----- .../comps/comps/listViewComp/listViewComp.tsx | 1 + .../listViewComp/listViewPropertyView.tsx | 3 ++ .../packages/lowcoder/src/i18n/locales/en.ts | 3 +- 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/listViewComp/listView.tsx b/client/packages/lowcoder/src/comps/comps/listViewComp/listView.tsx index a51175b04..826ff0e38 100644 --- a/client/packages/lowcoder/src/comps/comps/listViewComp/listView.tsx +++ b/client/packages/lowcoder/src/comps/comps/listViewComp/listView.tsx @@ -23,8 +23,7 @@ import { childrenToProps } from "@lowcoder-ee/comps/generators/multi"; import { AnimationStyleType } from "@lowcoder-ee/comps/controls/styleControlConstants"; import { getBackgroundStyle } from "@lowcoder-ee/util/styleUtils"; import { DndContext } from "@dnd-kit/core"; -import { SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable"; -import { restrictToVerticalAxis } from "@dnd-kit/modifiers"; +import { SortableContext, useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { JSONObject } from "@lowcoder-ee/index.sdk"; @@ -94,7 +93,7 @@ const MinHorizontalWidthContext = createContext({ minHorizontalWidth: '100px', }); -const ContainerInListView = (props: ContainerBaseProps & {itemIdx: number} ) => { +const ContainerInListView = (props: ContainerBaseProps & {itemIdx: number, enableSorting?: boolean} ) => { const { horizontalWidth, minHorizontalWidth @@ -104,6 +103,24 @@ const ContainerInListView = (props: ContainerBaseProps & {itemIdx: number} ) => id: String(props.itemIdx), }); + if (!props.enableSorting) { + return ( +
+ +
+ ) + } + return (
void; minHorizontalWidth?: string; horizontalWidth: string; + enableSorting?: boolean; }; function ListItem({ @@ -154,6 +172,7 @@ function ListItem({ scrollContainerRef, minHeight, horizontalGridCells, + enableSorting, } = props; // disable the unmount function to save user's state with pagination @@ -195,6 +214,7 @@ function ListItem({ overflow={"hidden"} minHeight={minHeight} enableGridLines={true} + enableSorting={enableSorting} /> ); @@ -248,6 +268,8 @@ export function ListView(props: Props) { }; }, [children.pagination, totalCount]); + const enableSorting = useMemo(() => children.enableSorting.getView(), [children.enableSorting]); + useEffect(() => { children.listData.dispatchChangeValueAction(data); }, [JSON.stringify(data)]); @@ -313,6 +335,7 @@ export function ListView(props: Props) { unMountFn={unMountFn} horizontalWidth={`${100 / noOfColumns}%`} minHorizontalWidth={horizontal ? minHorizontalWidth : undefined} + enableSorting={enableSorting} /> ); })} @@ -365,15 +388,20 @@ export function ListView(props: Props) { $isGrid={noOfColumns > 1} $autoHeight={autoHeight} > - - String(colIdx)) - } - > - {renders} - - + {!enableSorting + ? renders + : ( + + String(colIdx)) + } + > + {renders} + + + ) + } )} > diff --git a/client/packages/lowcoder/src/comps/comps/listViewComp/listViewComp.tsx b/client/packages/lowcoder/src/comps/comps/listViewComp/listViewComp.tsx index f23edafe4..00f6807cf 100644 --- a/client/packages/lowcoder/src/comps/comps/listViewComp/listViewComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/listViewComp/listViewComp.tsx @@ -61,6 +61,7 @@ const childrenMap = { animationStyle: styleControl(AnimationStyle, 'animationStyle'), horizontal: withDefault(BoolControl, false), minHorizontalWidth: withDefault(RadiusControl, '100px'), + enableSorting: withDefault(BoolControl, false), }; const ListViewTmpComp = new UICompBuilder(childrenMap, () => <>) diff --git a/client/packages/lowcoder/src/comps/comps/listViewComp/listViewPropertyView.tsx b/client/packages/lowcoder/src/comps/comps/listViewComp/listViewPropertyView.tsx index 6e9a4b865..22e288d73 100644 --- a/client/packages/lowcoder/src/comps/comps/listViewComp/listViewPropertyView.tsx +++ b/client/packages/lowcoder/src/comps/comps/listViewComp/listViewPropertyView.tsx @@ -57,6 +57,9 @@ export function listPropertyView(compType: ListCompType) {
{hiddenPropertyView(children)} {showDataLoadingIndicatorsPropertyView(children)} + {children.enableSorting.propertyView({ + label: trans('listView.enableSorting'), + })}
)} diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 735b797d4..acc648866 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -2598,7 +2598,8 @@ export const en = { "itemDataNameDesc": "The Variable Name Referring to the Item's Data Object, Default as {default}", "itemsDesc": "Exposing Data of Components in List", "dataDesc": "The JSON Data Used in the Current List", - "dataTooltip": "If You just Set a Number, This Field Will Be Regarded as Row Count, and the Data Will Be Regarded as Empty." + "dataTooltip": "If You just Set a Number, This Field Will Be Regarded as Row Count, and the Data Will Be Regarded as Empty.", + "enableSorting": "Allow Sorting" }, "navigation": { "addText": "Add Submenu Item",