Skip to content

Commit

Permalink
Merge pull request #28 from CarlaPaiva/feat/new-converter-component
Browse files Browse the repository at this point in the history
New converter component
  • Loading branch information
CarlaPaiva authored Jan 20, 2024
2 parents 1792029 + 9713361 commit 731a38d
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 21 deletions.
75 changes: 75 additions & 0 deletions src/components/converter/converter-steps/index.tsx
Original file line number Diff line number Diff line change
@@ -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<void>
clearStates: () => void
downloadFiles: () => void
}

const ConverterSteps = memo(function ConverterStepsComponent(props: ConverterStepsProps): JSX.Element {
const [ currentStep, setCurrentStep ] = useState<Steps>(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 <UploadStep
allowedUploadingFormats={props.allowedUploadingFormats}
extensionOptions={props.extensionOptions}
onConvertClick={onConvertClick}
populateFileList={props.populateFileList} />
}, [props.allowedUploadingFormats, props.extensionOptions, props.populateFileList, onConvertClick])

const getResultStep = useCallback(() => {
return <ResultStep
convertedItems={props.convertedFiles}
downloadFiles={props.downloadFiles}
convertMoreFiles={convertMoreFiles} />
}, [convertMoreFiles, props.convertedFiles, props.downloadFiles])

const converterSteps = {
upload: getUploadStep(),
result: getResultStep()
}

return (
<>
{ converterSteps[currentStep] }
<Spin
tip="Converting..."
size="large"
spinning={isConverting}
fullscreen
/>

</>
)
})

export default ConverterSteps
Original file line number Diff line number Diff line change
@@ -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 (
<Result
status="warning"
title="There are some problems with your operation."
subTitle={`${filesWithSuccess} files converted, ${filesWithError} files with error.`}
/>
)
} else {
return (
<Result
icon={<SmileOutlined />}
title="Great, we have done all the operations!"
/>
)
}

}, [filesWithError, filesWithSuccess])

return (
<div className="container">
{ getResult() }
{
filesWithError.length > 0 && (
<List
size="small"
className="container__items"
header={<div className="container__failed-list">Failed to convert these files</div>}
bordered
dataSource={filesWithError}
renderItem={(item) => <List.Item>{item.newFileName}</List.Item>}
/>

)
}
{
filesWithSuccess.length > 0 && (
<Button
size="large"
type="primary"
className="container__items"
htmlType="button"
onClick={props.downloadFiles}>Download converted files</Button>
)
}
<Button
size="large"
type="default"
className="container__items"
htmlType="button"
onClick={props.convertMoreFiles}>Convert more files</Button>
</div>
)
})


export default ResultStep
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.container {
max-width: 700px;
margin: 0 auto;
transition: 0.8s linear
}

.container__items {
margin-top: 5px;
width: 100%;
}
116 changes: 116 additions & 0 deletions src/components/converter/converter-steps/upload-step/upload-step.tsx
Original file line number Diff line number Diff line change
@@ -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<UploadStepProps>): JSX.Element {
const [ isErrorMessageVisible, setIsErrorMessageVisible ] = useState<ErrorMessage>()
const [ files, setFiles ] = useState<UploadFile[]>([])
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 (
<div className="container">
<DraggerUpload
accept={acceptedTypes}
onUploadDone={onUploadDone} />
<Select
value={format}
placeholder="Convert all to..."
size="large"
className="container__items"
onChange={onSelectedFormatChange}
options={options}
>
</Select>
<Button
size="large"
type="primary"
className="container__items"
htmlType="button"
onClick={onClick}>Convert</Button>

{ isErrorMessageVisible?.isVisible && <Alert
className="container__items"
description={isErrorMessageVisible.message}
message="Error"
type="error"
showIcon />
}
</div>
)
})

export default UploadStep
54 changes: 33 additions & 21 deletions src/components/converter/index.tsx
Original file line number Diff line number Diff line change
@@ -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<UploadFile[]>([])
const [convertedFiles, setConvertedFiles ] = useState<ConvertedFile[]>([])

const onUploadDone = useCallback((files: UploadFile[]) => {
setFileList(files)
Expand All @@ -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 (
<div>
<DraggerUpload
onUploadDone={onUploadDone} />
<Button
size="large"
type="primary"
className="button-convert"
onClick={convert}>Convert</Button>
</div>
<ConverterSteps
allowedUploadingFormats={props.allowedUploadingFormats}
convertedFiles={convertedFiles}
extensionOptions={props.optionsToConvertTo}
populateFileList={onUploadDone}
convert={convert}
clearStates={clearStates}
downloadFiles={downloadFiles} />
)
}
})


export default memo(Converter)
export default Converter
Loading

0 comments on commit 731a38d

Please sign in to comment.