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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy