Skip to content

Commit ac8e4ac

Browse files
author
hoanx
committed
full frontend
1 parent bfcc9b1 commit ac8e4ac

20 files changed

+2333
-41
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6+
"@nextui-org/react": "^2.2.1",
67
"@testing-library/jest-dom": "^5.17.0",
78
"@testing-library/react": "^13.4.0",
89
"@testing-library/user-event": "^13.5.0",
910
"clsx": "^2.0.0",
1011
"flourite": "^1.2.4",
12+
"framer-motion": "^10.16.4",
1113
"highlight.js": "^11.9.0",
14+
"html-to-image": "^1.11.11",
1215
"re-resizable": "^6.9.11",
1316
"react": "^18.2.0",
1417
"react-dom": "^18.2.0",
1518
"react-scripts": "5.0.1",
1619
"react-simple-code-editor": "^0.13.1",
20+
"react-toastify": "^9.1.3",
1721
"tailwind-merge": "^2.0.0",
1822
"tailwindcss-animate": "^1.0.7",
1923
"web-vitals": "^2.1.4",

public/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
work correctly both with client-side routing and a non-root public URL.
2525
Learn how to configure a non-root public URL by running `npm run build`.
2626
-->
27-
<title>React App</title>
27+
<title>Code2Image</title>
2828
</head>
29-
<body>
29+
<body class="dark text-foreground bg-background">
3030
<noscript>You need to enable JavaScript to run this app.</noscript>
3131
<div id="root"></div>
3232
<!--

public/manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"short_name": "React App",
3-
"name": "Create React App Sample",
2+
"short_name": "Code2Image",
3+
"name": "Convert your source code to image",
44
"icons": [
55
{
66
"src": "favicon.ico",

src/App.jsx

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { useEffect, useRef, useState } from 'react';
2-
import './App.css';
32
import './index.css';
43
import CodeEditor from './components/CodeEditor';
54
import useStore from './store';
65
import { themes } from './configs/theme';
76
import { fonts } from './configs/font';
87
import { mergeClassNames } from './utils/mergeClassName';
98
import { Resizable } from "re-resizable";
9+
import ToolBar from './components/ToolBar';
10+
import WidthMeasurement from './components/WidthMeasurement';
11+
import { Button } from '@nextui-org/react';
1012

1113
function App() {
1214
const [width, setWidth] = useState("auto");
@@ -22,7 +24,7 @@ function App() {
2224
}, []);
2325

2426
return (
25-
<main className="dark min-h-screen flex justify-center items-center bg-neutral-950 text-white">
27+
<main className="w-full p-4 min-h-screen items-center dark text-foreground bg-background">
2628
<link
2729
rel="stylesheet"
2830
href={themes[theme].theme}
@@ -33,27 +35,43 @@ function App() {
3335
href={fonts[fontStyle].src}
3436
crossOrigin="anonymous"
3537
/>
36-
<div className="App">
37-
<Resizable
38-
enable={{ left: true, right: true }}
39-
minWidth={padding * 2 + 400}
40-
maxWidth={window.innerWidth - 600}
41-
size={{ width }}
42-
onResize={(e, dir, ref) => setWidth(ref.offsetWidth)}
43-
onResizeStart={() => setShowWidth(true)}
44-
onResizeStop={() => setShowWidth(false)}
45-
>
46-
<div
47-
className={mergeClassNames(
48-
"overflow-hidden mb-2 transition-all ease-out",
49-
showBackground ? themes[theme].background : "ring ring-neutral-900"
50-
)}
51-
style={{ padding }}
52-
ref={editorRef}
38+
<div className='container flex-column'>
39+
<ToolBar />
40+
<div className="w-full justify-center flex">
41+
<Resizable
42+
enable={{ left: true, right: true }}
43+
minWidth={padding * 2 + 500}
44+
maxWidth={'100%'}
45+
size={{ width }}
46+
onResize={(e, dir, ref) => setWidth(ref.offsetWidth)}
47+
onResizeStart={() => setShowWidth(true)}
48+
onResizeStop={() => setShowWidth(false)}
5349
>
54-
<CodeEditor />
55-
</div>
56-
</Resizable>
50+
<div
51+
className={mergeClassNames(
52+
"overflow-hidden mb-2 transition-all ease-out",
53+
showBackground ? themes[theme].background : "ring ring-neutral-900"
54+
)}
55+
style={{ padding }}
56+
ref={editorRef}
57+
>
58+
<CodeEditor />
59+
</div>
60+
<WidthMeasurement showWidth={showWidth} width={width} />
61+
<div
62+
className={mergeClassNames(
63+
"transition-opacity w-fit mx-auto -mt-4",
64+
showWidth || width === "auto"
65+
? "invisible opacity-0"
66+
: "visible opacity-100"
67+
)}
68+
>
69+
<Button size="sm" onClick={() => setWidth("auto")} variant="ghost">
70+
Reset width
71+
</Button>
72+
</div>
73+
</Resizable>
74+
</div>
5775
</div>
5876
</main>
5977
);

src/components/CodeEditor.jsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,17 @@ export default function CodeEditor() {
2424

2525
useEffect(() => {
2626
if (store.autoDetectLanguage) {
27+
// TODO: check and handle if language is (c++, c#)
2728
const { language } = flourite(store.code, { noUnknown: true });
2829
useStore.setState({
2930
language: language.toLowerCase() || "plaintext",
3031
});
3132
}
32-
}, [store.autoDetectLanguage, store.code])
33+
}, [store.autoDetectLanguage, store.code]);
34+
35+
const onChangeTitle = (e) => {
36+
useStore.setState({ title: e.target.value });
37+
}
3338

3439
return (
3540
<div
@@ -50,7 +55,7 @@ export default function CodeEditor() {
5055
<input
5156
type="text"
5257
value={store.title}
53-
onChange={(e) => { }}
58+
onChange={onChangeTitle}
5459
spellCheck={false}
5560
onClick={(e) => e.target.select()}
5661
className="bg-transparent text-center text-gray-400 text-sm font-medium focus:outline-none"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from "react";
2+
3+
export const CopyToClipboardIcon = (props) => {
4+
return (
5+
<svg
6+
fill="none"
7+
stroke="currentColor"
8+
strokeLinecap="round"
9+
strokeLinejoin="round"
10+
strokeWidth={2}
11+
viewBox="0 0 24 24"
12+
height="1em"
13+
width="1em"
14+
{...props}
15+
>
16+
<path d="M9 2 H15 A1 1 0 0 1 16 3 V5 A1 1 0 0 1 15 6 H9 A1 1 0 0 1 8 5 V3 A1 1 0 0 1 9 2 z" />
17+
<path d="M8 4H6a2 2 0 00-2 2v14a2 2 0 002 2h12a2 2 0 002-2v-2M16 4h2a2 2 0 012 2v4M21 14H11" />
18+
<path d="M15 10l-4 4 4 4" />
19+
</svg>
20+
);
21+
}

src/components/SVGIcons/Moon.jsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from "react";
2+
3+
export const MoonIcon = (props) => (
4+
<svg
5+
aria-hidden="true"
6+
focusable="false"
7+
height="1em"
8+
role="presentation"
9+
viewBox="0 0 24 24"
10+
width="1em"
11+
{...props}
12+
>
13+
<path
14+
d="M21.53 15.93c-.16-.27-.61-.69-1.73-.49a8.46 8.46 0 01-1.88.13 8.409 8.409 0 01-5.91-2.82 8.068 8.068 0 01-1.44-8.66c.44-1.01.13-1.54-.09-1.76s-.77-.55-1.83-.11a10.318 10.318 0 00-6.32 10.21 10.475 10.475 0 007.04 8.99 10 10 0 002.89.55c.16.01.32.02.48.02a10.5 10.5 0 008.47-4.27c.67-.93.49-1.519.32-1.79z"
15+
fill="currentColor"
16+
/>
17+
</svg>
18+
);

src/components/SVGIcons/SaveAsPNG.jsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from "react";
2+
3+
export const SaveAsPNGIcon
4+
= (props) => {
5+
return (
6+
<svg
7+
viewBox="0 0 24 24"
8+
fill="currentColor"
9+
height="1em"
10+
width="1em"
11+
{...props}
12+
>
13+
<path d="M22.71 6.29a1 1 0 00-1.42 0L20 7.59V2a1 1 0 00-2 0v5.59l-1.29-1.3a1 1 0 00-1.42 1.42l3 3a1 1 0 00.33.21.94.94 0 00.76 0 1 1 0 00.33-.21l3-3a1 1 0 000-1.42zM19 13a1 1 0 00-1 1v.38l-1.48-1.48a2.79 2.79 0 00-3.93 0l-.7.7-2.48-2.48a2.85 2.85 0 00-3.93 0L4 12.6V7a1 1 0 011-1h8a1 1 0 000-2H5a3 3 0 00-3 3v12a3 3 0 003 3h12a3 3 0 003-3v-5a1 1 0 00-1-1zM5 20a1 1 0 01-1-1v-3.57l2.9-2.9a.79.79 0 011.09 0l3.17 3.17 4.3 4.3zm13-1a.89.89 0 01-.18.53L13.31 15l.7-.7a.77.77 0 011.1 0L18 17.21z" />
14+
</svg>
15+
);
16+
}

src/components/SVGIcons/SaveAsSVG.jsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from "react";
2+
3+
export const SaveAsSVGIcon = (props) => {
4+
return (
5+
<svg
6+
viewBox="0 0 24 24"
7+
fill="currentColor"
8+
height="1em"
9+
width="1em"
10+
{...props}
11+
>
12+
<path d="M8 8a1 1 0 000 2h1a1 1 0 000-2zm5 12H6a1 1 0 01-1-1V5a1 1 0 011-1h5v3a3 3 0 003 3h3v2a1 1 0 002 0V9v-.06a1.31 1.31 0 00-.06-.27v-.09a1.07 1.07 0 00-.19-.28l-6-6a1.07 1.07 0 00-.28-.19.29.29 0 00-.1 0 1.1 1.1 0 00-.31-.11H6a3 3 0 00-3 3v14a3 3 0 003 3h7a1 1 0 000-2zm0-14.59L15.59 8H14a1 1 0 01-1-1zM14 12H8a1 1 0 000 2h6a1 1 0 000-2zm6.71 6.29a1 1 0 00-1.42 0l-.29.3V16a1 1 0 00-2 0v2.59l-.29-.3a1 1 0 00-1.42 1.42l2 2a1 1 0 00.33.21.94.94 0 00.76 0 1 1 0 00.33-.21l2-2a1 1 0 000-1.42zM12 18a1 1 0 000-2H8a1 1 0 000 2z" />
13+
</svg>
14+
);
15+
}

src/components/SVGIcons/Share.jsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from "react";
2+
export const ShareIcon = (props) => {
3+
return (
4+
<svg fill="none"
5+
viewBox="0 0 15 15"
6+
height="1.5em"
7+
width="1.5em"
8+
{...props}>
9+
<path
10+
fill="currentColor"
11+
fillRule="evenodd"
12+
d="M3.5 5a.5.5 0 00-.5.5v6a.5.5 0 00.5.5h8a.5.5 0 00.5-.5v-6a.5.5 0 00-.5-.5h-1.25a.5.5 0 010-1h1.25A1.5 1.5 0 0113 5.5v6a1.5 1.5 0 01-1.5 1.5h-8A1.5 1.5 0 012 11.5v-6A1.5 1.5 0 013.5 4h1.25a.5.5 0 010 1H3.5zM7 1.636L5.568 3.068a.45.45 0 01-.636-.636l2.25-2.25a.45.45 0 01.636 0l2.25 2.25a.45.45 0 01-.636.636L8 1.636V8.5a.5.5 0 01-1 0V1.636z"
13+
clipRule="evenodd"
14+
/>
15+
</svg>
16+
);
17+
};

src/components/SVGIcons/Sun.jsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from "react";
2+
3+
export const SunIcon = (props) => (
4+
<svg
5+
aria-hidden="true"
6+
focusable="false"
7+
height="1em"
8+
role="presentation"
9+
viewBox="0 0 24 24"
10+
width="1em"
11+
{...props}
12+
>
13+
<g fill="currentColor">
14+
<path d="M19 12a7 7 0 11-7-7 7 7 0 017 7z" />
15+
<path d="M12 22.96a.969.969 0 01-1-.96v-.08a1 1 0 012 0 1.038 1.038 0 01-1 1.04zm7.14-2.82a1.024 1.024 0 01-.71-.29l-.13-.13a1 1 0 011.41-1.41l.13.13a1 1 0 010 1.41.984.984 0 01-.7.29zm-14.28 0a1.024 1.024 0 01-.71-.29 1 1 0 010-1.41l.13-.13a1 1 0 011.41 1.41l-.13.13a1 1 0 01-.7.29zM22 13h-.08a1 1 0 010-2 1.038 1.038 0 011.04 1 .969.969 0 01-.96 1zM2.08 13H2a1 1 0 010-2 1.038 1.038 0 011.04 1 .969.969 0 01-.96 1zm16.93-7.01a1.024 1.024 0 01-.71-.29 1 1 0 010-1.41l.13-.13a1 1 0 011.41 1.41l-.13.13a.984.984 0 01-.7.29zm-14.02 0a1.024 1.024 0 01-.71-.29l-.13-.14a1 1 0 011.41-1.41l.13.13a1 1 0 010 1.41.97.97 0 01-.7.3zM12 3.04a.969.969 0 01-1-.96V2a1 1 0 012 0 1.038 1.038 0 01-1 1.04z" />
16+
</g>
17+
</svg>
18+
);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { themes } from "../configs/theme";
2+
import { mergeClassNames } from "../utils/mergeClassName";
3+
4+
export const ThemeIconPlaceholder = ({ selectedTheme }) => {
5+
return (
6+
<span className={mergeClassNames("h-4 w-5 rounded-full", themes[selectedTheme].background ?? "bg-gradient-to-br from-pink-400 to-pink-600")}>
7+
</span>
8+
);
9+
}

0 commit comments

Comments
 (0)