From 0862706ae8e5845082796d5ab9c110461ac68770 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 14 Jan 2022 23:04:21 -0500 Subject: [PATCH] fix(striker-ui): complete file upload functionalities --- striker-ui/components/Files/FileInfo.tsx | 32 ++--- .../components/Files/FileUploadInfo.tsx | 126 +++++++++++++++--- striker-ui/components/Files/Files.tsx | 1 + striker-ui/lib/consts/UPLOAD_FILE_TYPES.ts | 4 +- .../lib/singletons/mainAxiosInstance.ts | 7 + striker-ui/types/FileInfoMetadata.d.ts | 1 + striker-ui/types/FileInfoProps.d.ts | 11 ++ striker-ui/types/UploadFileTypes.d.ts | 2 +- 8 files changed, 151 insertions(+), 33 deletions(-) create mode 100644 striker-ui/lib/singletons/mainAxiosInstance.ts create mode 100644 striker-ui/types/FileInfoMetadata.d.ts create mode 100644 striker-ui/types/FileInfoProps.d.ts diff --git a/striker-ui/components/Files/FileInfo.tsx b/striker-ui/components/Files/FileInfo.tsx index 4768472e..0edcedb7 100644 --- a/striker-ui/components/Files/FileInfo.tsx +++ b/striker-ui/components/Files/FileInfo.tsx @@ -7,23 +7,14 @@ import { TextField, } from '@mui/material'; +import { TEXT } from '../../lib/consts/DEFAULT_THEME'; import { UPLOAD_FILE_TYPES_ARRAY } from '../../lib/consts/UPLOAD_FILE_TYPES'; -type FileInfoProps = { - fileName: string; - fileType: string; - fileSyncAnvilList: { - anvilName: string; - anvilDescription: string; - anvilUUID: string; - isSync: boolean; - }[]; -}; - const FileInfo = ({ fileName, fileType, - fileSyncAnvilList, + fileSyncAnvils, + onChange, }: FileInfoProps): JSX.Element => { return ( @@ -31,9 +22,20 @@ const FileInfo = ({ defaultValue={fileName} id="file-name" label="File name" - sx={{ flexGrow: 1 }} + onChange={({ target: { value } }) => + onChange?.call(null, { fileName: value }) + } + sx={{ color: TEXT }} /> - + onChange?.call(null, { fileType: value as UploadFileType }) + } + sx={{ color: TEXT }} + > {UPLOAD_FILE_TYPES_ARRAY.map( ([fileTypeKey, [, fileTypeDisplayString]]) => { return ( @@ -44,7 +46,7 @@ const FileInfo = ({ }, )} - {fileSyncAnvilList.map( + {fileSyncAnvils.map( ({ anvilName, anvilDescription, anvilUUID, isSync }) => { return ( & { + progressValue: number; +}; + const FILE_UPLOAD_INFO_DEFAULT_PROPS = { openFilePickerEventEmitter: undefined, }; @@ -20,14 +37,15 @@ const FileUploadInfo = ({ }: FileUploadInfoProps = FILE_UPLOAD_INFO_DEFAULT_PROPS): JSX.Element => { const selectFileRef = useRef(); - const [selectedFileList, setSelectedFileList] = useState([]); + const [selectedFiles, setSelectedFiles] = useState([]); + const [inUploadFiles, setInUploadFiles] = useState([]); const convertMIMETypeToFileTypeKey = ( fileMIMEType: string, - ): UploadFileTypes => { + ): UploadFileType => { const fileTypesIterator = UPLOAD_FILE_TYPES.entries(); - let fileType: UploadFileTypes | undefined; + let fileType: UploadFileType | undefined; do { const fileTypesResult = fileTypesIterator.next(); @@ -50,8 +68,66 @@ const FileUploadInfo = ({ target: { files }, }) => { if (files) { - setSelectedFileList(Array.from(files)); + setSelectedFiles( + Array.from(files).map( + (file): SelectedFile => ({ + file, + metadata: { + fileName: file.name, + fileType: convertMIMETypeToFileTypeKey(file.type), + fileSyncAnvils: [], + }, + }), + ), + ); + } + }; + + const generateFileInfoOnChangeHandler = ( + fileIndex: number, + ): ((inputValues: Partial) => void) => (inputValues) => { + selectedFiles[fileIndex].metadata = { + ...selectedFiles[fileIndex].metadata, + ...inputValues, + }; + }; + + const uploadFiles: FormEventHandler = (event) => { + event.preventDefault(); + + while (selectedFiles.length > 0) { + const selectedFile = selectedFiles.shift(); + + if (selectedFile) { + const { + file, + metadata: { fileName, fileType }, + } = selectedFile; + + const fileFormData = new FormData(); + + fileFormData.append('file', new File([file], fileName, { ...file })); + fileFormData.append('file-type', fileType); + + const inUploadFile: InUploadFile = { fileName, progressValue: 0 }; + inUploadFiles.push(inUploadFile); + + mainAxiosInstance.post('/files', fileFormData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + onUploadProgress: ({ loaded, total }) => { + inUploadFile.progressValue = Math.round((loaded / total) * 100); + setInUploadFiles([...inUploadFiles]); + + // Should probably write a onUploadFileComplete to update the file list in the parent every time a file finishes upload. + }, + }); + } } + + setSelectedFiles([]); + setInUploadFiles([...inUploadFiles]); }; useEffect(() => { @@ -61,7 +137,7 @@ const FileUploadInfo = ({ }, [openFilePickerEventEmitter]); return ( -
+ - {selectedFileList.map((file: File) => ( - + {inUploadFiles.map(({ fileName, progressValue }) => ( + + + + + + ))} - {selectedFileList.length > 0 && ( - )} diff --git a/striker-ui/components/Files/Files.tsx b/striker-ui/components/Files/Files.tsx index 17673460..1d0e13a7 100644 --- a/striker-ui/components/Files/Files.tsx +++ b/striker-ui/components/Files/Files.tsx @@ -32,6 +32,7 @@ const Files = (): JSX.Element => { const { data: fileList, isLoading } = PeriodicFetch( `${process.env.NEXT_PUBLIC_API_URL?.replace('/cgi-bin', '/api')}/files`, + 0, ); const onAddFileButtonClick = () => { diff --git a/striker-ui/lib/consts/UPLOAD_FILE_TYPES.ts b/striker-ui/lib/consts/UPLOAD_FILE_TYPES.ts index 5fa3d4b0..1c1054dd 100644 --- a/striker-ui/lib/consts/UPLOAD_FILE_TYPES.ts +++ b/striker-ui/lib/consts/UPLOAD_FILE_TYPES.ts @@ -1,11 +1,11 @@ export const UPLOAD_FILE_TYPES_ARRAY: ReadonlyArray< - [UploadFileTypes, [string, string]] + [UploadFileType, [string, string]] > = [ ['iso', ['application/x-cd-image', 'ISO (optical disc)']], ['other', ['text/plain', 'Other file type']], ['script', ['text/plain', 'Script (program)']], ]; export const UPLOAD_FILE_TYPES: ReadonlyMap< - UploadFileTypes, + UploadFileType, [string, string] > = new Map(UPLOAD_FILE_TYPES_ARRAY); diff --git a/striker-ui/lib/singletons/mainAxiosInstance.ts b/striker-ui/lib/singletons/mainAxiosInstance.ts new file mode 100644 index 00000000..0c28780f --- /dev/null +++ b/striker-ui/lib/singletons/mainAxiosInstance.ts @@ -0,0 +1,7 @@ +import { Axios } from 'axios'; + +const mainAxiosInstance = new Axios({ + baseURL: process.env.NEXT_PUBLIC_API_URL?.replace('/cgi-bin', '/api'), +}); + +export default mainAxiosInstance; diff --git a/striker-ui/types/FileInfoMetadata.d.ts b/striker-ui/types/FileInfoMetadata.d.ts new file mode 100644 index 00000000..defb9b67 --- /dev/null +++ b/striker-ui/types/FileInfoMetadata.d.ts @@ -0,0 +1 @@ +declare type FileInfoMetadata = Omit; diff --git a/striker-ui/types/FileInfoProps.d.ts b/striker-ui/types/FileInfoProps.d.ts new file mode 100644 index 00000000..a91c9c4d --- /dev/null +++ b/striker-ui/types/FileInfoProps.d.ts @@ -0,0 +1,11 @@ +declare type FileInfoProps = { + fileName: string; + fileType: UploadFileType; + fileSyncAnvils: Array<{ + anvilName: string; + anvilDescription: string; + anvilUUID: string; + isSync: boolean; + }>; + onChange?: (inputValues: Partial) => void; +}; diff --git a/striker-ui/types/UploadFileTypes.d.ts b/striker-ui/types/UploadFileTypes.d.ts index c6978890..638d6fcb 100644 --- a/striker-ui/types/UploadFileTypes.d.ts +++ b/striker-ui/types/UploadFileTypes.d.ts @@ -1 +1 @@ -declare type UploadFileTypes = 'iso' | 'other' | 'script'; +declare type UploadFileType = 'iso' | 'other' | 'script';