Skip to content

Commit 121d377

Browse files
authored
Merge branch 'dev' into fix_rest_system_proxy
2 parents 52260d6 + cb27aef commit 121d377

File tree

2 files changed

+251
-30
lines changed

2 files changed

+251
-30
lines changed

client/packages/lowcoder/src/comps/comps/layout/mobileTabLayout.tsx

Lines changed: 224 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
1-
import { MultiCompBuilder, withViewFn } from "comps/generators";
1+
import { MultiCompBuilder, withDefault, withViewFn } from "comps/generators";
22
import { trans } from "i18n";
3-
import { Section } from "components/Section";
3+
import { Section, sectionNames } from "components/Section";
44
import { manualOptionsControl } from "comps/controls/optionsControl";
5-
import { BoolCodeControl, StringControl } from "comps/controls/codeControl";
5+
import { BoolCodeControl, StringControl, jsonControl } from "comps/controls/codeControl";
66
import { IconControl } from "comps/controls/iconControl";
77
import styled from "styled-components";
8-
import React, { Suspense, useContext, useState } from "react";
8+
import React, { Suspense, useContext, useEffect, useMemo, useState } from "react";
99
import { registerLayoutMap } from "comps/comps/uiComp";
1010
import { AppSelectComp } from "comps/comps/layout/appSelectComp";
1111
import { NameAndExposingInfo } from "comps/utils/exposingTypes";
12-
import { ConstructorToComp } from "lowcoder-core";
12+
import { ConstructorToComp, ConstructorToDataType } from "lowcoder-core";
1313
import { CanvasContainer } from "comps/comps/gridLayoutComp/canvasView";
1414
import { CanvasContainerID } from "constants/domLocators";
1515
import { EditorContainer, EmptyContent } from "pages/common/styledComponent";
1616
import { Layers } from "constants/Layers";
1717
import { ExternalEditorContext } from "util/context/ExternalEditorContext";
1818
import { default as Skeleton } from "antd/es/skeleton";
1919
import { hiddenPropertyView } from "comps/utils/propertyUtils";
20+
import { dropdownControl } from "@lowcoder-ee/comps/controls/dropdownControl";
21+
import { DataOption, DataOptionType, ModeOptions, menuItemStyleOptions, mobileNavJsonMenuItems } from "./navLayoutConstants";
22+
import { styleControl } from "@lowcoder-ee/comps/controls/styleControl";
23+
import { NavLayoutItemActiveStyle, NavLayoutItemActiveStyleType, NavLayoutItemHoverStyle, NavLayoutItemHoverStyleType, NavLayoutItemStyle, NavLayoutItemStyleType, NavLayoutStyle, NavLayoutStyleType, defaultTheme } from "@lowcoder-ee/comps/controls/styleControlConstants";
24+
import Segmented from "antd/es/segmented";
25+
import { controlItem } from "components/control";
26+
import { check } from "@lowcoder-ee/util/convertUtils";
27+
import { JSONObject } from "@lowcoder-ee/util/jsonTypes";
28+
import { isEmpty } from "lodash";
29+
import { ThemeContext } from "@lowcoder-ee/comps/utils/themeContext";
2030

2131
const TabBar = React.lazy(() => import("antd-mobile/es/components/tab-bar"));
2232
const TabBarItem = React.lazy(() =>
@@ -43,9 +53,12 @@ const TabLayoutViewContainer = styled.div`
4353
height: calc(100% - ${TabBarHeight}px);
4454
`;
4555

46-
const TabBarWrapper = styled.div<{ $readOnly: boolean }>`
56+
const TabBarWrapper = styled.div<{
57+
$readOnly: boolean,
58+
$canvasBg: string,
59+
}>`
4760
max-width: inherit;
48-
background: white;
61+
background: ${(props) => (props.$canvasBg)};
4962
margin: 0 auto;
5063
position: fixed;
5164
bottom: 0;
@@ -61,6 +74,65 @@ const TabBarWrapper = styled.div<{ $readOnly: boolean }>`
6174
}
6275
`;
6376

77+
const StyledTabBar = styled(TabBar)<{
78+
$tabStyle: NavLayoutStyleType,
79+
$tabItemStyle: NavLayoutItemStyleType,
80+
$tabItemHoverStyle: NavLayoutItemHoverStyleType,
81+
$tabItemActiveStyle: NavLayoutItemActiveStyleType,
82+
}>`
83+
width: ${(props) => `calc(100% - ${props.$tabStyle.margin} - ${props.$tabStyle.margin})`};
84+
border: ${(props) => props.$tabStyle.border};
85+
background: ${(props) => props.$tabStyle.background};
86+
border-radius: ${(props) => props.$tabStyle.radius };
87+
margin: ${(props) => props.$tabStyle.margin };
88+
padding: ${(props) => props.$tabStyle.padding };
89+
90+
.adm-tab-bar-item:not(:last-child) {
91+
border-right: ${(props) => props.$tabStyle.border};
92+
}
93+
.adm-tab-bar-item-icon, .adm-tab-bar-item-title {
94+
color: ${(props) => props.$tabStyle.text};
95+
}
96+
97+
.adm-tab-bar-item {
98+
background-color: ${(props) => props.$tabItemStyle?.background};
99+
color: ${(props) => props.$tabItemStyle?.text};
100+
border-radius: ${(props) => props.$tabItemStyle?.radius} !important;
101+
border: ${(props) => `1px solid ${props.$tabItemStyle?.border}`};
102+
margin: ${(props) => props.$tabItemStyle?.margin};
103+
padding: ${(props) => props.$tabItemStyle?.padding};
104+
}
105+
106+
.adm-tab-bar-item:hover {
107+
background-color: ${(props) => props.$tabItemHoverStyle?.background} !important;
108+
color: ${(props) => props.$tabItemHoverStyle?.text} !important;
109+
border: ${(props) => `1px solid ${props.$tabItemHoverStyle?.border}`};
110+
}
111+
112+
.adm-tab-bar-item.adm-tab-bar-item-active {
113+
background-color: ${(props) => props.$tabItemActiveStyle.background};
114+
// border: ${(props) => `1px solid ${props.$tabItemActiveStyle.border}`};
115+
.adm-tab-bar-item-icon, .adm-tab-bar-item-title {
116+
color: ${(props) => props.$tabItemActiveStyle.text};
117+
}
118+
}
119+
`;
120+
121+
const defaultStyle = {
122+
radius: '0px',
123+
margin: '0px',
124+
padding: '0px',
125+
}
126+
127+
type MenuItemStyleOptionValue = "normal" | "hover" | "active";
128+
129+
type JsonItemNode = {
130+
label: string;
131+
hidden?: boolean;
132+
icon?: any;
133+
app?: JSONObject,
134+
}
135+
64136
type TabBarProps = {
65137
tabs: Array<{
66138
title: string;
@@ -70,27 +142,56 @@ type TabBarProps = {
70142
selectedKey: string;
71143
onChange: (key: string) => void;
72144
readOnly: boolean;
145+
canvasBg: string;
146+
tabStyle: NavLayoutStyleType;
147+
tabItemStyle: NavLayoutItemStyleType;
148+
tabItemHoverStyle: NavLayoutItemHoverStyleType;
149+
tabItemActiveStyle: NavLayoutItemActiveStyleType;
73150
};
74151

152+
function checkDataNodes(value: any, key?: string): JsonItemNode[] | undefined {
153+
return check(value, ["array", "undefined"], key, (node, k) => {
154+
check(node, ["object"], k);
155+
check(node["label"], ["string"], "label");
156+
check(node["hidden"], ["boolean", "undefined"], "hidden");
157+
check(node["icon"], ["string", "undefined"], "icon");
158+
check(node["app"], ["object", "undefined"], "app");
159+
return node;
160+
});
161+
}
162+
163+
function convertTreeData(data: any) {
164+
return data === "" ? [] : checkDataNodes(data) ?? [];
165+
}
166+
75167
function TabBarView(props: TabBarProps) {
168+
const {
169+
canvasBg, tabStyle, tabItemStyle, tabItemHoverStyle, tabItemActiveStyle,
170+
} = props;
76171
return (
77172
<Suspense fallback={<Skeleton />}>
78-
<TabBarWrapper $readOnly={props.readOnly}>
79-
<TabBar
173+
<TabBarWrapper
174+
$readOnly={props.readOnly}
175+
$canvasBg={canvasBg}
176+
>
177+
<StyledTabBar
80178
onChange={(key: string) => {
81179
if (key) {
82180
props.onChange(key);
83181
}
84182
}}
85-
style={{ width: "100%" }}
86183
activeKey={props.selectedKey}
184+
$tabStyle={tabStyle}
185+
$tabItemStyle={tabItemStyle}
186+
$tabItemHoverStyle={tabItemHoverStyle}
187+
$tabItemActiveStyle={tabItemActiveStyle}
87188
>
88189
{props.tabs.map((tab) => {
89190
return (
90191
<TabBarItem key={tab.key} icon={tab.icon} title={tab.title} />
91192
);
92193
})}
93-
</TabBar>
194+
</StyledTabBar>
94195
</TabBarWrapper>
95196
</Suspense>
96197
);
@@ -126,6 +227,8 @@ const TabOptionComp = (function () {
126227

127228
let MobileTabLayoutTmp = (function () {
128229
const childrenMap = {
230+
dataOptionType: dropdownControl(DataOptionType, DataOption.Manual),
231+
jsonItems: jsonControl<JsonItemNode[]>(convertTreeData, mobileNavJsonMenuItems),
129232
tabs: manualOptionsControl(TabOptionComp, {
130233
initOptions: [
131234
{
@@ -142,17 +245,64 @@ let MobileTabLayoutTmp = (function () {
142245
},
143246
],
144247
}),
248+
jsonTabs: manualOptionsControl(TabOptionComp, {
249+
initOptions: [],
250+
}),
251+
backgroundImage: withDefault(StringControl, ""),
252+
navStyle: withDefault(styleControl(NavLayoutStyle), defaultStyle),
253+
navItemStyle: withDefault(styleControl(NavLayoutItemStyle), defaultStyle),
254+
navItemHoverStyle: withDefault(styleControl(NavLayoutItemHoverStyle), {}),
255+
navItemActiveStyle: withDefault(styleControl(NavLayoutItemActiveStyle), {}),
145256
};
146257
return new MultiCompBuilder(childrenMap, (props) => {
147258
return null;
148259
})
149260
.setPropertyViewFn((children) => {
261+
const [styleSegment, setStyleSegment] = useState('normal')
150262
return (
151-
<>
263+
<div style={{overflowY: 'auto'}}>
152264
<Section name={trans("aggregation.tabBar")}>
153-
{children.tabs.propertyView({})}
265+
{children.dataOptionType.propertyView({
266+
radioButton: true,
267+
type: "oneline",
268+
})}
269+
{
270+
children.dataOptionType.getView() === DataOption.Manual
271+
? children.tabs.propertyView({})
272+
: children.jsonItems.propertyView({
273+
label: "Json Data",
274+
})
275+
}
154276
</Section>
155-
</>
277+
<Section name={sectionNames.layout}>
278+
{children.backgroundImage.propertyView({
279+
label: `Background Image`,
280+
placeholder: 'https://temp.im/350x400',
281+
})}
282+
</Section>
283+
<Section name={trans("navLayout.navStyle")}>
284+
{ children.navStyle.getPropertyView() }
285+
</Section>
286+
<Section name={trans("navLayout.navItemStyle")}>
287+
{controlItem({}, (
288+
<Segmented
289+
block
290+
options={menuItemStyleOptions}
291+
value={styleSegment}
292+
onChange={(k) => setStyleSegment(k as MenuItemStyleOptionValue)}
293+
/>
294+
))}
295+
{styleSegment === 'normal' && (
296+
children.navItemStyle.getPropertyView()
297+
)}
298+
{styleSegment === 'hover' && (
299+
children.navItemHoverStyle.getPropertyView()
300+
)}
301+
{styleSegment === 'active' && (
302+
children.navItemActiveStyle.getPropertyView()
303+
)}
304+
</Section>
305+
</div>
156306
);
157307
})
158308
.build();
@@ -161,20 +311,54 @@ let MobileTabLayoutTmp = (function () {
161311
MobileTabLayoutTmp = withViewFn(MobileTabLayoutTmp, (comp) => {
162312
const [tabIndex, setTabIndex] = useState(0);
163313
const { readOnly } = useContext(ExternalEditorContext);
164-
const tabViews = (
165-
comp.children.tabs.children.manual.getView() as unknown as Array<
166-
ConstructorToComp<typeof TabOptionComp>
167-
>
168-
).filter((tab) => !tab.children.hidden.getView());
169-
const currentTab = tabViews[tabIndex];
170-
const appView = (currentTab &&
171-
currentTab.children.app.getAppId() &&
172-
currentTab.children.app.getView()) || (
173-
<EmptyContent
174-
text={readOnly ? "" : trans("aggregation.emptyTabTooltip")}
175-
style={{ height: "100%", backgroundColor: "white" }}
176-
/>
177-
);
314+
const navStyle = comp.children.navStyle.getView();
315+
const navItemStyle = comp.children.navItemStyle.getView();
316+
const navItemHoverStyle = comp.children.navItemHoverStyle.getView();
317+
const navItemActiveStyle = comp.children.navItemActiveStyle.getView();
318+
const backgroundImage = comp.children.backgroundImage.getView();
319+
const jsonItems = comp.children.jsonItems.getView();
320+
const dataOptionType = comp.children.dataOptionType.getView();
321+
const bgColor = (useContext(ThemeContext)?.theme || defaultTheme).canvas;
322+
323+
useEffect(() => {
324+
comp.children.jsonTabs.dispatchChangeValueAction({
325+
manual: jsonItems as unknown as Array<ConstructorToDataType<typeof TabOptionComp>>
326+
});
327+
}, [jsonItems]);
328+
329+
const tabViews = useMemo(() => {
330+
if (dataOptionType === DataOption.Manual) {
331+
return (comp.children.tabs.children.manual.getView() as unknown as Array<
332+
ConstructorToComp<typeof TabOptionComp>
333+
>
334+
).filter((tab) => !tab.children.hidden.getView());
335+
}
336+
if (dataOptionType === DataOption.Json) {
337+
return (comp.children.jsonTabs.children.manual.getView() as unknown as Array<
338+
ConstructorToComp<typeof TabOptionComp>
339+
>
340+
).filter((tab) => !tab.children.hidden.getView());
341+
}
342+
return [];
343+
}, [dataOptionType, jsonItems, comp.children.tabs, comp.children.jsonTabs])
344+
345+
const appView = useMemo(() => {
346+
const currentTab = tabViews[tabIndex];
347+
348+
return (currentTab &&
349+
currentTab.children.app.getAppId() &&
350+
currentTab.children.app.getView()) || (
351+
<EmptyContent
352+
text={readOnly ? "" : trans("aggregation.emptyTabTooltip")}
353+
style={{ height: "100%", backgroundColor: "white" }}
354+
/>
355+
)
356+
}, [tabIndex, tabViews, dataOptionType]);
357+
358+
let backgroundStyle = navStyle.background;
359+
if(!isEmpty(backgroundImage)) {
360+
backgroundStyle = `center / cover url('${backgroundImage}') no-repeat, ${backgroundStyle}`;
361+
}
178362

179363
const tabBarView = (
180364
<TabBarView
@@ -188,11 +372,21 @@ MobileTabLayoutTmp = withViewFn(MobileTabLayoutTmp, (comp) => {
188372
selectedKey={tabIndex + ""}
189373
onChange={(key) => setTabIndex(Number(key))}
190374
readOnly={!!readOnly}
375+
canvasBg={bgColor}
376+
tabStyle={{
377+
border: `1px solid ${navStyle.border}`,
378+
radius: navStyle.radius,
379+
text: navStyle.text,
380+
margin: navStyle.margin,
381+
padding: navStyle.padding,
382+
background: backgroundStyle,
383+
}}
384+
tabItemStyle={navItemStyle}
385+
tabItemHoverStyle={navItemHoverStyle}
386+
tabItemActiveStyle={navItemActiveStyle}
191387
/>
192388
);
193389

194-
//console.log("appView", appView);
195-
196390
if (readOnly) {
197391
return (
198392
<TabLayoutViewContainer>

client/packages/lowcoder/src/comps/comps/layout/navLayoutConstants.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,31 @@ export const jsonMenuItems = [
7474
newTab: true,
7575
},
7676
}
77+
]
78+
79+
export const mobileNavJsonMenuItems = [
80+
{
81+
label: "Option 1",
82+
icon: "https://cdn-icons-png.flaticon.com/128/149/149338.png",
83+
app: {
84+
appId: "",
85+
},
86+
hidden: false,
87+
},
88+
{
89+
label: "Option 2",
90+
icon: "https://cdn-icons-png.flaticon.com/128/149/149206.png",
91+
app: {
92+
appId: "",
93+
},
94+
hidden: false,
95+
},
96+
{
97+
label: "Option 2",
98+
icon: "https://cdn-icons-png.flaticon.com/128/149/149206.png",
99+
app: {
100+
appId: "",
101+
},
102+
hidden: true,
103+
}
77104
]

0 commit comments

Comments
 (0)