├── .eslintrc.json ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── index.html ├── netlify.toml ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── FSEX302.ttf ├── brand │ ├── duck.png │ └── duck_raw.png ├── buttons │ └── button │ │ ├── Default.svg │ │ ├── Focus.svg │ │ └── Hover.svg ├── icons │ ├── applications │ │ ├── Brosen_windrose.svg │ │ ├── calculator.svg │ │ ├── cardFile.svg │ │ ├── controlPanel.svg │ │ ├── disk.svg │ │ ├── paint.svg │ │ ├── pxArt (1).png │ │ ├── pyide.png │ │ ├── quachat.png │ │ └── terminal.png │ ├── arrows │ │ ├── arrow │ │ │ ├── down.svg │ │ │ ├── left.svg │ │ │ ├── right.svg │ │ │ └── up.svg │ │ └── chevron │ │ │ ├── down.svg │ │ │ ├── left.svg │ │ │ ├── right.svg │ │ │ └── up.svg │ ├── clock │ │ └── Clock_Face.svg │ ├── cursors │ │ ├── Cursor.svg │ │ ├── textCursor.svg │ │ └── touchCursor.svg │ ├── paint │ │ ├── airbrush.svg │ │ ├── circle │ │ │ ├── filled.svg │ │ │ └── outline.svg │ │ ├── curve.svg │ │ ├── ellipse │ │ │ ├── filled.svg │ │ │ └── outline.svg │ │ ├── eraser.svg │ │ ├── frame.svg │ │ ├── line.svg │ │ ├── move.svg │ │ ├── net.svg │ │ ├── object.svg │ │ ├── paintBucket.svg │ │ ├── paintbrush.svg │ │ ├── pencil.svg │ │ ├── rectangle │ │ │ ├── filled.svg │ │ │ └── outline.svg │ │ ├── roundedRectangle │ │ │ ├── filled.svg │ │ │ └── outline.svg │ │ ├── shape │ │ │ ├── filled.svg │ │ │ └── outline.svg │ │ ├── text.svg │ │ └── triangle │ │ │ ├── filled.svg │ │ │ └── outline.svg │ └── system │ │ ├── floppyDisk.svg │ │ └── warning.svg ├── pattern │ ├── diagonal │ │ ├── dark.svg │ │ └── light.svg │ ├── dotted │ │ ├── dark.svg │ │ ├── light.svg │ │ ├── lightAlt.svg │ │ └── medium.svg │ ├── hatch │ │ ├── dark.svg │ │ └── light.svg │ ├── scroll.svg │ └── vertical │ │ ├── dark.svg │ │ └── light.svg ├── screenshots │ ├── boot.png │ ├── home.png │ └── home_start.png └── vite.svg ├── src ├── App.tsx ├── components │ ├── AppIcon │ │ ├── index.tsx │ │ └── types.ts │ ├── Application │ │ ├── AppWrapper.tsx │ │ ├── Draggable.tsx │ │ ├── helper.ts │ │ ├── index.tsx │ │ └── types.ts │ ├── Apps │ │ ├── Calculator │ │ │ ├── ReversePolishNotation.spec.ts │ │ │ ├── ReversePolishNotation.ts │ │ │ ├── helper.ts │ │ │ ├── index.tsx │ │ │ └── types.ts │ │ ├── Clock.tsx │ │ ├── Navigator.tsx │ │ ├── PyIDE.tsx │ │ ├── QuaChat.tsx │ │ └── Terminal │ │ │ ├── helper.ts │ │ │ ├── index.tsx │ │ │ ├── terminal.module.css │ │ │ └── types.ts │ ├── Dropdown │ │ ├── index.tsx │ │ └── types.ts │ ├── TopBar │ │ ├── helper.tsx │ │ └── index.tsx │ ├── WelcomeCard.tsx │ └── ui │ │ └── Button.tsx ├── contexts │ ├── ApplicationContext.tsx │ ├── WebContainerContext.tsx │ └── WindowContext.tsx ├── hooks │ ├── useApp.tsx │ └── usePython.tsx ├── index.css ├── libs │ ├── chat.ts │ ├── protocol.ts │ └── workers │ │ └── python-worker.ts ├── main.tsx ├── pages │ ├── Desktop │ │ └── index.tsx │ ├── Home.tsx │ └── Loading.tsx ├── stores │ ├── iconsStore.ts │ └── pyIDEStore.tsx ├── styles │ └── globals.scss ├── types │ └── ApplicationType.ts └── vite.d.ts ├── tailwind.config.js ├── tsconfig.json └── vite.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "parser": "@typescript-eslint/parser", 7 | "extends": [ 8 | "plugin:react/recommended", 9 | "plugin:prettier/recommended" 10 | ], 11 | "settings": { 12 | "react": { 13 | "version": "detect" 14 | } 15 | }, 16 | "parserOptions": { 17 | "project": "./tsconfig.json" 18 | }, 19 | "overrides": [ 20 | ], 21 | "plugins": [ 22 | "react", 23 | "prettier" 24 | ], 25 | "ignorePatterns": [ 26 | "node_modules", 27 | "dist" 28 | ], 29 | "rules": { 30 | "react/react-in-jsx-scope": "off" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | /stats.html -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-tailwindcss"], 3 | "printWidth": 80, 4 | "tabWidth": 2, 5 | "singleQuote": false, 6 | "trailingComma": "all", 7 | "arrowParens": "always", 8 | "semi": false, 9 | "endOfLine": "auto" 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jônatas Araújo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

🦆 QuackOS - A (fake) web-based OS

4 |
5 | 6 |
7 | 8 | ![GitHub](https://img.shields.io/github/license/jnaraujo/quack-os-react) 9 | ![Most used language](https://img.shields.io/github/languages/top/jnaraujo/quack-os-react?style=flat-square) 10 | 11 |
12 | 13 | QuackOS is a web-based OS that I made for fun. It's (obviously) a fake OS, so it doesn't have any real functionality. 14 | 15 | You can check it out [here](https://quack.jnaraujo.com/). 16 | 17 | > **Note:** This project is still in development, so it's not ready for production yet. 18 | 19 | ## 🙏 Screenshots 20 | 21 |
22 | 23 | 24 | 25 |
26 | 27 | ## 💻 Tech Stack 28 | 29 | - [React](https://reactjs.org/) 30 | - [Vite](https://vitejs.dev/) 31 | - [TypeScript](https://www.typescriptlang.org/) 32 | - [Tailwind CSS](https://tailwindcss.com/) 33 | 34 | ## 🙏 Thanks 35 | 36 | ### Figma design 37 | 38 | - [macOS Classic](https://www.figma.com/community/file/907684770824957775) 39 | - [1985 Windows 1.0 Beta UI Kit](https://www.figma.com/community/file/818237089756436977) 40 | 41 | ### Fonts 42 | 43 | - [Fixedsys Excelsior](https://github.com/kika/fixedsys) 44 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | QuackOS - A Duck Corp. OS 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [[headers]] 2 | for = "/*" 3 | [headers.values] 4 | Cross-Origin-Embedder-Policy = "require-corp" 5 | Cross-Origin-Opener-Policy = "same-origin" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "duckos", 3 | "author": "jnaraujo", 4 | "private": true, 5 | "version": "0.0.0", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "tsc && vite build", 10 | "preview": "vite preview", 11 | "test:watch": "vitest", 12 | "lint": "eslint --ext .js,.jsx,.ts,.tsx . --fix" 13 | }, 14 | "dependencies": { 15 | "@uiw/react-textarea-code-editor": "^3.0.2", 16 | "@vitejs/plugin-react": "^4.2.1", 17 | "@webcontainer/api": "^1.1.5", 18 | "clsx": "^2.0.0", 19 | "framer-motion": "^7.6.1", 20 | "isomorphic-fetch": "^3.0.0", 21 | "prettier": "^3.0.0", 22 | "pyodide": "^0.26.4", 23 | "react": "^18.2.0", 24 | "react-console-emulator": "^5.0.2", 25 | "react-dom": "^18.2.0", 26 | "react-icons": "^4.4.0", 27 | "react-use": "^17.4.0", 28 | "vitest": "^0.24.3", 29 | "zustand": "^4.3.9" 30 | }, 31 | "devDependencies": { 32 | "@types/react": "^18.0.15", 33 | "@types/react-dom": "^18.0.6", 34 | "@typescript-eslint/eslint-plugin": "^6.1.0", 35 | "@typescript-eslint/parser": "^6.1.0", 36 | "autoprefixer": "^10.4.14", 37 | "eslint": "^8.0.1", 38 | "eslint-config-prettier": "^8.8.0", 39 | "eslint-plugin-import": "^2.25.2", 40 | "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", 41 | "eslint-plugin-prettier": "^5.0.0", 42 | "eslint-plugin-promise": "^6.0.0", 43 | "eslint-plugin-react": "^7.33.0", 44 | "postcss": "^8.4.27", 45 | "prettier-plugin-tailwindcss": "^0.4.1", 46 | "rollup-plugin-visualizer": "^5.8.3", 47 | "sass": "^1.64.1", 48 | "tailwindcss": "^3.3.3", 49 | "typescript": "*", 50 | "vite": "^5.1.4", 51 | "vite-plugin-wasm": "^3.3.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/FSEX302.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnaraujo/quack-os-react/9967800472fb4da33be07115b9fcfdff4d86a5f9/public/FSEX302.ttf -------------------------------------------------------------------------------- /public/brand/duck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnaraujo/quack-os-react/9967800472fb4da33be07115b9fcfdff4d86a5f9/public/brand/duck.png -------------------------------------------------------------------------------- /public/brand/duck_raw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnaraujo/quack-os-react/9967800472fb4da33be07115b9fcfdff4d86a5f9/public/brand/duck_raw.png -------------------------------------------------------------------------------- /public/buttons/button/Default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /public/buttons/button/Focus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /public/buttons/button/Hover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /public/icons/applications/Brosen_windrose.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | E 36 | W 37 | S 38 | N 39 | 40 | 41 | NW 42 | NE 43 | SE 44 | SW 45 | 46 | 47 | NNE 48 | ENE 49 | ESE 50 | SSE 51 | SSW 52 | WSW 53 | WNW 54 | NNW 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /public/icons/applications/calculator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/applications/cardFile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/icons/applications/controlPanel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/applications/disk.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/applications/paint.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/applications/pxArt (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnaraujo/quack-os-react/9967800472fb4da33be07115b9fcfdff4d86a5f9/public/icons/applications/pxArt (1).png -------------------------------------------------------------------------------- /public/icons/applications/pyide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnaraujo/quack-os-react/9967800472fb4da33be07115b9fcfdff4d86a5f9/public/icons/applications/pyide.png -------------------------------------------------------------------------------- /public/icons/applications/quachat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnaraujo/quack-os-react/9967800472fb4da33be07115b9fcfdff4d86a5f9/public/icons/applications/quachat.png -------------------------------------------------------------------------------- /public/icons/applications/terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnaraujo/quack-os-react/9967800472fb4da33be07115b9fcfdff4d86a5f9/public/icons/applications/terminal.png -------------------------------------------------------------------------------- /public/icons/arrows/arrow/down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/arrows/arrow/left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/arrows/arrow/right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/arrows/arrow/up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/arrows/chevron/down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/arrows/chevron/left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/arrows/chevron/right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/arrows/chevron/up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/clock/Clock_Face.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | -------------------------------------------------------------------------------- /public/icons/cursors/Cursor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/cursors/textCursor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/cursors/touchCursor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/airbrush.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/circle/filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/circle/outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/curve.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/paint/ellipse/filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/ellipse/outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/eraser.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/frame.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/paint/line.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/paint/move.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/paint/net.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/object.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/paintBucket.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/paintbrush.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/pencil.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/rectangle/filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/rectangle/outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/roundedRectangle/filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/roundedRectangle/outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/shape/filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/shape/outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/paint/triangle/filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/paint/triangle/outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/system/floppyDisk.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/system/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/pattern/diagonal/dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /public/pattern/diagonal/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /public/pattern/dotted/dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /public/pattern/dotted/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /public/pattern/dotted/lightAlt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /public/pattern/dotted/medium.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /public/pattern/hatch/dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | -------------------------------------------------------------------------------- /public/pattern/hatch/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /public/pattern/scroll.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /public/pattern/vertical/dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /public/pattern/vertical/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/screenshots/boot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnaraujo/quack-os-react/9967800472fb4da33be07115b9fcfdff4d86a5f9/public/screenshots/boot.png -------------------------------------------------------------------------------- /public/screenshots/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnaraujo/quack-os-react/9967800472fb4da33be07115b9fcfdff4d86a5f9/public/screenshots/home.png -------------------------------------------------------------------------------- /public/screenshots/home_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jnaraujo/quack-os-react/9967800472fb4da33be07115b9fcfdff4d86a5f9/public/screenshots/home_start.png -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import "./styles/globals.scss" 2 | import ApplicationProvider from "./contexts/ApplicationContext" 3 | 4 | // Pages 5 | import Main from "./pages/Home" 6 | import { WebContainerProvider } from "./contexts/WebContainerContext" 7 | 8 | const App = () => { 9 | return ( 10 | 11 | 12 |
13 | 14 | 15 | ) 16 | } 17 | 18 | export default App 19 | -------------------------------------------------------------------------------- /src/components/AppIcon/index.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from "react" 2 | import { useDragControls } from "framer-motion" 3 | import { useClickAway } from "react-use" 4 | import { motion } from "framer-motion" 5 | import clsx from "clsx" 6 | 7 | import { IAppIconProps } from "./types" 8 | import { useIconsStore } from "../../stores/iconsStore" 9 | 10 | export default function AppIcon({ 11 | onDoubleClick, 12 | id, 13 | defaultPosition, 14 | isDraggable, 15 | title, 16 | width = 60, 17 | height = 60, 18 | icon, 19 | }: IAppIconProps) { 20 | const updatePos = useIconsStore((state) => state.updatePos) 21 | const [showAppBg, setShowAppBg] = useState(false) 22 | const [clickCount, setClickCount] = useState(0) 23 | const ref = useRef(null) 24 | const controls = useDragControls() 25 | 26 | const onClickContent = () => { 27 | const isDoubleClick = clickCount === 1 28 | 29 | if (isDoubleClick) { 30 | onDoubleClick?.() 31 | } 32 | 33 | const timeout = setTimeout(() => { 34 | setClickCount(0) 35 | }, 300) 36 | 37 | setClickCount((prev) => { 38 | if (prev === 1) { 39 | setShowAppBg(false) 40 | return 0 41 | } 42 | setShowAppBg(true) 43 | return 1 44 | }) 45 | 46 | return () => clearTimeout(timeout) 47 | } 48 | 49 | useClickAway(ref, () => { 50 | setClickCount(0) 51 | setShowAppBg(false) 52 | }) 53 | 54 | return ( 55 | <> 56 | { 62 | const rawCoords = ref.current?.style.transform.match( 63 | /^translateX\((.+)px\) translateY\((.+)px\) translateZ/, 64 | ) 65 | 66 | if (!rawCoords) return 67 | 68 | const coords = { 69 | x: parseInt(rawCoords[1], 10), 70 | y: parseInt(rawCoords[2], 10), 71 | } 72 | updatePos(id, coords) 73 | }} 74 | ref={ref} 75 | onClickCapture={onClickContent} 76 | className={clsx( 77 | "flex h-fit w-fit flex-col items-center justify-center p-2", 78 | { 79 | "bg-black": showAppBg, 80 | }, 81 | )} 82 | > 83 |
91 | 98 | {title} 99 | 100 | 101 | 102 | ) 103 | } 104 | -------------------------------------------------------------------------------- /src/components/AppIcon/types.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationName } from "../../contexts/ApplicationContext" 2 | 3 | interface IAppIconProps extends React.HTMLAttributes { 4 | id: ApplicationName 5 | icon: string 6 | title: string 7 | isDraggable?: boolean 8 | defaultPosition?: { x: number; y: number } 9 | onDoubleClick?: () => void 10 | width?: number 11 | height?: number 12 | } 13 | 14 | export type { IAppIconProps } 15 | -------------------------------------------------------------------------------- /src/components/Application/AppWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, Suspense } from "react" 2 | 3 | interface IAppWrapperProps { 4 | Node: React.LazyExoticComponent> 5 | appID: string 6 | } 7 | 8 | const AppWrapper: React.FC = ({ Node, appID }) => { 9 | return ( 10 | 11 | 12 | 13 | ) 14 | } 15 | 16 | export default memo(AppWrapper) 17 | -------------------------------------------------------------------------------- /src/components/Application/Draggable.tsx: -------------------------------------------------------------------------------- 1 | import { motion, useDragControls } from "framer-motion" 2 | import { useEffect, useMemo } from "react" 3 | import { useWindowSize } from "react-use" 4 | 5 | interface Props { 6 | children: React.ReactNode 7 | 8 | drag: boolean 9 | setDrag: (drag: boolean) => void 10 | 11 | mouse?: MouseEvent 12 | 13 | isFullscreen?: boolean 14 | 15 | x?: number 16 | y?: number 17 | 18 | initialWidth: number 19 | initialHeight: number 20 | } 21 | 22 | export default function Draggable({ 23 | children, 24 | x, 25 | y, 26 | drag, 27 | setDrag, 28 | mouse, 29 | initialHeight, 30 | initialWidth, 31 | isFullscreen, 32 | }: Props) { 33 | const controls = useDragControls() 34 | const { width, height } = useWindowSize() 35 | 36 | useEffect(() => { 37 | if (drag && mouse && !isFullscreen) { 38 | controls.start(mouse) 39 | } 40 | }, [drag]) 41 | 42 | const onDragEnd = () => { 43 | setDrag(false) 44 | } 45 | 46 | const initialPosition = useMemo(() => { 47 | if (x !== undefined && y !== undefined) { 48 | return { 49 | x: x, 50 | y: y, 51 | } 52 | } 53 | return { 54 | x: (width - initialWidth) / 2, 55 | y: (height - initialHeight) / 2, 56 | } 57 | }, [x, y, width, height]) 58 | 59 | return ( 60 | 88 | {children} 89 | 90 | ) 91 | } 92 | -------------------------------------------------------------------------------- /src/components/Application/helper.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | export function useWindowContext() { 4 | const [isResizable, setIsResizable] = useState(false) 5 | const [initialSize, setInitialSize] = useState({ width: 500, height: 400 }) 6 | 7 | return { 8 | isResizable, 9 | setIsResizable, 10 | initialSize, 11 | setInitialSize, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Application/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import { useApps } from "../../hooks/useApp" 3 | import clsx from "clsx" 4 | import AppWrapper from "./AppWrapper" 5 | import { IApplicationProps } from "./types" 6 | import { WindowProvider } from "../../contexts/WindowContext" 7 | import { useWindowContext } from "./helper" 8 | import Draggable from "./Draggable" 9 | 10 | function Application({ Node, ...props }: IApplicationProps) { 11 | const [drag, setDrag] = useState(false) 12 | const [mouse, setMouse] = useState() 13 | const [isFullscreen, setIsFullscreen] = useState(false) 14 | const [loading, setLoading] = useState(true) 15 | 16 | const { removeApp, setAppOnFocus } = useApps() 17 | const { isResizable, setIsResizable, initialSize, setInitialSize } = 18 | useWindowContext() 19 | 20 | const move = (event: any) => { 21 | setMouse(event) 22 | setDrag(true) 23 | } 24 | 25 | const close = () => { 26 | setTimeout(() => { 27 | removeApp(props.id) 28 | }, 300) 29 | } 30 | 31 | function handleFullscreen() { 32 | setIsFullscreen((prev) => !prev) 33 | } 34 | 35 | useEffect(() => { 36 | setTimeout(() => { 37 | setLoading(false) 38 | }, 500) 39 | }, []) 40 | 41 | return ( 42 | 52 |
{ 61 | setAppOnFocus(props.id) 62 | }} 63 | > 64 |
68 | 73 | {props.title} 74 | 75 | 76 |
77 | {isResizable && ( 78 | 84 | )} 85 | 91 |
92 |
93 | 94 |
99 | 106 |
112 | 113 |
114 |
115 |
116 |
117 |
118 | ) 119 | } 120 | 121 | export default Application 122 | -------------------------------------------------------------------------------- /src/components/Application/types.ts: -------------------------------------------------------------------------------- 1 | import { App } from "../../types/ApplicationType" 2 | 3 | interface IApplicationProps extends Omit {} 4 | 5 | export type { IApplicationProps } 6 | -------------------------------------------------------------------------------- /src/components/Apps/Calculator/ReversePolishNotation.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "vitest" 2 | import { ReversePolishNotation } from "./ReversePolishNotation" 3 | 4 | describe("Reverse Polish Notation", () => { 5 | it("sum 1 + 1", () => { 6 | expect(ReversePolishNotation.calculate("1 1 +")).toBe(2) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /src/components/Apps/Calculator/ReversePolishNotation.ts: -------------------------------------------------------------------------------- 1 | export class ReversePolishNotation { 2 | private static readonly operators = ["+", "-", "*", "/"] 3 | private static readonly precedence = { 4 | "+": 1, 5 | "-": 1, 6 | "*": 2, 7 | "/": 2, 8 | } 9 | 10 | public static calculate(expression: string): number { 11 | const tokens = expression.split(" ") 12 | const stack: number[] = [] 13 | for (let i = 0; i < tokens.length; i++) { 14 | const token = tokens[i] 15 | if (this.isOperator(token)) { 16 | const b = stack.pop() 17 | const a = stack.pop() 18 | 19 | if (!a || !b) { 20 | throw new Error("Invalid expression") 21 | } 22 | 23 | const result = this.evaluate(a, b, token) 24 | stack.push(result) 25 | } else { 26 | stack.push(parseFloat(token)) 27 | } 28 | } 29 | return stack.pop() as number 30 | } 31 | 32 | private static isOperator(token: string): boolean { 33 | return this.operators.indexOf(token) !== -1 34 | } 35 | 36 | private static evaluate(a: number, b: number, operator: string): number { 37 | switch (operator) { 38 | case "+": 39 | return a + b 40 | case "-": 41 | return a - b 42 | case "*": 43 | return a * b 44 | case "/": 45 | return a / b 46 | default: 47 | throw new Error("Invalid operator") 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Apps/Calculator/helper.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch, SetStateAction } from "react" 2 | 3 | class CalculatorFunctions { 4 | constructor( 5 | private display: string, 6 | private setDisplay: Dispatch>, 7 | ) {} 8 | 9 | handleNumber = (number: string) => { 10 | if (this.display.length > 10) return 11 | 12 | if (this.display === "0" || this.display === "Error") { 13 | this.setDisplay(number) 14 | } else { 15 | this.setDisplay(this.display + number) 16 | } 17 | } 18 | 19 | handleClick = (value: string) => { 20 | if (this.isNumeric(value) || "*/-+".includes(value)) { 21 | return this.handleNumber(value) 22 | } 23 | if (value == ".") { 24 | if (this.display.endsWith(".")) return 25 | return this.handleNumber(value) 26 | } 27 | if (value === "C") { 28 | return this.setDisplay("0") 29 | } 30 | if (value === "=") { 31 | let result = "" 32 | try { 33 | const resultOfEval: number = eval(this.display) 34 | result = resultOfEval.toFixed(3).toString() 35 | } catch (error) { 36 | result = "Error" 37 | } 38 | return this.setDisplay(result) 39 | } 40 | if (value == "<") { 41 | return this.setDisplay(this.display.slice(0, -1)) 42 | } 43 | } 44 | 45 | isNumeric = (value: string) => { 46 | return /^-?\d+$/.test(value) 47 | } 48 | } 49 | 50 | export { CalculatorFunctions } 51 | -------------------------------------------------------------------------------- /src/components/Apps/Calculator/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react" 2 | import { CalculatorFunctions } from "./helper" 3 | import { ICalculatorProps } from "./types" 4 | import clsx from "clsx" 5 | import { useWindow } from "../../../contexts/WindowContext" 6 | 7 | const Button = ({ text, onClick, ...rest }: ICalculatorProps) => { 8 | return ( 9 | 21 | ) 22 | } 23 | 24 | export default function Calculator() { 25 | const { setInitialSize } = useWindow() 26 | const [display, setDisplay] = useState("0") 27 | const { handleClick } = new CalculatorFunctions(display, setDisplay) 28 | 29 | const buttons = [ 30 | ["C", "E", "<", "*"], 31 | ["7", "8", "9", "/"], 32 | ["4", "5", "6", "-"], 33 | ["1", "2", "3", "+"], 34 | ["0", ".", "="], 35 | ] 36 | 37 | useEffect(() => { 38 | setInitialSize({ 39 | width: 300, 40 | height: 460, 41 | }) 42 | }, []) 43 | 44 | return ( 45 |
53 |
54 |

{display.slice(0, 10)}

55 |
56 |
57 | {buttons.map((row, index) => ( 58 |
59 | {row.map((text, index) => ( 60 |
63 | ))} 64 |
65 |
66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /src/components/Apps/Calculator/types.ts: -------------------------------------------------------------------------------- 1 | interface ICalculatorProps { 2 | text: string 3 | onClick?: (value: string) => void 4 | className?: string 5 | } 6 | 7 | export type { ICalculatorProps } 8 | -------------------------------------------------------------------------------- /src/components/Apps/Clock.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import { useWindow } from "../../contexts/WindowContext" 3 | 4 | export default function Clock() { 5 | const { setInitialSize } = useWindow() 6 | const [time, setTime] = useState({ 7 | hours: "00", 8 | minutes: "00", 9 | seconds: "00", 10 | }) 11 | 12 | const updateClock = () => { 13 | const date = new Date() 14 | setTime({ 15 | hours: String(date.getHours()).padStart(2, "0"), 16 | minutes: String(date.getMinutes()).padStart(2, "0"), 17 | seconds: String(date.getSeconds()).padStart(2, "0"), 18 | }) 19 | } 20 | 21 | useEffect(() => { 22 | setInitialSize({ 23 | width: 380, 24 | height: 200, 25 | }) 26 | updateClock() 27 | const interval = setInterval(updateClock, 1000) 28 | 29 | return () => { 30 | clearInterval(interval) 31 | } 32 | }, []) 33 | 34 | return ( 35 |
36 |
37 | {`${time.hours} : ${time.minutes} : ${time.seconds}`} 38 |
39 |
40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Apps/Navigator.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react" 2 | import { useWindow } from "../../contexts/WindowContext" 3 | 4 | function Navigator() { 5 | const { setInitialSize, setIsResizable } = useWindow() 6 | const [url, setUrl] = useState(window.location.href) 7 | 8 | const [history, setHistory] = useState([]) 9 | 10 | useEffect(() => { 11 | setInitialSize({ 12 | width: 600, 13 | height: 450, 14 | }) 15 | setIsResizable(true) 16 | }, []) 17 | 18 | const backHistory = () => { 19 | const newHistory = [...history] 20 | newHistory.pop() 21 | setHistory(newHistory) 22 | setUrl(newHistory[newHistory.length - 1]) 23 | } 24 | 25 | const addHistory = (url: string) => { 26 | if (!url) { 27 | setHistory([]) 28 | return 29 | } 30 | if (history.at(-1) === url) return 31 | 32 | const newHistory = [...history] 33 | 34 | if (url.startsWith("http://") || url.startsWith("https://")) { 35 | newHistory.push(url) 36 | } else { 37 | newHistory.push(`https://${url}`) 38 | } 39 | setHistory(newHistory) 40 | } 41 | 42 | const onKeyDown = (e: React.KeyboardEvent) => { 43 | if (e.key === "Enter") { 44 | const url = e.currentTarget.value 45 | addHistory(url) 46 | } 47 | } 48 | 49 | const page = history[0] 50 | 51 | return ( 52 |
53 |
54 |
69 | {page ? ( 70 |