@etsoo/materialui 1.3.67 → 1.3.69

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.
@@ -10,6 +10,7 @@ import RemoveIcon from "@mui/icons-material/Remove";
10
10
  import AddIcon from "@mui/icons-material/Add";
11
11
  import { Labels } from "./app/Labels";
12
12
  import { FileUploadButton } from "./FileUploadButton";
13
+ const defaultSize = 300;
13
14
  const defaultState = {
14
15
  scale: 1,
15
16
  rotate: 0
@@ -22,11 +23,11 @@ const defaultState = {
22
23
  */
23
24
  export function UserAvatarEditor(props) {
24
25
  // Destruct
25
- const { border = 30, image, maxWidth, onDone, scaledResult = false, width = 200, height = 200, range = [0.1, 2, 0.1] } = props;
26
+ const { border = 30, image, maxWidth, onDone, scaledResult = false, width = defaultSize, height = defaultSize, range = [0.1, 2, 0.1] } = props;
26
27
  // Container width
27
28
  const containerWidth = width + 2 * border + 44 + 4;
28
29
  // Calculated max width
29
- const maxWidthCalculated = maxWidth == null || maxWidth < 200 ? 3 * width : maxWidth;
30
+ const maxWidthCalculated = maxWidth == null || maxWidth < defaultSize ? 2 * width : maxWidth;
30
31
  // Labels
31
32
  const labels = Labels.UserAvatarEditor;
32
33
  // Ref
@@ -41,6 +42,10 @@ export function UserAvatarEditor(props) {
41
42
  const [ready, setReady] = React.useState(false);
42
43
  // Editor states
43
44
  const [editorState, setEditorState] = React.useState(defaultState);
45
+ // Height
46
+ // noHeight: height is not set and will be updated dynamically
47
+ const noHeight = height <= 0;
48
+ const [localHeight, setHeight] = React.useState(noHeight ? defaultSize : height);
44
49
  // Range
45
50
  const [min, max, step] = range;
46
51
  const marks = [
@@ -69,7 +74,10 @@ export function UserAvatarEditor(props) {
69
74
  setScale(editorState.scale + (isAdd ? step : -step));
70
75
  };
71
76
  // Handle image load
72
- const handleLoad = () => {
77
+ const handleLoad = (imageInfo) => {
78
+ if (noHeight) {
79
+ setHeight((imageInfo.height * width) / imageInfo.width);
80
+ }
73
81
  setReady(true);
74
82
  };
75
83
  // Handle file upload
@@ -115,7 +123,7 @@ export function UserAvatarEditor(props) {
115
123
  };
116
124
  if (data.width > maxWidthCalculated) {
117
125
  // Target height
118
- const heightCalculated = (height * maxWidthCalculated) / width;
126
+ const heightCalculated = (localHeight * maxWidthCalculated) / width;
119
127
  // Target
120
128
  const to = document.createElement("canvas");
121
129
  to.width = maxWidthCalculated;
@@ -136,5 +144,5 @@ export function UserAvatarEditor(props) {
136
144
  };
137
145
  // Load the component
138
146
  const AE = React.lazy(() => import("react-avatar-editor"));
139
- return (_jsxs(Stack, { direction: "column", spacing: 0.5, width: containerWidth, children: [_jsx(FileUploadButton, { variant: "outlined", size: "medium", startIcon: _jsx(ComputerIcon, {}), fullWidth: true, onUploadFiles: handleFileUpload, inputProps: { multiple: false, accept: "image/png, image/jpeg" }, children: labels.upload }), _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(React.Suspense, { fallback: _jsx(Skeleton, { variant: "rounded", width: width, height: height }), children: _jsx(AE, { ref: ref, border: border, width: width, height: height, onLoadSuccess: handleLoad, image: previewImage ?? "", scale: editorState.scale, rotate: editorState.rotate }) }), _jsxs(ButtonGroup, { size: "small", orientation: "vertical", disabled: !ready, children: [_jsx(Button, { onClick: () => handleRotate(90), title: labels.rotateRight, children: _jsx(RotateRightIcon, {}) }), _jsx(Button, { onClick: () => handleRotate(-90), title: labels.rotateLeft, children: _jsx(RotateLeftIcon, {}) }), _jsx(Button, { onClick: handleReset, title: labels.reset, children: _jsx(ClearAllIcon, {}) })] })] }), _jsxs(Stack, { spacing: 0.5, direction: "row", sx: { paddingBottom: 2 }, alignItems: "center", children: [_jsx(IconButton, { size: "small", disabled: !ready || editorState.scale <= min, onClick: () => adjustScale(false), children: _jsx(RemoveIcon, {}) }), _jsx(Slider, { title: labels.zoom, disabled: !ready, min: min, max: max, step: step, value: editorState.scale, valueLabelDisplay: "auto", valueLabelFormat: (value) => `${Math.round(100 * value) / 100}`, marks: marks, onChange: handleZoom }), _jsx(IconButton, { size: "small", disabled: !ready || editorState.scale >= max, onClick: () => adjustScale(true), children: _jsx(AddIcon, {}) })] }), _jsx(Button, { ref: buttonRef, variant: "contained", startIcon: _jsx(DoneIcon, {}), disabled: !ready, onClick: handleDone, children: labels.done })] }));
147
+ return (_jsxs(Stack, { direction: "column", spacing: 0.5, width: containerWidth, children: [_jsx(FileUploadButton, { variant: "outlined", size: "medium", startIcon: _jsx(ComputerIcon, {}), fullWidth: true, onUploadFiles: handleFileUpload, inputProps: { multiple: false, accept: "image/png, image/jpeg" }, children: labels.upload }), _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(React.Suspense, { fallback: _jsx(Skeleton, { variant: "rounded", width: width, height: localHeight }), children: _jsx(AE, { ref: ref, border: border, width: width, height: localHeight, onLoadSuccess: handleLoad, image: previewImage ?? "", scale: editorState.scale, rotate: editorState.rotate }) }), _jsxs(ButtonGroup, { size: "small", orientation: "vertical", disabled: !ready, children: [_jsx(Button, { onClick: () => handleRotate(90), title: labels.rotateRight, children: _jsx(RotateRightIcon, {}) }), _jsx(Button, { onClick: () => handleRotate(-90), title: labels.rotateLeft, children: _jsx(RotateLeftIcon, {}) }), _jsx(Button, { onClick: handleReset, title: labels.reset, children: _jsx(ClearAllIcon, {}) })] })] }), _jsxs(Stack, { spacing: 0.5, direction: "row", sx: { paddingBottom: 2 }, alignItems: "center", children: [_jsx(IconButton, { size: "small", disabled: !ready || editorState.scale <= min, onClick: () => adjustScale(false), children: _jsx(RemoveIcon, {}) }), _jsx(Slider, { title: labels.zoom, disabled: !ready, min: min, max: max, step: step, value: editorState.scale, valueLabelDisplay: "auto", valueLabelFormat: (value) => `${Math.round(100 * value) / 100}`, marks: marks, onChange: handleZoom }), _jsx(IconButton, { size: "small", disabled: !ready || editorState.scale >= max, onClick: () => adjustScale(true), children: _jsx(AddIcon, {}) })] }), _jsx(Button, { ref: buttonRef, variant: "contained", startIcon: _jsx(DoneIcon, {}), disabled: !ready, onClick: handleDone, children: labels.done })] }));
140
148
  }
@@ -0,0 +1,50 @@
1
+ import { CustomFieldData } from "@etsoo/appscript";
2
+ import { GridProps, TypographyProps } from "@mui/material";
3
+ /**
4
+ * Custom field viewer props
5
+ * 自定义字段查看器属性
6
+ */
7
+ export type CustomFieldViewerProps = {
8
+ /**
9
+ * Custom fields
10
+ * 自定义字段
11
+ */
12
+ fields: CustomFieldData[];
13
+ /**
14
+ * Grid props
15
+ * 网格属性
16
+ */
17
+ gridProps?: GridProps;
18
+ /**
19
+ * JSON data
20
+ * JSON 数据
21
+ */
22
+ jsonData: unknown;
23
+ /**
24
+ * Title label props
25
+ * 标题标签属性
26
+ */
27
+ titleProps?: TypographyProps;
28
+ /**
29
+ * Vertical gap
30
+ * 垂直间距
31
+ */
32
+ verticalGap?: number;
33
+ /**
34
+ * Value label formatter
35
+ * 值标签格式化
36
+ */
37
+ valueLabelFormatter?: (value: any, field: CustomFieldData) => string;
38
+ /**
39
+ * Value label props
40
+ * 值标签属性
41
+ */
42
+ valueProps?: TypographyProps;
43
+ };
44
+ /**
45
+ * Custom field viewer
46
+ * 自定义字段查看器
47
+ * @param props Props
48
+ * @returns Component
49
+ */
50
+ export declare function CustomFieldViewer(props: CustomFieldViewerProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Grid, Typography } from "@mui/material";
3
+ import { CustomFieldUtils } from "./CustomFieldUtils";
4
+ import { MUGlobal } from "../MUGlobal";
5
+ import { VBox } from "../FlexBox";
6
+ import { DataTypes, Utils } from "@etsoo/shared";
7
+ /**
8
+ * Custom field viewer
9
+ * 自定义字段查看器
10
+ * @param props Props
11
+ * @returns Component
12
+ */
13
+ export function CustomFieldViewer(props) {
14
+ // Destruct
15
+ const { fields, gridProps, jsonData, titleProps, verticalGap = 0.5, valueLabelFormatter = (value, field) => {
16
+ if (value == null)
17
+ return "";
18
+ if (field.options) {
19
+ const option = field.options.find((o) => o.id === value);
20
+ if (option) {
21
+ return DataTypes.getListItemLabel(option);
22
+ }
23
+ }
24
+ if (typeof value === "object") {
25
+ if (value instanceof Date) {
26
+ return value.toLocaleString();
27
+ }
28
+ else {
29
+ return JSON.stringify(value);
30
+ }
31
+ }
32
+ else {
33
+ return `${value}`;
34
+ }
35
+ }, valueProps } = props;
36
+ const spacing = MUGlobal.half(MUGlobal.pagePaddings);
37
+ const data = typeof jsonData === "string" ? JSON.parse(jsonData) : jsonData;
38
+ if (data == null || typeof data !== "object" || Array.isArray(data)) {
39
+ throw new Error("Invalid JSON data");
40
+ }
41
+ return (_jsx(Grid, { container: true, justifyContent: "left", spacing: spacing, sx: {
42
+ ".MuiTypography-subtitle2": {
43
+ fontWeight: "bold"
44
+ }
45
+ }, ...gridProps, children: fields.map((field, index) => {
46
+ // Field name
47
+ const name = field.name;
48
+ if (!name)
49
+ return;
50
+ // Field value
51
+ const value = Utils.getNestedValue(data, name);
52
+ return (_jsx(Grid, { item: true, ...field.gridItemProps, ...CustomFieldUtils.transformSpace(field.space), children: _jsxs(VBox, { gap: verticalGap, children: [_jsx(Typography, { fontWeight: "bold", fontSize: "small", ...titleProps, children: field.label ?? name }), _jsx(Typography, { ...valueProps, children: valueLabelFormatter(value, field) })] }) }, name ?? index));
53
+ }) }));
54
+ }
package/lib/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export * from "./app/Labels";
9
9
  export * from "./app/ReactApp";
10
10
  export * from "./app/ServiceApp";
11
11
  export * from "./custom/CustomFieldUtils";
12
+ export * from "./custom/CustomFieldViewer";
12
13
  export * from "./custom/CustomFieldWindow";
13
14
  export * from "./messages/MessageUtils";
14
15
  export * from "./messages/OperationMessageContainer";
package/lib/index.js CHANGED
@@ -9,6 +9,7 @@ export * from "./app/Labels";
9
9
  export * from "./app/ReactApp";
10
10
  export * from "./app/ServiceApp";
11
11
  export * from "./custom/CustomFieldUtils";
12
+ export * from "./custom/CustomFieldViewer";
12
13
  export * from "./custom/CustomFieldWindow";
13
14
  export * from "./messages/MessageUtils";
14
15
  export * from "./messages/OperationMessageContainer";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.3.67",
3
+ "version": "1.3.69",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -54,9 +54,9 @@
54
54
  "@etsoo/notificationbase": "^1.1.42",
55
55
  "@etsoo/react": "^1.7.54",
56
56
  "@etsoo/shared": "^1.2.41",
57
- "@mui/icons-material": "^5.15.18",
58
- "@mui/material": "^5.15.18",
59
- "@mui/x-data-grid": "^7.5.1",
57
+ "@mui/icons-material": "^5.15.19",
58
+ "@mui/material": "^5.15.19",
59
+ "@mui/x-data-grid": "^7.6.1",
60
60
  "chart.js": "^4.4.3",
61
61
  "chartjs-plugin-datalabels": "^2.2.0",
62
62
  "eventemitter3": "^5.0.1",
@@ -17,6 +17,9 @@ import RemoveIcon from "@mui/icons-material/Remove";
17
17
  import AddIcon from "@mui/icons-material/Add";
18
18
  import { Labels } from "./app/Labels";
19
19
  import { FileUploadButton } from "./FileUploadButton";
20
+ import { ImageState } from "react-avatar-editor";
21
+
22
+ const defaultSize = 300;
20
23
 
21
24
  /**
22
25
  * User avatar editor to Blob helper
@@ -109,8 +112,8 @@ export function UserAvatarEditor(props: UserAvatarEditorProps) {
109
112
  maxWidth,
110
113
  onDone,
111
114
  scaledResult = false,
112
- width = 200,
113
- height = 200,
115
+ width = defaultSize,
116
+ height = defaultSize,
114
117
  range = [0.1, 2, 0.1]
115
118
  } = props;
116
119
 
@@ -119,7 +122,7 @@ export function UserAvatarEditor(props: UserAvatarEditorProps) {
119
122
 
120
123
  // Calculated max width
121
124
  const maxWidthCalculated =
122
- maxWidth == null || maxWidth < 200 ? 3 * width : maxWidth;
125
+ maxWidth == null || maxWidth < defaultSize ? 2 * width : maxWidth;
123
126
 
124
127
  // Labels
125
128
  const labels = Labels.UserAvatarEditor;
@@ -142,6 +145,13 @@ export function UserAvatarEditor(props: UserAvatarEditorProps) {
142
145
  // Editor states
143
146
  const [editorState, setEditorState] = React.useState(defaultState);
144
147
 
148
+ // Height
149
+ // noHeight: height is not set and will be updated dynamically
150
+ const noHeight = height <= 0;
151
+ const [localHeight, setHeight] = React.useState(
152
+ noHeight ? defaultSize : height
153
+ );
154
+
145
155
  // Range
146
156
  const [min, max, step] = range;
147
157
  const marks = [
@@ -179,7 +189,10 @@ export function UserAvatarEditor(props: UserAvatarEditorProps) {
179
189
  };
180
190
 
181
191
  // Handle image load
182
- const handleLoad = () => {
192
+ const handleLoad = (imageInfo: ImageState) => {
193
+ if (noHeight) {
194
+ setHeight((imageInfo.height * width) / imageInfo.width);
195
+ }
183
196
  setReady(true);
184
197
  };
185
198
 
@@ -238,7 +251,7 @@ export function UserAvatarEditor(props: UserAvatarEditorProps) {
238
251
 
239
252
  if (data.width > maxWidthCalculated) {
240
253
  // Target height
241
- const heightCalculated = (height * maxWidthCalculated) / width;
254
+ const heightCalculated = (localHeight * maxWidthCalculated) / width;
242
255
 
243
256
  // Target
244
257
  const to = document.createElement("canvas");
@@ -277,14 +290,14 @@ export function UserAvatarEditor(props: UserAvatarEditorProps) {
277
290
  <Stack direction="row" spacing={0.5}>
278
291
  <React.Suspense
279
292
  fallback={
280
- <Skeleton variant="rounded" width={width} height={height} />
293
+ <Skeleton variant="rounded" width={width} height={localHeight} />
281
294
  }
282
295
  >
283
296
  <AE
284
297
  ref={ref}
285
298
  border={border}
286
299
  width={width}
287
- height={height}
300
+ height={localHeight}
288
301
  onLoadSuccess={handleLoad}
289
302
  image={previewImage ?? ""}
290
303
  scale={editorState.scale}
@@ -0,0 +1,138 @@
1
+ import { CustomFieldData } from "@etsoo/appscript";
2
+ import { Grid, GridProps, Typography, TypographyProps } from "@mui/material";
3
+ import { CustomFieldUtils } from "./CustomFieldUtils";
4
+ import { MUGlobal } from "../MUGlobal";
5
+ import { VBox } from "../FlexBox";
6
+ import { DataTypes, Utils } from "@etsoo/shared";
7
+
8
+ /**
9
+ * Custom field viewer props
10
+ * 自定义字段查看器属性
11
+ */
12
+ export type CustomFieldViewerProps = {
13
+ /**
14
+ * Custom fields
15
+ * 自定义字段
16
+ */
17
+ fields: CustomFieldData[];
18
+
19
+ /**
20
+ * Grid props
21
+ * 网格属性
22
+ */
23
+ gridProps?: GridProps;
24
+
25
+ /**
26
+ * JSON data
27
+ * JSON 数据
28
+ */
29
+ jsonData: unknown;
30
+
31
+ /**
32
+ * Title label props
33
+ * 标题标签属性
34
+ */
35
+ titleProps?: TypographyProps;
36
+
37
+ /**
38
+ * Vertical gap
39
+ * 垂直间距
40
+ */
41
+ verticalGap?: number;
42
+
43
+ /**
44
+ * Value label formatter
45
+ * 值标签格式化
46
+ */
47
+ valueLabelFormatter?: (value: any, field: CustomFieldData) => string;
48
+
49
+ /**
50
+ * Value label props
51
+ * 值标签属性
52
+ */
53
+ valueProps?: TypographyProps;
54
+ };
55
+
56
+ /**
57
+ * Custom field viewer
58
+ * 自定义字段查看器
59
+ * @param props Props
60
+ * @returns Component
61
+ */
62
+ export function CustomFieldViewer(props: CustomFieldViewerProps) {
63
+ // Destruct
64
+ const {
65
+ fields,
66
+ gridProps,
67
+ jsonData,
68
+ titleProps,
69
+ verticalGap = 0.5,
70
+ valueLabelFormatter = (value, field) => {
71
+ if (value == null) return "";
72
+ if (field.options) {
73
+ const option = field.options.find((o) => o.id === value);
74
+ if (option) {
75
+ return DataTypes.getListItemLabel(option);
76
+ }
77
+ }
78
+
79
+ if (typeof value === "object") {
80
+ if (value instanceof Date) {
81
+ return value.toLocaleString();
82
+ } else {
83
+ return JSON.stringify(value);
84
+ }
85
+ } else {
86
+ return `${value}`;
87
+ }
88
+ },
89
+ valueProps
90
+ } = props;
91
+
92
+ const spacing = MUGlobal.half(MUGlobal.pagePaddings);
93
+ const data = typeof jsonData === "string" ? JSON.parse(jsonData) : jsonData;
94
+ if (data == null || typeof data !== "object" || Array.isArray(data)) {
95
+ throw new Error("Invalid JSON data");
96
+ }
97
+
98
+ return (
99
+ <Grid
100
+ container
101
+ justifyContent="left"
102
+ spacing={spacing}
103
+ sx={{
104
+ ".MuiTypography-subtitle2": {
105
+ fontWeight: "bold"
106
+ }
107
+ }}
108
+ {...gridProps}
109
+ >
110
+ {fields.map((field, index) => {
111
+ // Field name
112
+ const name = field.name;
113
+ if (!name) return;
114
+
115
+ // Field value
116
+ const value = Utils.getNestedValue(data, name);
117
+
118
+ return (
119
+ <Grid
120
+ item
121
+ key={name ?? index}
122
+ {...field.gridItemProps}
123
+ {...CustomFieldUtils.transformSpace(field.space)}
124
+ >
125
+ <VBox gap={verticalGap}>
126
+ <Typography fontWeight="bold" fontSize="small" {...titleProps}>
127
+ {field.label ?? name}
128
+ </Typography>
129
+ <Typography {...valueProps}>
130
+ {valueLabelFormatter(value, field)}
131
+ </Typography>
132
+ </VBox>
133
+ </Grid>
134
+ );
135
+ })}
136
+ </Grid>
137
+ );
138
+ }
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export * from "./app/ReactApp";
10
10
  export * from "./app/ServiceApp";
11
11
 
12
12
  export * from "./custom/CustomFieldUtils";
13
+ export * from "./custom/CustomFieldViewer";
13
14
  export * from "./custom/CustomFieldWindow";
14
15
 
15
16
  export * from "./messages/MessageUtils";