Skip to content

Commit 9aaa4be

Browse files
authoredApr 19, 2025··
Merge branch 'dev' into feature-iconscout-intergration

File tree

17 files changed

+345
-75
lines changed

17 files changed

+345
-75
lines changed
 

‎.github/workflows/sonarcloud.yml

+1
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ jobs:
3030
env:
3131
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
3232
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
33+
SONAR_SCANNER_OPTS: "-Dsonar.javascript.node.maxspace=8192 -Xmx8192m"

‎client/packages/lowcoder/src/api/apiUtils.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,14 @@ export const apiFailureResponseInterceptor = (error: any) => {
122122
if (!notAuthRequiredPath(error.config?.url)) {
123123
if (error.response.status === API_STATUS_CODES.REQUEST_NOT_AUTHORISED) {
124124
// get x-org-id from failed request
125-
const organizationId = error.response.headers['x-org-id'] || undefined;
125+
let organizationId;
126+
if (error.response.headers['x-org-id']) {
127+
organizationId = error.response.headers['x-org-id'];
128+
}
129+
if (localStorage.getItem('lowcoder_login_orgId')) {
130+
organizationId = localStorage.getItem('lowcoder_login_orgId');
131+
localStorage.removeItem('lowcoder_login_orgId');
132+
}
126133
// Redirect to login and set a redirect url.
127134
StoreRegistry.getStore().dispatch(
128135
logoutAction({

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

+129-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,10 @@ 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 } from "@dnd-kit/sortable";
27+
import { CSS } from "@dnd-kit/utilities";
28+
import { JSONObject } from "@lowcoder-ee/index.sdk";
2529

2630
const ListViewWrapper = styled.div<{ $style: any; $paddingWidth: string,$animationStyle:AnimationStyleType }>`
2731
height: 100%;
@@ -63,6 +67,22 @@ const ListOrientationWrapper = styled.div<{
6367
flex-direction: ${(props) => (props.$isHorizontal ? "row" : "column")};
6468
`;
6569

70+
const StyledDragIcon = styled(DragIcon)`
71+
height: 16px;
72+
width: 16px;
73+
color: #8b8fa3;
74+
75+
&:hover {
76+
cursor: grab;
77+
outline: none;
78+
}
79+
80+
&:focus {
81+
cursor: grab;
82+
outline: none;
83+
}
84+
`;
85+
6686
type MinHorizontalWidthContextType = {
6787
horizontalWidth: string,
6888
minHorizontalWidth?: string,
@@ -73,19 +93,48 @@ const MinHorizontalWidthContext = createContext<MinHorizontalWidthContextType>({
7393
minHorizontalWidth: '100px',
7494
});
7595

76-
const ContainerInListView = (props: ContainerBaseProps ) => {
96+
const ContainerInListView = (props: ContainerBaseProps & {itemIdx: number, enableSorting?: boolean} ) => {
7797
const {
7898
horizontalWidth,
7999
minHorizontalWidth
80100
} = useContext(MinHorizontalWidthContext);
81101

102+
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
103+
id: String(props.itemIdx),
104+
});
105+
106+
if (!props.enableSorting) {
107+
return (
108+
<div
109+
style={{
110+
width: horizontalWidth,
111+
minWidth: minHorizontalWidth || '0px',
112+
}}
113+
>
114+
<InnerGrid
115+
{...props}
116+
emptyRows={15}
117+
containerPadding={[4, 4]}
118+
hintPlaceholder={HintPlaceHolder}
119+
/>
120+
</div>
121+
)
122+
}
123+
82124
return (
83125
<div
126+
ref={setNodeRef}
84127
style={{
85128
width: horizontalWidth,
86129
minWidth: minHorizontalWidth || '0px',
130+
transform: CSS.Transform.toString(transform),
131+
transition,
132+
display: 'flex',
133+
flexWrap: 'nowrap',
134+
alignItems: 'center',
87135
}}
88136
>
137+
{<StyledDragIcon {...attributes} {...listeners} />}
89138
<InnerGrid
90139
{...props}
91140
emptyRows={15}
@@ -107,6 +156,7 @@ type ListItemProps = {
107156
unMountFn?: () => void;
108157
minHorizontalWidth?: string;
109158
horizontalWidth: string;
159+
enableSorting?: boolean;
110160
};
111161

112162
function ListItem({
@@ -122,6 +172,7 @@ function ListItem({
122172
scrollContainerRef,
123173
minHeight,
124174
horizontalGridCells,
175+
enableSorting,
125176
} = props;
126177

127178
// disable the unmount function to save user's state with pagination
@@ -133,35 +184,37 @@ function ListItem({
133184
// }, []);
134185

135186
return (
136-
<MinHorizontalWidthContext.Provider
137-
value={{
138-
horizontalWidth,
139-
minHorizontalWidth
187+
<MinHorizontalWidthContext.Provider
188+
value={{
189+
horizontalWidth,
190+
minHorizontalWidth
191+
}}
192+
>
193+
<ContainerInListView
194+
itemIdx={itemIdx}
195+
layout={containerProps.layout}
196+
items={gridItemCompToGridItems(containerProps.items)}
197+
horizontalGridCells={horizontalGridCells}
198+
positionParams={containerProps.positionParams}
199+
// all layout changes should only reflect on the commonContainer
200+
dispatch={itemIdx === offset ? containerProps.dispatch : _.noop}
201+
style={{
202+
height: "100%",
203+
// in case of horizontal mode, minHorizontalWidth is 0px
204+
width: minHorizontalWidth || '100%',
205+
backgroundColor: "transparent",
206+
// flex: "auto",
140207
}}
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}
208+
autoHeight={autoHeight}
209+
isDroppable={itemIdx === offset}
210+
isDraggable={itemIdx === offset}
211+
isResizable={itemIdx === offset}
212+
isSelectable={itemIdx === offset}
213+
scrollContainerRef={scrollContainerRef}
214+
overflow={"hidden"}
215+
minHeight={minHeight}
216+
enableGridLines={true}
217+
enableSorting={enableSorting}
165218
/>
166219
</MinHorizontalWidthContext.Provider>
167220
);
@@ -190,6 +243,7 @@ export function ListView(props: Props) {
190243
() => getData(children.noOfRows.getView()),
191244
[children.noOfRows]
192245
);
246+
const listData = useMemo(() => children.listData.getView(), [children.listData]);
193247
const horizontalGridCells = useMemo(() => children.horizontalGridCells.getView(), [children.horizontalGridCells]);
194248
const autoHeight = useMemo(() => children.autoHeight.getView(), [children.autoHeight]);
195249
const showHorizontalScrollbar = useMemo(() => children.showHorizontalScrollbar.getView(), [children.showHorizontalScrollbar]);
@@ -213,6 +267,13 @@ export function ListView(props: Props) {
213267
total,
214268
};
215269
}, [children.pagination, totalCount]);
270+
271+
const enableSorting = useMemo(() => children.enableSorting.getView(), [children.enableSorting]);
272+
273+
useEffect(() => {
274+
children.listData.dispatchChangeValueAction(data);
275+
}, [JSON.stringify(data)]);
276+
216277
const style = children.style.getView();
217278
const animationStyle = children.animationStyle.getView();
218279

@@ -229,6 +290,7 @@ export function ListView(props: Props) {
229290
// log.log("List. listHeight: ", listHeight, " minHeight: ", minHeight);
230291
const renders = _.range(0, noOfRows).map((rowIdx) => {
231292
// log.log("renders. i: ", i, "containerProps: ", containerProps, " text: ", Object.values(containerProps.items as Record<string, any>)[0].children.comp.children.text);
293+
const items = _.range(0, noOfColumns);
232294
const render = (
233295
<div
234296
key={rowIdx}
@@ -238,7 +300,7 @@ export function ListView(props: Props) {
238300
}}
239301
>
240302
<FlexWrapper>
241-
{_.range(0, noOfColumns).map((colIdx) => {
303+
{items.map((colIdx) => {
242304
const itemIdx = rowIdx * noOfColumns + colIdx + pageInfo.offset;
243305
if (
244306
itemIdx >= pageInfo.total ||
@@ -250,7 +312,7 @@ export function ListView(props: Props) {
250312
const containerProps = containerFn(
251313
{
252314
[itemIndexName]: itemIdx,
253-
[itemDataName]: getCurrentItemParams(data, itemIdx)
315+
[itemDataName]: getCurrentItemParams(listData as JSONObject[], itemIdx)
254316
},
255317
String(itemIdx)
256318
).getView();
@@ -259,6 +321,7 @@ export function ListView(props: Props) {
259321
deferAction(ContextContainerComp.batchDeleteAction([String(itemIdx)]))
260322
);
261323
};
324+
262325
return (
263326
<ListItem
264327
key={itemIdx}
@@ -272,12 +335,14 @@ export function ListView(props: Props) {
272335
unMountFn={unMountFn}
273336
horizontalWidth={`${100 / noOfColumns}%`}
274337
minHorizontalWidth={horizontal ? minHorizontalWidth : undefined}
338+
enableSorting={enableSorting}
275339
/>
276340
);
277341
})}
278342
</FlexWrapper>
279343
</div>
280344
);
345+
281346
return render;
282347
});
283348

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

290355
useMergeCompStyles(childrenProps, comp.dispatch);
291356

357+
const handleDragEnd = (e: { active: { id: string }; over: { id: string } | null }) => {
358+
if (!e.over) {
359+
return;
360+
}
361+
const fromIndex = Number(e.active.id);
362+
const toIndex = Number(e.over.id);
363+
if (fromIndex < 0 || toIndex < 0 || fromIndex === toIndex) {
364+
return;
365+
}
366+
367+
const newData = [...listData];
368+
const [movedItem] = newData.splice(fromIndex, 1);
369+
newData.splice(toIndex, 0, movedItem);
370+
371+
children.listData.dispatchChangeValueAction(newData);
372+
};
373+
292374
// log.debug("renders: ", renders);
293375
return (
294376
<BackgroundColorContext.Provider value={style.background}>
@@ -306,7 +388,20 @@ export function ListView(props: Props) {
306388
$isGrid={noOfColumns > 1}
307389
$autoHeight={autoHeight}
308390
>
309-
{renders}
391+
{!enableSorting
392+
? renders
393+
: (
394+
<DndContext onDragEnd={handleDragEnd}>
395+
<SortableContext
396+
items={
397+
_.range(0, totalCount).map((colIdx) => String(colIdx))
398+
}
399+
>
400+
{renders}
401+
</SortableContext>
402+
</DndContext>
403+
)
404+
}
310405
</ListOrientationWrapper>
311406
)}
312407
>

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

+4-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"),
@@ -60,6 +61,7 @@ const childrenMap = {
6061
animationStyle: styleControl(AnimationStyle, 'animationStyle'),
6162
horizontal: withDefault(BoolControl, false),
6263
minHorizontalWidth: withDefault(RadiusControl, '100px'),
64+
enableSorting: withDefault(BoolControl, false),
6365
};
6466

6567
const ListViewTmpComp = new UICompBuilder(childrenMap, () => <></>)
@@ -116,7 +118,7 @@ export class ListViewImplComp extends ListViewTmpComp implements IContainer {
116118
const { itemCount } = getData(this.children.noOfRows.getView());
117119
const itemIndexName = this.children.itemIndexName.getView();
118120
const itemDataName = this.children.itemDataName.getView();
119-
const dataExposingNode = this.children.noOfRows.exposingNode();
121+
const dataExposingNode = this.children.listData.exposingNode();
120122
const containerComp = this.children.container;
121123
// for each container expose each comps with params
122124
const exposingRecord = _(_.range(0, itemCount))

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

+3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ export function listPropertyView(compType: ListCompType) {
5757
<Section name={sectionNames.interaction}>
5858
{hiddenPropertyView(children)}
5959
{showDataLoadingIndicatorsPropertyView(children)}
60+
{children.enableSorting.propertyView({
61+
label: trans('listView.enableSorting'),
62+
})}
6063
</Section>
6164
)}
6265

‎client/packages/lowcoder/src/i18n/locales/en.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -2598,7 +2598,8 @@ export const en = {
25982598
"itemDataNameDesc": "The Variable Name Referring to the Item's Data Object, Default as {default}",
25992599
"itemsDesc": "Exposing Data of Components in List",
26002600
"dataDesc": "The JSON Data Used in the Current List",
2601-
"dataTooltip": "If You just Set a Number, This Field Will Be Regarded as Row Count, and the Data Will Be Regarded as Empty."
2601+
"dataTooltip": "If You just Set a Number, This Field Will Be Regarded as Row Count, and the Data Will Be Regarded as Empty.",
2602+
"enableSorting": "Allow Sorting"
26022603
},
26032604
"navigation": {
26042605
"addText": "Add Submenu Item",
@@ -3205,6 +3206,7 @@ export const en = {
32053206
"enterPassword": "Enter your password",
32063207
"selectAuthProvider": "Select Authentication Provider",
32073208
"selectWorkspace": "Select your workspace",
3209+
"userNotFound": "User not found. Please make sure you entered the correct email."
32083210
},
32093211
"preLoad": {
32103212
"jsLibraryHelpText": "Add JavaScript Libraries to Your Current Application via URL Addresses. lodash, day.js, uuid, numbro are Built into the System for Immediate Use. JavaScript Libraries are Loaded Before the Application is Initialized, Which Can Have an Impact on Application Performance.",

‎client/packages/lowcoder/src/pages/common/profileDropdown.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,13 @@ export default function ProfileDropdown(props: DropDownProps) {
150150
dispatch(profileSettingModalVisible(true));
151151
} else if (e.key === "logout") {
152152
// logout
153-
dispatch(logoutAction({}));
153+
const organizationId = localStorage.getItem('lowcoder_login_orgId');
154+
if (organizationId) {
155+
localStorage.removeItem('lowcoder_login_orgId');
156+
}
157+
dispatch(logoutAction({
158+
organizationId: organizationId || undefined,
159+
}));
154160
} else if (e.keyPath.includes("switchOrg")) {
155161
if (e.key === "newOrganization") {
156162
// create new organization

0 commit comments

Comments
 (0)
Please sign in to comment.