@etsoo/materialui 1.2.30 → 1.2.32
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/ComboBoxPro.js +5 -2
- package/lib/PullToRefreshUI.d.ts +2 -2
- package/lib/PullToRefreshUI.js +8 -4
- package/lib/UserAvatarEditor.d.ts +1 -1
- package/lib/UserAvatarEditor.js +18 -15
- package/lib/app/CommonApp.d.ts +3 -3
- package/lib/app/CommonApp.js +10 -10
- package/lib/app/IServiceApp.d.ts +5 -5
- package/lib/app/ReactApp.js +14 -13
- package/lib/app/ServiceApp.d.ts +2 -2
- package/lib/app/ServiceApp.js +2 -4
- package/package.json +7 -7
- package/src/ComboBoxPro.tsx +5 -2
- package/src/PullToRefreshUI.tsx +17 -10
- package/src/UserAvatarEditor.tsx +251 -251
- package/src/app/CommonApp.ts +189 -201
- package/src/app/IServiceApp.ts +37 -37
- package/src/app/ReactApp.ts +14 -14
- package/src/app/ServiceApp.ts +5 -6
package/src/UserAvatarEditor.tsx
CHANGED
|
@@ -1,80 +1,79 @@
|
|
|
1
|
-
import { Button, ButtonGroup, Slider, Stack } from
|
|
2
|
-
import React from
|
|
3
|
-
import AvatarEditor from
|
|
4
|
-
import RotateLeftIcon from
|
|
5
|
-
import RotateRightIcon from
|
|
6
|
-
import ClearAllIcon from
|
|
7
|
-
import ComputerIcon from
|
|
8
|
-
import DoneIcon from
|
|
9
|
-
import
|
|
10
|
-
import { Labels } from './app/Labels';
|
|
1
|
+
import { Button, ButtonGroup, Skeleton, Slider, Stack } from "@mui/material";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import type AvatarEditor from "react-avatar-editor";
|
|
4
|
+
import RotateLeftIcon from "@mui/icons-material/RotateLeft";
|
|
5
|
+
import RotateRightIcon from "@mui/icons-material/RotateRight";
|
|
6
|
+
import ClearAllIcon from "@mui/icons-material/ClearAll";
|
|
7
|
+
import ComputerIcon from "@mui/icons-material/Computer";
|
|
8
|
+
import DoneIcon from "@mui/icons-material/Done";
|
|
9
|
+
import { Labels } from "./app/Labels";
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* User avatar editor to Blob helper
|
|
14
13
|
*/
|
|
15
14
|
export interface UserAvatarEditorToBlob {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
(
|
|
16
|
+
canvas: HTMLCanvasElement,
|
|
17
|
+
mimeType?: string,
|
|
18
|
+
quality?: number
|
|
19
|
+
): Promise<Blob>;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
23
|
* User avatar editor on done handler
|
|
25
24
|
*/
|
|
26
25
|
export interface UserAvatarEditorOnDoneHandler {
|
|
27
|
-
|
|
26
|
+
(canvas: HTMLCanvasElement, toBlob: UserAvatarEditorToBlob): void;
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
30
|
* User avatar editor props
|
|
32
31
|
*/
|
|
33
32
|
export interface UserAvatarEditorProps {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Cropping border size
|
|
35
|
+
*/
|
|
36
|
+
border?: number;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Image source
|
|
40
|
+
*/
|
|
41
|
+
image?: string | File;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Max width to save
|
|
45
|
+
*/
|
|
46
|
+
maxWidth?: number;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* On done handler
|
|
50
|
+
*/
|
|
51
|
+
onDone: UserAvatarEditorOnDoneHandler;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Return scaled result?
|
|
55
|
+
*/
|
|
56
|
+
scaledResult?: boolean;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Width of the editor
|
|
60
|
+
*/
|
|
61
|
+
width?: number;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Height of the editor
|
|
65
|
+
*/
|
|
66
|
+
height?: number;
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
interface EditorState {
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
scale: number;
|
|
71
|
+
rotate: number;
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
const defaultState: EditorState = {
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
scale: 1,
|
|
76
|
+
rotate: 0
|
|
78
77
|
};
|
|
79
78
|
|
|
80
79
|
/**
|
|
@@ -84,204 +83,205 @@ const defaultState: EditorState = {
|
|
|
84
83
|
* @returns Component
|
|
85
84
|
*/
|
|
86
85
|
export function UserAvatarEditor(props: UserAvatarEditorProps) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
86
|
+
// Destruct
|
|
87
|
+
const {
|
|
88
|
+
border = 30,
|
|
89
|
+
image,
|
|
90
|
+
maxWidth,
|
|
91
|
+
onDone,
|
|
92
|
+
scaledResult = false,
|
|
93
|
+
width = 200,
|
|
94
|
+
height = 200
|
|
95
|
+
} = props;
|
|
96
|
+
|
|
97
|
+
// Container width
|
|
98
|
+
const containerWidth = width + 2 * border + 44 + 4;
|
|
99
|
+
|
|
100
|
+
// Calculated max width
|
|
101
|
+
const maxWidthCalculated =
|
|
102
|
+
maxWidth == null || maxWidth < 200 ? 3 * width : maxWidth;
|
|
103
|
+
|
|
104
|
+
// Labels
|
|
105
|
+
const labels = Labels.UserAvatarEditor;
|
|
106
|
+
|
|
107
|
+
// Ref
|
|
108
|
+
const ref = React.createRef<AvatarEditor>();
|
|
109
|
+
|
|
110
|
+
const [AE, setAE] = React.useState<typeof AvatarEditor>();
|
|
111
|
+
|
|
112
|
+
// Button ref
|
|
113
|
+
const buttonRef = React.createRef<HTMLButtonElement>();
|
|
114
|
+
|
|
115
|
+
// Preview image state
|
|
116
|
+
const [previewImage, setPreviewImage] = React.useState(image);
|
|
117
|
+
|
|
118
|
+
// Is ready state
|
|
119
|
+
const [ready, setReady] = React.useState(false);
|
|
120
|
+
|
|
121
|
+
// Editor states
|
|
122
|
+
const [editorState, setEditorState] = React.useState(defaultState);
|
|
123
|
+
|
|
124
|
+
// Handle zoom
|
|
125
|
+
const handleZoom = (
|
|
126
|
+
_event: Event,
|
|
127
|
+
value: number | number[],
|
|
128
|
+
_activeThumb: number
|
|
129
|
+
) => {
|
|
130
|
+
const scale = typeof value === "number" ? value : value[0];
|
|
131
|
+
const newState = { ...editorState, scale };
|
|
132
|
+
setEditorState(newState);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Handle image load
|
|
136
|
+
const handleLoad = () => {
|
|
137
|
+
setReady(true);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Handle file change
|
|
141
|
+
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
142
|
+
const files = event.target.files;
|
|
143
|
+
if (files == null || files.length == 0) return;
|
|
144
|
+
|
|
145
|
+
// Reset all settings
|
|
146
|
+
handleReset();
|
|
147
|
+
|
|
148
|
+
// Set new preview image
|
|
149
|
+
setPreviewImage(files[0]);
|
|
150
|
+
|
|
151
|
+
// Set ready state
|
|
152
|
+
setReady(false);
|
|
153
|
+
|
|
154
|
+
// Make the submit button visible
|
|
155
|
+
buttonRef.current?.scrollIntoView(false);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// Handle reset
|
|
159
|
+
const handleReset = () => {
|
|
160
|
+
setEditorState({ ...defaultState });
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Handle rotate
|
|
164
|
+
const handleRotate = (r: number) => {
|
|
165
|
+
let rotate = editorState.rotate + r;
|
|
166
|
+
if (rotate >= 360 || rotate <= -360) rotate = 0;
|
|
167
|
+
|
|
168
|
+
const newState = { ...editorState, rotate };
|
|
169
|
+
setEditorState(newState);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Handle done
|
|
173
|
+
const handleDone = async () => {
|
|
174
|
+
// Data
|
|
175
|
+
var data = scaledResult
|
|
176
|
+
? ref.current?.getImageScaledToCanvas()
|
|
177
|
+
: ref.current?.getImage();
|
|
178
|
+
if (data == null) return;
|
|
179
|
+
|
|
180
|
+
// pica
|
|
181
|
+
const pica = (await import("pica")).default;
|
|
182
|
+
const picaInstance = pica();
|
|
183
|
+
|
|
184
|
+
// toBlob helper
|
|
185
|
+
// Convenience method, similar to canvas.toBlob(), but with promise interface & polyfill for old browsers.
|
|
186
|
+
const toBlob = (
|
|
187
|
+
canvas: HTMLCanvasElement,
|
|
188
|
+
mimeType: string = "image/jpeg",
|
|
189
|
+
quality: number = 1
|
|
128
190
|
) => {
|
|
129
|
-
|
|
130
|
-
const newState = { ...editorState, scale };
|
|
131
|
-
setEditorState(newState);
|
|
191
|
+
return picaInstance.toBlob(canvas, mimeType, quality);
|
|
132
192
|
};
|
|
133
193
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
type="file"
|
|
228
|
-
accept="image/png, image/jpeg"
|
|
229
|
-
multiple={false}
|
|
230
|
-
hidden
|
|
231
|
-
onChange={handleFileChange}
|
|
232
|
-
/>
|
|
233
|
-
</Button>
|
|
234
|
-
<Stack direction="row" spacing={0.5}>
|
|
235
|
-
<AvatarEditor
|
|
236
|
-
ref={ref}
|
|
237
|
-
border={border}
|
|
238
|
-
width={width}
|
|
239
|
-
height={height}
|
|
240
|
-
onLoadSuccess={handleLoad}
|
|
241
|
-
image={previewImage ?? ''}
|
|
242
|
-
scale={editorState.scale}
|
|
243
|
-
rotate={editorState.rotate}
|
|
244
|
-
/>
|
|
245
|
-
<ButtonGroup
|
|
246
|
-
size="small"
|
|
247
|
-
orientation="vertical"
|
|
248
|
-
disabled={!ready}
|
|
249
|
-
>
|
|
250
|
-
<Button
|
|
251
|
-
onClick={() => handleRotate(90)}
|
|
252
|
-
title={labels.rotateRight}
|
|
253
|
-
>
|
|
254
|
-
<RotateRightIcon />
|
|
255
|
-
</Button>
|
|
256
|
-
<Button
|
|
257
|
-
onClick={() => handleRotate(-90)}
|
|
258
|
-
title={labels.rotateLeft}
|
|
259
|
-
>
|
|
260
|
-
<RotateLeftIcon />
|
|
261
|
-
</Button>
|
|
262
|
-
<Button onClick={handleReset} title={labels.reset}>
|
|
263
|
-
<ClearAllIcon />
|
|
264
|
-
</Button>
|
|
265
|
-
</ButtonGroup>
|
|
266
|
-
</Stack>
|
|
267
|
-
<Slider
|
|
268
|
-
title={labels.zoom}
|
|
269
|
-
disabled={!ready}
|
|
270
|
-
min={1}
|
|
271
|
-
max={5}
|
|
272
|
-
step={0.01}
|
|
273
|
-
value={editorState.scale}
|
|
274
|
-
onChange={handleZoom}
|
|
275
|
-
/>
|
|
276
|
-
<Button
|
|
277
|
-
ref={buttonRef}
|
|
278
|
-
variant="contained"
|
|
279
|
-
startIcon={<DoneIcon />}
|
|
280
|
-
disabled={!ready}
|
|
281
|
-
onClick={handleDone}
|
|
282
|
-
>
|
|
283
|
-
{labels.done}
|
|
284
|
-
</Button>
|
|
285
|
-
</Stack>
|
|
286
|
-
);
|
|
194
|
+
if (data.width > maxWidthCalculated) {
|
|
195
|
+
// Target height
|
|
196
|
+
const heightCalculated = (height * maxWidthCalculated) / width;
|
|
197
|
+
|
|
198
|
+
// Target
|
|
199
|
+
const to = document.createElement("canvas");
|
|
200
|
+
to.width = maxWidthCalculated;
|
|
201
|
+
to.height = heightCalculated;
|
|
202
|
+
|
|
203
|
+
// Large photo, resize it
|
|
204
|
+
// https://github.com/nodeca/pica
|
|
205
|
+
picaInstance
|
|
206
|
+
.resize(data, to, {
|
|
207
|
+
unsharpAmount: 160,
|
|
208
|
+
unsharpRadius: 0.6,
|
|
209
|
+
unsharpThreshold: 1
|
|
210
|
+
})
|
|
211
|
+
.then((result) => onDone(result, toBlob));
|
|
212
|
+
} else {
|
|
213
|
+
onDone(data, toBlob);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
React.useEffect(() => {
|
|
218
|
+
import("react-avatar-editor").then((result) => setAE(result.default));
|
|
219
|
+
}, []);
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<Stack direction="column" spacing={0.5} width={containerWidth}>
|
|
223
|
+
<Button
|
|
224
|
+
variant="outlined"
|
|
225
|
+
size="medium"
|
|
226
|
+
component="label"
|
|
227
|
+
startIcon={<ComputerIcon />}
|
|
228
|
+
fullWidth
|
|
229
|
+
>
|
|
230
|
+
{labels.upload}
|
|
231
|
+
<input
|
|
232
|
+
id="fileInput"
|
|
233
|
+
type="file"
|
|
234
|
+
accept="image/png, image/jpeg"
|
|
235
|
+
multiple={false}
|
|
236
|
+
hidden
|
|
237
|
+
onChange={handleFileChange}
|
|
238
|
+
/>
|
|
239
|
+
</Button>
|
|
240
|
+
<Stack direction="row" spacing={0.5}>
|
|
241
|
+
{AE == null ? (
|
|
242
|
+
<Skeleton variant="rounded" width={width} height={height} />
|
|
243
|
+
) : (
|
|
244
|
+
<AE
|
|
245
|
+
ref={ref}
|
|
246
|
+
border={border}
|
|
247
|
+
width={width}
|
|
248
|
+
height={height}
|
|
249
|
+
onLoadSuccess={handleLoad}
|
|
250
|
+
image={previewImage ?? ""}
|
|
251
|
+
scale={editorState.scale}
|
|
252
|
+
rotate={editorState.rotate}
|
|
253
|
+
/>
|
|
254
|
+
)}
|
|
255
|
+
<ButtonGroup size="small" orientation="vertical" disabled={!ready}>
|
|
256
|
+
<Button onClick={() => handleRotate(90)} title={labels.rotateRight}>
|
|
257
|
+
<RotateRightIcon />
|
|
258
|
+
</Button>
|
|
259
|
+
<Button onClick={() => handleRotate(-90)} title={labels.rotateLeft}>
|
|
260
|
+
<RotateLeftIcon />
|
|
261
|
+
</Button>
|
|
262
|
+
<Button onClick={handleReset} title={labels.reset}>
|
|
263
|
+
<ClearAllIcon />
|
|
264
|
+
</Button>
|
|
265
|
+
</ButtonGroup>
|
|
266
|
+
</Stack>
|
|
267
|
+
<Slider
|
|
268
|
+
title={labels.zoom}
|
|
269
|
+
disabled={!ready}
|
|
270
|
+
min={1}
|
|
271
|
+
max={5}
|
|
272
|
+
step={0.01}
|
|
273
|
+
value={editorState.scale}
|
|
274
|
+
onChange={handleZoom}
|
|
275
|
+
/>
|
|
276
|
+
<Button
|
|
277
|
+
ref={buttonRef}
|
|
278
|
+
variant="contained"
|
|
279
|
+
startIcon={<DoneIcon />}
|
|
280
|
+
disabled={!ready}
|
|
281
|
+
onClick={handleDone}
|
|
282
|
+
>
|
|
283
|
+
{labels.done}
|
|
284
|
+
</Button>
|
|
285
|
+
</Stack>
|
|
286
|
+
);
|
|
287
287
|
}
|