diff --git a/src/components/converter/converter-steps/index.tsx b/src/components/converter/converter-steps/index.tsx new file mode 100644 index 0000000..9e48cf2 --- /dev/null +++ b/src/components/converter/converter-steps/index.tsx @@ -0,0 +1,75 @@ +import { memo, useCallback, useState } from "react" +import UploadStep from "./upload-step/upload-step" +import { Format } from "../../../model/format" +import { Spin, UploadFile } from "antd" +import ResultStep from "./result-step/result-step" +import { ConvertedFile } from "../../../model/converted-file" + +enum Steps { + Upload = "upload", + Result = "result" +} + +type ConverterStepsProps = { + extensionOptions:Format[] + populateFileList: (files: UploadFile[]) => void + allowedUploadingFormats: string[] + convertedFiles: ConvertedFile[] + convert: () => Promise + clearStates: () => void + downloadFiles: () => void +} + +const ConverterSteps = memo(function ConverterStepsComponent(props: ConverterStepsProps): JSX.Element { + const [ currentStep, setCurrentStep ] = useState(Steps.Upload) + const [ isConverting, setIsConverting ] = useState(false) + + const onConvertClick = useCallback(async () => { + setIsConverting(true) + await props.convert() + + setCurrentStep(Steps.Result) + setIsConverting(false) + }, [props]) + + const convertMoreFiles = useCallback(() => { + props.clearStates() + setIsConverting(false) + setCurrentStep(Steps.Upload) + }, [props]) + + const getUploadStep = useCallback(() => { + return + }, [props.allowedUploadingFormats, props.extensionOptions, props.populateFileList, onConvertClick]) + + const getResultStep = useCallback(() => { + return + }, [convertMoreFiles, props.convertedFiles, props.downloadFiles]) + + const converterSteps = { + upload: getUploadStep(), + result: getResultStep() + } + + return ( + <> + { converterSteps[currentStep] } + + + + ) +}) + +export default ConverterSteps \ No newline at end of file diff --git a/src/components/converter/converter-steps/result-step/result-step.tsx b/src/components/converter/converter-steps/result-step/result-step.tsx new file mode 100644 index 0000000..e8144b5 --- /dev/null +++ b/src/components/converter/converter-steps/result-step/result-step.tsx @@ -0,0 +1,81 @@ +import { memo, useCallback, useMemo } from "react" +import { ConvertedFile } from "../../../../model/converted-file" +import "./result-steps.css" +import { Button, List, Result } from "antd" +import { SmileOutlined } from "@ant-design/icons" + +type ResultStepProps = { + convertedItems: ConvertedFile[] + downloadFiles: () => void + convertMoreFiles: () => void +} + + +const ResultStep = memo(function ResultStepComponent(props: ResultStepProps): JSX.Element { + + const filesWithError = useMemo(() => { + return props.convertedItems.filter(file => file.result === "error") + }, [props.convertedItems]) + + const filesWithSuccess = useMemo(() => { + return props.convertedItems.filter(file => file.result === "success") + }, [props.convertedItems]) + + const getResult = useCallback(() => { + if (filesWithError.length > 0) { + return ( + + ) + } else { + return ( + } + title="Great, we have done all the operations!" + /> + ) + } + + }, [filesWithError, filesWithSuccess]) + + return ( +
+ { getResult() } + { + filesWithError.length > 0 && ( + Failed to convert these files
} + bordered + dataSource={filesWithError} + renderItem={(item) => {item.newFileName}} + /> + + ) + } + { + filesWithSuccess.length > 0 && ( + + ) + } + + + ) +}) + + +export default ResultStep \ No newline at end of file diff --git a/src/components/converter/converter-steps/result-step/result-steps.css b/src/components/converter/converter-steps/result-step/result-steps.css new file mode 100644 index 0000000..af0c8b7 --- /dev/null +++ b/src/components/converter/converter-steps/result-step/result-steps.css @@ -0,0 +1,15 @@ +.container { + max-width: 700px; + margin: 0 auto; + transition: 0.8s linear +} + +.container__items { + margin-top: 5px; + width: 100%; +} + +.container__failed-list { + color: red; + font-weight: bold; +} \ No newline at end of file diff --git a/src/components/converter/converter-steps/upload-step/upload-step.css b/src/components/converter/converter-steps/upload-step/upload-step.css new file mode 100644 index 0000000..1fbcdda --- /dev/null +++ b/src/components/converter/converter-steps/upload-step/upload-step.css @@ -0,0 +1,10 @@ +.container { + max-width: 700px; + margin: 0 auto; + transition: 0.8s linear +} + +.container__items { + margin-top: 5px; + width: 100%; +} \ No newline at end of file diff --git a/src/components/converter/converter-steps/upload-step/upload-step.tsx b/src/components/converter/converter-steps/upload-step/upload-step.tsx new file mode 100644 index 0000000..27f0ef9 --- /dev/null +++ b/src/components/converter/converter-steps/upload-step/upload-step.tsx @@ -0,0 +1,116 @@ +import { Alert, Button, Select, UploadFile } from "antd" +import DraggerUpload from "../../../dragger-upload" +import "./upload-step.css" +import { memo, useCallback, useMemo, useState } from "react" +import { Format } from "../../../../model/format" + +type UploadStepProps = { + extensionOptions:Format[] + allowedUploadingFormats: string[] + populateFileList: (files: UploadFile[]) => void + onConvertClick: () => void +} + +type ErrorMessage = { + isVisible: boolean + message: string +} + +const hideErrorMessage: ErrorMessage = { + isVisible: false, + message: "" +} + +const fileErrorMessage: ErrorMessage = { + isVisible: true, + message: "Please upload at least one file." +} + +const formatErrorMessage: ErrorMessage = { + isVisible: true, + message: "Please select the format." +} + +const UploadStep = memo(function UploadStepComponent(props: Readonly): JSX.Element { + const [ isErrorMessageVisible, setIsErrorMessageVisible ] = useState() + const [ files, setFiles ] = useState([]) + const [ format, setFormat ] = useState('') + + const onUploadDone = useCallback((files: UploadFile[]) => { + setIsErrorMessageVisible(hideErrorMessage) + setFiles(files) + props.populateFileList(files) + }, [props]) + + const acceptedTypes = useMemo(() => { + let types = '' + + props.allowedUploadingFormats.forEach((type, index) => { + if (index === props.allowedUploadingFormats.length - 1) { + types += type + } + + types += type + ", " + }) + + return types + }, [props.allowedUploadingFormats]) + + const onSelectedFormatChange = useCallback((value: string) => { + setFormat(value) + }, []) + + const onClick = useCallback(() => { + + if (files.length <= 0) { + setIsErrorMessageVisible(fileErrorMessage) + return + } + + if (format === '') { + setIsErrorMessageVisible(formatErrorMessage) + return + } + + props.onConvertClick() + }, [files.length, format, props]) + + const options = useMemo(() => { + return props.extensionOptions.map((item) => { + return { value: item.extension, label: item.name } + }) + }, [props.extensionOptions]) + + return ( +
+ + + + + { isErrorMessageVisible?.isVisible && + } +
+ ) +}) + +export default UploadStep \ No newline at end of file diff --git a/src/components/converter/index.tsx b/src/components/converter/index.tsx index 79a90bb..00f98d1 100644 --- a/src/components/converter/index.tsx +++ b/src/components/converter/index.tsx @@ -1,21 +1,22 @@ import { memo, useCallback, useState } from "react" -import DraggerUpload from "../dragger-upload" -import { Button, UploadFile } from "antd" import convertFiles from "../../services/converter" import { Format } from "../../model/format" import { ConvertedFile } from "../../model/converted-file" - +import ConverterSteps from "./converter-steps" +import { UploadFile } from "antd" import './converter.css' /** Properties of Converter */ type ConverterProps = { /** Define allowed types to be uploaded */ - allowedUploadingFormats: Format[] + allowedUploadingFormats: string[] /** Formats available to be converted at */ optionsToConvertTo: Format[] } -function Converter(props: ConverterProps): JSX.Element { + +const Converter = memo(function ConverterComponent(props: ConverterProps): JSX.Element { const [fileList, setFileList] = useState([]) + const [convertedFiles, setConvertedFiles ] = useState([]) const onUploadDone = useCallback((files: UploadFile[]) => { setFileList(files) @@ -34,25 +35,36 @@ function Converter(props: ConverterProps): JSX.Element { }, []) const convert = useCallback(async () => { - const blobs = await convertFiles(fileList) + const files = await convertFiles(fileList) + setConvertedFiles(files) + }, [fileList]) + + const downloadFiles = useCallback(() => { + convertedFiles.forEach(file => { + if (file.result === "error") { + return + } - for (const blob of blobs) { - downloadBlob(blob) - } - }, [downloadBlob, fileList]) + downloadBlob(file) + }) + }, [convertedFiles, downloadBlob]) + + const clearStates = useCallback(() => { + setFileList([]) + setConvertedFiles([]) + }, []) return ( -
- - -
+ ) -} +}) -export default memo(Converter) \ No newline at end of file +export default Converter \ No newline at end of file diff --git a/src/components/dragger-upload/index.tsx b/src/components/dragger-upload/index.tsx index bf3cdd3..60cfdb6 100644 --- a/src/components/dragger-upload/index.tsx +++ b/src/components/dragger-upload/index.tsx @@ -5,6 +5,7 @@ import { Upload } from 'antd'; import { UploadChangeParam } from 'antd/es/upload'; type DraggerUploadProps = { + accept: string onUploadDone(file: UploadFile[]): void | Promise } @@ -16,6 +17,7 @@ const DraggerUpload = (props: DraggerUploadProps): JSX.Element => { return (