Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add svg & qrcode #310

Merged
merged 3 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ui/src/components/Layouts/Menu/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ export const MENU_ITEMS = [
icon: "Box",
show: IN_DEVELOPMENT,
},
{
name: "Svg Formatter",
url: "/css/svg-formatter",
icon: "Command",
show: IN_DEVELOPMENT,
},
],
},
{
Expand Down Expand Up @@ -91,6 +97,12 @@ export const MENU_ITEMS = [
icon: "BadgeHelp",
show: true,
},
{
name: "QR Code",
url: "/generator/qrcode",
icon: "QrCode",
show: true,
},
{
name: "Sorting",
url: "/generator/sorting",
Expand Down
46 changes: 46 additions & 0 deletions ui/src/pages/CSS/SvgFormatter/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Card, Form, Input } from "antd";
import CodeHighlightWithCopy from "components/General/CodeHighlightWithCopy";
import PageGrid from "components/Layouts/PageGrid";
import React, { ChangeEvent, useState } from "react";
import { combineSVGPaths } from "./utils/helper";

const { TextArea } = Input;

const SvgFormatter: React.FC = () => {
const [value, setValue] = useState("");
const [combinedSVG, setCombinedSVG] = useState<string>("");

const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
const newValue = e.target.value;
setValue(newValue);

const combinedSvgOutput = combineSVGPaths(newValue);
setCombinedSVG(combinedSvgOutput);
};
return (
<PageGrid>
<Card>
<Form layout="vertical">
<Form.Item label="Svg Input">
<TextArea
placeholder="Enter svg value"
value={value}
rows={10}
onChange={handleChange}
data-gramm={false}
allowClear
/>
</Form.Item>
</Form>
</Card>
<Card>
<CodeHighlightWithCopy
language="css"
codeString={combinedSVG}
/>
</Card>
</PageGrid>
);
};

export default SvgFormatter;
35 changes: 35 additions & 0 deletions ui/src/pages/CSS/SvgFormatter/utils/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
function combineSVGPaths(svgInput: string): string {
try {
const parser = new DOMParser();
const svgDocument = parser.parseFromString(svgInput, "image/svg+xml");

Check warning

Code scanning / CodeQL

DOM text reinterpreted as HTML

[DOM text](1) is reinterpreted as HTML without escaping meta-characters.

const pathElements = svgDocument.querySelectorAll("path");

if (pathElements.length === 0) {
throw new Error("No <path> elements found in the SVG.");
}

const pathDataArray = Array.from(pathElements).map((path) =>
path.getAttribute("d")
);

const combinedPathData = pathDataArray.join("\n");
const svgElement = svgDocument.querySelector("svg");

if (svgElement) {
svgElement.innerHTML = `<path d="${combinedPathData}" stroke="#F0F" />`;
} else {
throw new Error("<svg> element not found in the SVG.");
}

const serializer = new XMLSerializer();
const combinedSVGString = serializer.serializeToString(svgDocument);

return combinedSVGString;
} catch (error) {
console.error(error);
return "Error combining SVG paths.";
}
}

export { combineSVGPaths };
23 changes: 23 additions & 0 deletions ui/src/pages/Data/QRcode/QRcode.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.qrcode {
&__label {
display: flex;
width: 100vw;
justify-content: space-between;
}

&__output {
height: 266px;
&__result,
&__notfound {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

&__notfound {
height: 200px;
text-align: center;
}
}
}
74 changes: 74 additions & 0 deletions ui/src/pages/Data/QRcode/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Button, Card, Form, QRCode, Input, Badge } from "antd";
import PageGrid from "components/Layouts/PageGrid";
import React, { useEffect, useState } from "react";
import { detectQrData, downloadQRCode } from "./utils/helper";
import Icon from "components/General/Icon";
import style from "./QRcode.module.scss";

const { TextArea } = Input;

const QRcode: React.FC = () => {
const [value, setValue] = useState("Generate QR code ...");
const [dataType, setDataType] = useState("");

useEffect(() => {
setDataType(detectQrData(value));
}, [value]);

return (
<PageGrid>
<Card>
<Form layout="vertical">
<Form.Item
label={
<div className={style.qrcode__label}>
<p>{`Input data`}</p>
<Badge
text={`${dataType} detected`}
color={
dataType === "No data"
? "yellow"
: "green"
}
/>
</div>
}
>
<TextArea
value={value}
rows={7}
onChange={(e) => setValue(e.target.value)}
data-gramm={false}
allowClear
/>
</Form.Item>
</Form>
</Card>
<Card className={style.qrcode__output}>
{value.length > 0 ? (
<div id="myqrcode" className={style.qrcode__output__result}>
<QRCode
value={value}
bgColor="#fff"
style={{ marginBottom: 16 }}
/>
<Button onClick={downloadQRCode}>Download</Button>
</div>
) : (
<div className={style.qrcode__output__notfound}>
<span>
<Icon name="AlertTriangle" size={30} />
</span>

<p>
There is no data for generating QR code, please
provide data first.
</p>
</div>
)}
</Card>
</PageGrid>
);
};

export default QRcode;
39 changes: 39 additions & 0 deletions ui/src/pages/Data/QRcode/utils/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
function isValidUrl(url: string) {
const pattern = new RegExp(
"^(https?:\\/\\/)?" +
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" +

Check failure

Code scanning / CodeQL

Inefficient regular expression

This part of the regular expression may cause exponential backtracking on strings starting with '0' and containing many repetitions of '0'.
"((\\d{1,3}\\.){3}\\d{1,3}))" +
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" +
"(\\?[;&a-z\\d%_.~+=-]*)?" +
"(\\#[-a-z\\d_]*)?$",
"i"
);
return pattern.test(url);
}

function detectQrData(data: string) {
if (data.length === 0) {
return "No data";
}

if (isValidUrl(data)) return "Url";

return typeof data;
}

const downloadQRCode = () => {
const canvas = document
.getElementById("myqrcode")
?.querySelector<HTMLCanvasElement>("canvas");
if (canvas) {
const url = canvas.toDataURL();
const a = document.createElement("a");
a.download = "QRCode.png";
a.href = url;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
};

export { detectQrData, downloadQRCode };
12 changes: 12 additions & 0 deletions ui/src/pages/Routes/utils/constant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import {
Avatar,
PageNotFound,
Mimetype,
QRcode,
SvgFormatter,
} from "pages/pages";

const routes: Route[] = [
Expand All @@ -56,6 +58,11 @@ const routes: Route[] = [
title: "Border Shadow",
component: BoxShadow,
},
{
path: "/css/svg-formatter",
title: "SVG Formatter",
component: SvgFormatter,
},
{
path: "/converter/base-64",
title: "Base64",
Expand Down Expand Up @@ -86,6 +93,11 @@ const routes: Route[] = [
title: "Image Generator From Colors",
component: ImageGeneratorFromColors,
},
{
path: "/generator/qrcode",
title: "QR Code Generator",
component: QRcode,
},
{
path: "/generator/sorting",
title: "Sorting",
Expand Down
7 changes: 2 additions & 5 deletions ui/src/pages/info/Mimetype/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import React from "react";

import useFetchList from "lib/utils/hooks/useFetchList";
import MimeSearchResult from "./components/MimeSearchResult";
import { MIME_KEY, MIME_URL } from "./utils/constants";

const Mimetype: React.FC = () => {
const { data, isError, isLoading } = useFetchList(
"mime",
"/mime/data.json"
);
const { data, isError, isLoading } = useFetchList(MIME_KEY, MIME_URL);

return (
<MimeSearchResult isError={isError} isLoading={isLoading} data={data} />
Expand Down
5 changes: 3 additions & 2 deletions ui/src/pages/info/Mimetype/utils/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { ColumnsType } from "antd/es/table";
import { MimeTableDataType } from "./types";
import { Tag } from "antd";
import CodeHighlightWithCopy from "components/General/CodeHighlightWithCopy";

const MIME_URL = "/mime/data.json";
const MIME_KEY = "mimetype";
// Column
const MIME_COLUMNS: ColumnsType<MimeTableDataType> = [
{
Expand Down Expand Up @@ -43,4 +44,4 @@ const MIME_COLUMNS: ColumnsType<MimeTableDataType> = [
},
];

export { MIME_COLUMNS };
export { MIME_COLUMNS, MIME_URL, MIME_KEY };
4 changes: 4 additions & 0 deletions ui/src/pages/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const ImageGeneratorFromColors = lazy(
() => import("pages/Data/ImageGeneratorFromColors")
);
const Sorting = lazy(() => import("pages/Data/Sorting"));
const QRcode = lazy(() => import("pages/Data/QRcode"));
const SvgFormatter = lazy(() => import("pages/CSS/SvgFormatter"));

const Blog = lazy(() => import("pages/List/Blog"));
const Book = lazy(() => import("pages/List/Book"));
Expand Down Expand Up @@ -62,6 +64,7 @@ export {
Icon,
ImageGeneratorFromColors,
Avatar,
QRcode,
JsonToTypescript,
MarkdownEditor,
Movie,
Expand All @@ -79,5 +82,6 @@ export {
UiUx,
YouTube,
Mimetype,
SvgFormatter,
PageNotFound,
};