fix(striker-ui): complete file upload functionalities

main
Tsu-ba-me 3 years ago
parent eee01ce3d3
commit 0862706ae8
  1. 32
      striker-ui/components/Files/FileInfo.tsx
  2. 126
      striker-ui/components/Files/FileUploadInfo.tsx
  3. 1
      striker-ui/components/Files/Files.tsx
  4. 4
      striker-ui/lib/consts/UPLOAD_FILE_TYPES.ts
  5. 7
      striker-ui/lib/singletons/mainAxiosInstance.ts
  6. 1
      striker-ui/types/FileInfoMetadata.d.ts
  7. 11
      striker-ui/types/FileInfoProps.d.ts
  8. 2
      striker-ui/types/UploadFileTypes.d.ts

@ -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 (
<FormControl>
@ -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 }}
/>
<Select defaultValue={fileType} id="file-type" label="File type">
<Select
defaultValue={fileType}
id="file-type"
label="File type"
onChange={({ target: { value } }) =>
onChange?.call(null, { fileType: value as UploadFileType })
}
sx={{ color: TEXT }}
>
{UPLOAD_FILE_TYPES_ARRAY.map(
([fileTypeKey, [, fileTypeDisplayString]]) => {
return (
@ -44,7 +46,7 @@ const FileInfo = ({
},
)}
</Select>
{fileSyncAnvilList.map(
{fileSyncAnvils.map(
({ anvilName, anvilDescription, anvilUUID, isSync }) => {
return (
<FormControlLabel

@ -1,16 +1,33 @@
import { ChangeEventHandler, useEffect, useRef, useState } from 'react';
import {
ChangeEventHandler,
FormEventHandler,
useEffect,
useRef,
useState,
} from 'react';
import { Box, Button, Input, InputLabel } from '@mui/material';
import EventEmitter from 'events';
import { UPLOAD_FILE_TYPES } from '../../lib/consts/UPLOAD_FILE_TYPES';
import FileInfo from './FileInfo';
import { ProgressBar } from '../Bars';
import { BodyText } from '../Text';
import mainAxiosInstance from '../../lib/singletons/mainAxiosInstance';
type FileUploadInfoProps = {
openFilePickerEventEmitter?: EventEmitter;
};
type SelectedFile = {
file: File;
metadata: FileInfoMetadata;
};
type InUploadFile = Pick<FileInfoMetadata, 'fileName'> & {
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<HTMLInputElement>();
const [selectedFileList, setSelectedFileList] = useState<File[]>([]);
const [selectedFiles, setSelectedFiles] = useState<SelectedFile[]>([]);
const [inUploadFiles, setInUploadFiles] = useState<InUploadFile[]>([]);
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<FileInfoMetadata>) => void) => (inputValues) => {
selectedFiles[fileIndex].metadata = {
...selectedFiles[fileIndex].metadata,
...inputValues,
};
};
const uploadFiles: FormEventHandler<HTMLFormElement> = (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 (
<form encType="multipart/form-data">
<form onSubmit={uploadFiles}>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<InputLabel htmlFor="select-file">
<Input
@ -73,16 +149,36 @@ const FileUploadInfo = ({
type="file"
/>
</InputLabel>
{selectedFileList.map((file: File) => (
<FileInfo
fileName={file.name}
fileType={convertMIMETypeToFileTypeKey(file.type)}
fileSyncAnvilList={[]}
key={file.name}
/>
{inUploadFiles.map(({ fileName, progressValue }) => (
<Box
key={`in-upload-${fileName}`}
sx={{ display: 'flex', flexDirection: 'row' }}
>
<BodyText text={fileName} />
<Box sx={{ flexGrow: 1 }}>
<ProgressBar progressPercentage={progressValue} />
</Box>
</Box>
))}
{selectedFileList.length > 0 && (
<Button sx={{ textTransform: 'none' }}>
{selectedFiles.map(
(
{
file: { name: originalFileName },
metadata: { fileName, fileType, fileSyncAnvils },
},
fileIndex,
) => (
<FileInfo
{...{ fileName, fileType, fileSyncAnvils }}
// Use a non-changing key to prevent recreating the component.
// fileName holds the string from the file-name input, thus it changes when users makes a change.
key={`selected-${originalFileName}`}
onChange={generateFileInfoOnChangeHandler(fileIndex)}
/>
),
)}
{selectedFiles.length > 0 && (
<Button sx={{ textTransform: 'none' }} type="submit">
<BodyText text="Upload" />
</Button>
)}

@ -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 = () => {

@ -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);

@ -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;

@ -0,0 +1 @@
declare type FileInfoMetadata = Omit<FileInfoProps, 'onChange'>;

@ -0,0 +1,11 @@
declare type FileInfoProps = {
fileName: string;
fileType: UploadFileType;
fileSyncAnvils: Array<{
anvilName: string;
anvilDescription: string;
anvilUUID: string;
isSync: boolean;
}>;
onChange?: (inputValues: Partial<FileInfoMetadata>) => void;
};

@ -1 +1 @@
declare type UploadFileTypes = 'iso' | 'other' | 'script';
declare type UploadFileType = 'iso' | 'other' | 'script';

Loading…
Cancel
Save