@etsoo/materialui 1.5.19 → 1.5.21
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/FileUploadButton.d.ts +5 -1
- package/lib/cjs/FileUploadButton.js +56 -31
- package/lib/cjs/html/HtmlDiv.d.ts +7 -0
- package/lib/cjs/html/HtmlDiv.js +39 -0
- package/lib/cjs/index.d.ts +1 -0
- package/lib/cjs/index.js +1 -0
- package/lib/mjs/FileUploadButton.d.ts +5 -1
- package/lib/mjs/FileUploadButton.js +56 -31
- package/lib/mjs/html/HtmlDiv.d.ts +7 -0
- package/lib/mjs/html/HtmlDiv.js +33 -0
- package/lib/mjs/index.d.ts +1 -0
- package/lib/mjs/index.js +1 -0
- package/package.json +6 -5
- package/src/FileUploadButton.tsx +83 -34
- package/src/global.d.ts +5 -0
- package/src/html/HtmlDiv.tsx +39 -0
- package/src/index.ts +2 -0
- package/tsconfig.cjs.json +2 -0
- package/tsconfig.json +1 -0
- package/' +0 -0
|
@@ -4,6 +4,10 @@ import React from "react";
|
|
|
4
4
|
* File upload button props
|
|
5
5
|
*/
|
|
6
6
|
export type FileUploadButtonProps = ButtonProps<"label"> & {
|
|
7
|
+
/**
|
|
8
|
+
* Drop files label
|
|
9
|
+
*/
|
|
10
|
+
dropFilesLabel?: React.ReactNode;
|
|
7
11
|
/**
|
|
8
12
|
* Max files allowed
|
|
9
13
|
*/
|
|
@@ -26,7 +30,7 @@ export type FileUploadButtonProps = ButtonProps<"label"> & {
|
|
|
26
30
|
* Upload files callback
|
|
27
31
|
* @param files Files
|
|
28
32
|
*/
|
|
29
|
-
onUploadFiles
|
|
33
|
+
onUploadFiles: (files: FileList) => void;
|
|
30
34
|
};
|
|
31
35
|
/**
|
|
32
36
|
* File upload button
|
|
@@ -5,7 +5,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.FileUploadButton = FileUploadButton;
|
|
7
7
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const material_1 = require("@mui/material");
|
|
8
9
|
const Button_1 = __importDefault(require("@mui/material/Button"));
|
|
10
|
+
const react_1 = __importDefault(require("react"));
|
|
11
|
+
const FlexBox_1 = require("./FlexBox");
|
|
9
12
|
/**
|
|
10
13
|
* File upload button
|
|
11
14
|
* @param props Props
|
|
@@ -13,37 +16,59 @@ const Button_1 = __importDefault(require("@mui/material/Button"));
|
|
|
13
16
|
*/
|
|
14
17
|
function FileUploadButton(props) {
|
|
15
18
|
// Destruct
|
|
16
|
-
const { maxFiles, maxFileSize, inputProps, onFileInvalid, onUploadFiles, children, ...rest } = props;
|
|
19
|
+
const { dropFilesLabel, maxFiles, maxFileSize, inputProps, onFileInvalid, onUploadFiles, children = "Browse", ...rest } = props;
|
|
17
20
|
const { onChange } = inputProps ?? {};
|
|
21
|
+
const [dragOver, setDragOver] = react_1.default.useState(false);
|
|
22
|
+
const handleDrop = (e) => {
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
setDragOver(false);
|
|
25
|
+
validateFiles(e.dataTransfer.files);
|
|
26
|
+
};
|
|
27
|
+
const handleDragOver = (e) => {
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
setDragOver(true);
|
|
30
|
+
};
|
|
31
|
+
const handleDragLeave = () => setDragOver(false);
|
|
32
|
+
const validateFiles = (files) => {
|
|
33
|
+
const fl = files.length;
|
|
34
|
+
if (fl === 0)
|
|
35
|
+
return;
|
|
36
|
+
if (maxFiles && maxFiles > 0 && fl > maxFiles) {
|
|
37
|
+
if (onFileInvalid)
|
|
38
|
+
onFileInvalid([maxFiles, fl]);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (maxFileSize && maxFileSize > 0) {
|
|
42
|
+
for (let f = 0; f < fl; f++) {
|
|
43
|
+
const file = files[f];
|
|
44
|
+
if (file.size > maxFileSize) {
|
|
45
|
+
if (onFileInvalid)
|
|
46
|
+
onFileInvalid([maxFileSize, file.size], file);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
onUploadFiles(files);
|
|
52
|
+
};
|
|
18
53
|
// Layout
|
|
19
|
-
return ((0, jsx_runtime_1.jsxs)(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (file.size > maxFileSize) {
|
|
40
|
-
if (onFileInvalid)
|
|
41
|
-
onFileInvalid([maxFileSize, file.size], file);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
onUploadFiles(files);
|
|
47
|
-
}
|
|
48
|
-
}, ...inputProps })] }));
|
|
54
|
+
return ((0, jsx_runtime_1.jsxs)(FlexBox_1.HBox, { alignItems: "center", flexWrap: "wrap", border: (theme) => dragOver ? "1px dashed " + theme.palette.warning.main : undefined, spacing: 0.5, ...(dropFilesLabel == null
|
|
55
|
+
? undefined
|
|
56
|
+
: {
|
|
57
|
+
onDrop: handleDrop,
|
|
58
|
+
onDragOver: handleDragOver,
|
|
59
|
+
onDragLeave: handleDragLeave
|
|
60
|
+
}), children: [dropFilesLabel &&
|
|
61
|
+
(typeof dropFilesLabel === "string" ? ((0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", children: dropFilesLabel })) : (dropFilesLabel)), (0, jsx_runtime_1.jsxs)(Button_1.default, { component: "label", sx: { textTransform: "none" }, ...rest, children: [children, (0, jsx_runtime_1.jsx)("input", { type: "file", hidden: true, onChange: (event) => {
|
|
62
|
+
if (onChange)
|
|
63
|
+
onChange(event);
|
|
64
|
+
if (event.isDefaultPrevented())
|
|
65
|
+
return;
|
|
66
|
+
const files = event.target.files;
|
|
67
|
+
if (files == null)
|
|
68
|
+
return;
|
|
69
|
+
const fl = files.length;
|
|
70
|
+
if (fl === 0)
|
|
71
|
+
return;
|
|
72
|
+
validateFiles(files);
|
|
73
|
+
}, ...inputProps })] })] }));
|
|
49
74
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom HTML element that sanitizes and displays HTML content
|
|
3
|
+
* 自定义 HTML 元素,用于清理和显示 HTML 内容
|
|
4
|
+
* @param props Properties
|
|
5
|
+
* @returns Component
|
|
6
|
+
*/
|
|
7
|
+
export declare function HtmlDiv(props: React.PropsWithChildren<HTMLElement>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.HtmlDiv = HtmlDiv;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const dompurify_1 = __importDefault(require("dompurify"));
|
|
9
|
+
class HtmlDivElement extends HTMLElement {
|
|
10
|
+
constructor() {
|
|
11
|
+
super();
|
|
12
|
+
}
|
|
13
|
+
connectedCallback() {
|
|
14
|
+
// Create a shadow root
|
|
15
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
16
|
+
// Create a wrapper element to hold the sanitized HTML content
|
|
17
|
+
const wrapper = document.createElement("div");
|
|
18
|
+
if (this.textContent) {
|
|
19
|
+
wrapper.innerHTML = dompurify_1.default.sanitize(this.textContent);
|
|
20
|
+
}
|
|
21
|
+
shadow.appendChild(wrapper);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Define the custom element only once
|
|
25
|
+
if (!customElements.get("html-div")) {
|
|
26
|
+
customElements.define("html-div", HtmlDivElement);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Custom HTML element that sanitizes and displays HTML content
|
|
30
|
+
* 自定义 HTML 元素,用于清理和显示 HTML 内容
|
|
31
|
+
* @param props Properties
|
|
32
|
+
* @returns Component
|
|
33
|
+
*/
|
|
34
|
+
function HtmlDiv(props) {
|
|
35
|
+
// Destruct
|
|
36
|
+
const { children, ...rest } = props;
|
|
37
|
+
// Layout
|
|
38
|
+
return (0, jsx_runtime_1.jsx)("html-div", { ...rest, children: children });
|
|
39
|
+
}
|
package/lib/cjs/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./app/ServiceApp";
|
|
|
9
9
|
export * from "./custom/CustomFieldUtils";
|
|
10
10
|
export * from "./custom/CustomFieldViewer";
|
|
11
11
|
export * from "./custom/CustomFieldWindow";
|
|
12
|
+
export * from "./html/HtmlDiv";
|
|
12
13
|
export * from "./messages/MessageUtils";
|
|
13
14
|
export * from "./messages/OperationMessageContainer";
|
|
14
15
|
export * from "./messages/OperationMessageDto";
|
package/lib/cjs/index.js
CHANGED
|
@@ -25,6 +25,7 @@ __exportStar(require("./app/ServiceApp"), exports);
|
|
|
25
25
|
__exportStar(require("./custom/CustomFieldUtils"), exports);
|
|
26
26
|
__exportStar(require("./custom/CustomFieldViewer"), exports);
|
|
27
27
|
__exportStar(require("./custom/CustomFieldWindow"), exports);
|
|
28
|
+
__exportStar(require("./html/HtmlDiv"), exports);
|
|
28
29
|
__exportStar(require("./messages/MessageUtils"), exports);
|
|
29
30
|
__exportStar(require("./messages/OperationMessageContainer"), exports);
|
|
30
31
|
__exportStar(require("./messages/OperationMessageDto"), exports);
|
|
@@ -4,6 +4,10 @@ import React from "react";
|
|
|
4
4
|
* File upload button props
|
|
5
5
|
*/
|
|
6
6
|
export type FileUploadButtonProps = ButtonProps<"label"> & {
|
|
7
|
+
/**
|
|
8
|
+
* Drop files label
|
|
9
|
+
*/
|
|
10
|
+
dropFilesLabel?: React.ReactNode;
|
|
7
11
|
/**
|
|
8
12
|
* Max files allowed
|
|
9
13
|
*/
|
|
@@ -26,7 +30,7 @@ export type FileUploadButtonProps = ButtonProps<"label"> & {
|
|
|
26
30
|
* Upload files callback
|
|
27
31
|
* @param files Files
|
|
28
32
|
*/
|
|
29
|
-
onUploadFiles
|
|
33
|
+
onUploadFiles: (files: FileList) => void;
|
|
30
34
|
};
|
|
31
35
|
/**
|
|
32
36
|
* File upload button
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Typography } from "@mui/material";
|
|
2
3
|
import Button from "@mui/material/Button";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { HBox } from "./FlexBox";
|
|
3
6
|
/**
|
|
4
7
|
* File upload button
|
|
5
8
|
* @param props Props
|
|
@@ -7,37 +10,59 @@ import Button from "@mui/material/Button";
|
|
|
7
10
|
*/
|
|
8
11
|
export function FileUploadButton(props) {
|
|
9
12
|
// Destruct
|
|
10
|
-
const { maxFiles, maxFileSize, inputProps, onFileInvalid, onUploadFiles, children, ...rest } = props;
|
|
13
|
+
const { dropFilesLabel, maxFiles, maxFileSize, inputProps, onFileInvalid, onUploadFiles, children = "Browse", ...rest } = props;
|
|
11
14
|
const { onChange } = inputProps ?? {};
|
|
15
|
+
const [dragOver, setDragOver] = React.useState(false);
|
|
16
|
+
const handleDrop = (e) => {
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
setDragOver(false);
|
|
19
|
+
validateFiles(e.dataTransfer.files);
|
|
20
|
+
};
|
|
21
|
+
const handleDragOver = (e) => {
|
|
22
|
+
e.preventDefault();
|
|
23
|
+
setDragOver(true);
|
|
24
|
+
};
|
|
25
|
+
const handleDragLeave = () => setDragOver(false);
|
|
26
|
+
const validateFiles = (files) => {
|
|
27
|
+
const fl = files.length;
|
|
28
|
+
if (fl === 0)
|
|
29
|
+
return;
|
|
30
|
+
if (maxFiles && maxFiles > 0 && fl > maxFiles) {
|
|
31
|
+
if (onFileInvalid)
|
|
32
|
+
onFileInvalid([maxFiles, fl]);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (maxFileSize && maxFileSize > 0) {
|
|
36
|
+
for (let f = 0; f < fl; f++) {
|
|
37
|
+
const file = files[f];
|
|
38
|
+
if (file.size > maxFileSize) {
|
|
39
|
+
if (onFileInvalid)
|
|
40
|
+
onFileInvalid([maxFileSize, file.size], file);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
onUploadFiles(files);
|
|
46
|
+
};
|
|
12
47
|
// Layout
|
|
13
|
-
return (_jsxs(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (file.size > maxFileSize) {
|
|
34
|
-
if (onFileInvalid)
|
|
35
|
-
onFileInvalid([maxFileSize, file.size], file);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
onUploadFiles(files);
|
|
41
|
-
}
|
|
42
|
-
}, ...inputProps })] }));
|
|
48
|
+
return (_jsxs(HBox, { alignItems: "center", flexWrap: "wrap", border: (theme) => dragOver ? "1px dashed " + theme.palette.warning.main : undefined, spacing: 0.5, ...(dropFilesLabel == null
|
|
49
|
+
? undefined
|
|
50
|
+
: {
|
|
51
|
+
onDrop: handleDrop,
|
|
52
|
+
onDragOver: handleDragOver,
|
|
53
|
+
onDragLeave: handleDragLeave
|
|
54
|
+
}), children: [dropFilesLabel &&
|
|
55
|
+
(typeof dropFilesLabel === "string" ? (_jsx(Typography, { variant: "body2", children: dropFilesLabel })) : (dropFilesLabel)), _jsxs(Button, { component: "label", sx: { textTransform: "none" }, ...rest, children: [children, _jsx("input", { type: "file", hidden: true, onChange: (event) => {
|
|
56
|
+
if (onChange)
|
|
57
|
+
onChange(event);
|
|
58
|
+
if (event.isDefaultPrevented())
|
|
59
|
+
return;
|
|
60
|
+
const files = event.target.files;
|
|
61
|
+
if (files == null)
|
|
62
|
+
return;
|
|
63
|
+
const fl = files.length;
|
|
64
|
+
if (fl === 0)
|
|
65
|
+
return;
|
|
66
|
+
validateFiles(files);
|
|
67
|
+
}, ...inputProps })] })] }));
|
|
43
68
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom HTML element that sanitizes and displays HTML content
|
|
3
|
+
* 自定义 HTML 元素,用于清理和显示 HTML 内容
|
|
4
|
+
* @param props Properties
|
|
5
|
+
* @returns Component
|
|
6
|
+
*/
|
|
7
|
+
export declare function HtmlDiv(props: React.PropsWithChildren<HTMLElement>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import DOMPurify from "dompurify";
|
|
3
|
+
class HtmlDivElement extends HTMLElement {
|
|
4
|
+
constructor() {
|
|
5
|
+
super();
|
|
6
|
+
}
|
|
7
|
+
connectedCallback() {
|
|
8
|
+
// Create a shadow root
|
|
9
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
10
|
+
// Create a wrapper element to hold the sanitized HTML content
|
|
11
|
+
const wrapper = document.createElement("div");
|
|
12
|
+
if (this.textContent) {
|
|
13
|
+
wrapper.innerHTML = DOMPurify.sanitize(this.textContent);
|
|
14
|
+
}
|
|
15
|
+
shadow.appendChild(wrapper);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// Define the custom element only once
|
|
19
|
+
if (!customElements.get("html-div")) {
|
|
20
|
+
customElements.define("html-div", HtmlDivElement);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Custom HTML element that sanitizes and displays HTML content
|
|
24
|
+
* 自定义 HTML 元素,用于清理和显示 HTML 内容
|
|
25
|
+
* @param props Properties
|
|
26
|
+
* @returns Component
|
|
27
|
+
*/
|
|
28
|
+
export function HtmlDiv(props) {
|
|
29
|
+
// Destruct
|
|
30
|
+
const { children, ...rest } = props;
|
|
31
|
+
// Layout
|
|
32
|
+
return _jsx("html-div", { ...rest, children: children });
|
|
33
|
+
}
|
package/lib/mjs/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./app/ServiceApp";
|
|
|
9
9
|
export * from "./custom/CustomFieldUtils";
|
|
10
10
|
export * from "./custom/CustomFieldViewer";
|
|
11
11
|
export * from "./custom/CustomFieldWindow";
|
|
12
|
+
export * from "./html/HtmlDiv";
|
|
12
13
|
export * from "./messages/MessageUtils";
|
|
13
14
|
export * from "./messages/OperationMessageContainer";
|
|
14
15
|
export * from "./messages/OperationMessageDto";
|
package/lib/mjs/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./app/ServiceApp";
|
|
|
9
9
|
export * from "./custom/CustomFieldUtils";
|
|
10
10
|
export * from "./custom/CustomFieldViewer";
|
|
11
11
|
export * from "./custom/CustomFieldWindow";
|
|
12
|
+
export * from "./html/HtmlDiv";
|
|
12
13
|
export * from "./messages/MessageUtils";
|
|
13
14
|
export * from "./messages/OperationMessageContainer";
|
|
14
15
|
export * from "./messages/OperationMessageDto";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/materialui",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.21",
|
|
4
4
|
"description": "TypeScript Material-UI Implementation",
|
|
5
5
|
"main": "lib/cjs/index.js",
|
|
6
6
|
"module": "lib/mjs/index.js",
|
|
@@ -40,15 +40,16 @@
|
|
|
40
40
|
"@dnd-kit/sortable": "^10.0.0",
|
|
41
41
|
"@emotion/react": "^11.14.0",
|
|
42
42
|
"@emotion/styled": "^11.14.0",
|
|
43
|
-
"@etsoo/appscript": "^1.6.
|
|
43
|
+
"@etsoo/appscript": "^1.6.22",
|
|
44
44
|
"@etsoo/notificationbase": "^1.1.60",
|
|
45
|
-
"@etsoo/react": "^1.8.
|
|
45
|
+
"@etsoo/react": "^1.8.38",
|
|
46
46
|
"@etsoo/shared": "^1.2.66",
|
|
47
|
-
"@mui/icons-material": "^7.0.
|
|
48
|
-
"@mui/material": "^7.0.
|
|
47
|
+
"@mui/icons-material": "^7.0.2",
|
|
48
|
+
"@mui/material": "^7.0.2",
|
|
49
49
|
"@mui/x-data-grid": "^7.28.3",
|
|
50
50
|
"chart.js": "^4.4.8",
|
|
51
51
|
"chartjs-plugin-datalabels": "^2.2.0",
|
|
52
|
+
"dompurify": "^3.2.5",
|
|
52
53
|
"eventemitter3": "^5.0.1",
|
|
53
54
|
"pica": "^9.0.1",
|
|
54
55
|
"pulltorefreshjs": "^0.1.22",
|
package/src/FileUploadButton.tsx
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import { Typography } from "@mui/material";
|
|
1
2
|
import Button, { ButtonProps } from "@mui/material/Button";
|
|
2
3
|
import React from "react";
|
|
4
|
+
import { HBox } from "./FlexBox";
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* File upload button props
|
|
6
8
|
*/
|
|
7
9
|
export type FileUploadButtonProps = ButtonProps<"label"> & {
|
|
10
|
+
/**
|
|
11
|
+
* Drop files label
|
|
12
|
+
*/
|
|
13
|
+
dropFilesLabel?: React.ReactNode;
|
|
14
|
+
|
|
8
15
|
/**
|
|
9
16
|
* Max files allowed
|
|
10
17
|
*/
|
|
@@ -34,7 +41,7 @@ export type FileUploadButtonProps = ButtonProps<"label"> & {
|
|
|
34
41
|
* Upload files callback
|
|
35
42
|
* @param files Files
|
|
36
43
|
*/
|
|
37
|
-
onUploadFiles
|
|
44
|
+
onUploadFiles: (files: FileList) => void;
|
|
38
45
|
};
|
|
39
46
|
|
|
40
47
|
/**
|
|
@@ -45,56 +52,98 @@ export type FileUploadButtonProps = ButtonProps<"label"> & {
|
|
|
45
52
|
export function FileUploadButton(props: FileUploadButtonProps) {
|
|
46
53
|
// Destruct
|
|
47
54
|
const {
|
|
55
|
+
dropFilesLabel,
|
|
48
56
|
maxFiles,
|
|
49
57
|
maxFileSize,
|
|
50
58
|
inputProps,
|
|
51
59
|
onFileInvalid,
|
|
52
60
|
onUploadFiles,
|
|
53
|
-
children,
|
|
61
|
+
children = "Browse",
|
|
54
62
|
...rest
|
|
55
63
|
} = props;
|
|
56
64
|
|
|
57
65
|
const { onChange } = inputProps ?? {};
|
|
58
66
|
|
|
67
|
+
const [dragOver, setDragOver] = React.useState(false);
|
|
68
|
+
|
|
69
|
+
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
setDragOver(false);
|
|
72
|
+
validateFiles(e.dataTransfer.files);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
setDragOver(true);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleDragLeave = () => setDragOver(false);
|
|
81
|
+
|
|
82
|
+
const validateFiles = (files: FileList) => {
|
|
83
|
+
const fl = files.length;
|
|
84
|
+
if (fl === 0) return;
|
|
85
|
+
|
|
86
|
+
if (maxFiles && maxFiles > 0 && fl > maxFiles) {
|
|
87
|
+
if (onFileInvalid) onFileInvalid([maxFiles, fl]);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (maxFileSize && maxFileSize > 0) {
|
|
92
|
+
for (let f = 0; f < fl; f++) {
|
|
93
|
+
const file = files[f];
|
|
94
|
+
if (file.size > maxFileSize) {
|
|
95
|
+
if (onFileInvalid) onFileInvalid([maxFileSize, file.size], file);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
onUploadFiles(files);
|
|
102
|
+
};
|
|
103
|
+
|
|
59
104
|
// Layout
|
|
60
105
|
return (
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
106
|
+
<HBox
|
|
107
|
+
alignItems="center"
|
|
108
|
+
flexWrap="wrap"
|
|
109
|
+
border={(theme) =>
|
|
110
|
+
dragOver ? "1px dashed " + theme.palette.warning.main : undefined
|
|
111
|
+
}
|
|
112
|
+
spacing={0.5}
|
|
113
|
+
{...(dropFilesLabel == null
|
|
114
|
+
? undefined
|
|
115
|
+
: {
|
|
116
|
+
onDrop: handleDrop,
|
|
117
|
+
onDragOver: handleDragOver,
|
|
118
|
+
onDragLeave: handleDragLeave
|
|
119
|
+
})}
|
|
120
|
+
>
|
|
121
|
+
{dropFilesLabel &&
|
|
122
|
+
(typeof dropFilesLabel === "string" ? (
|
|
123
|
+
<Typography variant="body2">{dropFilesLabel}</Typography>
|
|
124
|
+
) : (
|
|
125
|
+
dropFilesLabel
|
|
126
|
+
))}
|
|
127
|
+
<Button component="label" sx={{ textTransform: "none" }} {...rest}>
|
|
128
|
+
{children}
|
|
129
|
+
<input
|
|
130
|
+
type="file"
|
|
131
|
+
hidden={true}
|
|
132
|
+
onChange={(event) => {
|
|
133
|
+
if (onChange) onChange(event);
|
|
134
|
+
if (event.isDefaultPrevented()) return;
|
|
135
|
+
|
|
71
136
|
const files = event.target.files;
|
|
72
137
|
if (files == null) return;
|
|
73
138
|
|
|
74
139
|
const fl = files.length;
|
|
75
140
|
if (fl === 0) return;
|
|
76
141
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
for (let f = 0; f < fl; f++) {
|
|
84
|
-
const file = files[f];
|
|
85
|
-
if (file.size > maxFileSize) {
|
|
86
|
-
if (onFileInvalid)
|
|
87
|
-
onFileInvalid([maxFileSize, file.size], file);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
onUploadFiles(files);
|
|
94
|
-
}
|
|
95
|
-
}}
|
|
96
|
-
{...inputProps}
|
|
97
|
-
/>
|
|
98
|
-
</Button>
|
|
142
|
+
validateFiles(files);
|
|
143
|
+
}}
|
|
144
|
+
{...inputProps}
|
|
145
|
+
/>
|
|
146
|
+
</Button>
|
|
147
|
+
</HBox>
|
|
99
148
|
);
|
|
100
149
|
}
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import DOMPurify from "dompurify";
|
|
2
|
+
|
|
3
|
+
class HtmlDivElement extends HTMLElement {
|
|
4
|
+
constructor() {
|
|
5
|
+
super();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
connectedCallback() {
|
|
9
|
+
// Create a shadow root
|
|
10
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
11
|
+
|
|
12
|
+
// Create a wrapper element to hold the sanitized HTML content
|
|
13
|
+
const wrapper = document.createElement("div");
|
|
14
|
+
if (this.textContent) {
|
|
15
|
+
wrapper.innerHTML = DOMPurify.sanitize(this.textContent);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
shadow.appendChild(wrapper);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Define the custom element only once
|
|
23
|
+
if (!customElements.get("html-div")) {
|
|
24
|
+
customElements.define("html-div", HtmlDivElement);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Custom HTML element that sanitizes and displays HTML content
|
|
29
|
+
* 自定义 HTML 元素,用于清理和显示 HTML 内容
|
|
30
|
+
* @param props Properties
|
|
31
|
+
* @returns Component
|
|
32
|
+
*/
|
|
33
|
+
export function HtmlDiv(props: React.PropsWithChildren<HTMLElement>) {
|
|
34
|
+
// Destruct
|
|
35
|
+
const { children, ...rest } = props;
|
|
36
|
+
|
|
37
|
+
// Layout
|
|
38
|
+
return <html-div {...rest}>{children}</html-div>;
|
|
39
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,8 @@ export * from "./custom/CustomFieldUtils";
|
|
|
11
11
|
export * from "./custom/CustomFieldViewer";
|
|
12
12
|
export * from "./custom/CustomFieldWindow";
|
|
13
13
|
|
|
14
|
+
export * from "./html/HtmlDiv";
|
|
15
|
+
|
|
14
16
|
export * from "./messages/MessageUtils";
|
|
15
17
|
export * from "./messages/OperationMessageContainer";
|
|
16
18
|
export * from "./messages/OperationMessageDto";
|
package/tsconfig.cjs.json
CHANGED
package/tsconfig.json
CHANGED
package/'
DELETED
|
File without changes
|