├── client ├── public │ └── favicon.ico ├── src │ ├── assets │ │ ├── fonts │ │ │ ├── Roboto-Bold.ttf │ │ │ └── Roboto-Medium.ttf │ │ └── icons │ │ │ └── index.jsx │ ├── helper │ │ ├── ui.js │ │ ├── keys.js │ │ ├── canvas.js │ │ └── element.js │ ├── api │ │ └── socket.js │ ├── components │ │ ├── Credits.jsx │ │ ├── UndoRedo.jsx │ │ ├── Canvas.jsx │ │ ├── Zoom.jsx │ │ ├── ToolBar.jsx │ │ ├── Ui.jsx │ │ ├── Menu.jsx │ │ ├── Collaboration.jsx │ │ └── Style.jsx │ ├── App.jsx │ ├── main.jsx │ ├── hooks │ │ ├── useDimension.jsx │ │ ├── useKeys.jsx │ │ ├── useHistory.jsx │ │ ├── useTextArea.jsx │ │ └── useCanvas.jsx │ ├── global │ │ └── var.js │ ├── views │ │ └── WorkSpace.jsx │ ├── provider │ │ └── AppStates.jsx │ └── styles │ │ └── index.css ├── vercel.json ├── vite.config.js ├── index.html ├── .gitignore ├── .eslintrc.cjs └── package.json ├── package.json ├── server ├── .gitignore ├── package.json ├── index.js └── package-lock.json ├── .gitignore └── readme.md /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toouil/sketchflow/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toouil/sketchflow/HEAD/client/src/assets/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /client/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { 4 | "source": "/(.*)", 5 | "destination": "/" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toouil/sketchflow/HEAD/client/src/assets/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "client": "npm --prefix client run dev", 4 | "server": "npm --prefix server run dev" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /client/src/helper/ui.js: -------------------------------------------------------------------------------- 1 | export function lockUI(lock) { 2 | if (lock) return document.body.classList.add("lock-ui"); 3 | document.body.classList.remove("lock-ui"); 4 | } 5 | -------------------------------------------------------------------------------- /client/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | }) 7 | -------------------------------------------------------------------------------- /client/src/api/socket.js: -------------------------------------------------------------------------------- 1 | import { io } from "socket.io-client"; 2 | import parser from "socket.io-msgpack-parser" 3 | 4 | const BACKEND_URL = import.meta.env.VITE_APP_SERVER_URL; 5 | 6 | export const socket = io(BACKEND_URL, { 7 | parser 8 | }); -------------------------------------------------------------------------------- /client/src/helper/keys.js: -------------------------------------------------------------------------------- 1 | import { SHORT_CUTS } from "../global/var"; 2 | 3 | export function shortKey(pressedKeys) { 4 | return SHORT_CUTS.some((subArray) => { 5 | const subArraySet = new Set(subArray); 6 | return [...subArraySet].every((item) => pressedKeys.has(item)); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /client/src/components/Credits.jsx: -------------------------------------------------------------------------------- 1 | import { Github } from "../assets/icons"; 2 | 3 | export default function Credits() { 4 | return ( 5 |
6 | 7 | Created by KYROS 8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # testing 5 | /coverage 6 | 7 | # production 8 | /build 9 | 10 | # env 11 | .env 12 | .env.local 13 | .env.development.local 14 | .env.test.local 15 | .env.production.local 16 | 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | 21 | # Editor directories and files 22 | .idea 23 | .DS_Store -------------------------------------------------------------------------------- /client/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { Route, Routes, Navigate } from "react-router-dom"; 2 | import WorkSpace from "./views/WorkSpace"; 3 | 4 | function App() { 5 | return ( 6 | 7 | } /> 8 | } /> 9 | 10 | ); 11 | } 12 | 13 | export default App; -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Sketchflow 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /client/src/main.jsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom/client"; 2 | import App from "./App.jsx"; 3 | import "./styles/index.css"; 4 | import { AppContextProvider } from "./provider/AppStates.jsx"; 5 | import { BrowserRouter } from "react-router-dom" 6 | 7 | ReactDOM.createRoot(document.getElementById("root")).render( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | dist 6 | dist-ssr 7 | *.local 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # env 16 | .env 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # Editor directories and files 27 | .vscode/* 28 | !.vscode/extensions.json 29 | .idea 30 | .DS_Store 31 | *.suo 32 | *.ntvs* 33 | *.njsproj 34 | *.sln 35 | *.sw? -------------------------------------------------------------------------------- /client/src/components/UndoRedo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Redo, Undo } from "../assets/icons"; 3 | import { useAppContext } from "../provider/AppStates"; 4 | 5 | export default function UndoRedo() { 6 | const { undo, redo } = useAppContext(); 7 | return ( 8 |
9 | 12 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "dev": "nodemon index.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "cors": "^2.8.5", 15 | "dotenv": "^16.4.5", 16 | "express": "^4.18.3", 17 | "socket.io": "^4.7.4", 18 | "socket.io-msgpack-parser": "^3.0.2" 19 | }, 20 | "devDependencies": { 21 | "nodemon": "^3.1.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react-refresh/only-export-components': [ 16 | 'warn', 17 | { allowConstantExport: true }, 18 | ], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /client/src/hooks/useDimension.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export default function useDimension() { 4 | const [dimension, setDimension] = useState({ 5 | width: window.innerWidth, 6 | height: window.innerHeight, 7 | }); 8 | 9 | useEffect(() => { 10 | const handleResize = () => { 11 | setDimension({ 12 | width: window.innerWidth, 13 | height: window.innerHeight, 14 | }); 15 | }; 16 | window.addEventListener("resize", handleResize); 17 | return () => { 18 | window.removeEventListener("resize", handleResize); 19 | }; 20 | }, []); 21 | 22 | return dimension; 23 | } 24 | -------------------------------------------------------------------------------- /client/src/components/Canvas.jsx: -------------------------------------------------------------------------------- 1 | import useCanvas from "../hooks/useCanvas"; 2 | 3 | export default function Canvas() { 4 | const { 5 | canvasRef, 6 | dimension, 7 | handleMouseDown, 8 | handleMouseMove, 9 | handleMouseUp, 10 | handleWheel, 11 | handleDoubleClick 12 | } = useCanvas(); 13 | 14 | return ( 15 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /client/src/components/Zoom.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useAppContext } from "../provider/AppStates"; 3 | 4 | export default function Zoom() { 5 | const { scale, onZoom } = useAppContext(); 6 | 7 | return ( 8 |
9 | 12 | onZoom("default")} 15 | title="Reset zoom" 16 | > 17 | {new Intl.NumberFormat("fr-CA", { style: "percent" }).format(scale)} 18 | 19 | 22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /client/src/global/var.js: -------------------------------------------------------------------------------- 1 | import { DashedLine, DottedLine, SolidLine } from "../assets/icons"; 2 | 3 | export const BACKGROUND_COLORS = [ 4 | "transparent", 5 | "rgb(255, 201, 201)", 6 | "rgb(178, 242, 187)", 7 | "rgb(165, 216, 255)", 8 | "rgb(255, 236, 153)", 9 | ]; 10 | 11 | export const STROKE_COLORS = [ 12 | "rgb(30, 30, 30)", 13 | "rgb(224, 49, 49)", 14 | "rgb(47, 158, 68)", 15 | "rgb(25, 113, 194)", 16 | "rgb(240, 140, 0)", 17 | ]; 18 | 19 | export const STROKE_STYLES = [ 20 | { 21 | slug: "solid", 22 | icon: SolidLine, 23 | }, 24 | { 25 | slug: "dashed", 26 | icon: DashedLine, 27 | }, 28 | { 29 | slug: "dotted", 30 | icon: DottedLine, 31 | }, 32 | ]; 33 | 34 | export const CANVAS_BACKGROUND = [ 35 | "rgb(255, 201, 201)", 36 | "rgb(178, 242, 187)", 37 | "rgb(165, 216, 255)", 38 | "rgb(255, 236, 153)", 39 | ]; -------------------------------------------------------------------------------- /client/src/components/ToolBar.jsx: -------------------------------------------------------------------------------- 1 | import { useAppContext } from "../provider/AppStates"; 2 | 3 | export default function ToolBar() { 4 | const { tools: toolCols, selectedTool, lockTool } = useAppContext(); 5 | 6 | return ( 7 |
8 | {toolCols.map((tools, index) => ( 9 |
10 | {tools.map((tool, index_) => ( 11 | 24 | ))} 25 |
26 | ))} 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /client/src/components/Ui.jsx: -------------------------------------------------------------------------------- 1 | import { useAppContext } from "../provider/AppStates"; 2 | import Style from "./Style"; 3 | import ToolBar from "./ToolBar"; 4 | import Zoom from "./Zoom"; 5 | import UndoRedo from "./UndoRedo"; 6 | import Menu from "./Menu"; 7 | import Collaboration from "./Collaboration"; 8 | import Credits from "./Credits"; 9 | 10 | export default function Ui() { 11 | const { selectedElement, selectedTool, style } = useAppContext(); 12 | 13 | return ( 14 |
15 |
16 | 17 | 18 | 19 |
20 | {(!["selection", "hand"].includes(selectedTool) || selectedElement) && ( 21 |