@susonwaiba/react-media-uploader 0.1.0
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/NEXTJS_INTEGRATION.md +102 -0
- package/README.md +62 -0
- package/bun.lock +158 -0
- package/dist/hooks/use-media-uploader.d.ts +22 -0
- package/dist/hooks/use-media-uploader.js +176 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/lib/media-helper.d.ts +31 -0
- package/dist/lib/media-helper.js +65 -0
- package/dist/types/media.d.ts +44 -0
- package/dist/types/media.js +16 -0
- package/package.json +32 -0
- package/src/hooks/use-media-uploader.ts +237 -0
- package/src/index.ts +3 -0
- package/src/lib/media-helper.ts +119 -0
- package/src/types/media.ts +47 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# NextJS Integration
|
|
2
|
+
|
|
3
|
+
## Prisma schema
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
enum MediaStatusEnum {
|
|
7
|
+
INIT
|
|
8
|
+
TEMP
|
|
9
|
+
ACTIVE
|
|
10
|
+
INACTIVE
|
|
11
|
+
CANCELED
|
|
12
|
+
DELETED
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
enum MediaTypeEnum {
|
|
16
|
+
IMAGE
|
|
17
|
+
PDF
|
|
18
|
+
DOCS
|
|
19
|
+
OTHER
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
model Media {
|
|
23
|
+
id String @id @default(cuid())
|
|
24
|
+
status MediaStatusEnum @default(INIT)
|
|
25
|
+
type MediaTypeEnum
|
|
26
|
+
title String
|
|
27
|
+
description String?
|
|
28
|
+
name String
|
|
29
|
+
dir String?
|
|
30
|
+
path String
|
|
31
|
+
provider String
|
|
32
|
+
container String?
|
|
33
|
+
mimeType String?
|
|
34
|
+
size Float?
|
|
35
|
+
height Float?
|
|
36
|
+
width Float?
|
|
37
|
+
duration Float?
|
|
38
|
+
tags String[] @default([])
|
|
39
|
+
checksum String?
|
|
40
|
+
createdAt DateTime @default(now())
|
|
41
|
+
updatedAt DateTime @default(now()) @updatedAt
|
|
42
|
+
deletedAt DateTime?
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API endpoints
|
|
47
|
+
|
|
48
|
+
- POST: `/api/media/generate-upload-url`
|
|
49
|
+
Response:
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"item": {},
|
|
53
|
+
"sasUrl": "<sas_upload_url>"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- POST: `/api/media/mark-media-as-active`
|
|
58
|
+
Payload:
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mediaIds": []
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
Response:
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"items": []
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- POST: `/api/media/mark-media-as-canceled`
|
|
72
|
+
Payload:
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"mediaIds": []
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
Response:
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"items": []
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- POST: `/api/media/mark-media-as-temp`
|
|
86
|
+
Payload:
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"mediaIds": []
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
Response:
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"items": []
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Storage cleanup
|
|
100
|
+
|
|
101
|
+
Run cron and filter with status for unused media cleanup.
|
|
102
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# React Media Uploader
|
|
2
|
+
|
|
3
|
+
`Status: Under development`
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install
|
|
9
|
+
|
|
10
|
+
bun run build
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## `useMediaUploader()` hook
|
|
14
|
+
|
|
15
|
+
#### Upload on select
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
export function Uploader() {
|
|
19
|
+
const uploader = useMediaUploader();
|
|
20
|
+
return (
|
|
21
|
+
<input
|
|
22
|
+
name="image"
|
|
23
|
+
type="file"
|
|
24
|
+
multiple
|
|
25
|
+
onChange={uploader.onFileInputChange}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
#### Manual upload
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
export function Uploader() {
|
|
35
|
+
const uploader = useMediaUploader({
|
|
36
|
+
enableManualUpload: true,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const onSubmit = async (e: React.FormEvent) => {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
const mediaValues = await uploader.uploadManually();
|
|
42
|
+
console.log("mediaValues ->", mediaValues);
|
|
43
|
+
// submit data to API
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<form onSubmit={onSubmit}>
|
|
48
|
+
<div className="mb-4">
|
|
49
|
+
<input
|
|
50
|
+
name="image"
|
|
51
|
+
type="file"
|
|
52
|
+
multiple
|
|
53
|
+
onChange={uploader.onFileInputChange}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
<div>
|
|
57
|
+
<button type="submit">Upload</button>
|
|
58
|
+
</div>
|
|
59
|
+
</form>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
package/bun.lock
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"workspaces": {
|
|
4
|
+
"": {
|
|
5
|
+
"name": "uploader",
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"@types/react": "^18",
|
|
8
|
+
"@types/react-dom": "^18",
|
|
9
|
+
"tsc-alias": "^1",
|
|
10
|
+
"typescript": "^5",
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"axios": ">=1",
|
|
14
|
+
"react": ">=18",
|
|
15
|
+
"react-dom": ">=18",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
"packages": {
|
|
20
|
+
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
|
21
|
+
|
|
22
|
+
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
|
23
|
+
|
|
24
|
+
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
|
25
|
+
|
|
26
|
+
"@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
|
|
27
|
+
|
|
28
|
+
"@types/react": ["@types/react@18.3.27", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w=="],
|
|
29
|
+
|
|
30
|
+
"@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="],
|
|
31
|
+
|
|
32
|
+
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
|
33
|
+
|
|
34
|
+
"array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="],
|
|
35
|
+
|
|
36
|
+
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
|
37
|
+
|
|
38
|
+
"axios": ["axios@1.13.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="],
|
|
39
|
+
|
|
40
|
+
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
|
41
|
+
|
|
42
|
+
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
|
43
|
+
|
|
44
|
+
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
|
45
|
+
|
|
46
|
+
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
|
47
|
+
|
|
48
|
+
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
|
49
|
+
|
|
50
|
+
"commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
|
|
51
|
+
|
|
52
|
+
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
|
53
|
+
|
|
54
|
+
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
|
55
|
+
|
|
56
|
+
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
|
|
57
|
+
|
|
58
|
+
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
|
59
|
+
|
|
60
|
+
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
|
61
|
+
|
|
62
|
+
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
|
63
|
+
|
|
64
|
+
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
|
65
|
+
|
|
66
|
+
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
|
67
|
+
|
|
68
|
+
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
|
69
|
+
|
|
70
|
+
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
|
71
|
+
|
|
72
|
+
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
|
73
|
+
|
|
74
|
+
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
|
|
75
|
+
|
|
76
|
+
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
|
|
77
|
+
|
|
78
|
+
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
|
79
|
+
|
|
80
|
+
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
|
81
|
+
|
|
82
|
+
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
|
83
|
+
|
|
84
|
+
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
|
85
|
+
|
|
86
|
+
"get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="],
|
|
87
|
+
|
|
88
|
+
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
|
89
|
+
|
|
90
|
+
"globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
|
|
91
|
+
|
|
92
|
+
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
|
93
|
+
|
|
94
|
+
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
|
95
|
+
|
|
96
|
+
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
|
97
|
+
|
|
98
|
+
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
|
99
|
+
|
|
100
|
+
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
|
101
|
+
|
|
102
|
+
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
|
|
103
|
+
|
|
104
|
+
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
|
105
|
+
|
|
106
|
+
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
|
107
|
+
|
|
108
|
+
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
|
109
|
+
|
|
110
|
+
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
|
111
|
+
|
|
112
|
+
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
|
113
|
+
|
|
114
|
+
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
|
115
|
+
|
|
116
|
+
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
|
117
|
+
|
|
118
|
+
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
|
119
|
+
|
|
120
|
+
"mylas": ["mylas@2.1.14", "", {}, "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog=="],
|
|
121
|
+
|
|
122
|
+
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
|
123
|
+
|
|
124
|
+
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
|
|
125
|
+
|
|
126
|
+
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
|
127
|
+
|
|
128
|
+
"plimit-lit": ["plimit-lit@1.6.1", "", { "dependencies": { "queue-lit": "^1.5.1" } }, "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA=="],
|
|
129
|
+
|
|
130
|
+
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
|
131
|
+
|
|
132
|
+
"queue-lit": ["queue-lit@1.5.2", "", {}, "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw=="],
|
|
133
|
+
|
|
134
|
+
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
|
135
|
+
|
|
136
|
+
"react": ["react@19.2.1", "", {}, "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw=="],
|
|
137
|
+
|
|
138
|
+
"react-dom": ["react-dom@19.2.1", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.1" } }, "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg=="],
|
|
139
|
+
|
|
140
|
+
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
|
141
|
+
|
|
142
|
+
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
|
143
|
+
|
|
144
|
+
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
|
145
|
+
|
|
146
|
+
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
|
147
|
+
|
|
148
|
+
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
|
149
|
+
|
|
150
|
+
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
|
|
151
|
+
|
|
152
|
+
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
|
153
|
+
|
|
154
|
+
"tsc-alias": ["tsc-alias@1.8.16", "", { "dependencies": { "chokidar": "^3.5.3", "commander": "^9.0.0", "get-tsconfig": "^4.10.0", "globby": "^11.0.4", "mylas": "^2.1.9", "normalize-path": "^3.0.0", "plimit-lit": "^1.2.6" }, "bin": { "tsc-alias": "dist/bin/index.js" } }, "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g=="],
|
|
155
|
+
|
|
156
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { MediaStatusEnum, type MediaItem } from "../types/media";
|
|
2
|
+
import { type AxiosProgressEvent } from "axios";
|
|
3
|
+
export interface UploadMediaInfo extends Omit<AxiosProgressEvent, "event"> {
|
|
4
|
+
event?: undefined;
|
|
5
|
+
cancel?: () => Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export interface UseMediaUploaderProps<T extends object> {
|
|
8
|
+
defaultValues?: T;
|
|
9
|
+
mediaUploadSuccessStatus?: MediaStatusEnum;
|
|
10
|
+
enableManualUpload?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface UseMediaUploaderResponse<T extends object> {
|
|
13
|
+
values: T;
|
|
14
|
+
setValues: (val: T) => void;
|
|
15
|
+
enableManualUpload?: boolean;
|
|
16
|
+
uploadManually: () => Promise<T>;
|
|
17
|
+
mediaItems: Record<string, MediaItem>;
|
|
18
|
+
uploadInfos: Record<string, UploadMediaInfo>;
|
|
19
|
+
onFileInputChange: (e: React.ChangeEvent<HTMLInputElement>) => Promise<void>;
|
|
20
|
+
onFileChange: (file: File, name: string, multiple?: boolean) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export declare function useMediaUploader<T extends object>({ defaultValues, mediaUploadSuccessStatus, enableManualUpload, }?: UseMediaUploaderProps<T>): UseMediaUploaderResponse<T>;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { markMediaAsCanceled, generateFileHash, generateUploadUrl, uploadToStorage, markMediaAsTemp, markMediaAsActive, generateMediaType, } from "../lib/media-helper";
|
|
2
|
+
import { MediaStatusEnum } from "../types/media";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
export function useMediaUploader({ defaultValues, mediaUploadSuccessStatus = MediaStatusEnum.TEMP, enableManualUpload = false, } = {}) {
|
|
5
|
+
const [values, setValues] = useState(defaultValues ?? {});
|
|
6
|
+
const [mediaItems, setMediaItems] = useState({});
|
|
7
|
+
const [uploadInfos, setUploadInfos] = useState({});
|
|
8
|
+
const onFileInputChange = async (e) => {
|
|
9
|
+
console.log("onFileInputChange -> e ->", e);
|
|
10
|
+
const target = e.target;
|
|
11
|
+
const name = target.name;
|
|
12
|
+
const multiple = target.multiple || false;
|
|
13
|
+
if (target.files && target.files.length) {
|
|
14
|
+
for (const file of target.files) {
|
|
15
|
+
onFileChange(file, name, multiple);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const onFileChange = async (file, name, multiple = false) => {
|
|
20
|
+
const localId = crypto.randomUUID();
|
|
21
|
+
const item = {
|
|
22
|
+
localId,
|
|
23
|
+
name,
|
|
24
|
+
multiple,
|
|
25
|
+
file,
|
|
26
|
+
tempPreviewUrl: URL.createObjectURL(file),
|
|
27
|
+
media: {
|
|
28
|
+
type: await generateMediaType(file),
|
|
29
|
+
name: file.name,
|
|
30
|
+
mimeType: file.type,
|
|
31
|
+
size: file.size,
|
|
32
|
+
checksum: await generateFileHash(file),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
setMediaItems((previous) => {
|
|
36
|
+
const newState = { ...previous };
|
|
37
|
+
newState[localId] = item;
|
|
38
|
+
return newState;
|
|
39
|
+
});
|
|
40
|
+
if (!enableManualUpload) {
|
|
41
|
+
uploadMediaFile(item);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const uploadMediaFile = async (item) => {
|
|
45
|
+
const sasUrlRes = await generateUploadUrl({ media: item?.media });
|
|
46
|
+
if (sasUrlRes?.data?.item && sasUrlRes?.data?.sasUrl) {
|
|
47
|
+
item["media"] = sasUrlRes?.data?.item;
|
|
48
|
+
setMediaItems((previous) => {
|
|
49
|
+
const newState = { ...previous };
|
|
50
|
+
newState[item.localId] = item;
|
|
51
|
+
return newState;
|
|
52
|
+
});
|
|
53
|
+
const abortController = new AbortController();
|
|
54
|
+
const uploadRes = await uploadToStorage({
|
|
55
|
+
sasUrl: sasUrlRes?.data?.sasUrl,
|
|
56
|
+
file: item.file,
|
|
57
|
+
onUploadProgress: (progressEvent) => {
|
|
58
|
+
const currentUploadInfo = {
|
|
59
|
+
...progressEvent,
|
|
60
|
+
event: undefined,
|
|
61
|
+
cancel: async () => {
|
|
62
|
+
abortController.abort();
|
|
63
|
+
setUploadInfos((previous) => {
|
|
64
|
+
const newState = { ...previous };
|
|
65
|
+
if (newState[item.localId]) {
|
|
66
|
+
delete newState[item.localId];
|
|
67
|
+
}
|
|
68
|
+
return newState;
|
|
69
|
+
});
|
|
70
|
+
await markMediaAsCanceled({
|
|
71
|
+
mediaIds: [sasUrlRes?.data?.item?.id],
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
setUploadInfos((previous) => {
|
|
76
|
+
const newState = { ...previous };
|
|
77
|
+
newState[item.localId] = currentUploadInfo;
|
|
78
|
+
return newState;
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
abortController,
|
|
82
|
+
});
|
|
83
|
+
if (uploadRes?.status === 201) {
|
|
84
|
+
return await onMediaUploadSuccess(item);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log("Media upload failed");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
};
|
|
92
|
+
const onMediaUploadSuccess = async (item) => {
|
|
93
|
+
if (item.media.id) {
|
|
94
|
+
let markRes;
|
|
95
|
+
if (mediaUploadSuccessStatus === MediaStatusEnum.TEMP) {
|
|
96
|
+
markRes = await markMediaAsTemp({
|
|
97
|
+
mediaIds: [item.media.id],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else if (mediaUploadSuccessStatus === MediaStatusEnum.ACTIVE) {
|
|
101
|
+
markRes = await markMediaAsActive({
|
|
102
|
+
mediaIds: [item.media.id],
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (markRes?.data?.items?.length && markRes.data.items[0]) {
|
|
106
|
+
const newMedia = markRes.data.items[0];
|
|
107
|
+
item["media"] = newMedia;
|
|
108
|
+
setMediaItems((previous) => {
|
|
109
|
+
const newState = { ...previous };
|
|
110
|
+
newState[item.localId] = item;
|
|
111
|
+
return newState;
|
|
112
|
+
});
|
|
113
|
+
const currentValues = {};
|
|
114
|
+
if (item.multiple) {
|
|
115
|
+
currentValues[item.name] = [newMedia.id];
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
currentValues[item.name] = newMedia.id;
|
|
119
|
+
}
|
|
120
|
+
setValues((previous) => {
|
|
121
|
+
const newState = { ...previous };
|
|
122
|
+
if (item.multiple) {
|
|
123
|
+
if (Array.isArray(newState[item.name])) {
|
|
124
|
+
newState[item.name].push(currentValues[item.name]);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
newState[item.name] = [currentValues[item.name]];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
newState[item.name] = currentValues[item.name];
|
|
132
|
+
}
|
|
133
|
+
return newState;
|
|
134
|
+
});
|
|
135
|
+
console.log("Media uploaded successfully");
|
|
136
|
+
return currentValues;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
};
|
|
141
|
+
const uploadManually = async () => {
|
|
142
|
+
const uploadInfoIds = Object.keys(uploadInfos);
|
|
143
|
+
const mediaItemsToBeUploaded = Object.values(mediaItems).filter((item) => !uploadInfoIds?.includes(item.localId));
|
|
144
|
+
const uploadResponses = await Promise.all(mediaItemsToBeUploaded?.map(async (item) => await uploadMediaFile(item)));
|
|
145
|
+
console.log("uploadResponses ->", uploadResponses);
|
|
146
|
+
const result = { ...values };
|
|
147
|
+
for (const uploadResponse of uploadResponses.filter((item) => item !== undefined)) {
|
|
148
|
+
for (const key in uploadResponse) {
|
|
149
|
+
if (Array.isArray(result[key])) {
|
|
150
|
+
if (Array.isArray(uploadResponse[key]) &&
|
|
151
|
+
uploadResponse[key]?.length) {
|
|
152
|
+
for (const mediaId of uploadResponse[key]) {
|
|
153
|
+
if (!result[key]?.includes(mediaId)) {
|
|
154
|
+
result[key].push(mediaId);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
result[key] = uploadResponse[key];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
values,
|
|
168
|
+
setValues,
|
|
169
|
+
enableManualUpload,
|
|
170
|
+
uploadManually,
|
|
171
|
+
mediaItems,
|
|
172
|
+
uploadInfos,
|
|
173
|
+
onFileInputChange,
|
|
174
|
+
onFileChange,
|
|
175
|
+
};
|
|
176
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Media, MediaTypeEnum } from "../types/media";
|
|
2
|
+
import { type AxiosProgressEvent } from "axios";
|
|
3
|
+
export declare const imageMimeTypes: string[];
|
|
4
|
+
export declare function generateMediaType(file: File): Promise<MediaTypeEnum>;
|
|
5
|
+
export declare function generateFileHash(file: File, algorithm?: string): Promise<string>;
|
|
6
|
+
export declare const defaultHeaders: {
|
|
7
|
+
"Content-Type": string;
|
|
8
|
+
};
|
|
9
|
+
export interface GenerateUploadUrlProps {
|
|
10
|
+
media: Partial<Media>;
|
|
11
|
+
}
|
|
12
|
+
export declare function generateUploadUrl({ media }: GenerateUploadUrlProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
13
|
+
export interface UploadToStorageProps {
|
|
14
|
+
sasUrl: string;
|
|
15
|
+
file: File;
|
|
16
|
+
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
|
|
17
|
+
abortController?: AbortController;
|
|
18
|
+
}
|
|
19
|
+
export declare function uploadToStorage({ sasUrl, file, onUploadProgress, abortController, }: UploadToStorageProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
20
|
+
export interface MarkMediaAsTempProps {
|
|
21
|
+
mediaIds: (string | number)[];
|
|
22
|
+
}
|
|
23
|
+
export declare function markMediaAsTemp({ mediaIds }: MarkMediaAsTempProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
24
|
+
export interface MarkMediaAsActiveProps {
|
|
25
|
+
mediaIds: (string | number)[];
|
|
26
|
+
}
|
|
27
|
+
export declare function markMediaAsActive({ mediaIds }: MarkMediaAsActiveProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
28
|
+
export interface MarkMediaAsCanceledProps {
|
|
29
|
+
mediaIds: (string | number)[];
|
|
30
|
+
}
|
|
31
|
+
export declare function markMediaAsCanceled({ mediaIds, }: MarkMediaAsActiveProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { MediaTypeEnum } from "../types/media";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
export const imageMimeTypes = [
|
|
4
|
+
"image/webp",
|
|
5
|
+
"image/gif",
|
|
6
|
+
"image/png",
|
|
7
|
+
"image/jpeg",
|
|
8
|
+
"image/jpg",
|
|
9
|
+
];
|
|
10
|
+
export async function generateMediaType(file) {
|
|
11
|
+
if (imageMimeTypes.includes(file.type)) {
|
|
12
|
+
return MediaTypeEnum.IMAGE;
|
|
13
|
+
}
|
|
14
|
+
if (file.type === "application/pdf") {
|
|
15
|
+
return MediaTypeEnum.PDF;
|
|
16
|
+
}
|
|
17
|
+
return MediaTypeEnum.OTHER;
|
|
18
|
+
}
|
|
19
|
+
export async function generateFileHash(file, algorithm = "sha-1") {
|
|
20
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
21
|
+
const hashBuffer = await crypto.subtle.digest(algorithm, arrayBuffer);
|
|
22
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
23
|
+
const hexHash = hashArray
|
|
24
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
25
|
+
.join("");
|
|
26
|
+
return hexHash;
|
|
27
|
+
}
|
|
28
|
+
export const defaultHeaders = {
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
};
|
|
31
|
+
export async function generateUploadUrl({ media }) {
|
|
32
|
+
const res = await axios.post("/api/media/generate-upload-url", media, {
|
|
33
|
+
headers: defaultHeaders,
|
|
34
|
+
});
|
|
35
|
+
return res;
|
|
36
|
+
}
|
|
37
|
+
export async function uploadToStorage({ sasUrl, file, onUploadProgress, abortController, }) {
|
|
38
|
+
const res = await axios.put(sasUrl, file, {
|
|
39
|
+
headers: {
|
|
40
|
+
"x-ms-blob-type": "BlockBlob",
|
|
41
|
+
"Content-Type": file.type,
|
|
42
|
+
},
|
|
43
|
+
onUploadProgress: onUploadProgress,
|
|
44
|
+
signal: abortController ? abortController?.signal : undefined,
|
|
45
|
+
});
|
|
46
|
+
return res;
|
|
47
|
+
}
|
|
48
|
+
export async function markMediaAsTemp({ mediaIds }) {
|
|
49
|
+
const res = await axios.post(`/api/media/mark-media-as-temp`, { mediaIds }, {
|
|
50
|
+
headers: defaultHeaders,
|
|
51
|
+
});
|
|
52
|
+
return res;
|
|
53
|
+
}
|
|
54
|
+
export async function markMediaAsActive({ mediaIds }) {
|
|
55
|
+
const res = await axios.post(`/api/media/mark-media-as-active`, { mediaIds }, {
|
|
56
|
+
headers: defaultHeaders,
|
|
57
|
+
});
|
|
58
|
+
return res;
|
|
59
|
+
}
|
|
60
|
+
export async function markMediaAsCanceled({ mediaIds, }) {
|
|
61
|
+
const res = await axios.post(`/api/media/mark-media-as-canceled`, [mediaIds], {
|
|
62
|
+
headers: defaultHeaders,
|
|
63
|
+
});
|
|
64
|
+
return res;
|
|
65
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export declare enum MediaTypeEnum {
|
|
2
|
+
IMAGE = "IMAGE",
|
|
3
|
+
PDF = "PDF",
|
|
4
|
+
DOCS = "DOCS",
|
|
5
|
+
OTHER = "OTHER"
|
|
6
|
+
}
|
|
7
|
+
export declare enum MediaStatusEnum {
|
|
8
|
+
INIT = "INIT",
|
|
9
|
+
TEMP = "TEMP",
|
|
10
|
+
ACTIVE = "ACTIVE",
|
|
11
|
+
INACTIVE = "INACTIVE",
|
|
12
|
+
CANCELED = "CANCELED",
|
|
13
|
+
DELETED = "DELETED"
|
|
14
|
+
}
|
|
15
|
+
export interface Media {
|
|
16
|
+
id: string | number;
|
|
17
|
+
type: MediaTypeEnum;
|
|
18
|
+
status: MediaStatusEnum;
|
|
19
|
+
title: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
name: string;
|
|
22
|
+
dir: string;
|
|
23
|
+
path: string;
|
|
24
|
+
provider: string;
|
|
25
|
+
container?: string;
|
|
26
|
+
mimeType?: string;
|
|
27
|
+
size?: number;
|
|
28
|
+
height?: number;
|
|
29
|
+
width?: number;
|
|
30
|
+
duration?: number;
|
|
31
|
+
tags?: string[];
|
|
32
|
+
checksum?: string;
|
|
33
|
+
createdAt?: Date | string;
|
|
34
|
+
updatedAt?: Date | string;
|
|
35
|
+
deletedAt?: Date | string;
|
|
36
|
+
}
|
|
37
|
+
export interface MediaItem {
|
|
38
|
+
localId: string;
|
|
39
|
+
name: string;
|
|
40
|
+
multiple?: boolean;
|
|
41
|
+
file: File;
|
|
42
|
+
tempPreviewUrl?: string;
|
|
43
|
+
media: Partial<Media>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export var MediaTypeEnum;
|
|
2
|
+
(function (MediaTypeEnum) {
|
|
3
|
+
MediaTypeEnum["IMAGE"] = "IMAGE";
|
|
4
|
+
MediaTypeEnum["PDF"] = "PDF";
|
|
5
|
+
MediaTypeEnum["DOCS"] = "DOCS";
|
|
6
|
+
MediaTypeEnum["OTHER"] = "OTHER";
|
|
7
|
+
})(MediaTypeEnum || (MediaTypeEnum = {}));
|
|
8
|
+
export var MediaStatusEnum;
|
|
9
|
+
(function (MediaStatusEnum) {
|
|
10
|
+
MediaStatusEnum["INIT"] = "INIT";
|
|
11
|
+
MediaStatusEnum["TEMP"] = "TEMP";
|
|
12
|
+
MediaStatusEnum["ACTIVE"] = "ACTIVE";
|
|
13
|
+
MediaStatusEnum["INACTIVE"] = "INACTIVE";
|
|
14
|
+
MediaStatusEnum["CANCELED"] = "CANCELED";
|
|
15
|
+
MediaStatusEnum["DELETED"] = "DELETED";
|
|
16
|
+
})(MediaStatusEnum || (MediaStatusEnum = {}));
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@susonwaiba/react-media-uploader",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"source": "src/index.ts",
|
|
5
|
+
"module": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "rm -rf dist && tsc && tsc-alias"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/susonwaiba/react-media-uploader.git"
|
|
14
|
+
},
|
|
15
|
+
"author": "Suson Waiba <susonwaiba@gmail.com> (https://susonwaiba.github.io)",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/susonwaiba/react-media-uploader/issues"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/susonwaiba/react-media-uploader#readme",
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"axios": ">=1",
|
|
23
|
+
"react": ">=18",
|
|
24
|
+
"react-dom": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/react": "^18",
|
|
28
|
+
"@types/react-dom": "^18",
|
|
29
|
+
"tsc-alias": "^1",
|
|
30
|
+
"typescript": "^5"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import {
|
|
2
|
+
markMediaAsCanceled,
|
|
3
|
+
generateFileHash,
|
|
4
|
+
generateUploadUrl,
|
|
5
|
+
uploadToStorage,
|
|
6
|
+
markMediaAsTemp,
|
|
7
|
+
markMediaAsActive,
|
|
8
|
+
generateMediaType,
|
|
9
|
+
} from "@/lib/media-helper";
|
|
10
|
+
import { type Media, MediaStatusEnum, type MediaItem } from "@/types/media";
|
|
11
|
+
import { type AxiosProgressEvent } from "axios";
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
|
|
14
|
+
export interface UploadMediaInfo extends Omit<AxiosProgressEvent, "event"> {
|
|
15
|
+
event?: undefined;
|
|
16
|
+
cancel?: () => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UseMediaUploaderProps<T extends object> {
|
|
20
|
+
defaultValues?: T;
|
|
21
|
+
mediaUploadSuccessStatus?: MediaStatusEnum;
|
|
22
|
+
enableManualUpload?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UseMediaUploaderResponse<T extends object> {
|
|
26
|
+
values: T;
|
|
27
|
+
setValues: (val: T) => void;
|
|
28
|
+
enableManualUpload?: boolean;
|
|
29
|
+
uploadManually: () => Promise<T>;
|
|
30
|
+
mediaItems: Record<string, MediaItem>;
|
|
31
|
+
uploadInfos: Record<string, UploadMediaInfo>;
|
|
32
|
+
onFileInputChange: (e: React.ChangeEvent<HTMLInputElement>) => Promise<void>;
|
|
33
|
+
onFileChange: (file: File, name: string, multiple?: boolean) => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useMediaUploader<T extends object>({
|
|
37
|
+
defaultValues,
|
|
38
|
+
mediaUploadSuccessStatus = MediaStatusEnum.TEMP,
|
|
39
|
+
enableManualUpload = false,
|
|
40
|
+
}: UseMediaUploaderProps<T> = {}): UseMediaUploaderResponse<T> {
|
|
41
|
+
const [values, setValues] = useState<T>(defaultValues ?? ({} as T));
|
|
42
|
+
const [mediaItems, setMediaItems] = useState<Record<string, MediaItem>>({});
|
|
43
|
+
const [uploadInfos, setUploadInfos] = useState<
|
|
44
|
+
Record<string, UploadMediaInfo>
|
|
45
|
+
>({});
|
|
46
|
+
|
|
47
|
+
const onFileInputChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
48
|
+
console.log("onFileInputChange -> e ->", e);
|
|
49
|
+
const target = e.target as any;
|
|
50
|
+
const name = target.name;
|
|
51
|
+
const multiple = target.multiple || false;
|
|
52
|
+
if (target.files && target.files.length) {
|
|
53
|
+
for (const file of target.files) {
|
|
54
|
+
onFileChange(file, name, multiple);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const onFileChange = async (
|
|
60
|
+
file: File,
|
|
61
|
+
name: string,
|
|
62
|
+
multiple: boolean = false,
|
|
63
|
+
) => {
|
|
64
|
+
const localId = crypto.randomUUID();
|
|
65
|
+
const item: MediaItem = {
|
|
66
|
+
localId,
|
|
67
|
+
name,
|
|
68
|
+
multiple,
|
|
69
|
+
file,
|
|
70
|
+
tempPreviewUrl: URL.createObjectURL(file),
|
|
71
|
+
media: {
|
|
72
|
+
type: await generateMediaType(file),
|
|
73
|
+
name: file.name,
|
|
74
|
+
mimeType: file.type,
|
|
75
|
+
size: file.size,
|
|
76
|
+
checksum: await generateFileHash(file),
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
setMediaItems((previous) => {
|
|
80
|
+
const newState = { ...previous };
|
|
81
|
+
newState[localId] = item;
|
|
82
|
+
return newState;
|
|
83
|
+
});
|
|
84
|
+
if (!enableManualUpload) {
|
|
85
|
+
uploadMediaFile(item);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const uploadMediaFile = async (item: MediaItem): Promise<T | undefined> => {
|
|
90
|
+
const sasUrlRes = await generateUploadUrl({ media: item?.media });
|
|
91
|
+
if (sasUrlRes?.data?.item && sasUrlRes?.data?.sasUrl) {
|
|
92
|
+
item["media"] = sasUrlRes?.data?.item;
|
|
93
|
+
setMediaItems((previous) => {
|
|
94
|
+
const newState = { ...previous };
|
|
95
|
+
newState[item.localId] = item;
|
|
96
|
+
return newState;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const abortController = new AbortController();
|
|
100
|
+
const uploadRes = await uploadToStorage({
|
|
101
|
+
sasUrl: sasUrlRes?.data?.sasUrl,
|
|
102
|
+
file: item.file,
|
|
103
|
+
onUploadProgress: (progressEvent) => {
|
|
104
|
+
const currentUploadInfo = {
|
|
105
|
+
...progressEvent,
|
|
106
|
+
event: undefined,
|
|
107
|
+
cancel: async () => {
|
|
108
|
+
abortController.abort();
|
|
109
|
+
setUploadInfos((previous) => {
|
|
110
|
+
const newState = { ...previous };
|
|
111
|
+
if (newState[item.localId]) {
|
|
112
|
+
delete newState[item.localId];
|
|
113
|
+
}
|
|
114
|
+
return newState;
|
|
115
|
+
});
|
|
116
|
+
await markMediaAsCanceled({
|
|
117
|
+
mediaIds: [sasUrlRes?.data?.item?.id],
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
setUploadInfos((previous) => {
|
|
122
|
+
const newState = { ...previous };
|
|
123
|
+
newState[item.localId] = currentUploadInfo;
|
|
124
|
+
return newState;
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
abortController,
|
|
128
|
+
});
|
|
129
|
+
if (uploadRes?.status === 201) {
|
|
130
|
+
return await onMediaUploadSuccess(item);
|
|
131
|
+
} else {
|
|
132
|
+
console.log("Media upload failed");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const onMediaUploadSuccess = async (
|
|
139
|
+
item: MediaItem,
|
|
140
|
+
): Promise<T | undefined> => {
|
|
141
|
+
if (item.media.id) {
|
|
142
|
+
let markRes:
|
|
143
|
+
| {
|
|
144
|
+
data?: {
|
|
145
|
+
items?: Array<Media>;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
| undefined;
|
|
149
|
+
if (mediaUploadSuccessStatus === MediaStatusEnum.TEMP) {
|
|
150
|
+
markRes = await markMediaAsTemp({
|
|
151
|
+
mediaIds: [item.media.id],
|
|
152
|
+
});
|
|
153
|
+
} else if (mediaUploadSuccessStatus === MediaStatusEnum.ACTIVE) {
|
|
154
|
+
markRes = await markMediaAsActive({
|
|
155
|
+
mediaIds: [item.media.id],
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (markRes?.data?.items?.length && markRes.data.items[0]) {
|
|
159
|
+
const newMedia = markRes.data.items[0];
|
|
160
|
+
|
|
161
|
+
item["media"] = newMedia;
|
|
162
|
+
setMediaItems((previous) => {
|
|
163
|
+
const newState = { ...previous };
|
|
164
|
+
newState[item.localId] = item;
|
|
165
|
+
return newState;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const currentValues: any = {};
|
|
169
|
+
if (item.multiple) {
|
|
170
|
+
currentValues[item.name] = [newMedia.id];
|
|
171
|
+
} else {
|
|
172
|
+
currentValues[item.name] = newMedia.id;
|
|
173
|
+
}
|
|
174
|
+
setValues((previous: T) => {
|
|
175
|
+
const newState: any = { ...previous };
|
|
176
|
+
if (item.multiple) {
|
|
177
|
+
if (Array.isArray(newState[item.name])) {
|
|
178
|
+
newState[item.name].push(currentValues[item.name]);
|
|
179
|
+
} else {
|
|
180
|
+
newState[item.name] = [currentValues[item.name]];
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
newState[item.name] = currentValues[item.name];
|
|
184
|
+
}
|
|
185
|
+
return newState;
|
|
186
|
+
});
|
|
187
|
+
console.log("Media uploaded successfully");
|
|
188
|
+
return currentValues;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return undefined;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const uploadManually = async () => {
|
|
195
|
+
const uploadInfoIds = Object.keys(uploadInfos);
|
|
196
|
+
const mediaItemsToBeUploaded = Object.values(mediaItems).filter(
|
|
197
|
+
(item) => !uploadInfoIds?.includes(item.localId),
|
|
198
|
+
);
|
|
199
|
+
const uploadResponses = await Promise.all(
|
|
200
|
+
mediaItemsToBeUploaded?.map(async (item) => await uploadMediaFile(item)),
|
|
201
|
+
);
|
|
202
|
+
console.log("uploadResponses ->", uploadResponses);
|
|
203
|
+
const result: any = { ...values };
|
|
204
|
+
for (const uploadResponse of uploadResponses.filter(
|
|
205
|
+
(item) => item !== undefined,
|
|
206
|
+
)) {
|
|
207
|
+
for (const key in uploadResponse) {
|
|
208
|
+
if (Array.isArray(result[key])) {
|
|
209
|
+
if (
|
|
210
|
+
Array.isArray(uploadResponse[key]) &&
|
|
211
|
+
uploadResponse[key]?.length
|
|
212
|
+
) {
|
|
213
|
+
for (const mediaId of uploadResponse[key]) {
|
|
214
|
+
if (!result[key]?.includes(mediaId)) {
|
|
215
|
+
result[key].push(mediaId);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
result[key] = uploadResponse[key];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
values,
|
|
229
|
+
setValues,
|
|
230
|
+
enableManualUpload,
|
|
231
|
+
uploadManually,
|
|
232
|
+
mediaItems,
|
|
233
|
+
uploadInfos,
|
|
234
|
+
onFileInputChange,
|
|
235
|
+
onFileChange,
|
|
236
|
+
};
|
|
237
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { type Media, MediaTypeEnum } from "@/types/media";
|
|
2
|
+
import axios, { type AxiosProgressEvent } from "axios";
|
|
3
|
+
|
|
4
|
+
export const imageMimeTypes = [
|
|
5
|
+
"image/webp",
|
|
6
|
+
"image/gif",
|
|
7
|
+
"image/png",
|
|
8
|
+
"image/jpeg",
|
|
9
|
+
"image/jpg",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export async function generateMediaType(file: File): Promise<MediaTypeEnum> {
|
|
13
|
+
if (imageMimeTypes.includes(file.type)) {
|
|
14
|
+
return MediaTypeEnum.IMAGE;
|
|
15
|
+
}
|
|
16
|
+
if (file.type === "application/pdf") {
|
|
17
|
+
return MediaTypeEnum.PDF;
|
|
18
|
+
}
|
|
19
|
+
return MediaTypeEnum.OTHER;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function generateFileHash(
|
|
23
|
+
file: File,
|
|
24
|
+
algorithm = "sha-1",
|
|
25
|
+
): Promise<string> {
|
|
26
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
27
|
+
const hashBuffer = await crypto.subtle.digest(algorithm, arrayBuffer);
|
|
28
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
29
|
+
const hexHash = hashArray
|
|
30
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
31
|
+
.join("");
|
|
32
|
+
return hexHash;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const defaultHeaders = {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export interface GenerateUploadUrlProps {
|
|
40
|
+
media: Partial<Media>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function generateUploadUrl({ media }: GenerateUploadUrlProps) {
|
|
44
|
+
const res = await axios.post("/api/media/generate-upload-url", media, {
|
|
45
|
+
headers: defaultHeaders,
|
|
46
|
+
});
|
|
47
|
+
return res;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface UploadToStorageProps {
|
|
51
|
+
sasUrl: string;
|
|
52
|
+
file: File;
|
|
53
|
+
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
|
|
54
|
+
abortController?: AbortController;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function uploadToStorage({
|
|
58
|
+
sasUrl,
|
|
59
|
+
file,
|
|
60
|
+
onUploadProgress,
|
|
61
|
+
abortController,
|
|
62
|
+
}: UploadToStorageProps) {
|
|
63
|
+
const res = await axios.put(sasUrl, file, {
|
|
64
|
+
headers: {
|
|
65
|
+
"x-ms-blob-type": "BlockBlob",
|
|
66
|
+
"Content-Type": file.type,
|
|
67
|
+
},
|
|
68
|
+
onUploadProgress: onUploadProgress,
|
|
69
|
+
signal: abortController ? abortController?.signal : undefined,
|
|
70
|
+
});
|
|
71
|
+
return res;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface MarkMediaAsTempProps {
|
|
75
|
+
mediaIds: (string | number)[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function markMediaAsTemp({ mediaIds }: MarkMediaAsTempProps) {
|
|
79
|
+
const res = await axios.post(
|
|
80
|
+
`/api/media/mark-media-as-temp`,
|
|
81
|
+
{ mediaIds },
|
|
82
|
+
{
|
|
83
|
+
headers: defaultHeaders,
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
return res;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface MarkMediaAsActiveProps {
|
|
90
|
+
mediaIds: (string | number)[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function markMediaAsActive({ mediaIds }: MarkMediaAsActiveProps) {
|
|
94
|
+
const res = await axios.post(
|
|
95
|
+
`/api/media/mark-media-as-active`,
|
|
96
|
+
{ mediaIds },
|
|
97
|
+
{
|
|
98
|
+
headers: defaultHeaders,
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
return res;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface MarkMediaAsCanceledProps {
|
|
105
|
+
mediaIds: (string | number)[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function markMediaAsCanceled({
|
|
109
|
+
mediaIds,
|
|
110
|
+
}: MarkMediaAsActiveProps) {
|
|
111
|
+
const res = await axios.post(
|
|
112
|
+
`/api/media/mark-media-as-canceled`,
|
|
113
|
+
[mediaIds],
|
|
114
|
+
{
|
|
115
|
+
headers: defaultHeaders,
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
return res;
|
|
119
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export enum MediaTypeEnum {
|
|
2
|
+
IMAGE = "IMAGE",
|
|
3
|
+
PDF = "PDF",
|
|
4
|
+
DOCS = "DOCS",
|
|
5
|
+
OTHER = "OTHER",
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export enum MediaStatusEnum {
|
|
9
|
+
INIT = "INIT",
|
|
10
|
+
TEMP = "TEMP",
|
|
11
|
+
ACTIVE = "ACTIVE",
|
|
12
|
+
INACTIVE = "INACTIVE",
|
|
13
|
+
CANCELED = "CANCELED",
|
|
14
|
+
DELETED = "DELETED",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface Media {
|
|
18
|
+
id: string | number;
|
|
19
|
+
type: MediaTypeEnum;
|
|
20
|
+
status: MediaStatusEnum;
|
|
21
|
+
title: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
name: string;
|
|
24
|
+
dir: string;
|
|
25
|
+
path: string;
|
|
26
|
+
provider: string;
|
|
27
|
+
container?: string;
|
|
28
|
+
mimeType?: string;
|
|
29
|
+
size?: number;
|
|
30
|
+
height?: number;
|
|
31
|
+
width?: number;
|
|
32
|
+
duration?: number;
|
|
33
|
+
tags?: string[];
|
|
34
|
+
checksum?: string;
|
|
35
|
+
createdAt?: Date | string;
|
|
36
|
+
updatedAt?: Date | string;
|
|
37
|
+
deletedAt?: Date | string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface MediaItem {
|
|
41
|
+
localId: string;
|
|
42
|
+
name: string;
|
|
43
|
+
multiple?: boolean;
|
|
44
|
+
file: File;
|
|
45
|
+
tempPreviewUrl?: string;
|
|
46
|
+
media: Partial<Media>;
|
|
47
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"jsx": "react-jsx",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"emitDeclarationOnly": false,
|
|
8
|
+
"outDir": "dist",
|
|
9
|
+
"rootDir": "src",
|
|
10
|
+
"moduleResolution": "Bundler",
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"paths": {
|
|
14
|
+
"@/*": ["./src/*"]
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"include": ["src"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|