@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.
@@ -1,80 +1,79 @@
1
- import { Button, ButtonGroup, Slider, Stack } from '@mui/material';
2
- import React from 'react';
3
- import 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 pica from 'pica';
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
- canvas: HTMLCanvasElement,
18
- mimeType?: string,
19
- quality?: number
20
- ): Promise<Blob>;
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
- (canvas: HTMLCanvasElement, toBlob: UserAvatarEditorToBlob): void;
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
- * Cropping border size
36
- */
37
- border?: number;
38
-
39
- /**
40
- * Image source
41
- */
42
- image?: string | File;
43
-
44
- /**
45
- * Max width to save
46
- */
47
- maxWidth?: number;
48
-
49
- /**
50
- * On done handler
51
- */
52
- onDone: UserAvatarEditorOnDoneHandler;
53
-
54
- /**
55
- * Return scaled result?
56
- */
57
- scaledResult?: boolean;
58
-
59
- /**
60
- * Width of the editor
61
- */
62
- width?: number;
63
-
64
- /**
65
- * Height of the editor
66
- */
67
- height?: number;
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
- scale: number;
72
- rotate: number;
70
+ scale: number;
71
+ rotate: number;
73
72
  }
74
73
 
75
74
  const defaultState: EditorState = {
76
- scale: 1,
77
- rotate: 0
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
- // Destruct
88
- const {
89
- border = 30,
90
- image,
91
- maxWidth,
92
- onDone,
93
- scaledResult = false,
94
- width = 200,
95
- height = 200
96
- } = props;
97
-
98
- // Container width
99
- const containerWidth = width + 2 * border + 44 + 4;
100
-
101
- // Calculated max width
102
- const maxWidthCalculated =
103
- maxWidth == null || maxWidth < 200 ? 3 * width : maxWidth;
104
-
105
- // Labels
106
- const labels = Labels.UserAvatarEditor;
107
-
108
- // Ref
109
- const ref = React.createRef<AvatarEditor>();
110
-
111
- // Button ref
112
- const buttonRef = React.createRef<HTMLButtonElement>();
113
-
114
- // Preview image state
115
- const [previewImage, setPreviewImage] = React.useState(image);
116
-
117
- // Is ready state
118
- const [ready, setReady] = React.useState(false);
119
-
120
- // Editor states
121
- const [editorState, setEditorState] = React.useState(defaultState);
122
-
123
- // Handle zoom
124
- const handleZoom = (
125
- _event: Event,
126
- value: number | number[],
127
- _activeThumb: number
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
- const scale = typeof value === 'number' ? value : value[0];
130
- const newState = { ...editorState, scale };
131
- setEditorState(newState);
191
+ return picaInstance.toBlob(canvas, mimeType, quality);
132
192
  };
133
193
 
134
- // Handle image load
135
- const handleLoad = () => {
136
- setReady(true);
137
- };
138
-
139
- // Handle file change
140
- const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
141
- const files = event.target.files;
142
- if (files == null || files.length == 0) return;
143
-
144
- // Reset all settings
145
- handleReset();
146
-
147
- // Set new preview image
148
- setPreviewImage(files[0]);
149
-
150
- // Set ready state
151
- setReady(false);
152
-
153
- // Make the submit button visible
154
- buttonRef.current?.scrollIntoView(false);
155
- };
156
-
157
- // Handle reset
158
- const handleReset = () => {
159
- setEditorState({ ...defaultState });
160
- };
161
-
162
- // Handle rotate
163
- const handleRotate = (r: number) => {
164
- let rotate = editorState.rotate + r;
165
- if (rotate >= 360 || rotate <= -360) rotate = 0;
166
-
167
- const newState = { ...editorState, rotate };
168
- setEditorState(newState);
169
- };
170
-
171
- // Handle done
172
- const handleDone = () => {
173
- // Data
174
- var data = scaledResult
175
- ? ref.current?.getImageScaledToCanvas()
176
- : ref.current?.getImage();
177
- if (data == null) return;
178
-
179
- // pica
180
- const picaInstance = pica();
181
-
182
- // toBlob helper
183
- // Convenience method, similar to canvas.toBlob(), but with promise interface & polyfill for old browsers.
184
- const toBlob = (
185
- canvas: HTMLCanvasElement,
186
- mimeType: string = 'image/jpeg',
187
- quality: number = 1
188
- ) => {
189
- return picaInstance.toBlob(canvas, mimeType, quality);
190
- };
191
-
192
- if (data.width > maxWidthCalculated) {
193
- // Target height
194
- const heightCalculated = (height * maxWidthCalculated) / width;
195
-
196
- // Target
197
- const to = document.createElement('canvas');
198
- to.width = maxWidthCalculated;
199
- to.height = heightCalculated;
200
-
201
- // Large photo, resize it
202
- // https://github.com/nodeca/pica
203
- picaInstance
204
- .resize(data, to, {
205
- unsharpAmount: 160,
206
- unsharpRadius: 0.6,
207
- unsharpThreshold: 1
208
- })
209
- .then((result) => onDone(result, toBlob));
210
- } else {
211
- onDone(data, toBlob);
212
- }
213
- };
214
-
215
- return (
216
- <Stack direction="column" spacing={0.5} width={containerWidth}>
217
- <Button
218
- variant="outlined"
219
- size="medium"
220
- component="label"
221
- startIcon={<ComputerIcon />}
222
- fullWidth
223
- >
224
- {labels.upload}
225
- <input
226
- id="fileInput"
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
  }