Skip to content

Commit 199dca0

Browse files
authored
Merge pull request #817 from snowe2010/add-tour-component
Add new Tour component
2 parents 7e272ee + 781c09b commit 199dca0

File tree

16 files changed

+908
-9
lines changed

16 files changed

+908
-9
lines changed

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

+2-1
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

+1
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";
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+
]);
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 {};
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+
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

+1-2
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

+14-2
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") {

0 commit comments

Comments
 (0)