Skip to content

Commit

Permalink
file converter
Browse files Browse the repository at this point in the history
  • Loading branch information
lifeparticle committed Oct 30, 2023
1 parent fb7fd05 commit d0196a8
Show file tree
Hide file tree
Showing 16 changed files with 352 additions and 40 deletions.
4 changes: 4 additions & 0 deletions ui/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### [7.0.0] - 2023-10-30

- Add file converter

### [6.5.0] - 2023-10-30

- Update github issue
Expand Down
2 changes: 2 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"@emotion/react": "^11.11.0",
"@excalidraw/excalidraw": "^0.16.0",
"@faker-js/faker": "^7.6.0",
"@ffmpeg/ffmpeg": "^0.12.7",
"@ffmpeg/util": "^0.12.1",
"@mantine/core": "^6.0.10",
"@mantine/ds": "^6.0.10",
"@mantine/hooks": "^6.0.10",
Expand Down
11 changes: 9 additions & 2 deletions ui/src/components/General/Icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,16 @@ interface IconProps {
size?: number;
color?: string;
className?: string;
strokeWidth?: string;
}

const Icon: React.FC<IconProps> = ({ name, size = 18, color, className }) => {
const Icon: React.FC<IconProps> = ({
name,
size = 18,
color,
className,
strokeWidth = "1.3",
}) => {
let LucideIcon = icons[name];

if (!LucideIcon) {
Expand All @@ -31,7 +38,7 @@ const Icon: React.FC<IconProps> = ({ name, size = 18, color, className }) => {

return (
<LucideIcon
strokeWidth="1.3"
strokeWidth={strokeWidth}
size={size}
color={color}
className={className}
Expand Down
27 changes: 19 additions & 8 deletions ui/src/data/featureData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const LIBRARY_URLS: LibraryList = {
"js-beautify": "https://www.npmjs.com/package/js-beautify",
"@monaco-editor/react":
"https://www.npmjs.com/package/@monaco-editor/react",
"@ffmpeg/ffmpeg": "https://www.npmjs.com/package/@ffmpeg/ffmpeg",
"@ffmpeg/util": "https://www.npmjs.com/package/@ffmpeg/util",
};

interface Feature {
Expand Down Expand Up @@ -327,14 +329,23 @@ export const FEATURE_DATA: Feature[] = [
// link: routesById.units.path,
// library: [{ name: "Vanilla JS", url: "" }],
// },
// {
// key: routesById.imageconverter.id,
// name: routesById.imageconverter.title,
// shortDescription: routesById.imageconverter.description,
// fullDescription: "",
// link: routesById.imageconverter.path,
// library: [{ name: "Vanilla JS", url: "" }],
// },
{
key: routesById.fileconverter.id,
name: routesById.fileconverter.title,
shortDescription: routesById.fileconverter.description,
fullDescription: "",
link: routesById.fileconverter.path,
library: [
{
name: "FFmpeg",
url: LIBRARY_URLS["@ffmpeg/ffmpeg"],
},
{
name: "Fmpeg Util",
url: LIBRARY_URLS["@ffmpeg/util"],
},
],
},
// {
// key: routesById.docs.id,
// name: routesById.docs.title,
Expand Down
6 changes: 3 additions & 3 deletions ui/src/data/menuData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ export const MENU_ITEMS = [
show: true,
},
{
name: routesById.imageconverter.title,
url: routesById.imageconverter.path,
name: routesById.fileconverter.title,
url: routesById.fileconverter.path,
icon: "FileImage",
show: IN_DEVELOPMENT,
show: true,
},
{
name: routesById.pixelconverter.title,
Expand Down
27 changes: 13 additions & 14 deletions ui/src/data/routeData.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import Units from "pages/CSS/Units";
import ImageConverter from "pages/Converter/Image";
import Diagramming from "pages/Tools/Diagramming";

export type RouteId =
| "/"
| "about"
Expand All @@ -26,10 +22,10 @@ export type RouteId =
| "editor"
| "feedback"
| "feedback"
| "fileconverter"
| "github"
| "githubissue"
| "icon"
| "imageconverter"
| "imagegeneratorfromcolors"
| "interview"
| "jsontotypescript"
Expand Down Expand Up @@ -74,22 +70,25 @@ import {
Book,
BorderRadius,
BoxShadow,
CodeFormatter,
ColorPicker,
CookiePolicy,
Course,
DataGenerator,
DesignSystem,
Diagramming,
Diffchecker,
Docs,
Editor,
Feedback,
Github,
GithubIsuue,
Home,
Icon,
FileConverter,
ImageGeneratorFromColors,
Interview,
JsonToTypescript,
CodeFormatter,
Docs,
Editor,
Mimetype,
Movie,
News,
Expand All @@ -110,8 +109,8 @@ import {
Tool,
TvSeries,
UiUx,
Units,
YouTube,
GithubIsuue,
} from "pages/pages";

export const routes: Route[] = [
Expand Down Expand Up @@ -186,11 +185,11 @@ export const routes: Route[] = [
component: JsonToTypescript,
},
{
id: "imageconverter",
path: "/generator/ic",
title: "Image",
description: "Convert images to different formats.",
component: ImageConverter,
id: "fileconverter",
path: "/converter/fc",
title: "File",
description: "Convert files to different formats.",
component: FileConverter,
},
{
id: "codeformatter",
Expand Down
3 changes: 1 addition & 2 deletions ui/src/pages/About/About.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
padding: var(--bt-size-10);
border-radius: var(--bt-size-8);
background-repeat: repeat-y;
background-size: 100% 1px;
backdrop-filter: blur(10px);
background-size: 300% 1px;
}
&_text {
color: white !important;
Expand Down
31 changes: 31 additions & 0 deletions ui/src/pages/Converter/File/FileConverter.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.ic {
display: flex;
flex-direction: column;
gap: 20px;

&__item {
transition: opacity 0.3s, height 0.3s;
min-height: 66px;
padding: 8px;
border: 1px solid #d9d9d9;
border-radius: 8px;
display: grid;
align-items: center;
gap: 20px;
grid-template-columns: auto auto;

h5 {
overflow: hidden;
}

&_right {
display: flex;
align-items: center;
justify-content: end;
gap: 20px;
> div {
margin-bottom: 0px !important;
}
}
}
}
170 changes: 170 additions & 0 deletions ui/src/pages/Converter/File/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { useState } from "react";
import { fetchFile } from "@ffmpeg/util";
import { Button, message, Upload } from "antd";
import Icon from "components/General/Icon";
import type { UploadFile, UploadProps } from "antd";
import {
ResponsiveButton,
ResponsiveSelectWithLabel,
} from "components/General/FormComponents";
import styles from "./FileConverter.module.scss";
import { useFfmpeg } from "./useFfmpeg";
import {
getFileExtension,
removeExtension,
} from "utils/helper-functions/files";

const { Dragger } = Upload;

const IMAGE_TYPES = [
{ label: "PNG", value: ".png" },
{ label: "JPEG", value: ".jpeg" },
{ label: "SVG", value: ".svg" },
{ label: "WEBP", value: ".webp" },
];

interface FileConverter {
file: UploadFile;
from: string;
to: string;
}

function FileConverter() {
const [uploadFiles, setUploadedFiles] = useState<FileConverter[]>([]);
const [selectedFormat, setSelectedFormat] = useState(IMAGE_TYPES[0].value);
const { loaded, ffmpegRef } = useFfmpeg();

const transcode = async (fileConverter: FileConverter) => {
const ffmpeg = ffmpegRef.current;
const outputFileName = `${removeExtension(fileConverter.file.name)}${
fileConverter.to
}`;

await ffmpeg.writeFile(
fileConverter.file.name,
await fetchFile(fileConverter.file.originFileObj)
);
await ffmpeg.exec(["-i", fileConverter.file.name, outputFileName]);
const fileData = await ffmpeg.readFile(outputFileName);
const data = new Uint8Array(fileData as ArrayBuffer);

const blob = new Blob([data.buffer], {
type: `${fileConverter.file.type}/${fileConverter.to}`,
});
const url = URL.createObjectURL(blob);
return [url, outputFileName];
};

const convertFiles = async () => {
const convertedFilesPromises = uploadFiles.map(transcode);
const convertedFiles = await Promise.all(convertedFilesPromises);
convertedFiles.forEach(([url, fileName]) =>
downloadFiles(url, fileName)
);
};

const downloadFiles = async (url: string, fileName: string) => {
const a = document.createElement("a");
a.download = fileName;
a.href = url;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};

const props: UploadProps = {
name: "file",
multiple: true,
customRequest: (options) => {
if (options.onSuccess) {
options.onSuccess({}, new XMLHttpRequest());
}
},
onChange(info) {
const { status } = info.file;
if (status !== "uploading") {
// console.log(info.file, info.fileList);
}
if (status === "done") {
message.success(
`${info.file.name} file uploaded successfully.`
);

let fileExtension = "";
try {
fileExtension = getFileExtension(info.file.name);
} catch (e) {
console.log("e", e);
}

if (fileExtension === "") return;
setUploadedFiles((prevFiles) => [
...prevFiles,
{
file: info.file,
from: fileExtension,
to: selectedFormat,
},
]);
} else if (status === "error") {
message.error(`${info.file.name} file upload failed.`);
}
},
accept: "image/*,video/*,audio/*",
itemRender: (
_,
file: UploadFile,
__,
actions: {
download: (file: UploadFile) => void;
preview: (file: UploadFile) => void;
remove: (file: UploadFile) => void;
}
) => {
return (
<div className={styles.ic__item}>
<h5>{file.name}</h5>
<div className={styles.ic__item_right}>
<ResponsiveSelectWithLabel
label="Convert file to:"
value={selectedFormat}
options={IMAGE_TYPES}
onSelect={(_, info) => {
setSelectedFormat(info.value);
}}
/>
<Button
icon={<Icon name="Trash" />}
onClick={() => actions.remove(file)}
/>
</div>
</div>
);
},
};

return (
<div className={styles.ic}>
<Dragger {...props}>
<p className="ant-upload-drag-icon">
<Icon name="Inbox" size={100} strokeWidth="0.2" />
</p>
<p className="ant-upload-text">
Click or drag file to this area to upload
</p>
<p className="ant-upload-hint">
Support for a single or bulk upload.
</p>
</Dragger>
<ResponsiveButton
type="primary"
onClick={convertFiles}
disabled={!loaded}
>
{uploadFiles.length > 1 ? "Convert All" : "Convert"}
</ResponsiveButton>
</div>
);
}

export default FileConverter;
Loading

0 comments on commit d0196a8

Please sign in to comment.