Skip to content

Commit 7869f0b

Browse files
committed
Merge branch 'dev' into filter_group_members_role
2 parents 8ff5f78 + eef90fe commit 7869f0b

File tree

25 files changed

+543
-116
lines changed

25 files changed

+543
-116
lines changed

client/packages/lowcoder-design/src/icons/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ export { ReactComponent as VideoCameraStreamCompIconSmall } from "./v2/camera-st
355355
export { ReactComponent as VideoScreenshareCompIconSmall } from "./v2/screen-share-stream-s.svg"; // new
356356
export { ReactComponent as SignatureCompIconSmall } from "./v2/signature-s.svg";
357357
export { ReactComponent as StepCompIconSmall } from "./v2/steps-s.svg";
358+
export { ReactComponent as TagsCompIconSmall } from "./v2/tags-s.svg"
358359

359360

360361
export { ReactComponent as CandlestickChartCompIconSmall } from "./v2/candlestick-chart-s.svg"; // new
@@ -468,6 +469,7 @@ export { ReactComponent as SignatureCompIcon } from "./v2/signature-m.svg";
468469
export { ReactComponent as GanttCompIcon } from "./v2/gantt-chart-m.svg";
469470
export { ReactComponent as KanbanCompIconSmall } from "./v2/kanban-s.svg";
470471
export { ReactComponent as KanbanCompIcon } from "./v2/kanban-m.svg";
472+
export { ReactComponent as TagsCompIcon } from "./v2/tags-l.svg";
471473

472474
export { ReactComponent as CandlestickChartCompIcon } from "./v2/candlestick-chart-m.svg";
473475
export { ReactComponent as FunnelChartCompIcon } from "./v2/funnel-chart-m.svg";
Lines changed: 10 additions & 0 deletions
Loading
Lines changed: 10 additions & 0 deletions
Loading

client/packages/lowcoder/src/api/datasourceApi.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ export class DatasourceApi extends Api {
187187
return Api.get(DatasourceApi.url + `/listByOrg?orgId=${orgId}`, {...res});
188188
}
189189

190+
static getDatasourceById(id: string): AxiosPromise<GenericApiResponse<Datasource>> {
191+
return Api.get(`${DatasourceApi.url}/${id}`);
192+
}
193+
190194
static createDatasource(
191195
datasourceConfig: Partial<Datasource>
192196
): AxiosPromise<GenericApiResponse<Datasource>> {

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

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -208,17 +208,13 @@ const FormBaseComp = (function () {
208208
);
209209
})
210210
.setPropertyViewFn((children) => {
211-
const editorContext = useContext(EditorContext);
212-
const isLogicMode = editorContext.editorModeStatus === "logic" || editorContext.editorModeStatus === "both";
213-
const isLayoutMode = editorContext.editorModeStatus === "layout" || editorContext.editorModeStatus === "both";
214-
215211
return (
216212
<>
217213
<Section name={sectionNames.basic}>
218214
{children.resetAfterSubmit.propertyView({ label: trans("formComp.resetAfterSubmit") })}
219215
</Section>
220216

221-
{isLogicMode && (
217+
{(useContext(EditorContext).editorModeStatus === "logic" || useContext(EditorContext).editorModeStatus === "both") && (
222218
<><Section name={sectionNames.interaction}>
223219
{children.onEvent.getPropertyView()}
224220
{disabledPropertyView(children)}
@@ -229,22 +225,22 @@ const FormBaseComp = (function () {
229225
</>
230226
)}
231227

232-
{isLayoutMode && (
228+
{(useContext(EditorContext).editorModeStatus === "layout" || useContext(EditorContext).editorModeStatus === "both") && (
233229
<>
234230
<Section name={sectionNames.layout}>
235231
{children.container.getPropertyView()}
236232
</Section>
237233
</>
238234
)}
239235

240-
{isLogicMode && (
236+
{(useContext(EditorContext).editorModeStatus === "logic" || useContext(EditorContext).editorModeStatus === "both") && (
241237
<Section name={sectionNames.advanced}>
242238
{children.initialData.propertyView({ label: trans("formComp.initialData") })}
243239
{children.invalidFormMessage.propertyView({ label: trans("formComp.invalidFormMessage") })}
244240
</Section>
245241
)}
246242

247-
{isLayoutMode && (
243+
{(useContext(EditorContext).editorModeStatus === "layout" || useContext(EditorContext).editorModeStatus === "both") && (
248244
<>
249245
<Section name={sectionNames.style}>
250246
{children.container.stylePropertyView()}
@@ -289,7 +285,8 @@ let FormTmpComp = class extends FormBaseComp implements IForm {
289285
}
290286
traverseFormItems(consumer: (item: GridItemComp) => boolean) {
291287
return traverseCompTree(this.getCompTree(), (item) => {
292-
return item.children.comp.children.formDataKey ? consumer(item as GridItemComp) : true;
288+
const hasFormDataKey = item.children.comp.children.hasOwnProperty("formDataKey");
289+
return hasFormDataKey ? consumer(item as GridItemComp) : true;
293290
});
294291
}
295292
validateFormItems() {
@@ -333,12 +330,19 @@ let FormTmpComp = class extends FormBaseComp implements IForm {
333330
// For the properties, first find in data, then initialData, subcomponent default value (resetValue), empty value (clearValue)
334331
const newData = { ...(initialData ?? this.children.initialData.getView()), ...data };
335332

333+
// Only proceed if we have data to set
334+
if (!Object.keys(newData).length) {
335+
return Promise.resolve();
336+
}
337+
336338
return this.runMethodOfItems(
337339
{
338340
name: "setValue",
339341
getParams: (t) => {
340342
// use component name when formDataKey is empty
341-
const key = t.children.comp.children.formDataKey?.getView() || t.children.name.getView();
343+
const formDataKey = t.children.comp.children.formDataKey?.getView();
344+
const componentName = t.children.name.getView();
345+
const key = formDataKey || componentName;
342346
const value = newData[key];
343347
return value !== undefined ? [value as EvalParamType] : undefined;
344348
},
@@ -347,7 +351,9 @@ let FormTmpComp = class extends FormBaseComp implements IForm {
347351
name: "setRange",
348352
getParams: (t) => {
349353
// use component name when formDataKey is empty
350-
const key = t.children.comp.children.formDataKey?.getView() || t.children.name.getView();
354+
const formDataKey = t.children.comp.children.formDataKey?.getView();
355+
const componentName = t.children.name.getView();
356+
const key = formDataKey || componentName;
351357
const value = newData[key] ? newData[key] : undefined;
352358
return value !== undefined ? [value as EvalParamType] : undefined;
353359
},
@@ -387,7 +393,8 @@ let FormTmpComp = class extends FormBaseComp implements IForm {
387393
case CompActionTypes.UPDATE_NODES_V2: {
388394
const ret = super.reduce(action);
389395
// When the initial value changes, update the form
390-
requestAnimationFrame(() => {
396+
if (action.value["initialData"] !== undefined) {
397+
queueMicrotask(() => {
391398
this.dispatch(
392399
customAction<SetDataAction>(
393400
{
@@ -398,6 +405,7 @@ let FormTmpComp = class extends FormBaseComp implements IForm {
398405
)
399406
);
400407
});
408+
}
401409
return ret;
402410
}
403411
case CompActionTypes.CUSTOM:
@@ -548,4 +556,4 @@ export function defaultFormData(compName: string, nameGenerator: NameGenerator):
548556
showFooter: true,
549557
},
550558
};
551-
}
559+
}

client/packages/lowcoder/src/comps/comps/selectInputComp/multiSelectComp.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ let MultiSelectBasicComp = (function () {
3030
padding: PaddingControl,
3131
};
3232
return new UICompBuilder(childrenMap, (props, dispatch) => {
33-
const valueSet = new Set<any>(props.options.map((o) => o.value)); // Filter illegal default values entered by the user
33+
const valueSet = new Set<any>((props.options as any[]).map((o: any) => o.value)); // Filter illegal default values entered by the user
3434
const [
3535
validateState,
3636
handleChange,

client/packages/lowcoder/src/comps/comps/selectInputComp/selectComp.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ let SelectBasicComp = (function () {
3939
const propsRef = useRef<RecordConstructorToView<typeof childrenMap>>(props);
4040
propsRef.current = props;
4141

42-
const valueSet = new Set<any>(props.options.map((o) => o.value)); // Filter illegal default values entered by the user
42+
const valueSet = new Set<any>((props.options as any[]).map((o: any) => o.value)); // Filter illegal default values entered by the user
4343

4444
return props.label({
4545
required: props.required,
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import {
2+
BoolCodeControl,
3+
ButtonEventHandlerControl,
4+
InputLikeStyle,
5+
NameConfig,
6+
Section,
7+
UICompBuilder,
8+
hiddenPropertyView,
9+
sectionNames,
10+
showDataLoadingIndicatorsPropertyView,
11+
styleControl,
12+
withExposingConfigs
13+
} from "@lowcoder-ee/index.sdk";
14+
import styled from "styled-components";
15+
import React, { useContext } from "react";
16+
import { trans } from "i18n";
17+
import { Tag } from "antd";
18+
import { EditorContext } from "comps/editorState";
19+
import { PresetStatusColorTypes } from "antd/es/_util/colors";
20+
import { hashToNum } from "util/stringUtils";
21+
import { TagsCompOptionsControl } from "comps/controls/optionsControl";
22+
import { useCompClickEventHandler } from "@lowcoder-ee/comps/utils/useCompClickEventHandler";
23+
24+
const colors = PresetStatusColorTypes;
25+
26+
// These functions are used for individual tag styling
27+
function getTagColor(tagText : any, tagOptions: any[]) {
28+
const foundOption = tagOptions.find((option: { label: any; }) => option.label === tagText);
29+
if (foundOption) {
30+
if (foundOption.colorType === "preset") {
31+
return foundOption.presetColor;
32+
} else if (foundOption.colorType === "custom") {
33+
return undefined;
34+
}
35+
return foundOption.color;
36+
}
37+
const index = Math.abs(hashToNum(tagText)) % colors.length;
38+
return colors[index];
39+
}
40+
41+
const getTagStyle = (tagText: any, tagOptions: any[], baseStyle: any = {}) => {
42+
const foundOption = tagOptions.find((option: { label: any; }) => option.label === tagText);
43+
if (foundOption) {
44+
const style: any = { ...baseStyle };
45+
46+
if (foundOption.colorType === "custom") {
47+
style.backgroundColor = foundOption.color;
48+
style.color = foundOption.textColor;
49+
style.border = `1px solid ${foundOption.color}`;
50+
}
51+
52+
if (foundOption.border) {
53+
style.borderColor = foundOption.border;
54+
if (!foundOption.colorType || foundOption.colorType !== "custom") {
55+
style.border = `1px solid ${foundOption.border}`;
56+
}
57+
}
58+
59+
if (foundOption.radius) {
60+
style.borderRadius = foundOption.radius;
61+
}
62+
63+
if (foundOption.margin) {
64+
style.margin = foundOption.margin;
65+
}
66+
67+
if (foundOption.padding) {
68+
style.padding = foundOption.padding;
69+
}
70+
71+
return style;
72+
}
73+
return baseStyle;
74+
};
75+
76+
function getTagIcon(tagText: any, tagOptions: any[]) {
77+
const foundOption = tagOptions.find(option => option.label === tagText);
78+
return foundOption ? foundOption.icon : undefined;
79+
}
80+
81+
const multiTags = (function () {
82+
83+
const StyledTag = styled(Tag)<{ $style: any, $bordered: boolean, $customStyle: any }>`
84+
display: flex;
85+
justify-content: center;
86+
align-items: center;
87+
width: 100%;
88+
background: ${(props) => props.$customStyle?.backgroundColor || props.$style?.background};
89+
color: ${(props) => props.$customStyle?.color || props.$style?.text};
90+
border-radius: ${(props) => props.$customStyle?.borderRadius || props.$style?.borderRadius};
91+
border: ${(props) => {
92+
if (props.$customStyle?.border) return props.$customStyle.border;
93+
return props.$bordered ? `${props.$style?.borderStyle} ${props.$style?.borderWidth} ${props.$style?.border}` : 'none';
94+
}};
95+
padding: ${(props) => props.$customStyle?.padding || props.$style?.padding};
96+
margin: ${(props) => props.$customStyle?.margin || props.$style?.margin};
97+
font-size: ${(props) => props.$style?.textSize};
98+
font-weight: ${(props) => props.$style?.fontWeight};
99+
cursor: pointer;
100+
`;
101+
102+
const StyledTagContainer = styled.div`
103+
display: flex;
104+
gap: 5px;
105+
padding: 5px;
106+
`;
107+
108+
const childrenMap = {
109+
options: TagsCompOptionsControl,
110+
style: styleControl(InputLikeStyle, 'style'),
111+
onEvent: ButtonEventHandlerControl,
112+
borderless: BoolCodeControl,
113+
enableIndividualStyling: BoolCodeControl,
114+
};
115+
116+
return new UICompBuilder(childrenMap, (props) => {
117+
const handleClickEvent = useCompClickEventHandler({onEvent: props.onEvent});
118+
119+
return (
120+
<StyledTagContainer>
121+
{props.options.map((tag, index) => {
122+
123+
// Use individual styling only if enableIndividualStyling is true
124+
const tagColor = props.enableIndividualStyling ? getTagColor(tag.label, props.options) : undefined;
125+
const tagIcon = props.enableIndividualStyling ? getTagIcon(tag.label, props.options) : tag.icon;
126+
const tagStyle = props.enableIndividualStyling ? getTagStyle(tag.label, props.options, props.style) : {};
127+
128+
return (
129+
<StyledTag
130+
key={`tag-${index}`}
131+
$style={props.style}
132+
$bordered={!props.borderless}
133+
$customStyle={tagStyle}
134+
icon={tagIcon}
135+
color={tagColor}
136+
onClick={() => handleClickEvent()}
137+
>
138+
{tag.label}
139+
</StyledTag>
140+
);
141+
})}
142+
</StyledTagContainer>
143+
);
144+
})
145+
.setPropertyViewFn((children: any) => {
146+
return (
147+
<>
148+
<Section name="Basic">
149+
{children.options.propertyView({})}
150+
</Section>
151+
152+
{["logic", "both"].includes(useContext(EditorContext).editorModeStatus) && (
153+
<Section name={sectionNames.interaction}>
154+
{children.onEvent.getPropertyView()}
155+
{hiddenPropertyView(children)}
156+
{showDataLoadingIndicatorsPropertyView(children)}
157+
</Section>
158+
)}
159+
160+
{["layout", "both"].includes(
161+
useContext(EditorContext).editorModeStatus
162+
) && (
163+
<Section name={sectionNames.style}>
164+
{children.enableIndividualStyling.propertyView({
165+
label: trans("style.individualStyling"),
166+
tooltip: trans("style.individualStylingTooltip")
167+
})}
168+
{children.borderless.propertyView({ label: trans("style.borderless") })}
169+
{children.style.getPropertyView()}
170+
</Section>
171+
)}
172+
</>
173+
)
174+
})
175+
.build();
176+
})()
177+
178+
export const MultiTagsComp = withExposingConfigs(multiTags, [new NameConfig("options", "")]);
179+

client/packages/lowcoder/src/comps/comps/textInputComp/textInputConstants.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ export const useTextInputProps = (props: RecordConstructorToView<typeof textInpu
183183
props.value.onChange(defaultValue)
184184
}, [defaultValue]);
185185

186+
useEffect(() => {
187+
if (!changeRef.current) {
188+
setLocalInputValue(inputValue);
189+
}
190+
}, [inputValue]);
191+
186192
useEffect(() => {
187193
if (!changeRef.current) return;
188194

@@ -214,6 +220,7 @@ export const useTextInputProps = (props: RecordConstructorToView<typeof textInpu
214220
debounce(function (value: string, valueCtx: any) {
215221
propsRef.current.value.onChange(value);
216222
propsRef.current.onEvent("change");
223+
changeRef.current = false; // Reset after commit
217224
}, 1000)
218225
);
219226

0 commit comments

Comments
 (0)