Skip to content

Commit 5d1e993

Browse files
author
k.golikov
committed
A lot of improvements (QR Scanner, other)
1 parent 3e074f7 commit 5d1e993

File tree

47 files changed

+728
-232
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+728
-232
lines changed

_scripts/commands/createPage.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ const pagesDirectoryPath = path.resolve(appRootPath, 'src/pages');
1010
const getTsxContent = (pageName: string): string => {
1111
return `
1212
import React, { FunctionComponent } from 'react';
13-
import PageContainer from '../../components/pageContainer/PageContainer';
13+
import PageContainer from '../../layouts/pages/pageContainer/PageContainer';
1414
import styles from './${pageName}.module.scss';
1515
1616
const ${pageName}: FunctionComponent = () => {
1717
return (
1818
<PageContainer title="${pageName}">
19-
19+
2020
</PageContainer>
2121
);
2222
};

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"antd-img-crop": "^4.2.0",
1212
"axios": "^0.26.1",
1313
"bootstrap": "^5.1.3",
14+
"canvg": "^4.0.0",
1415
"classnames": "^2.3.1",
1516
"immer": "^9.0.12",
1617
"konva": "^8.3.5",
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React, { FunctionComponent } from 'react';
2+
import { monacoLanguages } from '../types/MonacoLanguage';
3+
import { Select, SelectProps } from 'antd';
4+
5+
type Props = SelectProps;
6+
7+
const MonacoLanguageSelect: FunctionComponent<Props> = ({ children, ...props }) => {
8+
return (
9+
<Select {...props}>
10+
{monacoLanguages.map((language) => (
11+
<Select.Option key={language}>{language}</Select.Option>
12+
))}
13+
{children}
14+
</Select>
15+
);
16+
};
17+
18+
export default MonacoLanguageSelect;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React, { FunctionComponent, ReactNode } from 'react';
2+
import ContentTransformer from '../contentTransformer/ContentTransformer';
3+
4+
const transform = (element: Element): ReactNode => {
5+
if (!(element instanceof HTMLCanvasElement)) {
6+
throw new Error('element is not a canvas');
7+
}
8+
9+
const { width, height } = window.getComputedStyle(element);
10+
11+
return <img src={element.toDataURL()} width={Number.parseInt(width)} height={Number.parseInt(height)} />;
12+
};
13+
14+
const CanvasImage: FunctionComponent = ({ children }) => {
15+
return <ContentTransformer transform={transform}>{children}</ContentTransformer>;
16+
};
17+
18+
export default CanvasImage;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React, { ReactNode, useCallback, useEffect, useRef } from 'react';
2+
import { useMutationObserver } from 'rooks';
3+
4+
interface Props {
5+
onContentChange: (transformingElement: Element, canvas: HTMLCanvasElement) => (() => void) | void;
6+
children?: ReactNode;
7+
}
8+
9+
function CanvasRenderer({ onContentChange, children }: Props) {
10+
const childrenWrapperRef = useRef<HTMLDivElement>(null);
11+
const canvasRef = useRef<HTMLCanvasElement>(null);
12+
13+
const handleChildrenMutation = useCallback(() => {
14+
const { current } = childrenWrapperRef;
15+
16+
if (!current) {
17+
return;
18+
}
19+
20+
const transformingElement = current.children?.[0];
21+
22+
if (!transformingElement) {
23+
return;
24+
}
25+
26+
if (!canvasRef.current) {
27+
throw new Error('no canvas');
28+
}
29+
30+
return onContentChange(transformingElement, canvasRef.current);
31+
}, [onContentChange]);
32+
33+
useEffect(handleChildrenMutation, [handleChildrenMutation]);
34+
useMutationObserver(childrenWrapperRef, handleChildrenMutation);
35+
36+
return (
37+
<>
38+
<canvas ref={canvasRef} />
39+
<div ref={childrenWrapperRef} className="d-none">
40+
{children}
41+
</div>
42+
</>
43+
);
44+
}
45+
46+
export default CanvasRenderer;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
2+
import { useMutationObserver } from 'rooks';
3+
4+
interface Props {
5+
transform: (element: Element) => ReactNode;
6+
children?: ReactNode;
7+
}
8+
9+
function ContentTransformer({ transform, children }: Props) {
10+
const childrenWrapperRef = useRef<HTMLDivElement>(null);
11+
12+
const [transformedElement, setTransformedElement] = useState<ReactNode>();
13+
14+
const handleChildrenMutation = useCallback(() => {
15+
const { current } = childrenWrapperRef;
16+
17+
if (!current) {
18+
return;
19+
}
20+
21+
const transformingElement = current.children?.[0];
22+
23+
if (!transformingElement) {
24+
return;
25+
}
26+
27+
setTransformedElement(transform(transformingElement));
28+
}, [transform]);
29+
30+
useEffect(handleChildrenMutation, [handleChildrenMutation]);
31+
useMutationObserver(childrenWrapperRef, handleChildrenMutation);
32+
33+
return (
34+
<>
35+
{transformedElement}
36+
<div ref={childrenWrapperRef} className="d-none">
37+
{children}
38+
</div>
39+
</>
40+
);
41+
}
42+
43+
export default ContentTransformer;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React, { FunctionComponent } from 'react';
2+
import { Canvg } from 'canvg';
3+
import serializeXmlNode from '../../utils/serializeXmlNode';
4+
import CanvasRenderer from '../canvasRenderer/CanvasRenderer';
5+
6+
const handleContentChange = (element: Element, canvas: HTMLCanvasElement) => {
7+
const svgString = serializeXmlNode(element);
8+
9+
const canvasContext = canvas.getContext('2d');
10+
11+
if (!canvasContext) {
12+
throw new Error('no canvas context');
13+
}
14+
15+
const canvg = Canvg.fromString(canvasContext, svgString);
16+
canvg.start();
17+
18+
return () => {
19+
canvg.stop();
20+
};
21+
};
22+
23+
const SvgCanvas: FunctionComponent = ({ children }) => {
24+
return <CanvasRenderer onContentChange={handleContentChange}>{children}</CanvasRenderer>;
25+
};
26+
27+
export default SvgCanvas;

src/components/svgImage/SvgImage.tsx

Lines changed: 11 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,17 @@
1-
import React, { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react';
2-
import { useMutationObserver } from 'rooks';
1+
import React, { FunctionComponent } from 'react';
2+
import ContentTransformer from '../contentTransformer/ContentTransformer';
3+
import serializeXmlNode from '../../utils/serializeXmlNode';
34

4-
const xmlSerializer = new XMLSerializer();
5+
const transform = (element: Element) => {
6+
const svgString = serializeXmlNode(element);
7+
const svgBase64 = window.btoa(svgString);
8+
const src = 'data:image/svg+xml;base64,' + svgBase64;
59

6-
const SvgImage: FunctionComponent = ({ children }) => {
7-
const childrenWrapperRef = useRef<HTMLDivElement>(null);
8-
9-
const [svgSource, setSvgSource] = useState<string>();
10-
11-
const handleChildrenMutation = useCallback(() => {
12-
const { current } = childrenWrapperRef;
13-
14-
if (!current) {
15-
return;
16-
}
17-
18-
const svgElement = current.querySelector('svg');
19-
20-
if (!svgElement) {
21-
return;
22-
}
23-
24-
const svgString = xmlSerializer.serializeToString(svgElement);
25-
const svgBase64 = window.btoa(svgString);
26-
const svgSource = 'data:image/svg+xml;base64,' + svgBase64;
27-
setSvgSource(svgSource);
28-
}, []);
29-
30-
useEffect(handleChildrenMutation, []);
31-
useMutationObserver(childrenWrapperRef, handleChildrenMutation);
10+
return <img src={src} />;
11+
};
3212

33-
return (
34-
<>
35-
<div ref={childrenWrapperRef} className="d-none">
36-
{children}
37-
</div>
38-
<img src={svgSource} />
39-
</>
40-
);
13+
const SvgImage: FunctionComponent = ({ children }) => {
14+
return <ContentTransformer transform={transform}>{children}</ContentTransformer>;
4115
};
4216

4317
export default SvgImage;

src/constants/router/menuItems.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ const menuItems: MenuItem[] = [
6161
{
6262
route: routes.notificationsTest
6363
},
64+
{
65+
route: routes.imageCompressor
66+
},
6467
{
6568
route: routes.dateUtils,
6669
isGray: true

src/constants/router/routes.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import DiffEditorPage from '../../pages/diffEditorPage/DiffEditorPage';
2424
import JsonToTypeScriptPage from '../../pages/jsonToTypeScriptPage/JsonToTypeScriptPage';
2525
import NotificationsTestPage from '../../pages/notificationsTestPage/NotificationsTestPage';
2626
import DateUtilsPage from '../../pages/dateUtilsPage/DateUtilsPage';
27+
import ImageCompressorPage from '../../pages/imageCompressorPage/ImageCompressorPage';
2728

2829
export interface AppRoute extends Omit<RouteProps, 'element'> {
2930
path: string;
@@ -51,6 +52,7 @@ type AppRoutesMap = Readonly<{
5152
jsonToTypescript: AppRoute;
5253
notificationsTest: AppRoute;
5354
dateUtils: AppRoute;
55+
imageCompressor: AppRoute;
5456
settings: AppRoute;
5557
about: AppRoute;
5658
}>;
@@ -151,6 +153,11 @@ export const routes: AppRoutesMap = {
151153
component: DateUtilsPage,
152154
title: 'Date Utils'
153155
},
156+
imageCompressor: {
157+
path: '/tools/image-compressor',
158+
component: ImageCompressorPage,
159+
title: 'Image Compressor'
160+
},
154161
settings: {
155162
path: '/settings',
156163
component: SettingsPage,

0 commit comments

Comments
 (0)