@etsoo/materialui 1.4.90 → 1.4.92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/index.d.ts +1 -0
- package/lib/cjs/index.js +1 -0
- package/lib/cjs/pages/ViewPage.d.ts +39 -3
- package/lib/cjs/pages/ViewPage.js +134 -33
- package/lib/cjs/useCurrentBreakpoint.d.ts +6 -0
- package/lib/cjs/useCurrentBreakpoint.js +19 -0
- package/lib/mjs/index.d.ts +1 -0
- package/lib/mjs/index.js +1 -0
- package/lib/mjs/pages/ViewPage.d.ts +39 -3
- package/lib/mjs/pages/ViewPage.js +133 -33
- package/lib/mjs/useCurrentBreakpoint.d.ts +6 -0
- package/lib/mjs/useCurrentBreakpoint.js +16 -0
- package/package.json +1 -1
- package/src/app/ReactApp.ts +0 -1
- package/src/index.ts +1 -0
- package/src/pages/ViewPage.tsx +223 -49
- package/src/useCurrentBreakpoint.ts +17 -0
package/lib/cjs/index.d.ts
CHANGED
|
@@ -112,6 +112,7 @@ export * from "./TiplistPro";
|
|
|
112
112
|
export * from "./TagList";
|
|
113
113
|
export * from "./TagListPro";
|
|
114
114
|
export * from "./TwoFieldInput";
|
|
115
|
+
export * from "./useCurrentBreakpoint";
|
|
115
116
|
export * from "./TooltipClick";
|
|
116
117
|
export * from "./UserAvatar";
|
|
117
118
|
export * from "./UserAvatarEditor";
|
package/lib/cjs/index.js
CHANGED
|
@@ -128,6 +128,7 @@ __exportStar(require("./TiplistPro"), exports);
|
|
|
128
128
|
__exportStar(require("./TagList"), exports);
|
|
129
129
|
__exportStar(require("./TagListPro"), exports);
|
|
130
130
|
__exportStar(require("./TwoFieldInput"), exports);
|
|
131
|
+
__exportStar(require("./useCurrentBreakpoint"), exports);
|
|
131
132
|
__exportStar(require("./TooltipClick"), exports);
|
|
132
133
|
__exportStar(require("./UserAvatar"), exports);
|
|
133
134
|
__exportStar(require("./UserAvatarEditor"), exports);
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import { GridColumnRenderProps, GridDataType } from "@etsoo/react";
|
|
2
2
|
import { DataTypes } from "@etsoo/shared";
|
|
3
|
-
import { Grid2Props } from "@mui/material";
|
|
3
|
+
import { Breakpoint, Grid2Props } from "@mui/material";
|
|
4
4
|
import React from "react";
|
|
5
5
|
import { CommonPageProps } from "./CommonPage";
|
|
6
6
|
import type { OperationMessageHandlerAll } from "../messages/OperationMessageHandler";
|
|
7
|
+
/**
|
|
8
|
+
* View page item size
|
|
9
|
+
*/
|
|
10
|
+
export type ViewPageItemSize = Record<Breakpoint, number | undefined>;
|
|
11
|
+
/**
|
|
12
|
+
* View page grid item size
|
|
13
|
+
*/
|
|
14
|
+
export declare namespace ViewPageSize {
|
|
15
|
+
const medium: ViewPageItemSize;
|
|
16
|
+
const line: ViewPageItemSize;
|
|
17
|
+
const small: ViewPageItemSize;
|
|
18
|
+
const smallLine: ViewPageItemSize;
|
|
19
|
+
function matchSize(size: ViewPageItemSize): {
|
|
20
|
+
[k: string]: number | undefined;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
7
23
|
/**
|
|
8
24
|
* View page grid item properties
|
|
9
25
|
*/
|
|
@@ -21,7 +37,7 @@ export declare function ViewPageGridItem(props: ViewPageGridItemProps): import("
|
|
|
21
37
|
/**
|
|
22
38
|
* View page row width type
|
|
23
39
|
*/
|
|
24
|
-
export type ViewPageRowType = boolean | "default" | "small" | "medium" |
|
|
40
|
+
export type ViewPageRowType = boolean | "default" | "small" | "medium" | ViewPageItemSize;
|
|
25
41
|
/**
|
|
26
42
|
* View page display field
|
|
27
43
|
*/
|
|
@@ -51,7 +67,7 @@ type ViewPageFieldTypeNarrow<T extends object> = (string & keyof T) | [string &
|
|
|
51
67
|
/**
|
|
52
68
|
* View page field type
|
|
53
69
|
*/
|
|
54
|
-
export type ViewPageFieldType<T extends object> = ViewPageFieldTypeNarrow<T> | ((data: T, refresh: () => Promise<void>) => React.ReactNode);
|
|
70
|
+
export type ViewPageFieldType<T extends object> = ViewPageFieldTypeNarrow<T> | ((data: T, refresh: () => Promise<void>) => React.ReactNode | [React.ReactNode, ViewPageItemSize]);
|
|
55
71
|
/**
|
|
56
72
|
* View page props
|
|
57
73
|
*/
|
|
@@ -95,6 +111,26 @@ export interface ViewPageProps<T extends DataTypes.StringRecord> extends Omit<Co
|
|
|
95
111
|
id: number;
|
|
96
112
|
types: string[];
|
|
97
113
|
};
|
|
114
|
+
/**
|
|
115
|
+
* Title bar
|
|
116
|
+
* @param data Data to render
|
|
117
|
+
* @returns
|
|
118
|
+
*/
|
|
119
|
+
titleBar?: (data: T) => React.ReactNode;
|
|
120
|
+
/**
|
|
121
|
+
* Left container
|
|
122
|
+
*/
|
|
123
|
+
leftContainer?: (data: T) => React.ReactNode;
|
|
124
|
+
/**
|
|
125
|
+
* Left container height in lines
|
|
126
|
+
*/
|
|
127
|
+
leftContainerLines?: number;
|
|
128
|
+
/**
|
|
129
|
+
* Left container properties
|
|
130
|
+
*/
|
|
131
|
+
leftContainerProps?: Omit<Grid2Props, "size"> & {
|
|
132
|
+
size?: ViewPageItemSize;
|
|
133
|
+
};
|
|
98
134
|
}
|
|
99
135
|
/**
|
|
100
136
|
* View page
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ViewPageSize = void 0;
|
|
6
7
|
exports.ViewPageGridItem = ViewPageGridItem;
|
|
7
8
|
exports.ViewPage = ViewPage;
|
|
8
9
|
const react_1 = require("react");
|
|
@@ -19,6 +20,43 @@ const CommonPage_1 = require("./CommonPage");
|
|
|
19
20
|
const MessageUtils_1 = require("../messages/MessageUtils");
|
|
20
21
|
const OperationMessageContainer_1 = require("../messages/OperationMessageContainer");
|
|
21
22
|
const ReactApp_1 = require("../app/ReactApp");
|
|
23
|
+
const useCurrentBreakpoint_1 = require("../useCurrentBreakpoint");
|
|
24
|
+
const breakpoints = ["xs", "sm", "md", "lg", "xl"];
|
|
25
|
+
/**
|
|
26
|
+
* View page grid item size
|
|
27
|
+
*/
|
|
28
|
+
var ViewPageSize;
|
|
29
|
+
(function (ViewPageSize) {
|
|
30
|
+
ViewPageSize.medium = {
|
|
31
|
+
xs: 12,
|
|
32
|
+
sm: 12,
|
|
33
|
+
md: 6,
|
|
34
|
+
lg: 4,
|
|
35
|
+
xl: 3
|
|
36
|
+
};
|
|
37
|
+
ViewPageSize.line = {
|
|
38
|
+
xs: 12,
|
|
39
|
+
sm: 12,
|
|
40
|
+
md: 12,
|
|
41
|
+
lg: 12,
|
|
42
|
+
xl: 12
|
|
43
|
+
};
|
|
44
|
+
ViewPageSize.small = { xs: 6, sm: 6, md: 4, lg: 3, xl: 2 };
|
|
45
|
+
ViewPageSize.smallLine = {
|
|
46
|
+
xs: 12,
|
|
47
|
+
sm: 6,
|
|
48
|
+
md: 4,
|
|
49
|
+
lg: 3,
|
|
50
|
+
xl: 2
|
|
51
|
+
};
|
|
52
|
+
function matchSize(size) {
|
|
53
|
+
return Object.fromEntries(Object.entries(size).map(([key, value]) => [
|
|
54
|
+
key,
|
|
55
|
+
value == null ? undefined : value === 12 ? 12 : 12 - value
|
|
56
|
+
]));
|
|
57
|
+
}
|
|
58
|
+
ViewPageSize.matchSize = matchSize;
|
|
59
|
+
})(ViewPageSize || (exports.ViewPageSize = ViewPageSize = {}));
|
|
22
60
|
/**
|
|
23
61
|
* View page grid item
|
|
24
62
|
* @param props Props
|
|
@@ -51,33 +89,32 @@ function getResp(singleRow) {
|
|
|
51
89
|
const size = typeof singleRow === "object"
|
|
52
90
|
? singleRow
|
|
53
91
|
: singleRow === "medium"
|
|
54
|
-
?
|
|
92
|
+
? ViewPageSize.medium
|
|
55
93
|
: singleRow === true
|
|
56
|
-
?
|
|
57
|
-
:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
lg: 3,
|
|
62
|
-
xl: 2
|
|
63
|
-
};
|
|
64
|
-
return { size };
|
|
94
|
+
? ViewPageSize.line
|
|
95
|
+
: singleRow === false
|
|
96
|
+
? ViewPageSize.smallLine
|
|
97
|
+
: ViewPageSize.small;
|
|
98
|
+
return size;
|
|
65
99
|
}
|
|
66
100
|
function getItemField(app, field, data) {
|
|
67
101
|
// Item data and label
|
|
68
|
-
let itemData, itemLabel, gridProps = {};
|
|
102
|
+
let itemData, itemLabel, gridProps = {}, size;
|
|
69
103
|
if (Array.isArray(field)) {
|
|
70
104
|
const [fieldData, fieldType, renderProps, singleRow = "small"] = field;
|
|
71
105
|
itemData = (0, GridDataFormat_1.GridDataFormat)(data[fieldData], fieldType, renderProps);
|
|
72
106
|
itemLabel = app.get(fieldData) ?? fieldData;
|
|
73
|
-
|
|
107
|
+
size = getResp(singleRow);
|
|
108
|
+
gridProps = { size };
|
|
74
109
|
}
|
|
75
110
|
else if (typeof field === "object") {
|
|
76
111
|
// Destruct
|
|
77
112
|
const { data: fieldData, dataType, label: fieldLabel, renderProps, singleRow = "default", ...rest } = field;
|
|
113
|
+
// Size
|
|
114
|
+
size = getResp(singleRow);
|
|
78
115
|
gridProps = {
|
|
79
116
|
...rest,
|
|
80
|
-
|
|
117
|
+
size
|
|
81
118
|
};
|
|
82
119
|
// Field data
|
|
83
120
|
if (typeof fieldData === "function")
|
|
@@ -102,8 +139,22 @@ function getItemField(app, field, data) {
|
|
|
102
139
|
// Single field format
|
|
103
140
|
itemData = formatItemData(app, data[field]);
|
|
104
141
|
itemLabel = app.get(field) ?? field;
|
|
142
|
+
size = ViewPageSize.small;
|
|
143
|
+
gridProps = { size };
|
|
144
|
+
}
|
|
145
|
+
return [itemData, itemLabel, gridProps, size];
|
|
146
|
+
}
|
|
147
|
+
function getItemSize(bp, size) {
|
|
148
|
+
const v = size[bp];
|
|
149
|
+
if (v != null)
|
|
150
|
+
return v;
|
|
151
|
+
const index = breakpoints.indexOf(bp);
|
|
152
|
+
for (let i = index; i >= 0; i--) {
|
|
153
|
+
const v = size[breakpoints[i]];
|
|
154
|
+
if (v != null)
|
|
155
|
+
return v;
|
|
105
156
|
}
|
|
106
|
-
return
|
|
157
|
+
return 12;
|
|
107
158
|
}
|
|
108
159
|
/**
|
|
109
160
|
* View page
|
|
@@ -113,19 +164,80 @@ function ViewPage(props) {
|
|
|
113
164
|
// Global app
|
|
114
165
|
const app = (0, ReactApp_1.useRequiredAppContext)();
|
|
115
166
|
// Destruct
|
|
116
|
-
const { actions, children, fields, loadData, paddings = MUGlobal_1.MUGlobal.pagePaddings, spacing = MUGlobal_1.MUGlobal.half(MUGlobal_1.MUGlobal.pagePaddings), supportRefresh = true, fabColumnDirection = true, fabTop = true, supportBack = true, pullToRefresh = true, gridRef, operationMessageHandler, ...rest } = props;
|
|
167
|
+
const { actions, children, fields, loadData, paddings = MUGlobal_1.MUGlobal.pagePaddings, spacing = MUGlobal_1.MUGlobal.half(MUGlobal_1.MUGlobal.pagePaddings), supportRefresh = true, fabColumnDirection = true, fabTop = true, supportBack = true, pullToRefresh = true, gridRef, operationMessageHandler, titleBar, leftContainer, leftContainerLines = 3, leftContainerProps = {}, ...rest } = props;
|
|
168
|
+
// Current breakpoint
|
|
169
|
+
const bp = (0, useCurrentBreakpoint_1.useCurrentBreakpoint)();
|
|
117
170
|
// Data
|
|
118
171
|
const [data, setData] = react_3.default.useState();
|
|
119
172
|
// Labels
|
|
120
173
|
const labels = Labels_1.Labels.CommonPage;
|
|
121
174
|
// Container
|
|
122
175
|
const pullContainer = "#page-container";
|
|
176
|
+
// Left container
|
|
177
|
+
const { size = ViewPageSize.smallLine, ...leftContainerPropsRest } = leftContainerProps;
|
|
123
178
|
// Load data
|
|
124
179
|
const refresh = react_3.default.useCallback(async () => {
|
|
125
180
|
const result = await loadData();
|
|
126
181
|
// When failed or no data returned, show the loading bar
|
|
127
182
|
setData(result);
|
|
128
183
|
}, [loadData]);
|
|
184
|
+
// Create fields
|
|
185
|
+
const fieldIndexRef = react_3.default.useRef(0);
|
|
186
|
+
const createFields = react_3.default.useCallback((data, maxItems = 0) => {
|
|
187
|
+
let validItems = 0;
|
|
188
|
+
const items = [];
|
|
189
|
+
let i = fieldIndexRef.current;
|
|
190
|
+
for (; i < fields.length; i++) {
|
|
191
|
+
const field = fields[i];
|
|
192
|
+
let oneSize;
|
|
193
|
+
let oneItem;
|
|
194
|
+
if (typeof field === "function") {
|
|
195
|
+
// Most flexible way, do whatever you want
|
|
196
|
+
const createdResult = field(data, refresh);
|
|
197
|
+
if (createdResult == null || createdResult === "")
|
|
198
|
+
continue;
|
|
199
|
+
if (Array.isArray(createdResult)) {
|
|
200
|
+
const [created, size] = createdResult;
|
|
201
|
+
oneSize = size;
|
|
202
|
+
oneItem = created;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
oneSize = ViewPageSize.line;
|
|
206
|
+
oneItem = createdResult;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
const [itemData, itemLabel, gridProps, size] = getItemField(app, field, data);
|
|
211
|
+
// Some callback function may return '' instead of undefined
|
|
212
|
+
if (itemData == null || itemData === "")
|
|
213
|
+
continue;
|
|
214
|
+
oneSize = size;
|
|
215
|
+
oneItem = ((0, react_1.createElement)(ViewPageGridItem, { ...gridProps, key: i, data: itemData, label: itemLabel }));
|
|
216
|
+
}
|
|
217
|
+
// Max lines
|
|
218
|
+
if (maxItems > 0) {
|
|
219
|
+
const itemSize = getItemSize(bp, oneSize);
|
|
220
|
+
if (maxItems < validItems + itemSize) {
|
|
221
|
+
fieldIndexRef.current = i;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
items.push(oneItem);
|
|
226
|
+
validItems += itemSize;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
items.push(oneItem);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (maxItems === 0) {
|
|
234
|
+
fieldIndexRef.current = 0;
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
fieldIndexRef.current = i;
|
|
238
|
+
}
|
|
239
|
+
return items;
|
|
240
|
+
}, [app, refresh, fields, data, bp]);
|
|
129
241
|
react_3.default.useEffect(() => {
|
|
130
242
|
const refreshHandler = async () => {
|
|
131
243
|
await refresh();
|
|
@@ -135,29 +247,18 @@ function ViewPage(props) {
|
|
|
135
247
|
MessageUtils_1.MessageUtils.offRefresh(refreshHandler);
|
|
136
248
|
};
|
|
137
249
|
}, [refresh]);
|
|
138
|
-
|
|
250
|
+
let leftResult;
|
|
251
|
+
return ((0, jsx_runtime_1.jsx)(CommonPage_1.CommonPage, { paddings: paddings, onRefresh: supportRefresh ? refresh : undefined, onUpdate: supportRefresh ? undefined : refresh, sx: {
|
|
252
|
+
".MuiTypography-subtitle2": {
|
|
253
|
+
fontWeight: "bold"
|
|
254
|
+
}
|
|
255
|
+
}, ...rest, scrollContainer: globalThis, fabColumnDirection: fabColumnDirection, fabTop: fabTop, supportBack: supportBack, children: data == null ? ((0, jsx_runtime_1.jsx)(material_1.LinearProgress, {})) : ((0, jsx_runtime_1.jsxs)(react_3.default.Fragment, { children: [operationMessageHandler && ((0, jsx_runtime_1.jsx)(OperationMessageContainer_1.OperationMessageContainer, { handler: "id" in operationMessageHandler
|
|
139
256
|
? [
|
|
140
257
|
operationMessageHandler.types,
|
|
141
258
|
refresh,
|
|
142
259
|
operationMessageHandler.id
|
|
143
260
|
]
|
|
144
|
-
: operationMessageHandler })), (0, jsx_runtime_1.
|
|
145
|
-
".MuiTypography-subtitle2": {
|
|
146
|
-
fontWeight: "bold"
|
|
147
|
-
}
|
|
148
|
-
}, children: fields.map((field, index) => {
|
|
149
|
-
// Get data
|
|
150
|
-
if (typeof field === "function") {
|
|
151
|
-
// Most flexible way, do whatever you want
|
|
152
|
-
return field(data, refresh);
|
|
153
|
-
}
|
|
154
|
-
const [itemData, itemLabel, gridProps] = getItemField(app, field, data);
|
|
155
|
-
// Some callback function may return '' instead of undefined
|
|
156
|
-
if (itemData == null || itemData === "")
|
|
157
|
-
return undefined;
|
|
158
|
-
// Layout
|
|
159
|
-
return ((0, react_1.createElement)(ViewPageGridItem, { ...gridProps, key: index, data: itemData, label: itemLabel }));
|
|
160
|
-
}) }), actions !== null && ((0, jsx_runtime_1.jsx)(material_1.Stack, { className: "ET-ViewPage-Actions", direction: "row", width: "100%", flexWrap: "wrap", justifyContent: "flex-end", paddingTop: actions == null ? undefined : paddings, paddingBottom: paddings, gap: paddings, children: actions != null && shared_1.Utils.getResult(actions, data, refresh) })), shared_1.Utils.getResult(children, data, refresh), pullToRefresh && ((0, jsx_runtime_1.jsx)(PullToRefreshUI_1.PullToRefreshUI, { mainElement: pullContainer, triggerElement: pullContainer, instructionsPullToRefresh: labels.pullToRefresh, instructionsReleaseToRefresh: labels.releaseToRefresh, instructionsRefreshing: labels.refreshing, onRefresh: refresh, shouldPullToRefresh: () => {
|
|
261
|
+
: operationMessageHandler })), titleBar && titleBar(data), (0, jsx_runtime_1.jsxs)(material_1.Grid2, { container: true, justifyContent: "left", className: "ET-ViewPage", ref: gridRef, spacing: spacing, children: [leftContainer && (leftResult = leftContainer(data)) != null && ((0, jsx_runtime_1.jsxs)(react_3.default.Fragment, { children: [(0, jsx_runtime_1.jsx)(material_1.Grid2, { container: true, className: "ET-ViewPage-LeftContainer", spacing: spacing, size: size, ...leftContainerPropsRest, children: leftResult }), (0, jsx_runtime_1.jsx)(material_1.Grid2, { container: true, className: "ET-ViewPage-LeftOthers", spacing: spacing, size: ViewPageSize.matchSize(size), children: createFields(data, leftContainerLines * (12 - getItemSize(bp, size))) })] })), createFields(data)] }), actions !== null && ((0, jsx_runtime_1.jsx)(material_1.Stack, { className: "ET-ViewPage-Actions", direction: "row", width: "100%", flexWrap: "wrap", justifyContent: "flex-end", paddingTop: actions == null ? undefined : paddings, paddingBottom: paddings, gap: paddings, children: actions != null && shared_1.Utils.getResult(actions, data, refresh) })), shared_1.Utils.getResult(children, data, refresh), pullToRefresh && ((0, jsx_runtime_1.jsx)(PullToRefreshUI_1.PullToRefreshUI, { mainElement: pullContainer, triggerElement: pullContainer, instructionsPullToRefresh: labels.pullToRefresh, instructionsReleaseToRefresh: labels.releaseToRefresh, instructionsRefreshing: labels.refreshing, onRefresh: refresh, shouldPullToRefresh: () => {
|
|
161
262
|
const container = document.querySelector(pullContainer);
|
|
162
263
|
return !container?.scrollTop;
|
|
163
264
|
} })), (0, jsx_runtime_1.jsx)(react_2.ScrollRestoration, {})] })) }));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useCurrentBreakpoint = useCurrentBreakpoint;
|
|
4
|
+
const material_1 = require("@mui/material");
|
|
5
|
+
/**
|
|
6
|
+
* Hook to get the current breakpoint
|
|
7
|
+
* @returns The current breakpoint
|
|
8
|
+
*/
|
|
9
|
+
function useCurrentBreakpoint() {
|
|
10
|
+
const theme = (0, material_1.useTheme)();
|
|
11
|
+
const items = [
|
|
12
|
+
(0, material_1.useMediaQuery)(theme.breakpoints.down("xs")) ? "xs" : null,
|
|
13
|
+
(0, material_1.useMediaQuery)(theme.breakpoints.between("xs", "sm")) ? "sm" : null,
|
|
14
|
+
(0, material_1.useMediaQuery)(theme.breakpoints.between("sm", "md")) ? "md" : null,
|
|
15
|
+
(0, material_1.useMediaQuery)(theme.breakpoints.between("md", "lg")) ? "lg" : null,
|
|
16
|
+
(0, material_1.useMediaQuery)(theme.breakpoints.up("lg")) ? "xl" : null
|
|
17
|
+
];
|
|
18
|
+
return items.find((item) => item != null) ?? "lg";
|
|
19
|
+
}
|
package/lib/mjs/index.d.ts
CHANGED
|
@@ -112,6 +112,7 @@ export * from "./TiplistPro";
|
|
|
112
112
|
export * from "./TagList";
|
|
113
113
|
export * from "./TagListPro";
|
|
114
114
|
export * from "./TwoFieldInput";
|
|
115
|
+
export * from "./useCurrentBreakpoint";
|
|
115
116
|
export * from "./TooltipClick";
|
|
116
117
|
export * from "./UserAvatar";
|
|
117
118
|
export * from "./UserAvatarEditor";
|
package/lib/mjs/index.js
CHANGED
|
@@ -112,6 +112,7 @@ export * from "./TiplistPro";
|
|
|
112
112
|
export * from "./TagList";
|
|
113
113
|
export * from "./TagListPro";
|
|
114
114
|
export * from "./TwoFieldInput";
|
|
115
|
+
export * from "./useCurrentBreakpoint";
|
|
115
116
|
export * from "./TooltipClick";
|
|
116
117
|
export * from "./UserAvatar";
|
|
117
118
|
export * from "./UserAvatarEditor";
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import { GridColumnRenderProps, GridDataType } from "@etsoo/react";
|
|
2
2
|
import { DataTypes } from "@etsoo/shared";
|
|
3
|
-
import { Grid2Props } from "@mui/material";
|
|
3
|
+
import { Breakpoint, Grid2Props } from "@mui/material";
|
|
4
4
|
import React from "react";
|
|
5
5
|
import { CommonPageProps } from "./CommonPage";
|
|
6
6
|
import type { OperationMessageHandlerAll } from "../messages/OperationMessageHandler";
|
|
7
|
+
/**
|
|
8
|
+
* View page item size
|
|
9
|
+
*/
|
|
10
|
+
export type ViewPageItemSize = Record<Breakpoint, number | undefined>;
|
|
11
|
+
/**
|
|
12
|
+
* View page grid item size
|
|
13
|
+
*/
|
|
14
|
+
export declare namespace ViewPageSize {
|
|
15
|
+
const medium: ViewPageItemSize;
|
|
16
|
+
const line: ViewPageItemSize;
|
|
17
|
+
const small: ViewPageItemSize;
|
|
18
|
+
const smallLine: ViewPageItemSize;
|
|
19
|
+
function matchSize(size: ViewPageItemSize): {
|
|
20
|
+
[k: string]: number | undefined;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
7
23
|
/**
|
|
8
24
|
* View page grid item properties
|
|
9
25
|
*/
|
|
@@ -21,7 +37,7 @@ export declare function ViewPageGridItem(props: ViewPageGridItemProps): import("
|
|
|
21
37
|
/**
|
|
22
38
|
* View page row width type
|
|
23
39
|
*/
|
|
24
|
-
export type ViewPageRowType = boolean | "default" | "small" | "medium" |
|
|
40
|
+
export type ViewPageRowType = boolean | "default" | "small" | "medium" | ViewPageItemSize;
|
|
25
41
|
/**
|
|
26
42
|
* View page display field
|
|
27
43
|
*/
|
|
@@ -51,7 +67,7 @@ type ViewPageFieldTypeNarrow<T extends object> = (string & keyof T) | [string &
|
|
|
51
67
|
/**
|
|
52
68
|
* View page field type
|
|
53
69
|
*/
|
|
54
|
-
export type ViewPageFieldType<T extends object> = ViewPageFieldTypeNarrow<T> | ((data: T, refresh: () => Promise<void>) => React.ReactNode);
|
|
70
|
+
export type ViewPageFieldType<T extends object> = ViewPageFieldTypeNarrow<T> | ((data: T, refresh: () => Promise<void>) => React.ReactNode | [React.ReactNode, ViewPageItemSize]);
|
|
55
71
|
/**
|
|
56
72
|
* View page props
|
|
57
73
|
*/
|
|
@@ -95,6 +111,26 @@ export interface ViewPageProps<T extends DataTypes.StringRecord> extends Omit<Co
|
|
|
95
111
|
id: number;
|
|
96
112
|
types: string[];
|
|
97
113
|
};
|
|
114
|
+
/**
|
|
115
|
+
* Title bar
|
|
116
|
+
* @param data Data to render
|
|
117
|
+
* @returns
|
|
118
|
+
*/
|
|
119
|
+
titleBar?: (data: T) => React.ReactNode;
|
|
120
|
+
/**
|
|
121
|
+
* Left container
|
|
122
|
+
*/
|
|
123
|
+
leftContainer?: (data: T) => React.ReactNode;
|
|
124
|
+
/**
|
|
125
|
+
* Left container height in lines
|
|
126
|
+
*/
|
|
127
|
+
leftContainerLines?: number;
|
|
128
|
+
/**
|
|
129
|
+
* Left container properties
|
|
130
|
+
*/
|
|
131
|
+
leftContainerProps?: Omit<Grid2Props, "size"> & {
|
|
132
|
+
size?: ViewPageItemSize;
|
|
133
|
+
};
|
|
98
134
|
}
|
|
99
135
|
/**
|
|
100
136
|
* View page
|
|
@@ -12,6 +12,43 @@ import { CommonPage } from "./CommonPage";
|
|
|
12
12
|
import { MessageUtils } from "../messages/MessageUtils";
|
|
13
13
|
import { OperationMessageContainer } from "../messages/OperationMessageContainer";
|
|
14
14
|
import { useRequiredAppContext } from "../app/ReactApp";
|
|
15
|
+
import { useCurrentBreakpoint } from "../useCurrentBreakpoint";
|
|
16
|
+
const breakpoints = ["xs", "sm", "md", "lg", "xl"];
|
|
17
|
+
/**
|
|
18
|
+
* View page grid item size
|
|
19
|
+
*/
|
|
20
|
+
export var ViewPageSize;
|
|
21
|
+
(function (ViewPageSize) {
|
|
22
|
+
ViewPageSize.medium = {
|
|
23
|
+
xs: 12,
|
|
24
|
+
sm: 12,
|
|
25
|
+
md: 6,
|
|
26
|
+
lg: 4,
|
|
27
|
+
xl: 3
|
|
28
|
+
};
|
|
29
|
+
ViewPageSize.line = {
|
|
30
|
+
xs: 12,
|
|
31
|
+
sm: 12,
|
|
32
|
+
md: 12,
|
|
33
|
+
lg: 12,
|
|
34
|
+
xl: 12
|
|
35
|
+
};
|
|
36
|
+
ViewPageSize.small = { xs: 6, sm: 6, md: 4, lg: 3, xl: 2 };
|
|
37
|
+
ViewPageSize.smallLine = {
|
|
38
|
+
xs: 12,
|
|
39
|
+
sm: 6,
|
|
40
|
+
md: 4,
|
|
41
|
+
lg: 3,
|
|
42
|
+
xl: 2
|
|
43
|
+
};
|
|
44
|
+
function matchSize(size) {
|
|
45
|
+
return Object.fromEntries(Object.entries(size).map(([key, value]) => [
|
|
46
|
+
key,
|
|
47
|
+
value == null ? undefined : value === 12 ? 12 : 12 - value
|
|
48
|
+
]));
|
|
49
|
+
}
|
|
50
|
+
ViewPageSize.matchSize = matchSize;
|
|
51
|
+
})(ViewPageSize || (ViewPageSize = {}));
|
|
15
52
|
/**
|
|
16
53
|
* View page grid item
|
|
17
54
|
* @param props Props
|
|
@@ -44,33 +81,32 @@ function getResp(singleRow) {
|
|
|
44
81
|
const size = typeof singleRow === "object"
|
|
45
82
|
? singleRow
|
|
46
83
|
: singleRow === "medium"
|
|
47
|
-
?
|
|
84
|
+
? ViewPageSize.medium
|
|
48
85
|
: singleRow === true
|
|
49
|
-
?
|
|
50
|
-
:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
lg: 3,
|
|
55
|
-
xl: 2
|
|
56
|
-
};
|
|
57
|
-
return { size };
|
|
86
|
+
? ViewPageSize.line
|
|
87
|
+
: singleRow === false
|
|
88
|
+
? ViewPageSize.smallLine
|
|
89
|
+
: ViewPageSize.small;
|
|
90
|
+
return size;
|
|
58
91
|
}
|
|
59
92
|
function getItemField(app, field, data) {
|
|
60
93
|
// Item data and label
|
|
61
|
-
let itemData, itemLabel, gridProps = {};
|
|
94
|
+
let itemData, itemLabel, gridProps = {}, size;
|
|
62
95
|
if (Array.isArray(field)) {
|
|
63
96
|
const [fieldData, fieldType, renderProps, singleRow = "small"] = field;
|
|
64
97
|
itemData = GridDataFormat(data[fieldData], fieldType, renderProps);
|
|
65
98
|
itemLabel = app.get(fieldData) ?? fieldData;
|
|
66
|
-
|
|
99
|
+
size = getResp(singleRow);
|
|
100
|
+
gridProps = { size };
|
|
67
101
|
}
|
|
68
102
|
else if (typeof field === "object") {
|
|
69
103
|
// Destruct
|
|
70
104
|
const { data: fieldData, dataType, label: fieldLabel, renderProps, singleRow = "default", ...rest } = field;
|
|
105
|
+
// Size
|
|
106
|
+
size = getResp(singleRow);
|
|
71
107
|
gridProps = {
|
|
72
108
|
...rest,
|
|
73
|
-
|
|
109
|
+
size
|
|
74
110
|
};
|
|
75
111
|
// Field data
|
|
76
112
|
if (typeof fieldData === "function")
|
|
@@ -95,8 +131,22 @@ function getItemField(app, field, data) {
|
|
|
95
131
|
// Single field format
|
|
96
132
|
itemData = formatItemData(app, data[field]);
|
|
97
133
|
itemLabel = app.get(field) ?? field;
|
|
134
|
+
size = ViewPageSize.small;
|
|
135
|
+
gridProps = { size };
|
|
136
|
+
}
|
|
137
|
+
return [itemData, itemLabel, gridProps, size];
|
|
138
|
+
}
|
|
139
|
+
function getItemSize(bp, size) {
|
|
140
|
+
const v = size[bp];
|
|
141
|
+
if (v != null)
|
|
142
|
+
return v;
|
|
143
|
+
const index = breakpoints.indexOf(bp);
|
|
144
|
+
for (let i = index; i >= 0; i--) {
|
|
145
|
+
const v = size[breakpoints[i]];
|
|
146
|
+
if (v != null)
|
|
147
|
+
return v;
|
|
98
148
|
}
|
|
99
|
-
return
|
|
149
|
+
return 12;
|
|
100
150
|
}
|
|
101
151
|
/**
|
|
102
152
|
* View page
|
|
@@ -106,19 +156,80 @@ export function ViewPage(props) {
|
|
|
106
156
|
// Global app
|
|
107
157
|
const app = useRequiredAppContext();
|
|
108
158
|
// Destruct
|
|
109
|
-
const { actions, children, fields, loadData, paddings = MUGlobal.pagePaddings, spacing = MUGlobal.half(MUGlobal.pagePaddings), supportRefresh = true, fabColumnDirection = true, fabTop = true, supportBack = true, pullToRefresh = true, gridRef, operationMessageHandler, ...rest } = props;
|
|
159
|
+
const { actions, children, fields, loadData, paddings = MUGlobal.pagePaddings, spacing = MUGlobal.half(MUGlobal.pagePaddings), supportRefresh = true, fabColumnDirection = true, fabTop = true, supportBack = true, pullToRefresh = true, gridRef, operationMessageHandler, titleBar, leftContainer, leftContainerLines = 3, leftContainerProps = {}, ...rest } = props;
|
|
160
|
+
// Current breakpoint
|
|
161
|
+
const bp = useCurrentBreakpoint();
|
|
110
162
|
// Data
|
|
111
163
|
const [data, setData] = React.useState();
|
|
112
164
|
// Labels
|
|
113
165
|
const labels = Labels.CommonPage;
|
|
114
166
|
// Container
|
|
115
167
|
const pullContainer = "#page-container";
|
|
168
|
+
// Left container
|
|
169
|
+
const { size = ViewPageSize.smallLine, ...leftContainerPropsRest } = leftContainerProps;
|
|
116
170
|
// Load data
|
|
117
171
|
const refresh = React.useCallback(async () => {
|
|
118
172
|
const result = await loadData();
|
|
119
173
|
// When failed or no data returned, show the loading bar
|
|
120
174
|
setData(result);
|
|
121
175
|
}, [loadData]);
|
|
176
|
+
// Create fields
|
|
177
|
+
const fieldIndexRef = React.useRef(0);
|
|
178
|
+
const createFields = React.useCallback((data, maxItems = 0) => {
|
|
179
|
+
let validItems = 0;
|
|
180
|
+
const items = [];
|
|
181
|
+
let i = fieldIndexRef.current;
|
|
182
|
+
for (; i < fields.length; i++) {
|
|
183
|
+
const field = fields[i];
|
|
184
|
+
let oneSize;
|
|
185
|
+
let oneItem;
|
|
186
|
+
if (typeof field === "function") {
|
|
187
|
+
// Most flexible way, do whatever you want
|
|
188
|
+
const createdResult = field(data, refresh);
|
|
189
|
+
if (createdResult == null || createdResult === "")
|
|
190
|
+
continue;
|
|
191
|
+
if (Array.isArray(createdResult)) {
|
|
192
|
+
const [created, size] = createdResult;
|
|
193
|
+
oneSize = size;
|
|
194
|
+
oneItem = created;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
oneSize = ViewPageSize.line;
|
|
198
|
+
oneItem = createdResult;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const [itemData, itemLabel, gridProps, size] = getItemField(app, field, data);
|
|
203
|
+
// Some callback function may return '' instead of undefined
|
|
204
|
+
if (itemData == null || itemData === "")
|
|
205
|
+
continue;
|
|
206
|
+
oneSize = size;
|
|
207
|
+
oneItem = (_createElement(ViewPageGridItem, { ...gridProps, key: i, data: itemData, label: itemLabel }));
|
|
208
|
+
}
|
|
209
|
+
// Max lines
|
|
210
|
+
if (maxItems > 0) {
|
|
211
|
+
const itemSize = getItemSize(bp, oneSize);
|
|
212
|
+
if (maxItems < validItems + itemSize) {
|
|
213
|
+
fieldIndexRef.current = i;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
items.push(oneItem);
|
|
218
|
+
validItems += itemSize;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
items.push(oneItem);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (maxItems === 0) {
|
|
226
|
+
fieldIndexRef.current = 0;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
fieldIndexRef.current = i;
|
|
230
|
+
}
|
|
231
|
+
return items;
|
|
232
|
+
}, [app, refresh, fields, data, bp]);
|
|
122
233
|
React.useEffect(() => {
|
|
123
234
|
const refreshHandler = async () => {
|
|
124
235
|
await refresh();
|
|
@@ -128,29 +239,18 @@ export function ViewPage(props) {
|
|
|
128
239
|
MessageUtils.offRefresh(refreshHandler);
|
|
129
240
|
};
|
|
130
241
|
}, [refresh]);
|
|
131
|
-
|
|
242
|
+
let leftResult;
|
|
243
|
+
return (_jsx(CommonPage, { paddings: paddings, onRefresh: supportRefresh ? refresh : undefined, onUpdate: supportRefresh ? undefined : refresh, sx: {
|
|
244
|
+
".MuiTypography-subtitle2": {
|
|
245
|
+
fontWeight: "bold"
|
|
246
|
+
}
|
|
247
|
+
}, ...rest, scrollContainer: globalThis, fabColumnDirection: fabColumnDirection, fabTop: fabTop, supportBack: supportBack, children: data == null ? (_jsx(LinearProgress, {})) : (_jsxs(React.Fragment, { children: [operationMessageHandler && (_jsx(OperationMessageContainer, { handler: "id" in operationMessageHandler
|
|
132
248
|
? [
|
|
133
249
|
operationMessageHandler.types,
|
|
134
250
|
refresh,
|
|
135
251
|
operationMessageHandler.id
|
|
136
252
|
]
|
|
137
|
-
: operationMessageHandler })),
|
|
138
|
-
".MuiTypography-subtitle2": {
|
|
139
|
-
fontWeight: "bold"
|
|
140
|
-
}
|
|
141
|
-
}, children: fields.map((field, index) => {
|
|
142
|
-
// Get data
|
|
143
|
-
if (typeof field === "function") {
|
|
144
|
-
// Most flexible way, do whatever you want
|
|
145
|
-
return field(data, refresh);
|
|
146
|
-
}
|
|
147
|
-
const [itemData, itemLabel, gridProps] = getItemField(app, field, data);
|
|
148
|
-
// Some callback function may return '' instead of undefined
|
|
149
|
-
if (itemData == null || itemData === "")
|
|
150
|
-
return undefined;
|
|
151
|
-
// Layout
|
|
152
|
-
return (_createElement(ViewPageGridItem, { ...gridProps, key: index, data: itemData, label: itemLabel }));
|
|
153
|
-
}) }), actions !== null && (_jsx(Stack, { className: "ET-ViewPage-Actions", direction: "row", width: "100%", flexWrap: "wrap", justifyContent: "flex-end", paddingTop: actions == null ? undefined : paddings, paddingBottom: paddings, gap: paddings, children: actions != null && Utils.getResult(actions, data, refresh) })), Utils.getResult(children, data, refresh), pullToRefresh && (_jsx(PullToRefreshUI, { mainElement: pullContainer, triggerElement: pullContainer, instructionsPullToRefresh: labels.pullToRefresh, instructionsReleaseToRefresh: labels.releaseToRefresh, instructionsRefreshing: labels.refreshing, onRefresh: refresh, shouldPullToRefresh: () => {
|
|
253
|
+
: operationMessageHandler })), titleBar && titleBar(data), _jsxs(Grid2, { container: true, justifyContent: "left", className: "ET-ViewPage", ref: gridRef, spacing: spacing, children: [leftContainer && (leftResult = leftContainer(data)) != null && (_jsxs(React.Fragment, { children: [_jsx(Grid2, { container: true, className: "ET-ViewPage-LeftContainer", spacing: spacing, size: size, ...leftContainerPropsRest, children: leftResult }), _jsx(Grid2, { container: true, className: "ET-ViewPage-LeftOthers", spacing: spacing, size: ViewPageSize.matchSize(size), children: createFields(data, leftContainerLines * (12 - getItemSize(bp, size))) })] })), createFields(data)] }), actions !== null && (_jsx(Stack, { className: "ET-ViewPage-Actions", direction: "row", width: "100%", flexWrap: "wrap", justifyContent: "flex-end", paddingTop: actions == null ? undefined : paddings, paddingBottom: paddings, gap: paddings, children: actions != null && Utils.getResult(actions, data, refresh) })), Utils.getResult(children, data, refresh), pullToRefresh && (_jsx(PullToRefreshUI, { mainElement: pullContainer, triggerElement: pullContainer, instructionsPullToRefresh: labels.pullToRefresh, instructionsReleaseToRefresh: labels.releaseToRefresh, instructionsRefreshing: labels.refreshing, onRefresh: refresh, shouldPullToRefresh: () => {
|
|
154
254
|
const container = document.querySelector(pullContainer);
|
|
155
255
|
return !container?.scrollTop;
|
|
156
256
|
} })), _jsx(ScrollRestoration, {})] })) }));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useMediaQuery, useTheme } from "@mui/material";
|
|
2
|
+
/**
|
|
3
|
+
* Hook to get the current breakpoint
|
|
4
|
+
* @returns The current breakpoint
|
|
5
|
+
*/
|
|
6
|
+
export function useCurrentBreakpoint() {
|
|
7
|
+
const theme = useTheme();
|
|
8
|
+
const items = [
|
|
9
|
+
useMediaQuery(theme.breakpoints.down("xs")) ? "xs" : null,
|
|
10
|
+
useMediaQuery(theme.breakpoints.between("xs", "sm")) ? "sm" : null,
|
|
11
|
+
useMediaQuery(theme.breakpoints.between("sm", "md")) ? "md" : null,
|
|
12
|
+
useMediaQuery(theme.breakpoints.between("md", "lg")) ? "lg" : null,
|
|
13
|
+
useMediaQuery(theme.breakpoints.up("lg")) ? "xl" : null
|
|
14
|
+
];
|
|
15
|
+
return items.find((item) => item != null) ?? "lg";
|
|
16
|
+
}
|
package/package.json
CHANGED
package/src/app/ReactApp.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -117,6 +117,7 @@ export * from "./TiplistPro";
|
|
|
117
117
|
export * from "./TagList";
|
|
118
118
|
export * from "./TagListPro";
|
|
119
119
|
export * from "./TwoFieldInput";
|
|
120
|
+
export * from "./useCurrentBreakpoint";
|
|
120
121
|
export * from "./TooltipClick";
|
|
121
122
|
export * from "./UserAvatar";
|
|
122
123
|
export * from "./UserAvatarEditor";
|
package/src/pages/ViewPage.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "@etsoo/react";
|
|
6
6
|
import { DataTypes, Utils } from "@etsoo/shared";
|
|
7
7
|
import {
|
|
8
|
+
Breakpoint,
|
|
8
9
|
Grid2,
|
|
9
10
|
Grid2Props,
|
|
10
11
|
LinearProgress,
|
|
@@ -22,6 +23,50 @@ import { MessageUtils } from "../messages/MessageUtils";
|
|
|
22
23
|
import type { RefreshHandler } from "../messages/RefreshHandler";
|
|
23
24
|
import { OperationMessageContainer } from "../messages/OperationMessageContainer";
|
|
24
25
|
import { ReactAppType, useRequiredAppContext } from "../app/ReactApp";
|
|
26
|
+
import { useCurrentBreakpoint } from "../useCurrentBreakpoint";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* View page item size
|
|
30
|
+
*/
|
|
31
|
+
export type ViewPageItemSize = Record<Breakpoint, number | undefined>;
|
|
32
|
+
|
|
33
|
+
const breakpoints: Breakpoint[] = ["xs", "sm", "md", "lg", "xl"];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* View page grid item size
|
|
37
|
+
*/
|
|
38
|
+
export namespace ViewPageSize {
|
|
39
|
+
export const medium: ViewPageItemSize = {
|
|
40
|
+
xs: 12,
|
|
41
|
+
sm: 12,
|
|
42
|
+
md: 6,
|
|
43
|
+
lg: 4,
|
|
44
|
+
xl: 3
|
|
45
|
+
};
|
|
46
|
+
export const line: ViewPageItemSize = {
|
|
47
|
+
xs: 12,
|
|
48
|
+
sm: 12,
|
|
49
|
+
md: 12,
|
|
50
|
+
lg: 12,
|
|
51
|
+
xl: 12
|
|
52
|
+
};
|
|
53
|
+
export const small: ViewPageItemSize = { xs: 6, sm: 6, md: 4, lg: 3, xl: 2 };
|
|
54
|
+
export const smallLine: ViewPageItemSize = {
|
|
55
|
+
xs: 12,
|
|
56
|
+
sm: 6,
|
|
57
|
+
md: 4,
|
|
58
|
+
lg: 3,
|
|
59
|
+
xl: 2
|
|
60
|
+
};
|
|
61
|
+
export function matchSize(size: ViewPageItemSize) {
|
|
62
|
+
return Object.fromEntries(
|
|
63
|
+
Object.entries(size).map(([key, value]) => [
|
|
64
|
+
key,
|
|
65
|
+
value == null ? undefined : value === 12 ? 12 : 12 - value
|
|
66
|
+
])
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
25
70
|
|
|
26
71
|
/**
|
|
27
72
|
* View page grid item properties
|
|
@@ -69,7 +114,12 @@ export function ViewPageGridItem(props: ViewPageGridItemProps) {
|
|
|
69
114
|
/**
|
|
70
115
|
* View page row width type
|
|
71
116
|
*/
|
|
72
|
-
export type ViewPageRowType =
|
|
117
|
+
export type ViewPageRowType =
|
|
118
|
+
| boolean
|
|
119
|
+
| "default"
|
|
120
|
+
| "small"
|
|
121
|
+
| "medium"
|
|
122
|
+
| ViewPageItemSize;
|
|
73
123
|
|
|
74
124
|
/**
|
|
75
125
|
* View page display field
|
|
@@ -111,7 +161,10 @@ type ViewPageFieldTypeNarrow<T extends object> =
|
|
|
111
161
|
*/
|
|
112
162
|
export type ViewPageFieldType<T extends object> =
|
|
113
163
|
| ViewPageFieldTypeNarrow<T>
|
|
114
|
-
| ((
|
|
164
|
+
| ((
|
|
165
|
+
data: T,
|
|
166
|
+
refresh: () => Promise<void>
|
|
167
|
+
) => React.ReactNode | [React.ReactNode, ViewPageItemSize]);
|
|
115
168
|
|
|
116
169
|
/**
|
|
117
170
|
* View page props
|
|
@@ -168,6 +221,28 @@ export interface ViewPageProps<T extends DataTypes.StringRecord>
|
|
|
168
221
|
operationMessageHandler?:
|
|
169
222
|
| OperationMessageHandlerAll
|
|
170
223
|
| { id: number; types: string[] };
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Title bar
|
|
227
|
+
* @param data Data to render
|
|
228
|
+
* @returns
|
|
229
|
+
*/
|
|
230
|
+
titleBar?: (data: T) => React.ReactNode;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Left container
|
|
234
|
+
*/
|
|
235
|
+
leftContainer?: (data: T) => React.ReactNode;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Left container height in lines
|
|
239
|
+
*/
|
|
240
|
+
leftContainerLines?: number;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Left container properties
|
|
244
|
+
*/
|
|
245
|
+
leftContainerProps?: Omit<Grid2Props, "size"> & { size?: ViewPageItemSize };
|
|
171
246
|
}
|
|
172
247
|
|
|
173
248
|
function formatItemData(
|
|
@@ -185,34 +260,32 @@ function getResp(singleRow: ViewPageRowType) {
|
|
|
185
260
|
typeof singleRow === "object"
|
|
186
261
|
? singleRow
|
|
187
262
|
: singleRow === "medium"
|
|
188
|
-
?
|
|
263
|
+
? ViewPageSize.medium
|
|
189
264
|
: singleRow === true
|
|
190
|
-
?
|
|
191
|
-
:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
lg: 3,
|
|
196
|
-
xl: 2
|
|
197
|
-
};
|
|
198
|
-
return { size };
|
|
265
|
+
? ViewPageSize.line
|
|
266
|
+
: singleRow === false
|
|
267
|
+
? ViewPageSize.smallLine
|
|
268
|
+
: ViewPageSize.small;
|
|
269
|
+
return size;
|
|
199
270
|
}
|
|
200
271
|
|
|
201
272
|
function getItemField<T extends object>(
|
|
202
273
|
app: ReactAppType,
|
|
203
274
|
field: ViewPageFieldTypeNarrow<T>,
|
|
204
275
|
data: T
|
|
205
|
-
): [React.ReactNode, React.ReactNode, Grid2Props] {
|
|
276
|
+
): [React.ReactNode, React.ReactNode, Grid2Props, ViewPageItemSize] {
|
|
206
277
|
// Item data and label
|
|
207
278
|
let itemData: React.ReactNode,
|
|
208
279
|
itemLabel: React.ReactNode,
|
|
209
|
-
gridProps: Grid2Props = {}
|
|
280
|
+
gridProps: Grid2Props = {},
|
|
281
|
+
size: ViewPageItemSize;
|
|
210
282
|
|
|
211
283
|
if (Array.isArray(field)) {
|
|
212
284
|
const [fieldData, fieldType, renderProps, singleRow = "small"] = field;
|
|
213
285
|
itemData = GridDataFormat(data[fieldData], fieldType, renderProps);
|
|
214
286
|
itemLabel = app.get<string>(fieldData) ?? fieldData;
|
|
215
|
-
|
|
287
|
+
size = getResp(singleRow);
|
|
288
|
+
gridProps = { size };
|
|
216
289
|
} else if (typeof field === "object") {
|
|
217
290
|
// Destruct
|
|
218
291
|
const {
|
|
@@ -224,9 +297,12 @@ function getItemField<T extends object>(
|
|
|
224
297
|
...rest
|
|
225
298
|
} = field;
|
|
226
299
|
|
|
300
|
+
// Size
|
|
301
|
+
size = getResp(singleRow);
|
|
302
|
+
|
|
227
303
|
gridProps = {
|
|
228
304
|
...rest,
|
|
229
|
-
|
|
305
|
+
size
|
|
230
306
|
};
|
|
231
307
|
|
|
232
308
|
// Field data
|
|
@@ -249,9 +325,24 @@ function getItemField<T extends object>(
|
|
|
249
325
|
// Single field format
|
|
250
326
|
itemData = formatItemData(app, data[field]);
|
|
251
327
|
itemLabel = app.get<string>(field) ?? field;
|
|
328
|
+
size = ViewPageSize.small;
|
|
329
|
+
gridProps = { size };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return [itemData, itemLabel, gridProps, size];
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function getItemSize(bp: Breakpoint, size: ViewPageItemSize) {
|
|
336
|
+
const v = size[bp];
|
|
337
|
+
if (v != null) return v;
|
|
338
|
+
|
|
339
|
+
const index = breakpoints.indexOf(bp);
|
|
340
|
+
for (let i = index; i >= 0; i--) {
|
|
341
|
+
const v = size[breakpoints[i]];
|
|
342
|
+
if (v != null) return v;
|
|
252
343
|
}
|
|
253
344
|
|
|
254
|
-
return
|
|
345
|
+
return 12;
|
|
255
346
|
}
|
|
256
347
|
|
|
257
348
|
/**
|
|
@@ -279,9 +370,16 @@ export function ViewPage<T extends DataTypes.StringRecord>(
|
|
|
279
370
|
pullToRefresh = true,
|
|
280
371
|
gridRef,
|
|
281
372
|
operationMessageHandler,
|
|
373
|
+
titleBar,
|
|
374
|
+
leftContainer,
|
|
375
|
+
leftContainerLines = 3,
|
|
376
|
+
leftContainerProps = {},
|
|
282
377
|
...rest
|
|
283
378
|
} = props;
|
|
284
379
|
|
|
380
|
+
// Current breakpoint
|
|
381
|
+
const bp = useCurrentBreakpoint();
|
|
382
|
+
|
|
285
383
|
// Data
|
|
286
384
|
const [data, setData] = React.useState<T>();
|
|
287
385
|
|
|
@@ -291,6 +389,10 @@ export function ViewPage<T extends DataTypes.StringRecord>(
|
|
|
291
389
|
// Container
|
|
292
390
|
const pullContainer = "#page-container";
|
|
293
391
|
|
|
392
|
+
// Left container
|
|
393
|
+
const { size = ViewPageSize.smallLine, ...leftContainerPropsRest } =
|
|
394
|
+
leftContainerProps;
|
|
395
|
+
|
|
294
396
|
// Load data
|
|
295
397
|
const refresh = React.useCallback(async () => {
|
|
296
398
|
const result = await loadData();
|
|
@@ -298,6 +400,76 @@ export function ViewPage<T extends DataTypes.StringRecord>(
|
|
|
298
400
|
setData(result);
|
|
299
401
|
}, [loadData]);
|
|
300
402
|
|
|
403
|
+
// Create fields
|
|
404
|
+
const fieldIndexRef = React.useRef(0);
|
|
405
|
+
const createFields = React.useCallback(
|
|
406
|
+
(data: T, maxItems: number = 0) => {
|
|
407
|
+
let validItems = 0;
|
|
408
|
+
const items: React.ReactNode[] = [];
|
|
409
|
+
let i: number = fieldIndexRef.current;
|
|
410
|
+
for (; i < fields.length; i++) {
|
|
411
|
+
const field = fields[i];
|
|
412
|
+
let oneSize: ViewPageItemSize;
|
|
413
|
+
let oneItem: React.ReactNode;
|
|
414
|
+
if (typeof field === "function") {
|
|
415
|
+
// Most flexible way, do whatever you want
|
|
416
|
+
const createdResult = field(data, refresh);
|
|
417
|
+
if (createdResult == null || createdResult === "") continue;
|
|
418
|
+
if (Array.isArray(createdResult)) {
|
|
419
|
+
const [created, size] = createdResult;
|
|
420
|
+
oneSize = size;
|
|
421
|
+
oneItem = created;
|
|
422
|
+
} else {
|
|
423
|
+
oneSize = ViewPageSize.line;
|
|
424
|
+
oneItem = createdResult;
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
const [itemData, itemLabel, gridProps, size] = getItemField(
|
|
428
|
+
app,
|
|
429
|
+
field,
|
|
430
|
+
data
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
// Some callback function may return '' instead of undefined
|
|
434
|
+
if (itemData == null || itemData === "") continue;
|
|
435
|
+
|
|
436
|
+
oneSize = size;
|
|
437
|
+
oneItem = (
|
|
438
|
+
<ViewPageGridItem
|
|
439
|
+
{...gridProps}
|
|
440
|
+
key={i}
|
|
441
|
+
data={itemData}
|
|
442
|
+
label={itemLabel}
|
|
443
|
+
/>
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Max lines
|
|
448
|
+
if (maxItems > 0) {
|
|
449
|
+
const itemSize = getItemSize(bp, oneSize);
|
|
450
|
+
if (maxItems < validItems + itemSize) {
|
|
451
|
+
fieldIndexRef.current = i;
|
|
452
|
+
break;
|
|
453
|
+
} else {
|
|
454
|
+
items.push(oneItem);
|
|
455
|
+
validItems += itemSize;
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
items.push(oneItem);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (maxItems === 0) {
|
|
463
|
+
fieldIndexRef.current = 0;
|
|
464
|
+
} else {
|
|
465
|
+
fieldIndexRef.current = i;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return items;
|
|
469
|
+
},
|
|
470
|
+
[app, refresh, fields, data, bp]
|
|
471
|
+
);
|
|
472
|
+
|
|
301
473
|
React.useEffect(() => {
|
|
302
474
|
const refreshHandler: RefreshHandler = async () => {
|
|
303
475
|
await refresh();
|
|
@@ -309,11 +481,18 @@ export function ViewPage<T extends DataTypes.StringRecord>(
|
|
|
309
481
|
};
|
|
310
482
|
}, [refresh]);
|
|
311
483
|
|
|
484
|
+
let leftResult: React.ReactNode;
|
|
485
|
+
|
|
312
486
|
return (
|
|
313
487
|
<CommonPage
|
|
314
488
|
paddings={paddings}
|
|
315
489
|
onRefresh={supportRefresh ? refresh : undefined}
|
|
316
490
|
onUpdate={supportRefresh ? undefined : refresh}
|
|
491
|
+
sx={{
|
|
492
|
+
".MuiTypography-subtitle2": {
|
|
493
|
+
fontWeight: "bold"
|
|
494
|
+
}
|
|
495
|
+
}}
|
|
317
496
|
{...rest}
|
|
318
497
|
scrollContainer={globalThis}
|
|
319
498
|
fabColumnDirection={fabColumnDirection}
|
|
@@ -337,44 +516,39 @@ export function ViewPage<T extends DataTypes.StringRecord>(
|
|
|
337
516
|
}
|
|
338
517
|
/>
|
|
339
518
|
)}
|
|
519
|
+
{titleBar && titleBar(data)}
|
|
340
520
|
<Grid2
|
|
341
521
|
container
|
|
342
522
|
justifyContent="left"
|
|
343
|
-
spacing={spacing}
|
|
344
523
|
className="ET-ViewPage"
|
|
345
524
|
ref={gridRef}
|
|
346
|
-
|
|
347
|
-
".MuiTypography-subtitle2": {
|
|
348
|
-
fontWeight: "bold"
|
|
349
|
-
}
|
|
350
|
-
}}
|
|
525
|
+
spacing={spacing}
|
|
351
526
|
>
|
|
352
|
-
{
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
})}
|
|
527
|
+
{leftContainer && (leftResult = leftContainer(data)) != null && (
|
|
528
|
+
<React.Fragment>
|
|
529
|
+
<Grid2
|
|
530
|
+
container
|
|
531
|
+
className="ET-ViewPage-LeftContainer"
|
|
532
|
+
spacing={spacing}
|
|
533
|
+
size={size}
|
|
534
|
+
{...leftContainerPropsRest}
|
|
535
|
+
>
|
|
536
|
+
{leftResult}
|
|
537
|
+
</Grid2>
|
|
538
|
+
<Grid2
|
|
539
|
+
container
|
|
540
|
+
className="ET-ViewPage-LeftOthers"
|
|
541
|
+
spacing={spacing}
|
|
542
|
+
size={ViewPageSize.matchSize(size)}
|
|
543
|
+
>
|
|
544
|
+
{createFields(
|
|
545
|
+
data,
|
|
546
|
+
leftContainerLines * (12 - getItemSize(bp, size))
|
|
547
|
+
)}
|
|
548
|
+
</Grid2>
|
|
549
|
+
</React.Fragment>
|
|
550
|
+
)}
|
|
551
|
+
{createFields(data)}
|
|
378
552
|
</Grid2>
|
|
379
553
|
{actions !== null && (
|
|
380
554
|
<Stack
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Breakpoint, useMediaQuery, useTheme } from "@mui/material";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook to get the current breakpoint
|
|
5
|
+
* @returns The current breakpoint
|
|
6
|
+
*/
|
|
7
|
+
export function useCurrentBreakpoint(): Breakpoint {
|
|
8
|
+
const theme = useTheme();
|
|
9
|
+
const items: (Breakpoint | null)[] = [
|
|
10
|
+
useMediaQuery(theme.breakpoints.down("xs")) ? "xs" : null,
|
|
11
|
+
useMediaQuery(theme.breakpoints.between("xs", "sm")) ? "sm" : null,
|
|
12
|
+
useMediaQuery(theme.breakpoints.between("sm", "md")) ? "md" : null,
|
|
13
|
+
useMediaQuery(theme.breakpoints.between("md", "lg")) ? "lg" : null,
|
|
14
|
+
useMediaQuery(theme.breakpoints.up("lg")) ? "xl" : null
|
|
15
|
+
];
|
|
16
|
+
return items.find((item) => item != null) ?? "lg";
|
|
17
|
+
}
|