@contember/bindx-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/dist/UploaderError.d.ts +6 -0
- package/dist/UploaderError.d.ts.map +1 -0
- package/dist/components/MultiUploader.d.ts +43 -0
- package/dist/components/MultiUploader.d.ts.map +1 -0
- package/dist/components/Uploader.d.ts +58 -0
- package/dist/components/Uploader.d.ts.map +1 -0
- package/dist/components/UploaderDropzoneArea.d.ts +6 -0
- package/dist/components/UploaderDropzoneArea.d.ts.map +1 -0
- package/dist/components/UploaderDropzoneRoot.d.ts +8 -0
- package/dist/components/UploaderDropzoneRoot.d.ts.map +1 -0
- package/dist/components/UploaderEachFile.d.ts +14 -0
- package/dist/components/UploaderEachFile.d.ts.map +1 -0
- package/dist/components/UploaderFileStateSwitch.d.ts +24 -0
- package/dist/components/UploaderFileStateSwitch.d.ts.map +1 -0
- package/dist/components/UploaderHasFile.d.ts +14 -0
- package/dist/components/UploaderHasFile.d.ts.map +1 -0
- package/dist/components/index.d.ts +8 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/contexts.d.ts +32 -0
- package/dist/contexts.d.ts.map +1 -0
- package/dist/extractors/getAudioFileDataExtractor.d.ts +9 -0
- package/dist/extractors/getAudioFileDataExtractor.d.ts.map +1 -0
- package/dist/extractors/getFileUrlDataExtractor.d.ts +9 -0
- package/dist/extractors/getFileUrlDataExtractor.d.ts.map +1 -0
- package/dist/extractors/getGenericFileMetadataExtractor.d.ts +12 -0
- package/dist/extractors/getGenericFileMetadataExtractor.d.ts.map +1 -0
- package/dist/extractors/getImageFileDataExtractor.d.ts +10 -0
- package/dist/extractors/getImageFileDataExtractor.d.ts.map +1 -0
- package/dist/extractors/getVideoFileDataExtractor.d.ts +11 -0
- package/dist/extractors/getVideoFileDataExtractor.d.ts.map +1 -0
- package/dist/extractors/index.d.ts +6 -0
- package/dist/extractors/index.d.ts.map +1 -0
- package/dist/extractors/types.d.ts +13 -0
- package/dist/extractors/types.d.ts.map +1 -0
- package/dist/fileTypes/createAnyFileType.d.ts +27 -0
- package/dist/fileTypes/createAnyFileType.d.ts.map +1 -0
- package/dist/fileTypes/createAudioFileType.d.ts +29 -0
- package/dist/fileTypes/createAudioFileType.d.ts.map +1 -0
- package/dist/fileTypes/createImageFileType.d.ts +47 -0
- package/dist/fileTypes/createImageFileType.d.ts.map +1 -0
- package/dist/fileTypes/createVideoFileType.d.ts +33 -0
- package/dist/fileTypes/createVideoFileType.d.ts.map +1 -0
- package/dist/fileTypes/index.d.ts +5 -0
- package/dist/fileTypes/index.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/useS3Client.d.ts +7 -0
- package/dist/hooks/useS3Client.d.ts.map +1 -0
- package/dist/hooks/useUploaderStateFiles.d.ts +8 -0
- package/dist/hooks/useUploaderStateFiles.d.ts.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/internal/hooks/index.d.ts +5 -0
- package/dist/internal/hooks/index.d.ts.map +1 -0
- package/dist/internal/hooks/useFillEntity.d.ts +17 -0
- package/dist/internal/hooks/useFillEntity.d.ts.map +1 -0
- package/dist/internal/hooks/useGetPreviewUrls.d.ts +6 -0
- package/dist/internal/hooks/useGetPreviewUrls.d.ts.map +1 -0
- package/dist/internal/hooks/useUploadState.d.ts +12 -0
- package/dist/internal/hooks/useUploadState.d.ts.map +1 -0
- package/dist/internal/hooks/useUploaderDoUpload.d.ts +7 -0
- package/dist/internal/hooks/useUploaderDoUpload.d.ts.map +1 -0
- package/dist/internal/utils/executeExtractors.d.ts +16 -0
- package/dist/internal/utils/executeExtractors.d.ts.map +1 -0
- package/dist/internal/utils/index.d.ts +4 -0
- package/dist/internal/utils/index.d.ts.map +1 -0
- package/dist/internal/utils/resolveAccept.d.ts +7 -0
- package/dist/internal/utils/resolveAccept.d.ts.map +1 -0
- package/dist/internal/utils/uploaderErrorHandler.d.ts +7 -0
- package/dist/internal/utils/uploaderErrorHandler.d.ts.map +1 -0
- package/dist/types.d.ts +168 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/uploadClient/S3UploadClient.d.ts +19 -0
- package/dist/uploadClient/S3UploadClient.d.ts.map +1 -0
- package/dist/uploadClient/index.d.ts +3 -0
- package/dist/uploadClient/index.d.ts.map +1 -0
- package/dist/uploadClient/types.d.ts +29 -0
- package/dist/uploadClient/types.d.ts.map +1 -0
- package/dist/utils/attrAccept.d.ts +16 -0
- package/dist/utils/attrAccept.d.ts.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/urlSigner.d.ts +8 -0
- package/dist/utils/urlSigner.d.ts.map +1 -0
- package/package.json +35 -0
- package/src/UploaderError.ts +7 -0
- package/src/components/MultiUploader.tsx +210 -0
- package/src/components/Uploader.tsx +174 -0
- package/src/components/UploaderDropzoneArea.tsx +29 -0
- package/src/components/UploaderDropzoneRoot.tsx +32 -0
- package/src/components/UploaderEachFile.tsx +46 -0
- package/src/components/UploaderFileStateSwitch.tsx +66 -0
- package/src/components/UploaderHasFile.tsx +35 -0
- package/src/components/index.ts +7 -0
- package/src/contexts.ts +76 -0
- package/src/extractors/getAudioFileDataExtractor.ts +44 -0
- package/src/extractors/getFileUrlDataExtractor.ts +22 -0
- package/src/extractors/getGenericFileMetadataExtractor.ts +40 -0
- package/src/extractors/getImageFileDataExtractor.ts +50 -0
- package/src/extractors/getVideoFileDataExtractor.ts +60 -0
- package/src/extractors/index.ts +5 -0
- package/src/extractors/types.ts +14 -0
- package/src/fileTypes/createAnyFileType.ts +56 -0
- package/src/fileTypes/createAudioFileType.ts +67 -0
- package/src/fileTypes/createImageFileType.ts +87 -0
- package/src/fileTypes/createVideoFileType.ts +75 -0
- package/src/fileTypes/index.ts +4 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useS3Client.ts +26 -0
- package/src/hooks/useUploaderStateFiles.ts +22 -0
- package/src/index.ts +126 -0
- package/src/internal/hooks/index.ts +4 -0
- package/src/internal/hooks/useFillEntity.ts +89 -0
- package/src/internal/hooks/useGetPreviewUrls.ts +26 -0
- package/src/internal/hooks/useUploadState.ts +159 -0
- package/src/internal/hooks/useUploaderDoUpload.ts +129 -0
- package/src/internal/utils/executeExtractors.ts +46 -0
- package/src/internal/utils/index.ts +3 -0
- package/src/internal/utils/resolveAccept.ts +26 -0
- package/src/internal/utils/uploaderErrorHandler.ts +15 -0
- package/src/types.ts +242 -0
- package/src/uploadClient/S3UploadClient.ts +119 -0
- package/src/uploadClient/index.ts +2 -0
- package/src/uploadClient/types.ts +30 -0
- package/src/utils/attrAccept.ts +87 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/urlSigner.ts +117 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { FieldRef } from '@contember/bindx'
|
|
2
|
+
import type { FieldName, FileDataExtractor } from '../types.js'
|
|
3
|
+
|
|
4
|
+
export interface AudioFileDataExtractorProps<TEntity> {
|
|
5
|
+
durationField?: FieldName<TEntity>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates an extractor that extracts audio duration.
|
|
10
|
+
*/
|
|
11
|
+
export const getAudioFileDataExtractor = <TEntity extends Record<string, unknown>>({
|
|
12
|
+
durationField,
|
|
13
|
+
}: AudioFileDataExtractorProps<TEntity>): FileDataExtractor<TEntity> => ({
|
|
14
|
+
getFieldNames: () => durationField ? [durationField] : [],
|
|
15
|
+
extractFileData: async ({ previewUrl }) => {
|
|
16
|
+
if (!durationField) {
|
|
17
|
+
return undefined
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const result = await new Promise<{ duration: number }>((resolve, reject) => {
|
|
21
|
+
const audio = document.createElement('audio')
|
|
22
|
+
audio.preload = 'metadata'
|
|
23
|
+
|
|
24
|
+
audio.addEventListener('canplay', () => {
|
|
25
|
+
resolve({
|
|
26
|
+
duration: audio.duration,
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
audio.addEventListener('error', e => {
|
|
30
|
+
reject(e)
|
|
31
|
+
})
|
|
32
|
+
audio.src = previewUrl
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return ({ entity }) => {
|
|
36
|
+
const fields = (entity as { $fields: Record<string, FieldRef<unknown>> }).$fields
|
|
37
|
+
|
|
38
|
+
if (durationField) {
|
|
39
|
+
// Round duration for integer fields
|
|
40
|
+
fields[durationField]?.setValue(Math.round(result.duration))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { FieldRef } from '@contember/bindx'
|
|
2
|
+
import type { FieldName, FileDataExtractor } from '../types.js'
|
|
3
|
+
|
|
4
|
+
export interface FileUrlDataExtractorProps<TEntity> {
|
|
5
|
+
urlField: FieldName<TEntity>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates an extractor that populates the URL field with the upload result's public URL.
|
|
10
|
+
*/
|
|
11
|
+
export const getFileUrlDataExtractor = <TEntity extends Record<string, unknown>>({
|
|
12
|
+
urlField,
|
|
13
|
+
}: FileUrlDataExtractorProps<TEntity>): FileDataExtractor<TEntity> => ({
|
|
14
|
+
getFieldNames: () => [urlField],
|
|
15
|
+
populateFields: ({ entity, result }) => {
|
|
16
|
+
const fields = (entity as { $fields: Record<string, FieldRef<unknown>> }).$fields
|
|
17
|
+
const field = fields[urlField]
|
|
18
|
+
if (field) {
|
|
19
|
+
field.setValue(result.publicUrl ?? null)
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { FieldRef } from '@contember/bindx'
|
|
2
|
+
import type { FieldName, FileDataExtractor } from '../types.js'
|
|
3
|
+
|
|
4
|
+
export interface GenericFileMetadataExtractorProps<TEntity> {
|
|
5
|
+
fileNameField?: FieldName<TEntity>
|
|
6
|
+
lastModifiedField?: FieldName<TEntity>
|
|
7
|
+
fileSizeField?: FieldName<TEntity>
|
|
8
|
+
fileTypeField?: FieldName<TEntity>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates an extractor that populates generic file metadata fields.
|
|
13
|
+
*/
|
|
14
|
+
export const getGenericFileMetadataExtractor = <TEntity extends Record<string, unknown>>({
|
|
15
|
+
fileNameField,
|
|
16
|
+
fileSizeField,
|
|
17
|
+
fileTypeField,
|
|
18
|
+
lastModifiedField,
|
|
19
|
+
}: GenericFileMetadataExtractorProps<TEntity>): FileDataExtractor<TEntity> => ({
|
|
20
|
+
getFieldNames: () => [fileNameField, fileSizeField, fileTypeField, lastModifiedField].filter((f): f is FieldName<TEntity> => !!f),
|
|
21
|
+
extractFileData: ({ file }) => {
|
|
22
|
+
return ({ entity }) => {
|
|
23
|
+
const fields = (entity as { $fields: Record<string, FieldRef<unknown>> }).$fields
|
|
24
|
+
|
|
25
|
+
if (fileNameField) {
|
|
26
|
+
fields[fileNameField]?.setValue(file.name)
|
|
27
|
+
}
|
|
28
|
+
if (fileSizeField) {
|
|
29
|
+
fields[fileSizeField]?.setValue(file.size)
|
|
30
|
+
}
|
|
31
|
+
if (fileTypeField) {
|
|
32
|
+
fields[fileTypeField]?.setValue(file.type)
|
|
33
|
+
}
|
|
34
|
+
if (lastModifiedField) {
|
|
35
|
+
// Convert to ISO string for Date/DateTime fields
|
|
36
|
+
fields[lastModifiedField]?.setValue(new Date(file.lastModified).toISOString())
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { FieldRef } from '@contember/bindx'
|
|
2
|
+
import type { FieldName, FileDataExtractor } from '../types.js'
|
|
3
|
+
|
|
4
|
+
export interface ImageFileDataExtractorProps<TEntity> {
|
|
5
|
+
widthField?: FieldName<TEntity>
|
|
6
|
+
heightField?: FieldName<TEntity>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates an extractor that extracts image dimensions (width, height).
|
|
11
|
+
*/
|
|
12
|
+
export const getImageFileDataExtractor = <TEntity extends Record<string, unknown>>({
|
|
13
|
+
widthField,
|
|
14
|
+
heightField,
|
|
15
|
+
}: ImageFileDataExtractorProps<TEntity>): FileDataExtractor<TEntity> => ({
|
|
16
|
+
getFieldNames: () => [widthField, heightField].filter((f): f is FieldName<TEntity> => !!f),
|
|
17
|
+
extractFileData: async ({ previewUrl }) => {
|
|
18
|
+
if (!heightField && !widthField) {
|
|
19
|
+
return undefined
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const result = await new Promise<{
|
|
23
|
+
width: number
|
|
24
|
+
height: number
|
|
25
|
+
}>((resolve, reject) => {
|
|
26
|
+
const image = new Image()
|
|
27
|
+
image.addEventListener('load', () => {
|
|
28
|
+
resolve({
|
|
29
|
+
width: image.naturalWidth,
|
|
30
|
+
height: image.naturalHeight,
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
image.addEventListener('error', e => {
|
|
34
|
+
reject(e)
|
|
35
|
+
})
|
|
36
|
+
image.src = previewUrl
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return ({ entity }) => {
|
|
40
|
+
const fields = (entity as { $fields: Record<string, FieldRef<unknown>> }).$fields
|
|
41
|
+
|
|
42
|
+
if (widthField) {
|
|
43
|
+
fields[widthField]?.setValue(result.width ?? null)
|
|
44
|
+
}
|
|
45
|
+
if (heightField) {
|
|
46
|
+
fields[heightField]?.setValue(result.height ?? null)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { FieldRef } from '@contember/bindx'
|
|
2
|
+
import type { FieldName, FileDataExtractor } from '../types.js'
|
|
3
|
+
|
|
4
|
+
export interface VideoFileDataExtractorProps<TEntity> {
|
|
5
|
+
widthField?: FieldName<TEntity>
|
|
6
|
+
heightField?: FieldName<TEntity>
|
|
7
|
+
durationField?: FieldName<TEntity>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates an extractor that extracts video metadata (dimensions, duration).
|
|
12
|
+
*/
|
|
13
|
+
export const getVideoFileDataExtractor = <TEntity extends Record<string, unknown>>({
|
|
14
|
+
widthField,
|
|
15
|
+
heightField,
|
|
16
|
+
durationField,
|
|
17
|
+
}: VideoFileDataExtractorProps<TEntity>): FileDataExtractor<TEntity> => ({
|
|
18
|
+
getFieldNames: () => [widthField, heightField, durationField].filter((f): f is FieldName<TEntity> => !!f),
|
|
19
|
+
extractFileData: async ({ previewUrl }) => {
|
|
20
|
+
if (!heightField && !widthField && !durationField) {
|
|
21
|
+
return undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const result = await new Promise<{
|
|
25
|
+
width: number
|
|
26
|
+
height: number
|
|
27
|
+
duration: number
|
|
28
|
+
}>((resolve, reject) => {
|
|
29
|
+
const video = document.createElement('video')
|
|
30
|
+
video.preload = 'metadata'
|
|
31
|
+
|
|
32
|
+
video.addEventListener('loadedmetadata', () => {
|
|
33
|
+
resolve({
|
|
34
|
+
width: video.videoWidth,
|
|
35
|
+
height: video.videoHeight,
|
|
36
|
+
duration: video.duration,
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
video.addEventListener('error', e => {
|
|
40
|
+
reject(e)
|
|
41
|
+
})
|
|
42
|
+
video.src = previewUrl
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return ({ entity }) => {
|
|
46
|
+
const fields = (entity as { $fields: Record<string, FieldRef<unknown>> }).$fields
|
|
47
|
+
|
|
48
|
+
if (widthField) {
|
|
49
|
+
fields[widthField]?.setValue(result.width ?? null)
|
|
50
|
+
}
|
|
51
|
+
if (heightField) {
|
|
52
|
+
fields[heightField]?.setValue(result.height ?? null)
|
|
53
|
+
}
|
|
54
|
+
if (durationField) {
|
|
55
|
+
// Round duration for integer fields
|
|
56
|
+
fields[durationField]?.setValue(Math.round(result.duration))
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
})
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { getFileUrlDataExtractor, type FileUrlDataExtractorProps } from './getFileUrlDataExtractor.js'
|
|
2
|
+
export { getGenericFileMetadataExtractor, type GenericFileMetadataExtractorProps } from './getGenericFileMetadataExtractor.js'
|
|
3
|
+
export { getImageFileDataExtractor, type ImageFileDataExtractorProps } from './getImageFileDataExtractor.js'
|
|
4
|
+
export { getVideoFileDataExtractor, type VideoFileDataExtractorProps } from './getVideoFileDataExtractor.js'
|
|
5
|
+
export { getAudioFileDataExtractor, type AudioFileDataExtractorProps } from './getAudioFileDataExtractor.js'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FieldRef } from '@contember/bindx'
|
|
2
|
+
import type { FileWithMeta, FileUploadResult, FileDataExtractor, FileDataExtractorPopulator } from '../types.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generic entity type for extractors - allows any string field access
|
|
6
|
+
*/
|
|
7
|
+
export type ExtractorEntity = {
|
|
8
|
+
$fields: Record<string, FieldRef<unknown>>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Helper type for extractor factory function
|
|
13
|
+
*/
|
|
14
|
+
export type ExtractorFactory<TProps, TEntity = ExtractorEntity> = (props: TProps) => FileDataExtractor<TEntity>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { FieldName, FileType, UploadClient } from '../types.js'
|
|
2
|
+
import { getFileUrlDataExtractor } from '../extractors/getFileUrlDataExtractor.js'
|
|
3
|
+
import { getGenericFileMetadataExtractor } from '../extractors/getGenericFileMetadataExtractor.js'
|
|
4
|
+
|
|
5
|
+
export interface CreateAnyFileTypeProps<TEntity> {
|
|
6
|
+
/** Upload client to use */
|
|
7
|
+
uploader?: UploadClient<unknown>
|
|
8
|
+
/** Field name for the URL */
|
|
9
|
+
urlField: FieldName<TEntity>
|
|
10
|
+
/** Field name for file name (optional) */
|
|
11
|
+
fileNameField?: FieldName<TEntity>
|
|
12
|
+
/** Field name for file size (optional) */
|
|
13
|
+
fileSizeField?: FieldName<TEntity>
|
|
14
|
+
/** Field name for file type/MIME (optional) */
|
|
15
|
+
fileTypeField?: FieldName<TEntity>
|
|
16
|
+
/** Field name for last modified (optional) */
|
|
17
|
+
lastModifiedField?: FieldName<TEntity>
|
|
18
|
+
/** Additional extractors */
|
|
19
|
+
extractors?: FileType<TEntity>['extractors']
|
|
20
|
+
/** Custom file validator */
|
|
21
|
+
acceptFile?: FileType<TEntity>['acceptFile']
|
|
22
|
+
/** Accept MIME types (optional - defaults to any) */
|
|
23
|
+
accept?: FileType<TEntity>['accept']
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a file type configuration for any file upload.
|
|
28
|
+
* Includes extractors for URL and generic metadata only.
|
|
29
|
+
*/
|
|
30
|
+
export const createAnyFileType = <TEntity extends Record<string, unknown>>({
|
|
31
|
+
uploader,
|
|
32
|
+
urlField,
|
|
33
|
+
fileNameField,
|
|
34
|
+
fileSizeField,
|
|
35
|
+
fileTypeField,
|
|
36
|
+
lastModifiedField,
|
|
37
|
+
extractors = [],
|
|
38
|
+
acceptFile,
|
|
39
|
+
accept,
|
|
40
|
+
}: CreateAnyFileTypeProps<TEntity>): FileType<TEntity> => ({
|
|
41
|
+
accept,
|
|
42
|
+
acceptFile,
|
|
43
|
+
uploader,
|
|
44
|
+
extractors: [
|
|
45
|
+
getGenericFileMetadataExtractor<TEntity>({
|
|
46
|
+
fileNameField,
|
|
47
|
+
fileSizeField,
|
|
48
|
+
fileTypeField,
|
|
49
|
+
lastModifiedField,
|
|
50
|
+
}),
|
|
51
|
+
getFileUrlDataExtractor<TEntity>({
|
|
52
|
+
urlField,
|
|
53
|
+
}),
|
|
54
|
+
...extractors,
|
|
55
|
+
],
|
|
56
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { FieldName, FileType, UploadClient } from '../types.js'
|
|
2
|
+
import { getFileUrlDataExtractor } from '../extractors/getFileUrlDataExtractor.js'
|
|
3
|
+
import { getGenericFileMetadataExtractor } from '../extractors/getGenericFileMetadataExtractor.js'
|
|
4
|
+
import { getAudioFileDataExtractor } from '../extractors/getAudioFileDataExtractor.js'
|
|
5
|
+
|
|
6
|
+
export interface CreateAudioFileTypeProps<TEntity> {
|
|
7
|
+
/** Upload client to use */
|
|
8
|
+
uploader?: UploadClient<unknown>
|
|
9
|
+
/** Field name for the URL */
|
|
10
|
+
urlField: FieldName<TEntity>
|
|
11
|
+
/** Field name for duration (optional) */
|
|
12
|
+
durationField?: FieldName<TEntity>
|
|
13
|
+
/** Field name for file name (optional) */
|
|
14
|
+
fileNameField?: FieldName<TEntity>
|
|
15
|
+
/** Field name for file size (optional) */
|
|
16
|
+
fileSizeField?: FieldName<TEntity>
|
|
17
|
+
/** Field name for file type/MIME (optional) */
|
|
18
|
+
fileTypeField?: FieldName<TEntity>
|
|
19
|
+
/** Field name for last modified (optional) */
|
|
20
|
+
lastModifiedField?: FieldName<TEntity>
|
|
21
|
+
/** Additional extractors */
|
|
22
|
+
extractors?: FileType<TEntity>['extractors']
|
|
23
|
+
/** Custom file validator */
|
|
24
|
+
acceptFile?: FileType<TEntity>['acceptFile']
|
|
25
|
+
/** Override default accept MIME types */
|
|
26
|
+
accept?: FileType<TEntity>['accept']
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DEFAULT_AUDIO_ACCEPT = {
|
|
30
|
+
'audio/*': ['.mp3', '.wav', '.ogg', '.flac', '.m4a', '.aac', '.wma', '.aiff'],
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Creates a file type configuration for audio uploads.
|
|
35
|
+
* Includes extractors for URL, audio duration, and generic metadata.
|
|
36
|
+
*/
|
|
37
|
+
export const createAudioFileType = <TEntity extends Record<string, unknown>>({
|
|
38
|
+
uploader,
|
|
39
|
+
urlField,
|
|
40
|
+
durationField,
|
|
41
|
+
fileNameField,
|
|
42
|
+
fileSizeField,
|
|
43
|
+
fileTypeField,
|
|
44
|
+
lastModifiedField,
|
|
45
|
+
extractors = [],
|
|
46
|
+
acceptFile,
|
|
47
|
+
accept = DEFAULT_AUDIO_ACCEPT,
|
|
48
|
+
}: CreateAudioFileTypeProps<TEntity>): FileType<TEntity> => ({
|
|
49
|
+
accept,
|
|
50
|
+
acceptFile,
|
|
51
|
+
uploader,
|
|
52
|
+
extractors: [
|
|
53
|
+
getGenericFileMetadataExtractor<TEntity>({
|
|
54
|
+
fileNameField,
|
|
55
|
+
fileSizeField,
|
|
56
|
+
fileTypeField,
|
|
57
|
+
lastModifiedField,
|
|
58
|
+
}),
|
|
59
|
+
getAudioFileDataExtractor<TEntity>({
|
|
60
|
+
durationField,
|
|
61
|
+
}),
|
|
62
|
+
getFileUrlDataExtractor<TEntity>({
|
|
63
|
+
urlField,
|
|
64
|
+
}),
|
|
65
|
+
...extractors,
|
|
66
|
+
],
|
|
67
|
+
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { FieldName, FileType, UploadClient } from '../types.js'
|
|
2
|
+
import { getFileUrlDataExtractor } from '../extractors/getFileUrlDataExtractor.js'
|
|
3
|
+
import { getGenericFileMetadataExtractor } from '../extractors/getGenericFileMetadataExtractor.js'
|
|
4
|
+
import { getImageFileDataExtractor } from '../extractors/getImageFileDataExtractor.js'
|
|
5
|
+
|
|
6
|
+
export interface CreateImageFileTypeProps<TEntity> {
|
|
7
|
+
/** Upload client to use */
|
|
8
|
+
uploader?: UploadClient<unknown>
|
|
9
|
+
/** Field name for the URL */
|
|
10
|
+
urlField: FieldName<TEntity>
|
|
11
|
+
/** Field name for width (optional) */
|
|
12
|
+
widthField?: FieldName<TEntity>
|
|
13
|
+
/** Field name for height (optional) */
|
|
14
|
+
heightField?: FieldName<TEntity>
|
|
15
|
+
/** Field name for file name (optional) */
|
|
16
|
+
fileNameField?: FieldName<TEntity>
|
|
17
|
+
/** Field name for file size (optional) */
|
|
18
|
+
fileSizeField?: FieldName<TEntity>
|
|
19
|
+
/** Field name for file type/MIME (optional) */
|
|
20
|
+
fileTypeField?: FieldName<TEntity>
|
|
21
|
+
/** Field name for last modified (optional) */
|
|
22
|
+
lastModifiedField?: FieldName<TEntity>
|
|
23
|
+
/** Additional extractors */
|
|
24
|
+
extractors?: FileType<TEntity>['extractors']
|
|
25
|
+
/** Custom file validator */
|
|
26
|
+
acceptFile?: FileType<TEntity>['acceptFile']
|
|
27
|
+
/** Override default accept MIME types */
|
|
28
|
+
accept?: FileType<TEntity>['accept']
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const DEFAULT_IMAGE_ACCEPT = {
|
|
32
|
+
'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.webp'],
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates a file type configuration for image uploads.
|
|
37
|
+
* Includes extractors for URL, image dimensions, and generic metadata.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* interface Image {
|
|
42
|
+
* id: string
|
|
43
|
+
* url: string
|
|
44
|
+
* width: number
|
|
45
|
+
* height: number
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* const imageFileType = createImageFileType<Image>({
|
|
49
|
+
* urlField: 'url', // Type-checked: must be keyof Image
|
|
50
|
+
* widthField: 'width', // Type-checked
|
|
51
|
+
* heightField: 'height',
|
|
52
|
+
* })
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export const createImageFileType = <TEntity extends Record<string, unknown>>({
|
|
56
|
+
uploader,
|
|
57
|
+
urlField,
|
|
58
|
+
widthField,
|
|
59
|
+
heightField,
|
|
60
|
+
fileNameField,
|
|
61
|
+
fileSizeField,
|
|
62
|
+
fileTypeField,
|
|
63
|
+
lastModifiedField,
|
|
64
|
+
extractors = [],
|
|
65
|
+
acceptFile,
|
|
66
|
+
accept = DEFAULT_IMAGE_ACCEPT,
|
|
67
|
+
}: CreateImageFileTypeProps<TEntity>): FileType<TEntity> => ({
|
|
68
|
+
accept,
|
|
69
|
+
acceptFile,
|
|
70
|
+
uploader,
|
|
71
|
+
extractors: [
|
|
72
|
+
getGenericFileMetadataExtractor<TEntity>({
|
|
73
|
+
fileNameField,
|
|
74
|
+
fileSizeField,
|
|
75
|
+
fileTypeField,
|
|
76
|
+
lastModifiedField,
|
|
77
|
+
}),
|
|
78
|
+
getImageFileDataExtractor<TEntity>({
|
|
79
|
+
widthField,
|
|
80
|
+
heightField,
|
|
81
|
+
}),
|
|
82
|
+
getFileUrlDataExtractor<TEntity>({
|
|
83
|
+
urlField,
|
|
84
|
+
}),
|
|
85
|
+
...extractors,
|
|
86
|
+
],
|
|
87
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { FieldName, FileType, UploadClient } from '../types.js'
|
|
2
|
+
import { getFileUrlDataExtractor } from '../extractors/getFileUrlDataExtractor.js'
|
|
3
|
+
import { getGenericFileMetadataExtractor } from '../extractors/getGenericFileMetadataExtractor.js'
|
|
4
|
+
import { getVideoFileDataExtractor } from '../extractors/getVideoFileDataExtractor.js'
|
|
5
|
+
|
|
6
|
+
export interface CreateVideoFileTypeProps<TEntity> {
|
|
7
|
+
/** Upload client to use */
|
|
8
|
+
uploader?: UploadClient<unknown>
|
|
9
|
+
/** Field name for the URL */
|
|
10
|
+
urlField: FieldName<TEntity>
|
|
11
|
+
/** Field name for width (optional) */
|
|
12
|
+
widthField?: FieldName<TEntity>
|
|
13
|
+
/** Field name for height (optional) */
|
|
14
|
+
heightField?: FieldName<TEntity>
|
|
15
|
+
/** Field name for duration (optional) */
|
|
16
|
+
durationField?: FieldName<TEntity>
|
|
17
|
+
/** Field name for file name (optional) */
|
|
18
|
+
fileNameField?: FieldName<TEntity>
|
|
19
|
+
/** Field name for file size (optional) */
|
|
20
|
+
fileSizeField?: FieldName<TEntity>
|
|
21
|
+
/** Field name for file type/MIME (optional) */
|
|
22
|
+
fileTypeField?: FieldName<TEntity>
|
|
23
|
+
/** Field name for last modified (optional) */
|
|
24
|
+
lastModifiedField?: FieldName<TEntity>
|
|
25
|
+
/** Additional extractors */
|
|
26
|
+
extractors?: FileType<TEntity>['extractors']
|
|
27
|
+
/** Custom file validator */
|
|
28
|
+
acceptFile?: FileType<TEntity>['acceptFile']
|
|
29
|
+
/** Override default accept MIME types */
|
|
30
|
+
accept?: FileType<TEntity>['accept']
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const DEFAULT_VIDEO_ACCEPT = {
|
|
34
|
+
'video/*': ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.wmv', '.mkv', '.3gp'],
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates a file type configuration for video uploads.
|
|
39
|
+
* Includes extractors for URL, video dimensions, duration, and generic metadata.
|
|
40
|
+
*/
|
|
41
|
+
export const createVideoFileType = <TEntity extends Record<string, unknown>>({
|
|
42
|
+
uploader,
|
|
43
|
+
urlField,
|
|
44
|
+
widthField,
|
|
45
|
+
heightField,
|
|
46
|
+
durationField,
|
|
47
|
+
fileNameField,
|
|
48
|
+
fileSizeField,
|
|
49
|
+
fileTypeField,
|
|
50
|
+
lastModifiedField,
|
|
51
|
+
extractors = [],
|
|
52
|
+
acceptFile,
|
|
53
|
+
accept = DEFAULT_VIDEO_ACCEPT,
|
|
54
|
+
}: CreateVideoFileTypeProps<TEntity>): FileType<TEntity> => ({
|
|
55
|
+
accept,
|
|
56
|
+
acceptFile,
|
|
57
|
+
uploader,
|
|
58
|
+
extractors: [
|
|
59
|
+
getGenericFileMetadataExtractor<TEntity>({
|
|
60
|
+
fileNameField,
|
|
61
|
+
fileSizeField,
|
|
62
|
+
fileTypeField,
|
|
63
|
+
lastModifiedField,
|
|
64
|
+
}),
|
|
65
|
+
getVideoFileDataExtractor<TEntity>({
|
|
66
|
+
widthField,
|
|
67
|
+
heightField,
|
|
68
|
+
durationField,
|
|
69
|
+
}),
|
|
70
|
+
getFileUrlDataExtractor<TEntity>({
|
|
71
|
+
urlField,
|
|
72
|
+
}),
|
|
73
|
+
...extractors,
|
|
74
|
+
],
|
|
75
|
+
})
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createImageFileType, type CreateImageFileTypeProps } from './createImageFileType.js'
|
|
2
|
+
export { createVideoFileType, type CreateVideoFileTypeProps } from './createVideoFileType.js'
|
|
3
|
+
export { createAudioFileType, type CreateAudioFileTypeProps } from './createAudioFileType.js'
|
|
4
|
+
export { createAnyFileType, type CreateAnyFileTypeProps } from './createAnyFileType.js'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { useBindxContext } from '@contember/bindx-react'
|
|
3
|
+
import { S3UploadClient, type S3UploadClientOptions } from '../uploadClient/S3UploadClient.js'
|
|
4
|
+
import { createContentApiS3Signer } from '../utils/urlSigner.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates an S3 upload client using the current Contember GraphQL client.
|
|
8
|
+
* Uses the bindx context to get the GraphQL client for URL signing.
|
|
9
|
+
*/
|
|
10
|
+
export const useS3Client = (options: Partial<S3UploadClientOptions> = {}): S3UploadClient => {
|
|
11
|
+
const { adapter } = useBindxContext()
|
|
12
|
+
|
|
13
|
+
return useMemo(() => {
|
|
14
|
+
// Get the GraphQL client from the adapter
|
|
15
|
+
// ContemberAdapter has a graphQlClient property
|
|
16
|
+
const graphQlClient = (adapter as { graphQlClient?: { execute: (query: string, options?: unknown) => Promise<unknown> } }).graphQlClient
|
|
17
|
+
if (!graphQlClient) {
|
|
18
|
+
throw new Error('useS3Client requires a Contember adapter with GraphQL client')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return new S3UploadClient({
|
|
22
|
+
signUrl: createContentApiS3Signer(graphQlClient as Parameters<typeof createContentApiS3Signer>[0]),
|
|
23
|
+
...options,
|
|
24
|
+
})
|
|
25
|
+
}, [adapter, options])
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import type { UploaderFileState } from '../types.js'
|
|
3
|
+
import { useUploaderState } from '../contexts.js'
|
|
4
|
+
|
|
5
|
+
export type StateFilter = UploaderFileState['state'] | UploaderFileState['state'][]
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Filters uploader state by state type.
|
|
9
|
+
* Returns an array of matching file states.
|
|
10
|
+
*/
|
|
11
|
+
export const useUploaderStateFiles = (stateFilter?: StateFilter): UploaderFileState[] => {
|
|
12
|
+
const files = useUploaderState()
|
|
13
|
+
|
|
14
|
+
return useMemo(() => {
|
|
15
|
+
if (!stateFilter) {
|
|
16
|
+
return files
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const filterArray = Array.isArray(stateFilter) ? stateFilter : [stateFilter]
|
|
20
|
+
return files.filter(file => filterArray.includes(file.state))
|
|
21
|
+
}, [files, stateFilter])
|
|
22
|
+
}
|