Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d137a8c

Browse files
authoredApr 21, 2024
Merge branch 'dev' into fix/css-issues
2 parents c3f9c34 + f6bb40f commit d137a8c

File tree

27 files changed

+1297
-260
lines changed

27 files changed

+1297
-260
lines changed
 

‎client/packages/lowcoder-core/lib/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// <reference types="react" />
2-
import { ReactNode } from 'react';
2+
import React, { ReactNode } from 'react';
33

44
type EvalMethods = Record<string, Record<string, Function>>;
55
type CodeType = undefined | "JSON" | "Function" | "PureJSON";
@@ -613,6 +613,7 @@ declare abstract class MultiBaseComp<ChildrenType extends Record<string, Comp<un
613613
toJsonValue(): DataType;
614614
autoHeight(): boolean;
615615
changeChildAction(childName: string & keyof ChildrenType, value: ConstructorToDataType<new (...params: any) => ChildrenType[typeof childName]>): CompAction<JSONValue>;
616+
getRef(): React.RefObject<HTMLDivElement>;
616617
}
617618
declare function mergeExtra(e1: ExtraNodeType | undefined, e2: ExtraNodeType): ExtraNodeType;
618619

‎client/packages/lowcoder-design/src/icons/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export { ReactComponent as TextCompIcon } from "./icon-text-display.svg";
106106
export { ReactComponent as SwitchCompIcon } from "./icon-switch.svg";
107107
export { ReactComponent as TableCompIcon } from "./icon-table-comp.svg";
108108
export { ReactComponent as SelectCompIcon } from "./icon-insert-select.svg";
109+
export { ReactComponent as IconModal } from "./icon-modal.svg";
109110
export { ReactComponent as CheckboxCompIcon } from "./icon-checkboxes.svg";
110111
export { ReactComponent as RadioCompIcon } from "./icon-radio.svg";
111112
export { ReactComponent as TimeCompIcon } from "./icon-time.svg";

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ const StyledDivider = styled(Divider) <IProps>`
4646
}};
4747
padding: ${(props) => props.$style.padding};
4848
49-
border-top: ${(props) => (props.$style.borderWidth ? props.$style.borderWidth : "1px")} ${(props) => (props.dashed ? "dashed" : "solid")} ${(props) => props.$style.color};
50-
49+
border-top: ${(props) => (props.$style.borderWidth && props.$style.borderWidth != "0px" ? props.$style.borderWidth : "1px")} ${(props) => (props.dashed ? "dashed" : "solid")} ${(props) => props.$style.border};
50+
""
5151
.ant-divider-inner-text::before, .ant-divider-inner-text::after {
52-
border-block-start: ${(props) => (props.$style.borderWidth ? props.$style.borderWidth : "1px")} ${(props) => (props.dashed ? "dashed" : "solid")} ${(props) => props.$style.color} !important;
52+
border-block-start: ${(props) => (props.$style.borderWidth && props.$style.borderWidth != "0px" ? props.$style.borderWidth : "1px")} ${(props) => (props.dashed ? "dashed" : "solid")} ${(props) => props.$style.border} !important;
5353
border-block-start-color: inherit;
5454
border-block-end: 0;
5555
}
@@ -81,6 +81,8 @@ function fixOldStyleData(oldData: any) {
8181
return oldData;
8282
}
8383

84+
85+
8486
// Compatible with historical style data 2022-8-26
8587
export const DividerComp = migrateOldData(
8688
new UICompBuilder(childrenMap, (props) => {

‎client/packages/lowcoder/src/comps/comps/selectInputComp/stepControl.tsx

Lines changed: 92 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ import { BoolCodeControl } from "comps/controls/codeControl";
33
import { BoolControl } from "comps/controls/boolControl";
44
import { stringExposingStateControl, numberExposingStateControl } from "comps/controls/codeStateControl";
55
import { ChangeEventHandlerControl } from "comps/controls/eventHandlerControl";
6-
import { LabelControl } from "comps/controls/labelControl";
76
import { StepOptionControl } from "comps/controls/optionsControl";
87
import { styleControl } from "comps/controls/styleControl";
9-
import { StepsStyle, StepsStyleType } from "comps/controls/styleControlConstants";
8+
import { StepsStyle, StepsStyleType, heightCalculator, widthCalculator, marginCalculator } from "comps/controls/styleControlConstants";
109
import styled, { css } from "styled-components";
1110
import { UICompBuilder } from "../../generators";
1211
import { CommonNameConfig, NameConfig, withExposingConfigs } from "../../generators/withExposing";
@@ -17,7 +16,6 @@ import { trans } from "i18n";
1716
import { hasIcon } from "comps/utils";
1817
import { RefControl } from "comps/controls/refControl";
1918
import { dropdownControl } from "comps/controls/dropdownControl";
20-
2119
import { useContext, useState, useEffect } from "react";
2220
import { EditorContext } from "comps/editorState";
2321

@@ -79,15 +77,16 @@ const statusOptions = [
7977
]
8078

8179
const StepsChildrenMap = {
82-
initialValue: numberExposingStateControl("0"),
80+
initialValue: numberExposingStateControl("1"),
8381
value: stringExposingStateControl("value"),
84-
stepsStatus : stringExposingStateControl("process"),
82+
stepStatus : stringExposingStateControl("process"),
83+
stepPercent: numberExposingStateControl("60"),
8584
size: dropdownControl(sizeOptions, "default"),
8685
displayType : dropdownControl(typeOptions, "default"),
8786
direction: dropdownControl(directionOptions, "horizontal"),
8887
showDots : BoolControl,
8988
showIcons : BoolControl,
90-
label: LabelControl,
89+
selectable : BoolControl,
9190
labelPlacement: dropdownControl(directionOptions, "horizontal"),
9291
disabled: BoolCodeControl,
9392
onEvent: ChangeEventHandlerControl,
@@ -99,56 +98,81 @@ const StepsChildrenMap = {
9998
let StepControlBasicComp = (function () {
10099
return new UICompBuilder(StepsChildrenMap, (props) => {
101100

102-
// enabling user interaction to change the current step
103-
const [current, setCurrent] = useState(0);
101+
const StyledWrapper = styled.div<{ style: StepsStyleType }>`
102+
min-height: 24px;
103+
max-width: ${widthCalculator(props.style.margin)};
104+
max-height: ${heightCalculator(props.style.margin)};
105+
display: flex;
106+
flex-direction: column;
107+
justify-content: center;
108+
align-items: center;
109+
text-decoration: ${props.style.textDecoration};
110+
font-style: ${props.style.fontStyle};
111+
font-weight: ${props.style.textWeight};
112+
font-size: ${props.style.textSize};
113+
text-transform: ${props.style.textTransform};
114+
margin: ${props.style.margin};
115+
padding: ${props.style.padding};
116+
background-color: ${props.style.background};
117+
border: ${props.style.borderWidth} solid ${props.style.border};
118+
border-radius: ${props.style.radius};
119+
background-image: ${props.style.backgroundImage};
120+
background-repeat: ${props.style.backgroundImageRepeat};
121+
background-size: ${props.style.backgroundImageSize};
122+
background-position: ${props.style.backgroundImagePosition};
123+
background-origin: ${props.style.backgroundImageOrigin};
124+
.ant-steps-item { padding-top: 5px !important; }
125+
.ant-steps.ant-steps-label-vertical.ant-steps-small .ant-steps-item-icon { margin-top: 17px !important; }
126+
.ant-steps.ant-steps-label-vertical.ant-steps-default .ant-steps-item-icon { margin-top: 12px !important; }
127+
.ant-steps.ant-steps-dot .ant-steps-item-process .ant-steps-icon .ant-steps-icon-dot { margin-top: 4px !important; }
128+
.ant-steps.ant-steps-default .ant-steps-item-icon .ant-steps-icon-dot { margin-top: 9px !important; }
129+
.ant-steps.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot { margin-top: 4px !important; }
130+
.ant-steps.ant-steps-dot .ant-steps-item-title { margin-top: 10px !important; }
131+
.ant-steps.ant-steps-default.ant-steps-with-progress.ant-steps-label-horizontal .ant-steps-item.ant-steps-item-custom div.ant-steps-item-icon { margin-top:4px !important; }
132+
.ant-steps.ant-steps-default.ant-steps-with-progress.ant-steps-label-vertical .ant-steps-item.ant-steps-item-custom div.ant-steps-item-icon { margin-top:17px !important; }
133+
.ant-steps.ant-steps-dot.ant-steps-small.ant-steps-with-progress .ant-steps-item-icon .ant-progress { inset-block-start: -8px !important; inset-inline-start: -11px !important; }
134+
.ant-steps.ant-steps-dot.ant-steps-default.ant-steps-with-progress .ant-steps-item-icon .ant-progress { inset-block-start: -7px !important; inset-inline-start: -13px !important; }
135+
.ant-steps.ant-steps-small.ant-steps-with-progress .ant-steps-item:not(.ant-steps-item-custom) .ant-progress { inset-block-start: -5px !important; inset-inline-start: -5px !important; }
136+
.ant-steps.ant-steps-default.ant-steps-with-progress .ant-steps-item:not(.ant-steps-item-custom) .ant-progress { inset-block-start: -5px !important; inset-inline-start: -5px !important; }
137+
.ant-steps.ant-steps-small.ant-steps-with-progress .ant-steps-item.ant-steps-item-custom .ant-progress { inset-block-start: -5px !important; inset-inline-start: -10px !important; }
138+
.ant-steps.ant-steps-default.ant-steps-with-progress .ant-steps-item.ant-steps-item-custom .ant-progress { inset-block-start: -4px !important; inset-inline-start: -13px !important; }
139+
`;
140+
141+
const [current, setCurrent] = useState(props.initialValue.value - 1); // Convert 1-based index to 0-based.
104142

105-
// updating the state of current by the expose value
106143
useEffect(() => {
107-
setCurrent(Number(props.value.value));
144+
const newValue = Number(props.value.value);
145+
setCurrent(newValue - 1); // Adjust for 0-based index.
108146
}, [props.value.value]);
109147

110-
111-
const onChange = (current: number) => {
112-
setCurrent(current);
113-
if (props.options[current]?.value !== undefined) {
114-
props.value.onChange(props.options[current].value+"");
148+
const onChange = (index: number) => {
149+
if (props.selectable == false) return;
150+
const newIndex = Math.max(0, index);
151+
setCurrent(newIndex);
152+
if (props.options[newIndex]?.value !== undefined) {
153+
props.value.onChange(newIndex + 1 + ""); // Convert back to 1-based index for display.
115154
props.onEvent("change");
116155
}
117156
};
118157

119-
// margin-top: 17px; is important cause the dots where placed wrong.
120-
/*
121-
.ant-steps.ant-steps-small .ant-steps-item-icon {
122-
margin-top: 17px;
123-
}
124-
*/
125-
const StepsWrapper = styled.div`
126-
width: 100%;
127-
min-height: 24px;
128-
129-
`;
130-
131-
return props.label({
132-
children: (
133-
<StepsWrapper ref={props.viewRef}>
134-
<ConfigProvider
135-
theme={{
136-
components: {
137-
Steps: {
138-
colorPrimary: '#00b96b',
139-
algorithm: true,
140-
}
141-
},
142-
}}
143-
>
158+
return (
159+
<ConfigProvider
160+
theme={{
161+
token: {
162+
colorPrimary: props.style.activeBackground,
163+
colorText: props.style.titleText,
164+
colorTextDescription: props.style.text,
165+
fontFamily: props.style.fontFamily,
166+
}
167+
}}
168+
>
169+
<StyledWrapper style={props.style}>
144170
<Steps
145-
initial={Number(props.initialValue.value) - 1}
171+
initial={props.initialValue.value -1}
146172
current={current}
147-
onChange={(current) => {
148-
onChange(current);
149-
}}
150-
percent={60}
151-
status={props.stepsStatus.value as "error" | "finish" | "process" | "wait"}
173+
onChange={onChange}
174+
percent={props.stepPercent.value}
175+
status={props.stepStatus.value as "error" | "finish" | "process" | "wait"}
152176
type={props.displayType}
153177
size={props.size}
154178
labelPlacement={props.labelPlacement}
@@ -166,16 +190,16 @@ let StepControlBasicComp = (function () {
166190
/>
167191
))}
168192
</Steps>
169-
</ConfigProvider>
170-
</StepsWrapper>
171-
),
172-
});
193+
</StyledWrapper>
194+
</ConfigProvider>
195+
);
196+
173197
})
174198
.setPropertyViewFn((children) => (
175199
<>
176200
<Section name={sectionNames.basic}>
177201
{children.options.propertyView({})}
178-
{children.initialValue.propertyView({ label: trans("step.initialValue") })}
202+
{children.initialValue.propertyView({ label: trans("step.initialValue"), tooltip : trans("step.initialValueTooltip")})}
179203
</Section>
180204

181205
{["logic", "both"].includes(useContext(EditorContext).editorModeStatus) && (
@@ -184,14 +208,12 @@ let StepControlBasicComp = (function () {
184208
{children.onEvent.getPropertyView()}
185209
{disabledPropertyView(children)}
186210
{hiddenPropertyView(children)}
187-
{children.stepsStatus.propertyView({label: trans("step.status")})}
211+
{children.stepStatus.propertyView({label: trans("step.status")})}
212+
{children.stepPercent.propertyView({label: trans("step.percent")})}
213+
{children.selectable.propertyView({label: trans("step.selectable")})}
188214
</Section></>
189215
)}
190216

191-
{["layout", "both"].includes(useContext(EditorContext).editorModeStatus) && (
192-
children.label.getPropertyView()
193-
)}
194-
195217
{["layout", "both"].includes(useContext(EditorContext).editorModeStatus) && (
196218
<Section name={sectionNames.layout}>
197219
{children.size.propertyView({
@@ -206,12 +228,18 @@ let StepControlBasicComp = (function () {
206228
label: trans("step.direction"),
207229
radioButton: true,
208230
})}
209-
{children.labelPlacement.propertyView({
210-
label: trans("step.labelPlacement"),
211-
radioButton: true,
212-
})}
213-
{children.showDots.propertyView({label: trans("step.showDots")})}
214-
{children.showIcons.propertyView({label: trans("step.showIcons")})}
231+
{ children.direction.getView() == "horizontal" &&
232+
children.labelPlacement.propertyView({
233+
label: trans("step.labelPlacement"),
234+
radioButton: true,
235+
})
236+
}
237+
{ children.displayType.getView() != "inline" && !children.showIcons.getView() && (
238+
children.showDots.propertyView({label: trans("step.showDots")}
239+
))}
240+
{ children.displayType.getView() != "inline" && !children.showDots.getView() && (
241+
children.showIcons.propertyView({label: trans("step.showIcons")}
242+
))}
215243
</Section>
216244
)}
217245

@@ -228,5 +256,7 @@ let StepControlBasicComp = (function () {
228256

229257
export const StepComp = withExposingConfigs(StepControlBasicComp, [
230258
new NameConfig("value", trans("step.valueDesc")),
259+
new NameConfig("stepStatus", trans("step.status") ),
260+
new NameConfig("stepPercent", trans("step.percent")),
231261
...CommonNameConfig,
232262
]);
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { trans } from "i18n";
2+
import {
3+
CommonNameConfig,
4+
MultiBaseComp,
5+
NameConfig,
6+
stringExposingStateControl,
7+
UICompBuilder,
8+
withExposingConfigs,
9+
withMethodExposing
10+
} from "lowcoder-sdk";
11+
import { TourChildrenMap, TourPropertyView } from "./tourPropertyView";
12+
import { Tour, TourProps } from "antd";
13+
import React, { useContext } from "react";
14+
import { EditorContext } from "@lowcoder-ee/comps/editorState";
15+
import { GridItemComp } from "@lowcoder-ee/comps/comps/gridItemComp";
16+
import { HookComp } from "@lowcoder-ee/comps/hooks/hookComp";
17+
import { TemporaryStateItemComp } from "@lowcoder-ee/comps/comps/temporaryStateComp";
18+
19+
/**
20+
* This component builds the Property Panel and the fake 'UI' for the Tour component
21+
*/
22+
let TourBasicComp = (function() {
23+
const childrenMap = {
24+
...TourChildrenMap,
25+
defaultValue: stringExposingStateControl("defaultValue"),
26+
value: stringExposingStateControl("value")
27+
// style: styleControl(SelectStyle),
28+
};
29+
return new UICompBuilder(childrenMap, (props, dispatch) => {
30+
const editorState = useContext(EditorContext);
31+
const compMap: (GridItemComp | HookComp | InstanceType<typeof TemporaryStateItemComp>)[] = Object.values(editorState.getAllUICompMap());
32+
33+
const steps: TourProps["steps"] = props.options.map((step) => {
34+
const targetName = step.target;
35+
let target = undefined;
36+
const compListItem = compMap.find((compItem) => compItem.children.name.getView() === targetName);
37+
if (compListItem) {
38+
console.log(`setting selected comp to ${compListItem}`);
39+
try {
40+
target = ((compListItem as MultiBaseComp).children.comp as GridItemComp).getRef?.();
41+
} catch (e) {
42+
target = ((compListItem as MultiBaseComp).children.comp as HookComp).getRef?.();
43+
}
44+
}
45+
46+
return {
47+
/**
48+
* I'm pretty sure it's safe to use dangerouslySetInnerHTML here as any creator of an app
49+
* will have unrestricted access to the data of any user anyway. E.g. have a button that
50+
* just sends the current cookies wherever, thus the developer of the app must be trusted
51+
* in all cases
52+
* This even applies to things like <b onmouseover="alert('mouseover');">, because the
53+
* app creator might desire functionality like this.
54+
*/
55+
title: (<div dangerouslySetInnerHTML={{ __html: step.title }} />),
56+
description: (<div dangerouslySetInnerHTML={{ __html: step.description }} />),
57+
target: target?.current,
58+
arrow: step.arrow,
59+
placement: step.placement === "" ? undefined : step.placement,
60+
mask: step.mask,
61+
cover: step.cover ? (<img src={step.cover} />) : undefined,
62+
type: step.type === "" ? undefined : step.type,
63+
};
64+
});
65+
66+
return (
67+
<Tour
68+
steps={steps}
69+
open={props.open.value}
70+
onClose={() => props.open.onChange(false)}
71+
// indicatorsRender={(current, total) => props.indicatorsRender(current, total)} // todo enable later
72+
disabledInteraction={props.disabledInteraction}
73+
arrow={props.arrow}
74+
placement={props.placement === "" ? undefined : props.placement}
75+
type={props.type === "" ? undefined : props.type}
76+
mask={props.mask}
77+
/>
78+
);
79+
})
80+
.setPropertyViewFn((children) => <TourPropertyView {...children} />)
81+
.build();
82+
})();
83+
84+
export const TourComp = withMethodExposing(TourBasicComp, [
85+
{
86+
method: {
87+
name: "startTour",
88+
description: "Triggers the tour to start",
89+
params: []
90+
},
91+
execute: (comp, values) => {
92+
comp.children.open.getView().onChange(true);
93+
}
94+
}
95+
]);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export type PlacementType = 'left' | 'leftTop' | 'leftBottom' | 'right' | 'rightTop' | 'rightBottom' | 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight' | 'center' | '';
2+
export type TourStepType = 'default' | 'primary' | '';
3+
4+
export const PlacementOptions: {label: string, value: PlacementType}[] = [
5+
{ label: "​", value: ""},
6+
{ label: "Center", value: "center"},
7+
{ label: "Left", value: "left"},
8+
{ label: "Left Top", value: "leftTop"},
9+
{ label: "Left Bottom", value: "leftBottom"},
10+
{ label: "Right", value: "right"},
11+
{ label: "Right Top", value: "rightTop"},
12+
{ label: "Right Bottom", value: "rightBottom"},
13+
{ label: "Top", value: "top"},
14+
{ label: "Top Left", value: "topLeft"},
15+
{ label: "Top Right", value: "topRight"},
16+
{ label: "Bottom", value: "bottom"},
17+
{ label: "Bottom Left", value: "bottomLeft"},
18+
{ label: "Bottom Right", value: "bottomRight"},
19+
];
20+
21+
export const TypeOptions: {label: string, value: TourStepType}[] = [
22+
{ label: "​", value: ""},
23+
{ label: "Default", value: "default"},
24+
{ label: "Primary", value: "primary"},
25+
];
26+
27+
export {};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { RecordConstructorToComp } from "lowcoder-core";
2+
import { BoolControl } from "../../controls/boolControl";
3+
import { ArrowControl, BoolCodeControl, MaskControl } from "../../controls/codeControl";
4+
import { Section } from "lowcoder-design";
5+
import { TourStepControl } from "@lowcoder-ee/comps/controls/tourStepControl";
6+
import { booleanExposingStateControl, dropdownControl } from "lowcoder-sdk";
7+
import { trans } from "i18n";
8+
import { PlacementOptions, TypeOptions } from "@lowcoder-ee/comps/comps/tourComp/tourControlConstants";
9+
import {
10+
TourArrowTooltip,
11+
TourMaskTooltip,
12+
TourPlacementTooltip
13+
} from "@lowcoder-ee/comps/comps/tourComp/tourTooltips";
14+
15+
export const TourChildrenMap = {
16+
open: booleanExposingStateControl("open"),
17+
options: TourStepControl,
18+
// indicatorsRender: AlkjdfControl, // todo get this working later
19+
disabledInteraction: BoolControl,
20+
mask: MaskControl,
21+
placement: dropdownControl(PlacementOptions, "bottom"),
22+
arrow: ArrowControl,
23+
type: dropdownControl(TypeOptions, "default"),
24+
};
25+
26+
export const TourPropertyView = (
27+
children: RecordConstructorToComp<
28+
typeof TourChildrenMap & {
29+
hidden: typeof BoolCodeControl;
30+
}
31+
> //& {
32+
// style: { getPropertyView: () => ControlNode };
33+
// }
34+
) => (
35+
<>
36+
<Section name={trans("tour.section1Title")}>
37+
{children.options.propertyView({})}
38+
</Section>
39+
40+
<Section name="customization">
41+
{/*{children.indicatorsRender.propertyView({*/}
42+
{/* label: trans("tour.indicatorsRender.label"),*/}
43+
{/* tooltip: IndicatorsRenderTooltip,*/}
44+
{/*})}*/}
45+
{children.disabledInteraction.propertyView({
46+
label: trans("tour.disabledInteraction.label"),
47+
tooltip: trans("tour.disabledInteraction.tooltip")
48+
})}
49+
{children.mask.propertyView({
50+
label: trans("tour.mask.label"),
51+
tooltip: TourMaskTooltip,
52+
})}
53+
{children.placement.propertyView({
54+
label: trans("tour.placement.label"),
55+
tooltip: TourPlacementTooltip,
56+
radioButton: false
57+
})}
58+
{children.arrow.propertyView({
59+
label: trans("tour.arrow.label"),
60+
tooltip: TourArrowTooltip,
61+
})}
62+
{children.type.propertyView({
63+
label: trans("tour.type.label"),
64+
tooltip: trans("tour.type.tooltip")
65+
})}
66+
</Section>
67+
68+
{/*{["layout", "both"].includes(*/}
69+
{/* useContext(EditorContext).editorModeStatus*/}
70+
{/*) && (*/}
71+
{/* <Section name={sectionNames.style}>*/}
72+
{/* {children.style.getPropertyView()}*/}
73+
{/* </Section>*/}
74+
{/*)}*/}
75+
</>
76+
);
77+
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { trans } from "@lowcoder-ee/i18n";
2+
3+
const indicatorsRenderExample = `(current, total) => (
4+
<span>
5+
{current + 1} / {total}
6+
</span>
7+
)`;
8+
export const IndicatorsRenderTooltip = (
9+
<div>
10+
{trans("tour.indicatorsRender.tooltip")}
11+
<br />
12+
<br />
13+
{trans("tour.indicatorsRender.tooltipValidTypes")}
14+
<br />
15+
<br />
16+
<h4>{trans("tour.tooltipSignatureHeader")}</h4>
17+
<code>
18+
{trans("tour.indicatorsRender.tooltipFunctionSignature")}
19+
</code>
20+
<br />
21+
<br />
22+
<h4>{trans("tour.tooltipExampleHeader")}</h4>
23+
<code>
24+
{indicatorsRenderExample}
25+
</code>
26+
</div>
27+
);
28+
29+
let styleExample = {
30+
"style": { "boxShadow": "inset 0 0 15px #fff" },
31+
"color": "rgba(40, 0, 255, .4)"
32+
};
33+
34+
export const TourStepMaskTooltip = (
35+
<div>
36+
{trans("tour.options.mask.tooltip")}
37+
<br />
38+
<br />
39+
{trans("tour.options.mask.tooltipValidTypes")}
40+
<br />
41+
<br />
42+
<h3>Example:</h3>
43+
<code>
44+
{JSON.stringify(styleExample, null, 1)}
45+
</code>
46+
</div>
47+
);
48+
49+
export const TourMaskTooltip = (
50+
<div>
51+
{trans("tour.mask.tooltip")}
52+
<br />
53+
<br />
54+
{trans("tour.mask.tooltipValidTypes")}
55+
<br />
56+
<br />
57+
<h4>Example:</h4>
58+
<code>
59+
{JSON.stringify(styleExample, null, 1)}
60+
</code>
61+
</div>
62+
);
63+
64+
export const TourPlacementTooltip = (
65+
<div>
66+
{trans("tour.placement.tooltip")}
67+
<br />
68+
<br />
69+
<h4>{trans("tour.placement.tooltipValidOptions")}</h4>
70+
<h5>{trans("tour.placement.tooltipValidOptionsAbove")}</h5>
71+
<ul>
72+
<li><code>topLeft</code></li>
73+
<li><code>top</code></li>
74+
<li><code>topRight</code></li>
75+
</ul>
76+
<h5>{trans("tour.placement.tooltipValidOptionsLeft")}</h5>
77+
<ul>
78+
<li><code>leftTop</code></li>
79+
<li><code>left</code></li>
80+
<li><code>leftBottom</code></li>
81+
</ul>
82+
<h5>{trans("tour.placement.tooltipValidOptionsRight")}</h5>
83+
<ul>
84+
<li><code>rightTop</code></li>
85+
<li><code>right</code></li>
86+
<li><code>rightBottom</code></li>
87+
</ul>
88+
<h5>{trans("tour.placement.tooltipValidOptionsBelow")}</h5>
89+
<ul>
90+
<li><code>bottomLeft</code></li>
91+
<li><code>bottom</code></li>
92+
<li><code>bottomRight</code></li>
93+
</ul>
94+
<h5>{trans("tour.placement.tooltipValidOptionsOnTop")}</h5>
95+
<ul>
96+
<li>center</li>
97+
</ul>
98+
</div>
99+
);
100+
101+
const arrowTooltipSignature = `boolean | { pointAtCenter: boolean }`;
102+
export const TourStepArrowTooltip = (
103+
<div>
104+
{trans("tour.options.arrow.tooltip")}
105+
<br />
106+
<br />
107+
<h4>{trans("tour.tooltipSignatureHeader")}</h4>
108+
<code>{arrowTooltipSignature}</code>
109+
</div>
110+
);
111+
export const TourArrowTooltip = (
112+
<div>
113+
{trans("tour.arrow.tooltip")}
114+
<br />
115+
<br />
116+
<h4>{trans("tour.tooltipSignatureHeader")}</h4>
117+
<code>{arrowTooltipSignature}</code>
118+
</div>
119+
);
120+
121+
export {};

‎client/packages/lowcoder/src/comps/controls/actionSelector/executeCompAction.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { customAction, routeByNameAction } from "lowcoder-core";
2-
import { CompParams, ConstructorToDataType } from "lowcoder-core";
1+
import { CompParams, ConstructorToDataType, customAction, routeByNameAction } from "lowcoder-core";
32
import { GridItemComp } from "comps/comps/gridItemComp";
43
import { SimpleNameComp } from "comps/comps/simpleNameComp";
54
import { TemporaryStateItemComp } from "comps/comps/temporaryStateComp";

‎client/packages/lowcoder/src/comps/controls/codeControl.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ import {
3030
toHex,
3131
wrapperToControlItem,
3232
} from "lowcoder-design";
33-
import { lazy, ReactNode, Suspense } from "react";
33+
import { CSSProperties, lazy, ReactNode, Suspense } from "react";
3434
import {
3535
showTransform,
3636
toArrayJSONObject,
37-
toBoolean,
37+
toBoolean, toBooleanOrCss, toBooleanOrJsonObject,
3838
toJSONArray,
3939
toJSONObject,
4040
toJSONObjectArray,
@@ -318,6 +318,8 @@ export type CodeControlJSONType = ReturnType<typeof tmpFuncForJson>;
318318
export const StringControl = codeControl<string>(toString);
319319
export const NumberControl = codeControl<number>(toNumber);
320320
export const StringOrNumberControl = codeControl<string | number>(toStringOrNumber);
321+
export const MaskControl = codeControl<boolean | { style?: CSSProperties | undefined; color?: string | undefined; } | undefined>(toBooleanOrCss);
322+
export const ArrowControl = codeControl<boolean | { pointAtCenter: boolean } | undefined>(toBooleanOrJsonObject);
321323

322324
// rangeCheck, don't support Infinity temporarily
323325
export class RangeControl {
@@ -506,6 +508,16 @@ export const FunctionControl = codeControl<CodeFunction>(
506508
{ codeType: "Function", evalWithMethods: true }
507509
);
508510

511+
// export const AlkjdfControl = codeControl<(current: number, total: number)=>ReactNode>(
512+
// (value) => {
513+
// if (typeof value === "function") {
514+
// return value;
515+
// }
516+
// return (current,total) => (value as (current: number, total: number)=>ReactNode)(current,total);
517+
// },
518+
// { codeType: "Function", evalWithMethods: true }
519+
// );
520+
509521
export const TransformerCodeControl = codeControl<JSONValue>(
510522
(value) => {
511523
if (typeof value === "function") {

‎client/packages/lowcoder/src/comps/controls/optionsControl.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ StepOption = class extends StepOption implements OptionCompProperty {
651651
propertyView(param: { autoMap?: boolean }) {
652652
return (
653653
<>
654-
{this.children.value.propertyView({ label: trans("stepOptionsControl.value") })}
654+
{this.children.value.propertyView({ label: trans("stepOptionsControl.value"), tooltip: trans("stepOptionsControl.valueTooltip") })}
655655
{this.children.label.propertyView({ label: trans("stepOptionsControl.title") })}
656656
{this.children.subTitle.propertyView({ label: trans("stepOptionsControl.subTitle") })}
657657
{this.children.description.propertyView({ label: trans("stepOptionsControl.description") })}

‎client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -978,26 +978,46 @@ export const SegmentStyle = [
978978
] as const;
979979

980980
export const StepsStyle = [
981-
LABEL,
982-
...STYLING_FIELDS_SEQUENCE.filter((style) => ['border', 'borderWidth'].includes(style.name) === false),
983981
{
984-
name: "indicatorBackground",
985-
label: trans("style.indicatorBackground"),
986-
color: SURFACE_COLOR,
987-
},
988-
{
989-
name: "background",
990-
label: trans("style.background"),
991-
depName: "indicatorBackground",
982+
name: "activeBackground",
983+
label: trans("style.accent"),
984+
depName: "activeBackground",
992985
transformer: handleToSegmentBackground,
993986
},
994987
{
995-
name: "text",
996-
label: trans("text"),
997-
depName: "indicatorBackground",
988+
name: "titleText",
989+
label: trans("title"),
990+
depName: "background",
998991
depType: DEP_TYPE.CONTRAST_TEXT,
999992
transformer: contrastText,
1000993
},
994+
...STYLING_FIELDS_SEQUENCE.filter((style) => ['background', 'textSize','textDecoration'].includes(style.name) === false),
995+
getBackground(),
996+
{
997+
name: "backgroundImage",
998+
label: trans("style.backgroundImage"),
999+
backgroundImage: "backgroundImage",
1000+
},
1001+
{
1002+
name: "backgroundImageRepeat",
1003+
label: trans("style.backgroundImageRepeat"),
1004+
backgroundImageRepeat: "backgroundImageRepeat",
1005+
},
1006+
{
1007+
name: "backgroundImageSize",
1008+
label: trans("style.backgroundImageSize"),
1009+
backgroundImageSize: "backgroundImageSize",
1010+
},
1011+
{
1012+
name: "backgroundImagePosition",
1013+
label: trans("style.backgroundImagePosition"),
1014+
backgroundImagePosition: "backgroundImagePosition",
1015+
},
1016+
{
1017+
name: "backgroundImageOrigin",
1018+
label: trans("style.backgroundImageOrigin"),
1019+
backgroundImageOrigin: "backgroundImageOrigin",
1020+
},
10011021
] as const;
10021022

10031023
const LinkTextStyle = [
@@ -1166,11 +1186,11 @@ export const LinkStyle = [
11661186
] as const;
11671187

11681188
export const DividerStyle = [
1169-
{
1189+
/* {
11701190
name: "color",
11711191
label: trans("color"),
1172-
color: lightenColor(SECOND_SURFACE_COLOR, 0.05),
1173-
},
1192+
color: darkenColor(SECOND_SURFACE_COLOR, 0.1),
1193+
}, */
11741194
...STYLING_FIELDS_SEQUENCE.map((style) => {
11751195
if (style.name === 'text') {
11761196
return {

‎client/packages/lowcoder/src/comps/controls/tourStepControl.tsx

Lines changed: 427 additions & 0 deletions
Large diffs are not rendered by default.

‎client/packages/lowcoder/src/comps/generators/uiCompBuilder.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BoolCodeControl } from "comps/controls/codeControl";
2-
import React, { ReactNode, useContext } from "react";
2+
import React, { ReactNode, useContext, useRef } from "react";
33
import { ExternalEditorContext } from "util/context/ExternalEditorContext";
44
import { Comp, CompParams, MultiBaseComp } from "lowcoder-core";
55
import {
@@ -121,6 +121,8 @@ export class UICompBuilder<
121121
ToDataType<NewChildren<ChildrenCompMap>>,
122122
ToNodeType<NewChildren<ChildrenCompMap>>
123123
> {
124+
ref: React.RefObject<HTMLDivElement> = React.createRef();
125+
124126
override parseChildrenFromValue(
125127
params: CompParams<ToDataType<NewChildren<ChildrenCompMap>>>
126128
): NewChildren<ChildrenCompMap> {
@@ -131,8 +133,12 @@ export class UICompBuilder<
131133
return true;
132134
}
133135

136+
override getRef(): React.RefObject<HTMLDivElement> {
137+
return this.ref;
138+
}
139+
134140
override getView(): ViewReturn {
135-
return <UIView comp={this} viewFn={builder.viewFn} />;
141+
return (<div ref={this.ref}><UIView comp={this} viewFn={builder.viewFn} /></div>);
136142
}
137143

138144
override getPropertyView(): ReactNode {

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,9 @@ import {
141141
IconCompIcon,
142142
LayoutCompIcon,
143143
FloatingTextComp,
144+
IconModal,
144145
} from "lowcoder-design";
146+
import { TourComp } from "@lowcoder-ee/comps/comps/tourComp/tourComp";
145147

146148
type Registry = {
147149
[key in UICompType]?: UICompManifest;
@@ -548,6 +550,7 @@ export var uiCompMap: Registry = {
548550
compPath: 'comps/meetingComp/videoMeetingControllerComp',
549551
withoutLoading: true,
550552
},
553+
551554
comment: {
552555
name: trans("uiComp.commentCompName"),
553556
enName: "comment",
@@ -559,7 +562,7 @@ export var uiCompMap: Registry = {
559562
compName: 'CommentComp',
560563
compPath: 'comps/commentComp/commentComp',
561564
layoutInfo: {
562-
w: 13,
565+
w: 12,
563566
h: 55,
564567
},
565568
},
@@ -910,7 +913,7 @@ export var uiCompMap: Registry = {
910913
compName: 'StepComp',
911914
compPath: 'comps/selectInputComp/stepControl',
912915
layoutInfo: {
913-
w: 6,
916+
w: 19,
914917
h: 6,
915918
},
916919
},
@@ -1182,6 +1185,19 @@ export var uiCompMap: Registry = {
11821185
h: 5,
11831186
},
11841187
},
1188+
tour: {
1189+
name: trans("uiComp.tourCompName"),
1190+
enName: "Tour",
1191+
description: trans("uiComp.tourCompDesc"),
1192+
categories: ["multimedia", "itemHandling"],
1193+
icon: IconModal,
1194+
keywords: trans("uiComp.tourCompKeywords"),
1195+
comp: TourComp,
1196+
layoutInfo: {
1197+
w: 1,
1198+
h: 1,
1199+
},
1200+
},
11851201
multiSelect: {
11861202
name: trans("uiComp.multiSelectCompName"),
11871203
enName: "Multiselect",

‎client/packages/lowcoder/src/comps/uiCompRegistry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ export type UICompType =
132132
| "comment" //Added By Mousheng
133133
| "mention" //Added By Mousheng
134134
| "autocomplete" //Added By Mousheng
135-
| "responsiveLayout";
135+
| "responsiveLayout"
136+
| "tour";
136137

137138

138139
export const uiCompRegistry = {} as Record<UICompType | string, UICompManifest>;

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

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,10 @@ export const en = {
11051105
"iconCompName": "Icons",
11061106
"iconCompDesc": "Use various Icons to enhance the visual appeal and user experience of your application.",
11071107
"iconCompKeywords": "Icons, pictograms, symbols, shapes",
1108+
1109+
"tourCompName": "Tour",
1110+
"tourCompDesc": "A product tour for guiding users.",
1111+
"tourCompKeywords": "tour, product tour, walkthrough, interactive walkthrough",
11081112
},
11091113

11101114

@@ -1256,14 +1260,16 @@ export const en = {
12561260
},
12571261
"stepOptionsControl": {
12581262
"value": "Value / Key",
1263+
"valueTooltip": "Step Value must be a number. For the first Step, it must be equal to the initial value. Numbers must be in consistent and ascending order",
12591264
"title": "Step Title",
12601265
"subTitle": "Step Subtitle",
12611266
"description": "Step Description",
12621267
"status": "Step Status",
12631268
"icon": "Step Icon",
12641269
},
12651270
"step" : {
1266-
"initialValue": "Start Numbers from",
1271+
"initialValue": "Start Numbers at",
1272+
"initialValueTooltip": "Where to start the visual Numbering. Must be 1 or higher.",
12671273
"valueDesc": "Current Value",
12681274
"size" : "Steps Size",
12691275
"sizeSmall" : "Small",
@@ -1285,6 +1291,7 @@ export const en = {
12851291
"showDots" : "Show Dots instead Symbols",
12861292
"showIcons" : "Show Icons instead Symbols",
12871293
"responsive" : "Responsive",
1294+
"selectable" : "Selectable",
12881295
},
12891296
"radio": {
12901297
"options": "Options",
@@ -2977,6 +2984,83 @@ export const en = {
29772984
"navItemStyle": "Menu Item Style"
29782985
},
29792986

2987+
tour: {
2988+
section1Title: "Steps",
2989+
section1Subtitle: "Steps",
2990+
tooltipExampleHeader: "Example:",
2991+
tooltipSignatureHeader: "Signature:",
2992+
options: {
2993+
title: {
2994+
label: "Title",
2995+
placeholder: "Welcome",
2996+
tooltip: "The title of the step. Any HTML is valid here.",
2997+
},
2998+
description: {
2999+
label: "Description",
3000+
placeholder: "Welcome to lowcoder!",
3001+
tooltip: "The description of the step. Any HTML is valid here.",
3002+
},
3003+
mask: {
3004+
label: "Mask",
3005+
tooltip: "Whether to enable masking, change mask style and fill color by pass custom props, the default follows the `mask` property of Tour.",
3006+
tooltipValidTypes: "Valid input types: `true`, `false`, empty, or a JSON object following the CSSProperties Schema from Antd.",
3007+
},
3008+
arrow: {
3009+
label: "Arrow",
3010+
tooltip: "Turns the arrow on and off or moves it to point at the center of the component, if desired, otherwise the arrow will always point near the top of the component.",
3011+
tooltipFunctionSignature: "boolean | { pointAtCenter: boolean }",
3012+
},
3013+
type: {
3014+
label: "Type",
3015+
tooltip: "The type of tooltip, this affects the background color and text color. The colors can be controlled with the main tour styling section."
3016+
},
3017+
target: {
3018+
label: "Component",
3019+
tooltip: "The component you want to put the tooltip on, or leave it empty if you simply want a modal in the middle of the screen."
3020+
},
3021+
coverImage: {
3022+
label: "Cover Image URI",
3023+
tooltip: "A URI for an image you would like to display with the step",
3024+
}
3025+
},
3026+
indicatorsRender: {
3027+
label: "Indicators Render",
3028+
tooltip: "Provides a custom indicator for which step you are on",
3029+
tooltipValidTypes: "Format is a function that accepts two args, `current` and `total` and returns a ReactNode",
3030+
tooltipFunctionSignatureHeader: "Signature:",
3031+
tooltipFunctionSignature: "(current: number, total: number) => ReactNode",
3032+
tooltipExampleHeader: "Example:",
3033+
},
3034+
disabledInteraction: {
3035+
label: "Disable Interaction",
3036+
tooltip: "Disable interaction in the highlighted area."
3037+
},
3038+
mask: {
3039+
label: "Mask",
3040+
tooltip: "Whether to enable masking, change mask style and fill color by pass custom props, the default follows the `mask` property of Tour. Can be overridden at the step level.",
3041+
tooltipValidTypes: "Valid input types: `true`, `false`, empty, or a JSON object following the CSSProperties Schema from Antd.",
3042+
},
3043+
placement: {
3044+
label: "Placement",
3045+
tooltip: "Position of the guide card relative to the target element. Can be overridden at the step level.",
3046+
tooltipValidOptions: "Valid options",
3047+
tooltipValidOptionsAbove: "Above the component:",
3048+
tooltipValidOptionsLeft: "To the left of the component:",
3049+
tooltipValidOptionsRight: "To the right of the component:",
3050+
tooltipValidOptionsBelow: "Below the component:",
3051+
tooltipValidOptionsOnTop: "On top of the component:",
3052+
},
3053+
arrow: {
3054+
label: "Arrow",
3055+
tooltip: "Turns the arrow on and off or moves it to point at the center of the component, if desired, otherwise the arrow will always point near the top of the component. Can be overridden at the step level.",
3056+
tooltipFunctionSignature: "boolean | { pointAtCenter: boolean }",
3057+
},
3058+
type: {
3059+
label: "Type",
3060+
tooltip: "The type of tooltip, this affects the background color and text color. The colors can be controlled with the main tour styling section. Can be overridden at the step level."
3061+
},
3062+
},
3063+
29803064
docUrls: {
29813065
docHome: "https://docs.lowcoder.cloud/",
29823066
components: "https://app.lowcoder.cloud/components/{compType}",

‎client/packages/lowcoder/src/i18n/locales/translation_files/en.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2590,5 +2590,13 @@
25902590
"widthTooltip": "Pixel or Percentage, e.g. 520, 60%",
25912591
"navStyle": "Menu Style",
25922592
"navItemStyle": "Menu Item Style"
2593+
},
2594+
"tour": {
2595+
"options": {
2596+
"mask": {
2597+
"label": "Mask",
2598+
"tooltip": "Whether to enable masking, change mask style and fill color by pass custom props, the default follows the `mask` property of Tour."
2599+
}
2600+
}
25932601
}
25942602
}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,32 @@ optionsControl: {
11861186
"description": "描述",
11871187
"status": "状态",
11881188
},
1189+
"step" : {
1190+
"initialValue": "初始值",
1191+
"initialValueTooltip": "视觉编号的起始位置。必须是 1 或更高。",
1192+
"valueDesc": "当前价值",
1193+
"size" : "台阶大小",
1194+
"sizeSmall" : "小型",
1195+
"sizeDefault" : "默认值",
1196+
"percent" : "步骤 百分比",
1197+
"type" : "步骤类型",
1198+
"typeDefault" : "标准",
1199+
"typeNavigation" : "导航",
1200+
"typeInline" : "内联",
1201+
"direction" : "步骤 方向",
1202+
"directionVertical" : "垂直",
1203+
"directionHorizontal" : "横向",
1204+
"labelPlacement" : "步骤 标签放置",
1205+
"status" : "步骤状态",
1206+
"statusWait" : "等待",
1207+
"statusProcess" : "过程",
1208+
"statusFinish" : "完成",
1209+
"statusError" : "错误",
1210+
"showDots" : "用圆点代替数字",
1211+
"showIcons" : "显示图标而不是数字",
1212+
"responsive" : "响应式",
1213+
"selectable" : "可选择",
1214+
},
11891215
radio: {
11901216
options: "选项",
11911217
horizontal: "水平",

‎client/packages/lowcoder/src/pages/editor/LeftLayersContent.tsx

Lines changed: 146 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
LeftShow,
1111
} from "lowcoder-design";
1212
import React, { useCallback, useContext, useMemo, useState, useEffect, useRef } from "react";
13-
import _, { get } from "lodash";
13+
import _, { get, set } from "lodash";
1414
import styled from "styled-components";
1515
import { leftCompListClassName } from "pages/tutorials/tutorialsConstant";
1616
import type UIComp from "comps/comps/uiComp";
@@ -33,6 +33,8 @@ import { default as Input } from "antd/es/input";
3333
import { default as Menu } from "antd/es/menu";
3434
import { default as Space } from "antd/es/space";
3535
import { default as Switch } from "antd/es/switch";
36+
import { MenuProps } from "antd/es/menu";
37+
import type { InputRef } from 'antd';
3638
import {
3739
saveCollisionStatus,
3840
} from "util/localStorageUtil";
@@ -61,6 +63,81 @@ type NodeItem = {
6163
fixed?: boolean;
6264
};
6365

66+
const items: MenuProps['items'] = [
67+
{
68+
label: 'Hide Component',
69+
key: 'hidden',
70+
},
71+
{
72+
label: 'Disable Component',
73+
key: 'disable',
74+
},
75+
{
76+
label: 'Margin',
77+
key: 'style.margin',
78+
},
79+
{
80+
label: 'Padding',
81+
key: 'style.padding',
82+
},
83+
{
84+
label: 'Font Size',
85+
key: 'style.textSize',
86+
},
87+
{
88+
label: 'Font Weight',
89+
key: 'style.textWeight',
90+
},
91+
{
92+
label: 'Font Family',
93+
key: 'style.fontFamily',
94+
},
95+
{
96+
label: 'Font Style',
97+
key: 'style.fontStyle',
98+
},
99+
{
100+
label: 'Text Transform',
101+
key: 'style.textTransform',
102+
},
103+
{
104+
label: 'Text Decoration',
105+
key: 'style.textDecoration',
106+
},
107+
{
108+
label: 'Border Radius',
109+
key: 'style.borderRadius',
110+
},
111+
{
112+
label: 'Border Width',
113+
key: 'style.borderWidth',
114+
},
115+
{
116+
label: 'Border Style',
117+
key: 'style.borderStyle',
118+
},
119+
{
120+
label: 'Background Image',
121+
key: 'style.backgroundImage',
122+
},
123+
{
124+
label: 'Background Image Repeat',
125+
key: 'style.backgroundImageRepeat',
126+
},
127+
{
128+
label: 'Background Image Size',
129+
key: 'style.backgroundImageSize',
130+
},
131+
{
132+
label: 'Background Image Position',
133+
key: 'style.backgroundImagePosition',
134+
},
135+
{
136+
label: 'Background Image Origin',
137+
key: 'style.backgroundImageOrigin',
138+
}
139+
];
140+
64141
const LeftLayersContentWrapper = styled.div`
65142
height: calc(100vh - ${TopHeaderHeight});
66143
`;
@@ -83,10 +160,22 @@ export const LeftLayersContent = (props: LeftLayersContentProps) => {
83160
// added by Falk Wolsky to support a Layers in Lowcoder
84161
const [collisionStatus, setCollisionStatus] = useState(editorState.getCollisionStatus());
85162

163+
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
164+
const [actionValue, setActionValue] = useState<string>("");
165+
const [selectedActionKey, setSelectedActionKey] = useState<string | null>(null);
166+
const [placeholderText, setPlaceholderText] = useState<string>("");
167+
// const [color, setColor] = useState<string>("");
168+
const inputRef = useRef<InputRef>(null);
169+
86170
useEffect(() => {
87171
saveCollisionStatus(collisionStatus);
88172
}, [collisionStatus])
89173

174+
175+
const handleActionSelection = useCallback((key: string) => {
176+
setSelectedActionKey(key);
177+
}, []);
178+
90179
const handleToggleLayer = (checked: boolean) => {
91180
editorState.rootComp.children.settings.children.disableCollision.dispatchChangeValueAction(
92181
checked
@@ -255,11 +344,49 @@ export const LeftLayersContent = (props: LeftLayersContentProps) => {
255344

256345
// here we handle the checked keys of the component tree
257346

258-
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
259-
const [actionValue, setActionValue] = useState<string>("");
260-
const [selectedActionKey, setSelectedActionKey] = useState<string | null>(null);
261-
const [placeholderText, setPlaceholderText] = useState<string>("");
262-
const [color, setColor] = useState<string>("");
347+
const getPlaceholderText = useCallback((key: string) => {
348+
switch (key) {
349+
case 'hidden':
350+
case 'disable':
351+
return 'true | false';
352+
case 'style.border':
353+
return 'e.g., #ffffff'; // Example format indication
354+
case 'style.borderRadius':
355+
case 'style.radius': // Supporting legacy key if needed
356+
return 'e.g., 4px'; // Example format indication
357+
case 'style.borderWidth':
358+
return 'e.g., 2px'; // Example format indication
359+
case 'style.borderStyle':
360+
return 'solid | dashed | dotted';
361+
case 'style.backgroundImage':
362+
return 'URL as string';
363+
case 'style.backgroundImageRepeat':
364+
return 'repeat | repeat-x | repeat-y | no-repeat';
365+
case 'style.backgroundImageSize':
366+
return 'cover | contain | % | px';
367+
case 'style.backgroundImagePosition':
368+
return 'top | bottom | center | % | px';
369+
case 'style.backgroundImageOrigin':
370+
return 'padding-box | border-box | content-box';
371+
case 'style.margin':
372+
case 'style.padding':
373+
return 'e.g., 4px 8px 16px 32px'; // Example format indication
374+
case 'style.textSize':
375+
return 'e.g., 16px'; // Example format indication
376+
case 'style.textWeight':
377+
return 'bold | 900 | normal | 400';
378+
case 'style.fontFamily':
379+
return 'Arial, sans-serif';
380+
case 'style.fontStyle':
381+
return 'normal | italic | oblique';
382+
case 'style.textTransform':
383+
return 'none | capitalize | uppercase | lowercase';
384+
case 'style.textDecoration':
385+
return 'none | underline | overline | line-through';
386+
default:
387+
return 'Action Value';
388+
}
389+
}, []);
263390

264391
const handleColorChange = (color: string | undefined, actionType: string) => {
265392
const newColor = color || '#ffffff';
@@ -294,11 +421,6 @@ export const LeftLayersContent = (props: LeftLayersContentProps) => {
294421
}
295422
};
296423

297-
298-
/* const handleActionValueChange = (e: any) => {
299-
setActionValue(e.target.value);
300-
} */
301-
302424
// sync selected components with checked keys
303425
useEffect(() => {
304426
setCheckedKeys([]);
@@ -316,10 +438,10 @@ export const LeftLayersContent = (props: LeftLayersContentProps) => {
316438
}, [editorState]);
317439

318440
// make sure to handle the selectedActionKey for the changed input fields
319-
useEffect(() => {
441+
/* useEffect(() => {
320442
setActionValue('');
321-
setColor('#ffffff');
322-
}, [selectedActionKey, placeholderText]);
443+
// setColor('#ffffff');
444+
}, [selectedActionKey, placeholderText]); */
323445

324446
const onCheck = (checkedKeys: any, e: any) => {
325447
setCheckedKeys(checkedKeys);
@@ -369,78 +491,6 @@ export const LeftLayersContent = (props: LeftLayersContentProps) => {
369491
}
370492
}
371493
}, [getActionValue, getCheckedKeys]);
372-
373-
const handleActionSelection = useCallback((key: string) => {
374-
setSelectedActionKey(key);
375-
setPlaceholderText(getPlaceholderText(key));
376-
}, [handleComponentsActions]);
377-
378-
const layerActions: ItemType[] = [
379-
{
380-
label: 'Hide Component',
381-
key: 'hidden',
382-
},
383-
{
384-
label: 'Disable Component',
385-
key: 'disable',
386-
},
387-
{
388-
label: 'Margin',
389-
key: 'style.margin',
390-
},
391-
{
392-
label: 'Padding',
393-
key: 'style.padding',
394-
},
395-
{
396-
label: 'Border Radius',
397-
key: 'style.radius',
398-
},
399-
{
400-
label: 'Border Width',
401-
key: 'style.borderWidth',
402-
},
403-
{
404-
label: 'Font Size',
405-
key: 'style.textSize',
406-
},
407-
{
408-
label: 'Font Weight',
409-
key: 'style.textWeight',
410-
},
411-
{
412-
label: 'Font Family',
413-
key: 'style.fontFamily',
414-
}
415-
];
416-
417-
418-
const getPlaceholderText = (key: string) => {
419-
switch (key) {
420-
case 'hidden':
421-
case 'disable':
422-
return 'true | false';
423-
case 'style.background':
424-
case 'style.text':
425-
case 'style.border':
426-
return 'e.g., #ffffff'; // Indicate example format
427-
case 'style.radius':
428-
return 'e.g., 4px'; // Indicate example format
429-
case 'style.borderWidth':
430-
return 'e.g., 2px'; // Indicate example format
431-
case 'style.textSize':
432-
return 'e.g., 16px'; // Indicate example format
433-
case 'style.textWeight':
434-
return 'bold | 900';
435-
case 'style.fontFamily':
436-
return 'Arial, sans-serif';
437-
case 'style.margin':
438-
case 'style.padding':
439-
return 'e.g., 4px 8px 16px 32px'; // Indicate example format
440-
default:
441-
return 'Action Value';
442-
}
443-
};
444494

445495
const getTreeUI = () => {
446496
// here the components get sorted by name
@@ -453,6 +503,7 @@ export const LeftLayersContent = (props: LeftLayersContentProps) => {
453503
<>
454504
<div style={{margin:"0px 16px"}}>
455505
<div style={{marginBottom:"10px"}}>
506+
456507
{trans("leftPanel.activatelayers")}
457508
<Switch
458509
style={{margin : "0px 10px"}}
@@ -490,21 +541,21 @@ export const LeftLayersContent = (props: LeftLayersContentProps) => {
490541
<CustomDropdown
491542
dropdownRender={() => (
492543
<Menu
493-
items={layerActions}
494-
onClick={({key}) => handleActionSelection(key)}
544+
items={items}
545+
onClick={({ key }) => {
546+
handleActionSelection(key);
547+
}}
495548
/>
496549
)}
497550
>
498551
<Button size={"small"}>
499-
<Space>
500-
Action
501-
<DownOutlined />
502-
</Space>
552+
<Space>Action <DownOutlined /></Space>
503553
</Button>
504554
</CustomDropdown>
505-
<Input
506-
value={actionValue} // Use actionValue for the default case as well
507-
onChange={(e) => setActionValue(e.target.value)} // Handle changes to update actionValue
555+
<Input
556+
ref={inputRef}
557+
value={actionValue}
558+
onChange={(e) => setActionValue(e.target.value)}
508559
placeholder={placeholderText}
509560
/>
510561
<Button

‎client/packages/lowcoder/src/pages/editor/editorConstants.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,5 +122,5 @@ export const CompStateIcon: {
122122
autocomplete: <AutoCompleteCompIcon />,
123123
icon: <IconCompIcon />,
124124
responsiveLayout: <ResponsiveLayoutCompIcon />,
125-
125+
tour: <LeftSelect />,
126126
};

‎client/packages/lowcoder/src/util/convertUtils.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { JSONObject, JSONValue } from "util/jsonTypes";
2+
import { CSSProperties } from "react";
23

34
class EvalTypeError extends TypeError {
45
hint?: string;
@@ -245,3 +246,30 @@ export function check(
245246
)
246247
);
247248
}
249+
250+
export function toBooleanOrCss(value: any): boolean | undefined | {
251+
style?: CSSProperties | undefined;
252+
color?: string | undefined;
253+
} {
254+
if (value === undefined || value === null || value === "") {
255+
return undefined;
256+
}
257+
if (value.toLocaleLowerCase() === "true" || value.toLocaleLowerCase() === "false") {
258+
return value.toLocaleLowerCase() === "true";
259+
}
260+
return toJSONObject(JSON.parse(value));
261+
}
262+
263+
export function toBooleanOrJsonObject(value: any): boolean | undefined | { pointAtCenter: boolean } {
264+
if (value === undefined || value === null || value === "") {
265+
return undefined;
266+
}
267+
if (value.toLocaleLowerCase() === "true" || value.toLocaleLowerCase() === "false") {
268+
return value.toLocaleLowerCase() === "true";
269+
}
270+
const json = toJSONObject(JSON.parse(value));
271+
if (json.pointAtCenter) {
272+
return json as { pointAtCenter: boolean };
273+
}
274+
throw new TypeError(typeErrorMsg("Object or Boolean", value));
275+
}

‎deploy/docker/Dockerfile

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,21 +109,31 @@ CMD [ "/bin/sh", "/lowcoder/node-service/entrypoint.sh" ]
109109
## Build lowcoder client application
110110
##
111111
FROM node:20.2-slim AS build-client
112-
COPY ./client /lowcoder-client
113-
WORKDIR /lowcoder-client
114-
RUN yarn --immutable
115-
116-
# TODO: build lowcoder-comps
117112

118113
# curl is required for yarn build to succeed, because it calls it while building client
119114
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates
120115

116+
# Build client
117+
COPY ./client /lowcoder-client
118+
WORKDIR /lowcoder-client
119+
RUN yarn --immutable
120+
121121
ARG REACT_APP_COMMIT_ID=test
122122
ARG REACT_APP_ENV=production
123123
ARG REACT_APP_EDITION=community
124124
ARG REACT_APP_DISABLE_JS_SANDBOX=true
125125
RUN yarn build
126126

127+
# Build lowcoder-comps
128+
WORKDIR /lowcoder-client/packages/lowcoder-comps
129+
RUN yarn install
130+
RUN yarn build
131+
RUN tar -zxf lowcoder-comps-*.tgz && mv package lowcoder-comps
132+
133+
# Build lowcoder-sdk
134+
WORKDIR /lowcoder-client/packages/lowcoder-sdk
135+
RUN yarn install
136+
RUN yarn build
127137

128138
##
129139
## Intermediary Lowcoder client image
@@ -140,8 +150,13 @@ RUN usermod --login lowcoder --uid 9001 nginx \
140150
&& rm -f /etc/nginx/nginx.conf \
141151
&& mkdir -p /lowcoder/assets
142152

143-
# Copy lowcoder client data
153+
# Copy lowcoder client
144154
COPY --chown=lowcoder:lowcoder --from=build-client /lowcoder-client/packages/lowcoder/build/ /lowcoder/client
155+
# Copy lowcoder components
156+
COPY --chown=lowcoder:lowcoder --from=build-client /lowcoder-client/packages/lowcoder-comps/lowcoder-comps /lowcoder/client-comps
157+
# Copy lowcoder SDK
158+
COPY --chown=lowcoder:lowcoder --from=build-client /lowcoder-client/packages/lowcoder-sdk /lowcoder/client-sdk
159+
145160

146161
# Copy additional nginx init scripts
147162
COPY deploy/docker/frontend/00-change-nginx-user.sh /docker-entrypoint.d/00-change-nginx-user.sh
@@ -150,6 +165,7 @@ COPY deploy/docker/frontend/01-update-nginx-conf.sh /docker-entrypoint.d/01-upda
150165
RUN chmod +x /docker-entrypoint.d/00-change-nginx-user.sh && \
151166
chmod +x /docker-entrypoint.d/01-update-nginx-conf.sh
152167

168+
COPY deploy/docker/frontend/server.conf /etc/nginx/server.conf
153169
COPY deploy/docker/frontend/nginx-http.conf /etc/nginx/nginx-http.conf
154170
COPY deploy/docker/frontend/nginx-https.conf /etc/nginx/nginx-https.conf
155171
COPY deploy/docker/frontend/ssl-certificate.conf /etc/nginx/ssl-certificate.conf

‎deploy/docker/frontend/01-update-nginx-conf.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ else
1818
ln -s /etc/nginx/nginx-http.conf /etc/nginx/nginx.conf
1919
fi;
2020

21-
sed -i "s@__LOWCODER_MAX_QUERY_TIMEOUT__@${LOWCODER_MAX_QUERY_TIMEOUT:=120}@" /etc/nginx/nginx.conf
2221
sed -i "s@__LOWCODER_MAX_REQUEST_SIZE__@${LOWCODER_MAX_REQUEST_SIZE:=20m}@" /etc/nginx/nginx.conf
23-
sed -i "s@__LOWCODER_API_SERVICE_URL__@${LOWCODER_API_SERVICE_URL:=http://localhost:8080}@" /etc/nginx/nginx.conf
24-
sed -i "s@__LOWCODER_NODE_SERVICE_URL__@${LOWCODER_NODE_SERVICE_URL:=http://localhost:6060}@" /etc/nginx/nginx.conf
22+
sed -i "s@__LOWCODER_MAX_QUERY_TIMEOUT__@${LOWCODER_MAX_QUERY_TIMEOUT:=120}@" /etc/nginx/server.conf
23+
sed -i "s@__LOWCODER_API_SERVICE_URL__@${LOWCODER_API_SERVICE_URL:=http://localhost:8080}@" /etc/nginx/server.conf
24+
sed -i "s@__LOWCODER_NODE_SERVICE_URL__@${LOWCODER_NODE_SERVICE_URL:=http://localhost:6060}@" /etc/nginx/server.conf
2525

2626
echo "nginx config updated with:"
2727
echo " Lowcoder max upload size: ${LOWCODER_MAX_REQUEST_SIZE:=20m}"

‎deploy/docker/frontend/nginx-http.conf

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,37 +33,7 @@ http {
3333

3434
server {
3535
listen 3000 default_server;
36-
root /lowcoder/client;
3736

38-
proxy_connect_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
39-
proxy_send_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
40-
proxy_read_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
41-
42-
location / {
43-
try_files $uri /index.html;
44-
45-
if ($request_filename ~* .*.(html|htm)$) {
46-
add_header Cache-Control no-cache;
47-
}
48-
}
49-
50-
location /assets {
51-
alias /lowcoder/assets;
52-
expires 1M;
53-
}
54-
55-
location /api {
56-
proxy_set_header X-Forwarded-Proto $scheme;
57-
proxy_set_header X-Forwarded-Host $host;
58-
proxy_set_header X-Real-IP $remote_addr;
59-
proxy_pass __LOWCODER_API_SERVICE_URL__;
60-
}
61-
62-
location /node-service/plugin-icons {
63-
proxy_set_header X-Forwarded-Proto $scheme;
64-
proxy_set_header X-Forwarded-Host $host;
65-
proxy_set_header X-Real-IP $remote_addr;
66-
proxy_pass __LOWCODER_NODE_SERVICE_URL__;
67-
}
37+
include /etc/nginx/server.conf;
6838
}
6939
}

‎deploy/docker/frontend/nginx-https.conf

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,41 +33,11 @@ http {
3333

3434
server {
3535
listen 3443 ssl;
36-
root /lowcoder/client;
3736

3837
include /etc/nginx/ssl-certificate.conf;
3938
include /etc/nginx/ssl-params.conf;
4039

41-
proxy_connect_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
42-
proxy_send_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
43-
proxy_read_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
44-
45-
location / {
46-
try_files $uri /index.html;
47-
48-
if ($request_filename ~* .*.(html|htm)$) {
49-
add_header Cache-Control no-cache;
50-
}
51-
}
52-
53-
location /assets {
54-
alias /lowcoder/assets;
55-
expires 1M;
56-
}
57-
58-
location /api {
59-
proxy_set_header X-Forwarded-Proto $scheme;
60-
proxy_set_header X-Forwarded-Host $host;
61-
proxy_set_header X-Real-IP $remote_addr;
62-
proxy_pass __LOWCODER_API_SERVICE_URL__;
63-
}
64-
65-
location /node-service/plugin-icons {
66-
proxy_set_header X-Forwarded-Proto $scheme;
67-
proxy_set_header X-Forwarded-Host $host;
68-
proxy_set_header X-Real-IP $remote_addr;
69-
proxy_pass __LOWCODER_NODE_SERVICE_URL__;
70-
}
40+
include /etc/nginx/server.conf;
7141
}
7242

7343
}

‎deploy/docker/frontend/server.conf

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
root /lowcoder/client;
2+
3+
proxy_connect_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
4+
proxy_send_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
5+
proxy_read_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
6+
7+
location / {
8+
try_files $uri /index.html;
9+
10+
if ($request_filename ~* .*.(html|htm)$) {
11+
add_header Cache-Control no-cache;
12+
}
13+
}
14+
15+
location /sdk {
16+
try_files $uri =404;
17+
18+
alias /lowcoder/client-sdk;
19+
expires 1M;
20+
}
21+
22+
location /comps {
23+
try_files $uri =404;
24+
25+
alias /lowcoder/client-comps;
26+
expires 1M;
27+
}
28+
29+
location /assets {
30+
try_files $uri =404;
31+
32+
alias /lowcoder/assets;
33+
expires 1M;
34+
}
35+
36+
location /api {
37+
proxy_set_header X-Forwarded-Proto $scheme;
38+
proxy_set_header X-Forwarded-Host $host;
39+
proxy_set_header X-Real-IP $remote_addr;
40+
proxy_pass __LOWCODER_API_SERVICE_URL__;
41+
}
42+
43+
location /node-service/plugin-icons {
44+
proxy_set_header X-Forwarded-Proto $scheme;
45+
proxy_set_header X-Forwarded-Host $host;
46+
proxy_set_header X-Real-IP $remote_addr;
47+
proxy_pass __LOWCODER_NODE_SERVICE_URL__;
48+
}
49+

0 commit comments

Comments
 (0)
Please sign in to comment.