├── .DS_Store ├── .eslintrc.cjs ├── .gitattributes ├── .gitignore ├── README.md ├── electron-build.config.js ├── electron ├── electron-env.d.ts └── main.ts ├── exported_folder.zip ├── images └── nslogo.png ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── assets │ ├── index-23e17f6d.js │ └── index-9f783a8d.css └── index.html ├── server ├── ExportFolder │ └── nextsketch │ │ ├── .eslintrc.json │ │ ├── .gitignore │ │ ├── README.md │ │ ├── next.config.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── public │ │ ├── next.svg │ │ └── vercel.svg │ │ ├── src │ │ └── app │ │ │ ├── favicon.ico │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── tailwind.config.ts │ │ └── tsconfig.json ├── fileController.js ├── nextsketch │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── next.config.js │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ ├── next.svg │ │ └── vercel.svg │ ├── src │ │ └── app │ │ │ ├── favicon.ico │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ ├── tailwind.config.ts │ └── tsconfig.json └── server.js ├── src ├── App.css ├── App.tsx ├── components │ ├── left │ │ ├── CustomEndpoint.tsx │ │ ├── Modal.tsx │ │ ├── data │ │ │ └── folderData.ts │ │ ├── folder.tsx │ │ ├── hooks │ │ │ └── use-traverse-tree.ts │ │ └── styles.css │ ├── middle │ │ ├── DragOverlayWrapper.tsx │ │ ├── StaticTag.tsx │ │ └── StaticTagsContainer.tsx │ └── right │ │ ├── CodePreview.tsx │ │ ├── ContainerTag.tsx │ │ ├── DisplayContainer.tsx │ │ ├── ExportButton.tsx │ │ ├── NonContainerTag.tsx │ │ ├── Tree.tsx │ │ └── prism │ │ └── prism.css ├── context │ └── AppContext.ts ├── main.tsx ├── utils │ ├── generateId.ts │ └── interfaces.ts └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vercel.json └── vite.config.ts /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/next-sketch/ba6401d86bbcc10db930f1df5d058530088ba1be/.DS_Store -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true, node: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | "react/jsx-filename-extension": [0], 14 | 'react-refresh/only-export-components': [ 15 | 'warn', 16 | { allowConstantExport: true }, 17 | ], 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.dmg filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | dist 4 | dist-electron 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NextSketch 2 | 3 | Introducing your next-level prototyping tool — NextSketch. The tool streamlines the process of creating Next.js prototypes by simplifing your workflow and allows you, developers, to focus on designing and creating your application. 4 | 5 | ## Features 6 | 7 | Create your custom endpoint by submitting a name through the ‘New Endpoint’ form and selecting optional unique file conventions that are reserved for Next.js applications. Each route will be created within only the app folder and can be deleted through the (—) button. All routes and its files are displayed upon creation via a tree hierachy. 8 | 9 | ![Demo2](https://github.com/oslabs-beta/next-sketch/assets/137869546/553a6c2f-146b-4494-8368-0f5daa472aab) 10 | 11 | Drag-and-drop elements from the ‘Add Elements’ section to ‘My Page’ section to customize each file within your routes. Movement of elements are reflected by various colored highlighting. When a user hovers an element over a valid drop are, the area will turn green, blue, or red, indicating where the element will be dropped. A variety of elements such as div and form tags can have children, which is displayed by a blue highlighted area when hovering elements over the middle region of a container tag. Green and red areas depict either top or bottom placement of an element, respectively. Elements can also be dragged directly above, below, or within an element, and with no designated highlight, the element will be dropped to the bottom ‘My Page’ section. After you are satisifed with your prototype, click the ‘Export’ button to download your prototype to your local machine as a zip file. 12 | 13 | Congrats on making your first mock up of a Next.js application. Go ahead and view your new application to see the current routing structure! 14 | 15 | We would like to thank you all for your support ! If you enjoyed our application or are interested in discussing possible new features, please give us a star on GitHub and follow us on LinkedIn for new updates/features! 16 | 17 | ## Navigating the Command Line 18 | 19 | Fork and clone the next-sketch repository 20 | 21 | Install your dependencies: 22 | 23 | npm install 24 | 25 | Spin up the application: 26 | 27 | npm run dev 28 | 29 | ## Check Out NextSketch 30 | 31 | [NextSketch](https://nextsketch.vercel.app/) 32 | 33 | [Medium Article](https://medium.com/@jhuang4647/supercharge-prototyping-with-nextsketch-df90eec49682) 34 | 35 | ## NextSketch Team 36 | 37 | James Huang | [LinkedIn](https://www.linkedin.com/in/james-huang-220392243/) | [Github](https://github.com/jameshuangcoding) 38 | 39 | Jordan Lim | [LinkedIn](https://www.linkedin.com/in/jordanlim1/) | [Github](https://github.com/jordanlim1) 40 | 41 | Laura Ramirez | [LinkedIn](https://www.linkedin.com/in/laura-ramirez-0bb66885/) | [Github](https://github.com/lauraramirez05) 42 | 43 | Christopher Wardrip| [LinkedIn](https://www.linkedin.com/in/christopherwardrip/) | [Github](https://github.com/cwardrip) 44 | -------------------------------------------------------------------------------- /electron-build.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | productName: 'NextSketch', 3 | appId: 'com.NextSketch|electron.app', 4 | directories: { 5 | output: 'dist-electron', //where buildt application files will be output, same as main in package.json 6 | buildResources: ['server', 'images'], //additional resources 7 | app: 'src', //main code is located 8 | }, 9 | asar: true, //enble asar package 10 | files: [ 11 | 'dist-electron/**/*', // Include main process files 12 | 'images/**/*', 13 | 'server/**/*', // Include additional resources 14 | 'package.json', 15 | ], 16 | build: { 17 | mac: { 18 | icon: 'images/nslogo.png', 19 | category: 'public.app-category.productivity', 20 | target: { 21 | target: 'default', 22 | arch: ['x64', 'arm64'], 23 | }, 24 | }, 25 | dmg: { 26 | sign: false, 27 | background: null, 28 | backgroundColor: '#FFFFFF', 29 | window: { 30 | width: '400', 31 | height: '300', 32 | }, 33 | contents: [ 34 | { 35 | x: 100, 36 | y: 100, 37 | }, 38 | { 39 | x: 300, 40 | y: 100, 41 | type: 'link', 42 | path: '/Applications', 43 | }, 44 | ], 45 | }, 46 | win: { 47 | icon: 'images/nslogo.ico', 48 | target: [ 49 | { 50 | target: '--windows', 51 | arch: ['x64'], 52 | }, 53 | ], 54 | }, 55 | linux: { 56 | icon: 'images/nslogo.png', 57 | target: ['deb', 'rpm', 'snap', 'AppImage'], 58 | }, 59 | extraResources: [ 60 | 'node_modules/@fortawesome/**/*', 61 | 'node_modules/prismjs/**/*', 62 | 'node_modules/@mui/**/*', 63 | ], 64 | }, 65 | }; 66 | -------------------------------------------------------------------------------- /electron/electron-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare namespace NodeJS { 4 | interface ProcessEnv { 5 | /** 6 | * The built directory structure 7 | * 8 | * ```tree 9 | * ├─┬─┬ dist 10 | * │ │ └── index.html 11 | * │ │ 12 | * │ ├─┬ dist-electron 13 | * │ │ ├── main.js 14 | * │ │ └── preload.js 15 | * │ 16 | * ``` 17 | */ 18 | DIST: string 19 | /** /dist/ or /public/ */ 20 | VITE_PUBLIC: string 21 | } 22 | } 23 | 24 | // Used in Renderer process, expose in `preload.ts` 25 | interface Window { 26 | ipcRenderer: import('electron').IpcRenderer 27 | } 28 | -------------------------------------------------------------------------------- /electron/main.ts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow } from 'electron' 2 | import path from 'node:path' 3 | 4 | // The built directory structure 5 | // 6 | // ├─┬─┬ dist 7 | // │ │ └── index.html 8 | // │ │ 9 | // │ ├─┬ dist-electron 10 | // │ │ ├── main.js 11 | // │ │ └── preload.js 12 | // │ 13 | process.env.DIST = path.join(__dirname, '../dist') 14 | process.env.VITE_PUBLIC = app.isPackaged ? process.env.DIST : path.join(process.env.DIST, '../public') 15 | 16 | 17 | let win: BrowserWindow | null 18 | // 🚧 Use ['ENV_NAME'] avoid vite:define plugin - Vite@2.x 19 | const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL'] 20 | 21 | function createWindow() { 22 | win = new BrowserWindow({ 23 | width: 1200, 24 | height: 800 25 | }) 26 | 27 | // Test active push message to Renderer-process. 28 | win.webContents.on('did-finish-load', async () => { 29 | win?.webContents.send('main-process-message', (new Date).toLocaleString()) 30 | try { 31 | await fetch('http://localhost:3000/'); 32 | } catch (error) { 33 | console.error('Error during fetch:'); 34 | } 35 | }) 36 | 37 | if (VITE_DEV_SERVER_URL) { 38 | win.loadURL(VITE_DEV_SERVER_URL) 39 | } else { 40 | // win.loadFile('dist/index.html') 41 | win.loadFile(path.join(process.env.DIST, 'index.html')) 42 | } 43 | 44 | 45 | 46 | 47 | } 48 | 49 | // Quit when all windows are closed, except on macOS. There, it's common 50 | // for applications and their menu bar to stay active until the user quits 51 | // explicitly with Cmd + Q. 52 | app.on('window-all-closed', async () => { 53 | if (process.platform !== 'darwin') { 54 | 55 | app.quit() 56 | win = null 57 | } 58 | 59 | 60 | 61 | 62 | }) 63 | 64 | app.on('activate', () => { 65 | // On OS X it's common to re-create a window in the app when the 66 | // dock icon is clicked and there are no other windows open. 67 | if (BrowserWindow.getAllWindows().length === 0) { 68 | createWindow() 69 | } 70 | }) 71 | 72 | app.whenReady().then(createWindow) 73 | -------------------------------------------------------------------------------- /exported_folder.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/next-sketch/ba6401d86bbcc10db930f1df5d058530088ba1be/exported_folder.zip -------------------------------------------------------------------------------- /images/nslogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/next-sketch/ba6401d86bbcc10db930f1df5d058530088ba1be/images/nslogo.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | NextSketch 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextsketch", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "concurrently \"vite\" \"npm run start\"", 7 | "start": "node server/server.js", 8 | "build": "tsc && vite build && electron-builder", 9 | "build-windows": "npm run build && electron-builder --windows", 10 | "build-linux": "npm run build && electron-builder --linux", 11 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 12 | "preview": "vite preview", 13 | "package": "electron-builder --dir", 14 | "dist": "electron-builder" 15 | }, 16 | "engines": { 17 | "node": "18.x" 18 | }, 19 | "dependencies": { 20 | "@emotion/react": "^11.11.1", 21 | "@emotion/styled": "^11.11.0", 22 | "@fortawesome/fontawesome": "^1.1.8", 23 | "@fortawesome/fontawesome-free-regular": "^5.0.13", 24 | "@fortawesome/fontawesome-free-solid": "^5.0.13", 25 | "@fortawesome/fontawesome-svg-core": "^6.4.2", 26 | "@fortawesome/free-regular-svg-icons": "^6.4.2", 27 | "@fortawesome/free-solid-svg-icons": "^6.4.2", 28 | "@fortawesome/react-fontawesome": "^0.2.0", 29 | "@mui/icons-material": "^5.14.13", 30 | "@mui/lab": "^5.0.0-alpha.149", 31 | "@mui/material": "^5.14.13", 32 | "@mui/system": "^5.14.15", 33 | "archiver": "^6.0.1", 34 | "cors": "^2.8.5", 35 | "d3": "^7.8.5", 36 | "electron-fetch": "^1.9.1", 37 | "express": "^4.18.2", 38 | "file-saver": "^2.0.5", 39 | "fs-extra": "^11.1.1", 40 | "glob": "^10.3.10", 41 | "isbrowser": "^0.1.0", 42 | "jszip": "^3.10.1", 43 | "merge-dirs": "^0.2.1", 44 | "ncp": "^2.0.0", 45 | "nodemon": "^3.0.1", 46 | "prismjs": "^1.29.0", 47 | "react": "^18.2.0", 48 | "react-dom": "^18.2.0", 49 | "react-icons": "^4.11.0", 50 | "resize-observer-polyfill": "^1.5.1", 51 | "walk": "^2.3.15", 52 | "webfontloader": "^1.6.28" 53 | }, 54 | "devDependencies": { 55 | "@dnd-kit/core": "^6.0.8", 56 | "@dnd-kit/sortable": "^7.0.2", 57 | "@electron-forge/plugin-vite": "^6.4.2", 58 | "@emotion/react": "^11.11.1", 59 | "@emotion/styled": "^11.11.0", 60 | "@mui/material": "^5.14.13", 61 | "@types/d3": "^7.4.2", 62 | "@types/prismjs": "^1.26.1", 63 | "@types/react": "^18.2.21", 64 | "@types/react-dom": "^18.2.7", 65 | "@typescript-eslint/eslint-plugin": "^6.6.0", 66 | "@typescript-eslint/parser": "^6.6.0", 67 | "@vitejs/plugin-react": "^4.0.4", 68 | "autoprefixer": "^10.4.16", 69 | "concurrently": "^8.2.1", 70 | "electron": "^26.1.0", 71 | "electron-builder": "^24.6.4", 72 | "eslint": "^8.48.0", 73 | "eslint-plugin-react-hooks": "^4.6.0", 74 | "eslint-plugin-react-refresh": "^0.4.3", 75 | "npm-run-all": "^4.1.5", 76 | "postcss": "^8.4.31", 77 | "prettier": "^3.0.3", 78 | "tailwind-merge": "^1.14.0", 79 | "tailwindcss": "^3.3.3", 80 | "ts-node": "^10.9.1", 81 | "tsx": "^3.13.0", 82 | "typescript": "^5.2.2", 83 | "vite": "^4.4.9", 84 | "vite-plugin-electron": "^0.14.0", 85 | "vite-plugin-electron-renderer": "^0.14.5", 86 | "vite-tsconfig-paths": "^4.2.1" 87 | }, 88 | "main": "dist-electron/main.js" 89 | } 90 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/assets/index-9f783a8d.css: -------------------------------------------------------------------------------- 1 | *{margin:0 auto,}div{margin-top:5px}.folder{background-color:#fff;display:flex;align-items:center;justify-content:space-between;padding:5px;width:90%;cursor:pointer;border-radius:4px;box-shadow:0 2px 4px #0000001a;margin-top:3px;overflow-y:scroll}.folder span{font-family:Arial,sans-serif;font-size:16px;font-weight:400;color:#333}.folder button{background:none;border:none;cursor:pointer;font-size:16px;color:#333;margin-left:10px}.folder .folder-contents{display:block;padding-left:25px}.cursor{margin-top:3px}form{display:flex;justify-content:space-between;align-items:center;border:1px solid #e0e0e0;border-radius:5px;padding:10px;width:90%;max-width:600px;box-sizing:border-box;box-shadow:inset 2px -3px 10px #0003}form:hover{cursor:text}input{flex:1;padding:10px;border:10px solid #ccc;width:80%;border-radius:5px;font-size:16px;margin-right:5px;animation:blink 1s step-end 10}button[type=submit]{background-color:#007bff;color:#fff;border:none;border-radius:5px;padding:0 10px;font-size:18px;cursor:pointer;transition:background-color .2s;margin-left:20px;box-sizing:border-box}button[type=submit]:hover{background-color:#0056b3}button[type=submit]:disabled{background-color:#ccc;cursor:not-allowed}#tree-container{width:200%;max-height:1000px;overflow:auto;position:relative}.input-container{position:relative;height:100%}#searchInput{border:none;outline:none;padding:0;width:100%;line-height:100%;margin:0;font-size:20px;background-color:transparent}.text-cursor{position:absolute;top:0;left:0;height:100%;width:2px;animation:none}@keyframes cursor-blink{0%,to{background:transparent}}#searchInput::-moz-placeholder{color:#ccc}#searchInput::placeholder{color:#ccc}#searchInput:focus+.text-cursor{animation:cursor-blink 1s infinite}.buttons{display:flex;justify-content:right}@media (max-width: 768px){.tree-container{width:20%}}@media (max-width: 480px){.tree-container{width:80%}}.folder-node{fill:#00f}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.absolute{position:absolute}.relative{position:relative}.block{display:block}.flex{display:flex}.hidden{display:none}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.resize{resize:both}.border{border-width:1px}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;hyphens:none}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8292a2}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#f92672}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a6e22e}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#e6db74}.token.important,.token.regex{color:#fd971f}.token.bold,.token.important{font-weight:700}code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:none;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#272822}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:#8292a2}.token.punctuation{color:#f8f8f2}.token.namespace{opacity:.7}.token.property,.token.tag,.token.constant,.token.symbol,.token.deleted{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.selector,.token.attr-name,.token.string,.token.char,.token.builtin,.token.inserted{color:#a6e22e}.token.operator,.token.entity,.token.url,.language-css .token.string,.style .token.string,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.function,.token.class-name{color:#e6db74}.token.keyword{color:#66d9ef}.token.regex,.token.important{color:#fd971f}.token.important,.token.bold{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}.token.punctuation.brace-hover,.token.punctuation.brace-selected{outline:solid 1px}.rainbow-braces .token.punctuation.brace-level-1,.rainbow-braces .token.punctuation.brace-level-5,.rainbow-braces .token.punctuation.brace-level-9{color:#e50;opacity:1}.rainbow-braces .token.punctuation.brace-level-2,.rainbow-braces .token.punctuation.brace-level-6,.rainbow-braces .token.punctuation.brace-level-10{color:#0b3;opacity:1}.rainbow-braces .token.punctuation.brace-level-3,.rainbow-braces .token.punctuation.brace-level-7,.rainbow-braces .token.punctuation.brace-level-11{color:#26f;opacity:1}.rainbow-braces .token.punctuation.brace-level-4,.rainbow-braces .token.punctuation.brace-level-8,.rainbow-braces .token.punctuation.brace-level-12{color:#e0e;opacity:1} 2 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | NextSketch 8 | 9 | 10 | 11 | 12 | 13 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /server/ExportFolder/nextsketch/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /server/ExportFolder/nextsketch/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /server/ExportFolder/nextsketch/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /server/ExportFolder/nextsketch/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /server/ExportFolder/nextsketch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextsketch", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "react": "^18", 13 | "react-dom": "^18", 14 | "next": "13.5.6" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5", 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "autoprefixer": "^10", 22 | "postcss": "^8", 23 | "tailwindcss": "^3", 24 | "eslint": "^8", 25 | "eslint-config-next": "13.5.6" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/ExportFolder/nextsketch/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /server/ExportFolder/nextsketch/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/ExportFolder/nextsketch/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/ExportFolder/nextsketch/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/next-sketch/ba6401d86bbcc10db930f1df5d058530088ba1be/server/ExportFolder/nextsketch/src/app/favicon.ico -------------------------------------------------------------------------------- /server/ExportFolder/nextsketch/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /server/ExportFolder/nextsketch/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { Inter } from 'next/font/google' 3 | import './globals.css' 4 | 5 | const inter = Inter({ subsets: ['latin'] }) 6 | 7 | export const metadata: Metadata = { 8 | title: 'Create Next App', 9 | description: 'Generated by create next app', 10 | } 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: { 15 | children: React.ReactNode 16 | }) { 17 | return ( 18 | 19 | {children} 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /server/ExportFolder/nextsketch/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | 3 | export default function Home() { 4 | return ( 5 |
6 |
7 |

8 | Get started by editing  9 | src/app/page.tsx 10 |

11 |
12 | 18 | By{' '} 19 | Vercel Logo 27 | 28 |
29 |
30 | 31 |
32 | Next.js Logo 40 |
41 | 42 |
43 | 49 |

50 | Docs{' '} 51 | 52 | -> 53 | 54 |

55 |

56 | Find in-depth information about Next.js features and API. 57 |

58 |
59 | 60 | 66 |

67 | Learn{' '} 68 | 69 | -> 70 | 71 |

72 |

73 | Learn about Next.js in an interactive course with quizzes! 74 |

75 |
76 | 77 | 83 |

84 | Templates{' '} 85 | 86 | -> 87 | 88 |

89 |

90 | Explore the Next.js 13 playground. 91 |

92 |
93 | 94 | 100 |

101 | Deploy{' '} 102 | 103 | -> 104 | 105 |

106 |

107 | Instantly deploy your Next.js site to a shareable URL with Vercel. 108 |

109 |
110 |
111 |
112 | ) 113 | } 114 | -------------------------------------------------------------------------------- /server/ExportFolder/nextsketch/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | 3 | const config: Config = { 4 | content: [ 5 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | } 20 | export default config 21 | -------------------------------------------------------------------------------- /server/ExportFolder/nextsketch/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /server/fileController.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const path = require('path'); 3 | const { emitWarning } = require('process'); 4 | const execSync = require('child_process').execSync; 5 | const ncp = require('ncp').ncp; 6 | const glob = require('glob'); 7 | const archiver = require('archiver'); 8 | const JSZip = require('jszip'); 9 | 10 | const fileController = { 11 | postFolder: function (req, res, next) { 12 | const folderDir = 'server/ExportFolder/'; 13 | 14 | const fileProperties = [ 15 | req.body.folderName, 16 | req.body.fileName, 17 | req.body.codeSnippet, 18 | ]; 19 | 20 | res.locals.fileProps = fileProperties; 21 | 22 | if (req.body.name) { 23 | const dir = 'server/ExportFolder/NextSketch/src/app/'; 24 | fs.mkdirSync(path.join(dir, req.body.name)); 25 | fs.writeFileSync(path.join(dir + req.body.name, 'page.tsx'), ''); 26 | return next(); 27 | } 28 | 29 | if (req.body.fileName) { 30 | function recall(folderDir) { 31 | const fileList = fs.readdirSync(folderDir); 32 | 33 | for (const file of fileList) { 34 | const name = `${folderDir}/${file}`; 35 | if (file === 'node_modules') { 36 | continue; 37 | } 38 | if (file === req.body.folderName) { 39 | if (req.body.isFolder) { 40 | fs.mkdirSync(path.join(name, req.body.fileName)); 41 | fs.writeFileSync( 42 | path.join(name + '/' + req.body.fileName, 'page.tsx'), 43 | '' 44 | ); 45 | } else if (req.body.codeSnippet) { 46 | fs.writeFileSync( 47 | path.join(name, req.body.fileName), 48 | req.body.codeSnippet 49 | ); 50 | } else { 51 | fs.writeFileSync(path.join(name, req.body.fileName), ''); 52 | } 53 | return; 54 | } 55 | if (fs.statSync(name).isDirectory()) { 56 | recall(name); 57 | } 58 | } 59 | return; 60 | } 61 | 62 | recall(folderDir); 63 | return next(); 64 | } 65 | }, 66 | 67 | deleteFolder: function (req, res, next) { 68 | const folderDir = 'server/ExportFolder'; 69 | 70 | function recall(folderDir) { 71 | const fileList = fs.readdirSync(folderDir); 72 | 73 | for (const file of fileList) { 74 | const name = `${folderDir}/${file}`; 75 | if (name === 'node_modules') { 76 | if (req.body.name === 'node_modules') { 77 | fs.rmSync(name, { recursive: true }); 78 | } 79 | continue; 80 | } 81 | if (file === req.body.name) { 82 | if (fs.lstatSync(name).isDirectory()) { 83 | fs.rmSync(name, { recursive: true }); 84 | } else { 85 | fs.rmSync(name); 86 | } 87 | return; 88 | } 89 | if (fs.statSync(name).isDirectory()) { 90 | recall(name); 91 | } 92 | } 93 | return; 94 | } 95 | 96 | recall(folderDir); 97 | return next(); 98 | }, 99 | 100 | updateCode: function (req, res, next) { 101 | const folderDir = 'server/ExportFolder'; 102 | 103 | function recall(folderDir) { 104 | const fileList = fs.readdirSync(folderDir); 105 | 106 | for (const file of fileList) { 107 | const name = `${folderDir}/${file}`; 108 | if (name === 'node_modules') { 109 | continue; 110 | } 111 | 112 | if (file === req.body.folder) { 113 | fs.writeFileSync( 114 | path.join(name, req.body.fileName), 115 | req.body.codeSnippet 116 | ); 117 | } 118 | if (fs.statSync(name).isDirectory()) { 119 | recall(name); 120 | } 121 | } 122 | return; 123 | } 124 | 125 | recall(folderDir); 126 | return next(); 127 | }, 128 | 129 | deleteExport: function (req, res, next) { 130 | const folderDir = 'server/ExportFolder/NextSketch'; 131 | fs.rmSync(folderDir, { recursive: true }); 132 | return next(); 133 | }, 134 | 135 | createExport: function (req, res, next) { 136 | const targetDir = 'server/ExportFolder'; 137 | const sourceDir = 'server/NextSketch'; 138 | 139 | fs.copy( 140 | sourceDir, 141 | path.join(targetDir, 'NextSketch'), 142 | { recursive: true }, 143 | (err) => { 144 | if (err) { 145 | console.error(`Error copying directory: ${err}`); 146 | } else { 147 | console.log('Directory and its contents copied successfully.'); 148 | } 149 | } 150 | ); 151 | 152 | return next(); 153 | }, 154 | }; 155 | 156 | module.exports = fileController; 157 | -------------------------------------------------------------------------------- /server/nextsketch/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /server/nextsketch/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /server/nextsketch/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /server/nextsketch/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /server/nextsketch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextsketch", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "react": "^18", 13 | "react-dom": "^18", 14 | "next": "13.5.6" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5", 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "autoprefixer": "^10", 22 | "postcss": "^8", 23 | "tailwindcss": "^3", 24 | "eslint": "^8", 25 | "eslint-config-next": "13.5.6" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/nextsketch/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /server/nextsketch/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/nextsketch/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/nextsketch/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/next-sketch/ba6401d86bbcc10db930f1df5d058530088ba1be/server/nextsketch/src/app/favicon.ico -------------------------------------------------------------------------------- /server/nextsketch/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /server/nextsketch/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { Inter } from 'next/font/google' 3 | import './globals.css' 4 | 5 | const inter = Inter({ subsets: ['latin'] }) 6 | 7 | export const metadata: Metadata = { 8 | title: 'Create Next App', 9 | description: 'Generated by create next app', 10 | } 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: { 15 | children: React.ReactNode 16 | }) { 17 | return ( 18 | 19 | {children} 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /server/nextsketch/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | 3 | export default function Home() { 4 | return ( 5 |
6 |
7 |

8 | Get started by editing  9 | src/app/page.tsx 10 |

11 |
12 | 18 | By{' '} 19 | Vercel Logo 27 | 28 |
29 |
30 | 31 |
32 | Next.js Logo 40 |
41 | 42 |
43 | 49 |

50 | Docs{' '} 51 | 52 | -> 53 | 54 |

55 |

56 | Find in-depth information about Next.js features and API. 57 |

58 |
59 | 60 | 66 |

67 | Learn{' '} 68 | 69 | -> 70 | 71 |

72 |

73 | Learn about Next.js in an interactive course with quizzes! 74 |

75 |
76 | 77 | 83 |

84 | Templates{' '} 85 | 86 | -> 87 | 88 |

89 |

90 | Explore the Next.js 13 playground. 91 |

92 |
93 | 94 | 100 |

101 | Deploy{' '} 102 | 103 | -> 104 | 105 |

106 |

107 | Instantly deploy your Next.js site to a shareable URL with Vercel. 108 |

109 |
110 |
111 |
112 | ) 113 | } 114 | -------------------------------------------------------------------------------- /server/nextsketch/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | 3 | const config: Config = { 4 | content: [ 5 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | } 20 | export default config 21 | -------------------------------------------------------------------------------- /server/nextsketch/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const express = require('express'); 4 | 5 | const app = express(); 6 | const cors = require('cors'); 7 | const PORT = process.env.PORT || 3000; 8 | app.use(cors()); 9 | const fileController = require('./fileController.js'); 10 | const archiver = require('archiver'); 11 | 12 | app.use(express.urlencoded({ extended: true })); 13 | app.use(express.json()); 14 | 15 | 16 | 17 | app.get('/export', (req, res) => { 18 | const folderPath = 'server/ExportFolder/NextSketch'; // Replace with the actual folder path 19 | const output = fs.createWriteStream('exported_folder.zip'); 20 | const archive = archiver('zip', { 21 | zlib: { level: 9 }, // Maximum compression 22 | }); 23 | 24 | archive.pipe(output); 25 | 26 | // Recursively add all files and folders within the specified folder 27 | function addFolderToZip(folderPath, folderName) { 28 | const folderContents = fs.readdirSync(folderPath); 29 | folderContents.forEach((item) => { 30 | const itemPath = path.join(folderPath, item); 31 | const itemStat = fs.statSync(itemPath); 32 | if (itemStat.isDirectory()) { 33 | addFolderToZip(itemPath, path.join(folderName, item)); 34 | } else { 35 | archive.file(itemPath, { name: path.join(folderName, item) }); 36 | } 37 | }); 38 | } 39 | 40 | addFolderToZip(folderPath, ''); 41 | 42 | archive.finalize(); 43 | output.on('close', () => { 44 | res.download('exported_folder.zip'); 45 | }); 46 | }); 47 | 48 | app.get( 49 | '/', 50 | fileController.deleteExport, 51 | fileController.createExport, 52 | (req, res) => { 53 | return res.status(200); 54 | } 55 | ); 56 | 57 | app.put('/updatecode', fileController.updateCode, (req, res) => { 58 | res.status(200).send(); 59 | }); 60 | 61 | app.post('/', fileController.postFolder, (req, res) => { 62 | return res.status(200).json(res.locals.fileProps); 63 | }); 64 | 65 | app.delete('/', fileController.deleteFolder, (req, res) => { 66 | return res.status(200).send(); 67 | }); 68 | 69 | app.use((err, req, res, next) => { 70 | const defaultErr = { 71 | log: 'Express error handler caught unknown middleware error', 72 | status: 500, 73 | message: { err: 'An error occurred' }, 74 | }; 75 | const errorObj = Object.assign({}, defaultErr, err); 76 | console.log(errorObj.log); 77 | return res.status(errorObj.status).json(errorObj.message); 78 | }); 79 | 80 | app.listen(PORT, () => { 81 | console.log(`Server listening on port: ${PORT}`); 82 | }); 83 | 84 | module.exports = app; 85 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | Dispatch, 3 | SetStateAction, 4 | useState, 5 | useEffect, 6 | createContext, 7 | } from 'react'; 8 | import { Box, Grid, Typography, AppBar, Toolbar, Button } from '@mui/material'; 9 | import StaticTagsContainer from './components/middle/StaticTagsContainer'; 10 | import './App.css'; 11 | import CreateComponentBtn from './components/middle/CreateComponentBtn'; 12 | import explorer from './components/left/data/folderData'; 13 | import Folder from './components/left/folder'; 14 | import useTraverseTree from './components/left/hooks/use-traverse-tree'; 15 | import CustomEndpoint from './components/left/CustomEndpoint'; 16 | import TabsComponent from './components/right/TabsComponent'; 17 | import DisplayContainer from './components/right/DisplayContainer'; 18 | import { Tag, Elements, ComponentNameType, CodeSnippetType } from './utils/interfaces'; 19 | import { generateId } from './utils/generateId'; 20 | import WebFont from 'webfontloader'; 21 | import ExportButton from './components/right/ExportButton'; 22 | import AppContext from './context/AppContext'; 23 | import Tree from './components/right/Tree'; 24 | import CodePreview from './components/right/CodePreview'; 25 | import { DndContext } from '@dnd-kit/core'; 26 | import DragOverlayWrapper from './components/middle/DragOverlayWrapper'; 27 | import { image } from 'd3'; 28 | 29 | // test 30 | 31 | 32 | 33 | export const CodeContext = createContext( 34 | undefined 35 | ); 36 | 37 | export const CodeSnippetContext = createContext( 38 | undefined 39 | ); 40 | 41 | const App = () => { 42 | const [folderExpanded, setFolderExpanded] = useState(false); 43 | const [open, setOpen] = useState(false); 44 | const [explorerData, setExplorerData] = useState(explorer); 45 | const [componentName, setComponentName] = useState('Page'); 46 | const [codeSnippet, setCodeSnippet] = useState( 47 | undefined 48 | ); 49 | const [folder, setFolder] = useState(''); 50 | const [file, setFile] = useState(''); 51 | const [postData, setPostData] = useState(false); 52 | 53 | // tags context 54 | const [tags, setTags] = useState([]); 55 | const [currentId, setCurrentId] = useState(8); 56 | const [update, setUpdate] = useState(false); 57 | const [reset, setReset] = useState(false); 58 | const [previewFolder, setPreviewFolder] = useState(''); 59 | const [currentParent, setCurrentParent] = useState(''); 60 | 61 | const [srcApp, setSrcApp] = useState(explorer.items[2]); 62 | 63 | const { 64 | insertNode, 65 | deleteNode, 66 | createCustomEndpoint, 67 | insertBoilerFiles, 68 | updatePreview, 69 | initialPreview, 70 | } = useTraverseTree(); 71 | 72 | const handleInsertNode = ( 73 | folderId: number, 74 | item: string, 75 | isFolder: boolean, 76 | preview?: string 77 | ) => { 78 | const finalTree: typeof explorer = insertNode( 79 | explorerData, 80 | folderId, 81 | item, 82 | isFolder, 83 | preview 84 | ); 85 | 86 | setExplorerData(finalTree); 87 | 88 | for (let items of finalTree.items) { 89 | if (items.name === 'src') setSrcApp(items); 90 | } 91 | }; 92 | 93 | const handleDeleteNode = (folderId?: number) => { 94 | const finalTree: typeof explorer = deleteNode(explorerData, folderId); 95 | setExplorerData(finalTree); 96 | for (let items of finalTree.items) { 97 | if (items.name === 'src') setSrcApp(items); 98 | } 99 | }; 100 | 101 | const handleUpdatePreview = (fileId: number, preview: string, tags: Tag[]): typeof explorer | void => { 102 | const finalTree: typeof explorer = updatePreview(explorerData, fileId, preview, tags); 103 | 104 | setExplorerData(finalTree); 105 | }; 106 | 107 | const handleInitialPreview = ( 108 | folderName: string, 109 | fileName: string, 110 | preview: string, 111 | tags: [] 112 | ): typeof explorer | void => { 113 | const finalTree: typeof explorer = initialPreview( 114 | explorerData, 115 | folderName, 116 | fileName, 117 | preview, 118 | tags 119 | ); 120 | 121 | setExplorerData(finalTree); 122 | }; 123 | 124 | const handleCreateCustomEndpoint = ( 125 | folderId: number, 126 | item: string, 127 | isFolder?: boolean 128 | ): typeof explorer | void => { 129 | const finalTree = createCustomEndpoint( 130 | explorerData, 131 | folderId, 132 | item, 133 | isFolder 134 | ); 135 | setExplorerData(finalTree); 136 | 137 | for (let items of finalTree.items) { 138 | if (items.name === 'src') setSrcApp(items); 139 | } 140 | }; 141 | 142 | const handleInputBoilerFiles = ( 143 | folderId: number, 144 | item: string, 145 | folderName: string, 146 | preview?: string 147 | ): typeof explorer | void => { 148 | const finalTree: any = insertBoilerFiles( 149 | explorerData, 150 | folderId, 151 | item, 152 | folderName, 153 | preview, 154 | ); 155 | 156 | setExplorerData(finalTree); 157 | 158 | for (let items of finalTree.items) { 159 | if (items.name === 'src') setSrcApp(items); 160 | } 161 | }; 162 | 163 | return ( 164 | 165 | 166 | 167 |
168 | 169 | 170 | 171 | 182 | 183 | NextSketch 184 | 185 |
186 | 187 | 188 |
189 |
190 | 191 | {/* */} 209 | 210 | 211 | 227 | 237 | 258 | 273 | 287 | 288 | 289 | 290 | 305 | 306 | 307 | 308 | 309 | 321 | 322 | 323 | 324 | 325 | 339 | 354 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | //
373 | ); 374 | }; 375 | 376 | export default App; 377 | -------------------------------------------------------------------------------- /src/components/left/CustomEndpoint.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState, useEffect } from 'react'; 2 | import { Box, Button, Typography, Modal, Checkbox } from '@mui/material'; 3 | import { CodeSnippetContext, CodeContext } from '../../App'; 4 | import Code from '@mui/icons-material/Code'; 5 | import { modalLayout, CustomEndpointProps } from '../../utils/interfaces'; 6 | import Prism from 'prismjs'; 7 | import AppContext, {AppContextType} from '../../context/AppContext'; 8 | 9 | //---------------- 10 | const style = { 11 | position: 'absolute' as 'absolute', 12 | top: '50%', 13 | left: '50%', 14 | transform: 'translate(-50%, -50%)', 15 | width: 500, 16 | height: 'fit-content', 17 | bgcolor: 'background.paper', 18 | border: '2px solid #000', 19 | boxShadow: 24, 20 | p: 4, 21 | borderRadius: '20px', 22 | fontSize: '1.6rem' 23 | 24 | }; 25 | //MUI styling fior modal 26 | //----------------- 27 | 28 | 29 | 30 | const CustomEndpoint = ({ 31 | handleCreateCustomEndpoint, 32 | handleInputBoilerFiles, 33 | handleUpdatePreview, 34 | handleInitialPreview, 35 | explorer, 36 | setFolder, 37 | folder, 38 | setFile, 39 | file, 40 | setPostData, 41 | postData, 42 | }: CustomEndpointProps) => { 43 | const [open, setOpen] = useState(false); 44 | const [componentName, setComponentName] = useContext(CodeContext); 45 | const [codeSnippet, setCodeSnippet] = useContext(CodeSnippetContext); 46 | 47 | const { 48 | tags, 49 | setTags, 50 | update, 51 | setUpdate, 52 | currentId, 53 | previewFolder, 54 | setPreviewFolder, 55 | currentParent, 56 | setCurrentParent, 57 | }: AppContextType = useContext(AppContext)!; 58 | 59 | 60 | const handleClose = () => { 61 | setOpen(false); 62 | setFolder(''); 63 | setSelectedItems({ 64 | default: false, 65 | error: false, 66 | layout: false, 67 | loading: false, 68 | notFound: false, 69 | route: false, 70 | template: false, 71 | page: true, 72 | }); 73 | }; 74 | 75 | const [selectedItems, setSelectedItems] = useState({ 76 | default: false, 77 | error: false, 78 | layout: false, 79 | loading: false, 80 | notFound: false, 81 | route: false, 82 | template: false, 83 | page: true, 84 | }); 85 | 86 | function handleChange(e?: React.ChangeEvent) { 87 | setFolder(e?.target.value); 88 | setPreviewFolder(e?.target.value as string); 89 | } 90 | 91 | useEffect(() => { 92 | //creating new files with code 93 | if (postData === true) { 94 | 95 | handlePostingFiles(folder, componentName, codeSnippet); 96 | } 97 | //updating code in existing files 98 | if (update === true) { 99 | handleUpdatingFiles(componentName, codeSnippet, previewFolder); 100 | 101 | handleUpdatePreview(currentId, codeSnippet, tags); 102 | 103 | handleInitialPreview(currentParent, componentName, codeSnippet, tags); 104 | 105 | setUpdate(false); 106 | } 107 | Prism.highlightAll(); 108 | }, [codeSnippet]); 109 | 110 | async function handleModalChange(e?: React.ChangeEvent) { 111 | const name = e?.target.name.slice(0, -4); 112 | 113 | setTags([]); 114 | 115 | setSelectedItems({ 116 | ...selectedItems, 117 | [name as string]: true, 118 | }); 119 | 120 | const fileName = e?.target.name; 121 | setFile(fileName); 122 | 123 | setComponentName(fileName); 124 | setPostData(true); 125 | } 126 | 127 | const handlePostingFiles = async ( 128 | folderName: string, 129 | fileName: string, 130 | code: string 131 | ) => { 132 | handleInputBoilerFiles(explorer.id, file, folder, codeSnippet); 133 | const body = { 134 | fileName: fileName, 135 | folderName: folderName, 136 | codeSnippet: code, 137 | }; 138 | await fetch('http://localhost:3000/', { 139 | method: 'POST', 140 | headers: { 141 | 'Content-Type': 'application/json', 142 | }, 143 | body: JSON.stringify(body), 144 | }) 145 | .then((response) => response.json()) 146 | .then((data) => { 147 | console.log(data); 148 | setPostData(false); 149 | setCurrentParent(data[0]); 150 | handleInitialPreview(data[0], data[1], data[2], tags); 151 | }); 152 | }; 153 | 154 | const handleUpdatingFiles = async ( 155 | file: string, 156 | code: string, 157 | previewFolder 158 | ) => { 159 | const body = { 160 | fileName: file, 161 | codeSnippet: code, 162 | folder: previewFolder, 163 | }; 164 | await fetch('/updatecode', { 165 | method: 'PUT', 166 | headers: { 167 | 'Content-Type': 'application/json', 168 | }, 169 | body: JSON.stringify(body), 170 | }); 171 | }; 172 | 173 | const handleCreateCustomFolder = async (e?: React.MouseEvent) => { 174 | e?.stopPropagation(); 175 | e?.preventDefault(); 176 | 177 | const body = { name: folder }; 178 | 179 | await fetch('http://localhost:3000/', { 180 | method: 'POST', 181 | headers: { 182 | 'Content-Type': 'application/json', 183 | }, 184 | body: JSON.stringify(body), 185 | }); 186 | 187 | if (folder) { 188 | handleCreateCustomEndpoint(explorer.id, folder); 189 | setOpen(true); 190 | } 191 | }; 192 | return ( 193 |
194 |
195 | 203 |
204 | 205 | 208 |
209 | 210 | 215 | 216 | 222 | Choose Your Template Files 223 | 224 | 225 |
226 | 227 | page.tsx 228 |
229 | 230 |
231 | 236 | default.tsx 237 |
238 | 239 |
240 | 245 | error.tsx 246 |
247 | 248 |
249 | 254 | layout.tsx 255 |
256 | 257 |
258 | 263 | loading.tsx 264 |
265 | 266 |
267 | 272 | notFound.tsx 273 |
274 | 275 |
276 | 281 | route.tsx 282 |
283 | 284 |
285 | 290 | template.tsx 291 |
292 | 293 | 296 |
297 |
298 |
299 | ); 300 | }; 301 | 302 | export default CustomEndpoint; 303 | -------------------------------------------------------------------------------- /src/components/left/Modal.tsx: -------------------------------------------------------------------------------- 1 | // import Box from '@mui/material/Box'; 2 | // import Button from '@mui/material/Button'; 3 | // import Typography from '@mui/material/Typography'; 4 | // import Modal from '@mui/material/Modal'; 5 | // import Checkbox from '@mui/material/Checkbox'; 6 | //import { modalLayout } from '../../utils/interfaces'; 7 | 8 | // const Modals = ({open, handleModalChange}: any) => { 9 | 10 | // const style = { 11 | // position: 'absolute' as 'absolute', 12 | // top: '50%', 13 | // left: '50%', 14 | // transform: 'translate(-50%, -50%)', 15 | // width: 500, 16 | // height: 500, 17 | // bgcolor: 'background.paper', 18 | // border: '2px solid #000', 19 | // boxShadow: 24, 20 | // p: 4, 21 | // }; 22 | 23 | // const handleClose = () => { 24 | // setSelectedItems({}) 25 | // }; 26 | 27 | 28 | // const [selectedItems, setSelectedItems] = useState({ 29 | // default: false, 30 | // error: false, 31 | // layout: false, 32 | // loading: false, 33 | // notFound: false, 34 | // route: false, 35 | // template: false, 36 | // }); 37 | 38 | 39 | // return( 40 | // 46 | // 47 | // 53 | // Choose Your Template Files 54 | // 55 | 56 | //
57 | // 62 | // default.tsx 63 | //
64 | 65 | //
66 | // 71 | // error.tsx 72 | //
73 | 74 | //
75 | // 80 | // layout.tsx 81 | //
82 | 83 | //
84 | // 89 | // loading.tsx 90 | //
91 | 92 | //
93 | // 98 | // notFound.tsx 99 | //
100 | 101 | //
102 | // 107 | // route.tsx 108 | //
109 | 110 | //
111 | // 116 | // template.tsx 117 | //
118 | 119 | // 122 | //
123 | //
124 | // ) 125 | // } 126 | 127 | // export default Modals; -------------------------------------------------------------------------------- /src/components/left/data/folderData.ts: -------------------------------------------------------------------------------- 1 | const explorer = { 2 | id: 1, 3 | name: 'NextSketch', 4 | isFolder: true, 5 | items: [ 6 | { 7 | id: 2, 8 | name: 'node_modules', 9 | isFolder: true, 10 | items: [ 11 | { 12 | id: 23, 13 | name: 'yaml', 14 | isFolder: true, 15 | items: [ 16 | { 17 | id: 124314, 18 | name: 'These files will be available for view in your Export!', 19 | isFolder: false, 20 | items: [] 21 | } 22 | ], 23 | }, 24 | { 25 | id: 24, 26 | name: 'yocto-queue', 27 | isFolder: true, 28 | items: [ 29 | { 30 | id:'111', 31 | name: 'index.d.ts', 32 | isFolder: false, 33 | items:[] 34 | }, 35 | { 36 | id: 112, 37 | name: 'index.js', 38 | isFolder: false, 39 | items:[] 40 | }, 41 | { 42 | id: 113, 43 | name: 'license', 44 | isFolder: false, 45 | items:[] 46 | }, 47 | { 48 | id: 114, 49 | name: 'package.json', 50 | isFolder: false, 51 | items:[] 52 | }, 53 | { 54 | id: 115, 55 | name: 'readme.md', 56 | isFolder: false, 57 | items:[] 58 | } 59 | ] 60 | } 61 | ], 62 | }, 63 | { 64 | id: 3, 65 | name: 'public', 66 | isFolder: true, 67 | items: [ 68 | { 69 | id: 3.33, 70 | name: 'next.svg', 71 | isFolder: false, 72 | items: [] 73 | }, 74 | { 75 | id: 3.66, 76 | name: 'vercel.svg', 77 | isFolder: false, 78 | items: [] 79 | }, 80 | ], 81 | }, 82 | { 83 | id: 4, 84 | name: 'src', 85 | isFolder: true, 86 | items: [ 87 | { 88 | id: 4.5, 89 | name: 'app', 90 | isFolder: true, 91 | items: [ 92 | { 93 | id: 5, 94 | name: 'favicon.ico', 95 | isFolder: false, 96 | items: [], 97 | }, 98 | { 99 | id: 6, 100 | name: 'globals.css', 101 | isFolder: false, 102 | items: [], 103 | }, 104 | { 105 | id: 7, 106 | name: 'layout.tsx', 107 | isFolder: false, 108 | items: [], 109 | preview: ` 110 | import React from 'react'; 111 | 112 | const Layout = () => { 113 | return ( 114 | <> 115 | {/* Your page content goes here */} 116 | 117 | ); 118 | }; 119 | 120 | export default Layout; 121 | `, 122 | }, 123 | { 124 | id: 8, 125 | name: 'page.tsx', 126 | isFolder: false, 127 | items: [], 128 | preview: ` 129 | import React from 'react'; 130 | 131 | const Page = () => { 132 | return ( 133 | <> 134 | {/* Your page content goes here */} 135 | 136 | ); 137 | }; 138 | 139 | export default Page; 140 | `, 141 | }, 142 | ], 143 | }, 144 | ], 145 | }, 146 | { 147 | id: 9, 148 | name: '.eslintrc.json', 149 | isFolder: false, 150 | items: [], 151 | }, 152 | { 153 | id: 10, 154 | name: '.gitignore', 155 | isFolder: false, 156 | items: [], 157 | }, 158 | { 159 | id: 11, 160 | name: 'next-env.d.ts', 161 | isFolder: false, 162 | items: [], 163 | }, 164 | { 165 | id: 12, 166 | name: 'next.config.js', 167 | isFolder: false, 168 | items: [], 169 | }, 170 | { 171 | id: 13, 172 | name: 'package-lock.json', 173 | isFolder: false, 174 | items: [], 175 | }, 176 | { 177 | id: 14, 178 | name: 'package.json', 179 | isFolder: false, 180 | items: [], 181 | }, 182 | { 183 | id: 15, 184 | name: 'postcss.config.js', 185 | isFolder: false, 186 | items: [], 187 | }, 188 | { 189 | id: 16, 190 | name: 'README.md', 191 | isFolder: false, 192 | items: [], 193 | }, 194 | { 195 | id: 17, 196 | name: 'tailwind.config.ts', 197 | isFolder: false, 198 | items: [], 199 | }, 200 | { 201 | id: 18, 202 | name: 'tsconfig.json', 203 | isFolder: false, 204 | items: [], 205 | }, 206 | ], 207 | }; 208 | 209 | export default explorer; 210 | -------------------------------------------------------------------------------- /src/components/left/folder.tsx: -------------------------------------------------------------------------------- 1 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 2 | import { 3 | faFolderPlus, 4 | faFolderClosed, 5 | faFolderOpen, 6 | faTrash, 7 | faFileCirclePlus, 8 | faAtom, 9 | faMinus, 10 | faSliders, 11 | } from '@fortawesome/free-solid-svg-icons'; 12 | import Modal from '@mui/material/Modal'; 13 | import Checkbox from '@mui/material/Checkbox'; 14 | import Box from '@mui/material/Box'; 15 | import Button from '@mui/material/Button'; 16 | import Typography from '@mui/material/Typography'; 17 | import React, { useContext, useEffect, useState } from 'react'; 18 | import { CodeContext, CodeSnippetContext } from '../../App'; 19 | import { FaReact } from 'react-icons/fa'; 20 | import { useCode } from '../../utils/reducer/CodeContext'; 21 | import AppContext, {AppContextType} from '../../context/AppContext'; 22 | 23 | import { modalLayout, FolderProps, Input } from '../../utils/interfaces'; 24 | import { app } from 'electron'; 25 | import { file } from 'jszip'; 26 | import Tree from '../right/Tree'; 27 | 28 | 29 | const cacheModal: string[] = []; 30 | 31 | 32 | function Folder({ 33 | handleInsertNode, 34 | handleDeleteNode, 35 | handleInputBoilerFiles, 36 | explorer, 37 | setFolder, 38 | folder, 39 | setFile, 40 | file, 41 | setPostData, 42 | postData, 43 | }: FolderProps) { 44 | const [folderIcon, setFolderIcon] = useState('▶'); 45 | const [folderLogo, setFolderLogo] = useState( 46 | 47 | ); 48 | 49 | const [componentName, setComponentName] = useContext(CodeContext); 50 | const [codeSnippet, setCodeSnippet] = useContext(CodeSnippetContext); 51 | const { 52 | tags, 53 | setTags, 54 | currentId, 55 | setCurrentId, 56 | reset, 57 | setReset, 58 | previewFolder, 59 | setPreviewFolder, 60 | }: AppContextType = useContext(AppContext)!; 61 | 62 | 63 | const [open, setOpen] = useState(false); 64 | const [expand, setExpand] = useState(false); 65 | 66 | const style = { 67 | position: 'absolute' as 'absolute', 68 | top: '50%', 69 | left: '50%', 70 | transform: 'translate(-50%, -50%)', 71 | width: 500, 72 | height: 'fit-content', 73 | bgcolor: 'background.paper', 74 | border: '2px solid #000', 75 | boxShadow: 24, 76 | color: 'black', 77 | p: 4, 78 | borderRadius: '20px', 79 | fontSize: '1.6rem' 80 | }; 81 | 82 | const [selectedItems, setSelectedItems] = useState({ 83 | default: false, 84 | error: false, 85 | layout: false, 86 | loading: false, 87 | notFound: false, 88 | route: false, 89 | template: false, 90 | page: true, 91 | }); 92 | 93 | const [showInput, setShowInput] = useState({ 94 | visible: false, 95 | isFolder: false, 96 | }); 97 | 98 | const handleClose = () => { 99 | setOpen(false); 100 | setFolder(''); 101 | setSelectedItems({ 102 | default: false, 103 | error: false, 104 | layout: false, 105 | loading: false, 106 | notFound: false, 107 | route: false, 108 | template: false, 109 | page: true, 110 | }); 111 | }; 112 | 113 | const handleNewFolder = (e?: React.MouseEvent, arg?: boolean) => { 114 | e?.stopPropagation(); 115 | setExpand(true); 116 | setFolderIcon('▼'); 117 | setFolderLogo(); 118 | 119 | setShowInput({ 120 | visible: true, 121 | isFolder: arg, 122 | }); 123 | 124 | if (arg === false) { 125 | setComponentName(); 126 | } 127 | }; 128 | 129 | const handleModalChange = async (e?: React.ChangeEvent) => { 130 | const name: string | undefined = e?.target?.name.slice(0, -4); 131 | setPostData(true); 132 | setTags([]); 133 | 134 | setSelectedItems({ 135 | ...selectedItems, 136 | [name as string]: true, 137 | }); 138 | 139 | const fileName = e?.target.name; 140 | setFile(fileName as string); 141 | 142 | if (!cacheModal.includes(fileName as string)) { 143 | cacheModal.push(fileName as string); 144 | } 145 | 146 | setComponentName(fileName); 147 | }; 148 | 149 | const retrieveCode = (e?: React.SyntheticEvent) => { 150 | //This is to avoid posting a new file every time you click it (useEffect in customEndPoint) 151 | 152 | setPreviewFolder(explorer.parent); 153 | 154 | setPostData(false); 155 | 156 | //activates the reset, it helps so the useEffect in codePreview doesn't run completely 157 | setReset(true); 158 | 159 | //data to relate to the new code snippet 160 | setComponentName(explorer.name); 161 | setCodeSnippet(explorer.preview); 162 | setCurrentId(explorer.id); 163 | 164 | //clears the tags 165 | if (explorer.tags === undefined) { 166 | setTags([]); 167 | } else { 168 | setTags(explorer.tags); 169 | } 170 | // console.log('EXPLORER TAGS', explorer.tags) 171 | // setTags(explorer.tags) 172 | }; 173 | 174 | const onAddFolder = async (e?: React.KeyboardEvent) => { 175 | if (e?.key === 'Enter' && e?.currentTarget.value) { 176 | handleInsertNode(explorer.id, e.currentTarget.value, showInput.isFolder as boolean); 177 | 178 | setFolder(e.currentTarget.value); 179 | 180 | const body = { 181 | fileName: e.currentTarget.value, 182 | folderName: explorer.name, 183 | isFolder: showInput.isFolder, 184 | }; 185 | 186 | await fetch('http://localhost:3000/', { 187 | method: 'POST', 188 | headers: { 189 | 'Content-Type': 'application/json', 190 | }, 191 | body: JSON.stringify(body), 192 | }); 193 | 194 | setShowInput({ ...showInput, visible: false }); 195 | 196 | if (showInput.isFolder) setOpen(true); 197 | } 198 | }; 199 | 200 | const handleDeleteFolder = async (e?: React.MouseEvent, arg?: boolean) => { 201 | e?.stopPropagation(); 202 | handleDeleteNode(explorer.id); 203 | setComponentName('Page'); 204 | setTags([]); 205 | 206 | await fetch('/', { 207 | method: 'Delete', 208 | headers: { 209 | 'Content-Type': 'application/json', 210 | }, 211 | body: JSON.stringify({ name: explorer.name }), 212 | }); 213 | 214 | setShowInput({ ...showInput, visible: false }); 215 | }; 216 | 217 | if (explorer.isFolder) { 218 | return ( 219 |
220 | 225 | 226 | 232 | Choose Your Template Files 233 | 234 | 235 |
236 | 237 | page.tsx 238 |
239 | 240 |
241 | 246 | default.tsx 247 |
248 | 249 |
250 | 255 | error.tsx 256 |
257 | 258 |
259 | 264 | layout.tsx 265 |
266 | 267 |
268 | 273 | loading.tsx 274 |
275 | 276 |
277 | 282 | notFound.tsx 283 |
284 | 285 |
286 | 291 | route.tsx 292 |
293 | 294 |
295 | 300 | template.tsx 301 |
302 | 303 | 306 |
307 |
308 |
{ 311 | if (!expand) { 312 | setFolderIcon('▼'); 313 | setFolderLogo(); 314 | } else { 315 | setFolderIcon('▶'); 316 | setFolderLogo(); 317 | } 318 | setExpand(!expand); 319 | }} 320 | > 321 | 322 | {folderIcon} {folderLogo} {explorer.name} 323 | 324 |
325 | {explorer.name !== 'app' && 326 | explorer.name !== 'src' && 327 | explorer.name !== 'node_modules' && 328 | explorer.name !== 'public' && 329 | explorer.name !== 'NextSketch' ? ( 330 | 338 | ) : ( 339 | '' 340 | )} 341 | 342 | {explorer.name !== 'src' && 343 | explorer.name !== 'node_modules' && 344 | explorer.name !== 'public' && 345 | explorer.name !== 'NextSketch' ? ( 346 | 349 | ) : ( 350 | '' 351 | )} 352 | 353 | {explorer.name !== 'app' && 354 | explorer.name !== 'src' && 355 | explorer.name !== 'NextSketch' ? ( 356 | 359 | ) : ( 360 | '' 361 | )} 362 |
363 |
364 | 365 |
366 | {showInput.visible && ( 367 |
368 | {showInput.isFolder ? ' 📁' : '📄'} 369 | { 375 | setShowInput({ ...showInput, visible: false }); 376 | setFolderIcon('▶'); 377 | setFolderLogo(); 378 | setExpand(false); 379 | }} 380 | style = {{backgroundColor: 'transparent', color: 'white'}} 381 | /> 382 | 383 | 384 |
385 | )} 386 | 387 | {explorer.items.map((exp: any) => { 388 | return ( 389 | 402 | ); 403 | })} 404 |
405 |
406 | ); 407 | } else if (explorer.name) { 408 | return ( 409 |
410 | {explorer.name.slice(-3) === 'tsx' ? ( 411 | 412 | ) : ( 413 | '📄' 414 | )} 415 | {explorer.name} 416 | {explorer.name === 'page.tsx' ? ( 417 | ' ' 418 | ) : ( 419 | 433 | )} 434 |
435 | ); 436 | } 437 | } 438 | 439 | export default Folder; 440 | -------------------------------------------------------------------------------- /src/components/left/hooks/use-traverse-tree.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | const useTraverseTree = () => { 3 | const insertNode = ( 4 | tree: any, 5 | folderId: number, 6 | item: string, 7 | isFolder: boolean, 8 | preview: string 9 | ) => { 10 | if (tree.id === folderId && tree.isFolder) { 11 | for (const files of tree.items) { 12 | if (files.name.toLowerCase() === item.toLowerCase()) { 13 | alert('Folder name already exists'); 14 | return a(); 15 | } 16 | 17 | if (tree.name.toLowerCase() === item.toLowerCase()) { 18 | alert( 19 | 'A nested endpoint should not have the same name as its parent' 20 | ); 21 | return a(); 22 | } 23 | } 24 | 25 | tree.items.unshift({ 26 | id: new Date().getTime(), 27 | name: item, 28 | isFolder, 29 | items: [ 30 | { 31 | id: new Date().getTime(), 32 | name: 'page.tsx', 33 | isFolder: false, 34 | items: [], 35 | }, 36 | ], 37 | preview: '', 38 | }); 39 | return tree; 40 | } 41 | let latestNode = []; 42 | latestNode = tree.items.map((ob: object) => { 43 | return insertNode(ob, folderId, item, isFolder, preview); 44 | }); 45 | 46 | return { ...tree, items: latestNode }; 47 | }; 48 | 49 | const deleteNode = (tree: any, folderId: number) => { 50 | const items = tree.items.filter((ob: any) => { 51 | return ob.id !== folderId; 52 | }); 53 | 54 | if (items.length === tree.items.length) { 55 | const temp = tree.items.map((obj) => deleteNode(obj, folderId)); 56 | return { ...tree, items: temp }; 57 | } 58 | 59 | return { ...tree, items: items }; 60 | }; 61 | 62 | const createCustomEndpoint = ( 63 | tree: any, 64 | folderId: number, 65 | item: string, 66 | isFolder: boolean 67 | ) => { 68 | let fileAlreadyExists = false; 69 | 70 | if (tree.name === 'app') { 71 | for (const files of tree.items) { 72 | if (files.name.toLowerCase() === item.toLowerCase()) { 73 | alert('Folder name already exists!'); 74 | fileAlreadyExists = true; 75 | return laura(); 76 | } 77 | } 78 | 79 | if (fileAlreadyExists === false) { 80 | tree.items.unshift({ 81 | id: new Date().getTime(), 82 | name: item, 83 | isFolder: true, 84 | items: [ 85 | { 86 | id: new Date().getTime(), 87 | name: 'page.tsx', 88 | isFolder: false, 89 | preview: ` 90 | import React from 'react'; 91 | 92 | const Page = () => { 93 | return ( 94 | <> 95 | {/* Your page content goes here */} 96 | 97 | ); 98 | }; 99 | 100 | export default Page; 101 | `, 102 | items: [], 103 | }, 104 | ], 105 | }); 106 | return tree; 107 | } 108 | } 109 | 110 | let latestNode = []; 111 | latestNode = tree.items.map((ob: object) => { 112 | return createCustomEndpoint(ob, folderId, item, isFolder); 113 | }); 114 | 115 | return { ...tree, items: latestNode }; 116 | }; 117 | // const retrieveCode = 118 | 119 | const insertBoilerFiles = ( 120 | tree: any, 121 | folderId: number, 122 | item: string, 123 | folderName: string, 124 | preview: string, 125 | parent: string, 126 | tags: [] 127 | ) => { 128 | if (tree.name === folderName) { 129 | tree.items.unshift({ 130 | id: new Date().getTime(), 131 | name: item, 132 | items: [], 133 | preview: preview, 134 | parent: folderName, 135 | tags: tags, 136 | }); 137 | return tree; 138 | } 139 | 140 | let latestNode = []; 141 | latestNode = tree.items.map((ob: object) => { 142 | return insertBoilerFiles( 143 | ob, 144 | folderId, 145 | item, 146 | folderName, 147 | preview, 148 | parent, 149 | tags 150 | ); 151 | }); 152 | 153 | return { ...tree, items: latestNode }; 154 | }; 155 | 156 | const updatePreview = ( 157 | tree: any, 158 | fileId: number, 159 | 160 | preview: string, 161 | tags: [] 162 | ) => { 163 | if (fileId == tree.id) { 164 | console.log('TAGS', tags); 165 | // console.log('TREE PREVIEW', tree.preview) 166 | // console.log('PASSED IN PREVIEW', preview) 167 | tree.preview = preview; 168 | tree.tags = tags; 169 | } 170 | 171 | let latestNode = []; 172 | latestNode = tree.items.map((ob: object) => { 173 | return updatePreview(ob, fileId, preview, tags); 174 | }); 175 | 176 | return { ...tree, items: latestNode }; 177 | }; 178 | 179 | const initialPreview = ( 180 | tree: any, 181 | folderName: string, 182 | fileName: string, 183 | preview: string, 184 | tags: [] 185 | ) => { 186 | // console.log('folderName', folderName); 187 | // console.log('fileName', fileName); 188 | if (folderName == tree.parent && fileName == tree.name) { 189 | tree.preview = preview; 190 | tree.tags = tags; 191 | } 192 | let latestNode = []; 193 | latestNode = tree.items.map((ob: object) => { 194 | return initialPreview(ob, folderName, fileName, preview, tags); 195 | }); 196 | 197 | return { ...tree, items: latestNode }; 198 | }; 199 | 200 | return { 201 | insertNode, 202 | deleteNode, 203 | createCustomEndpoint, 204 | insertBoilerFiles, 205 | updatePreview, 206 | initialPreview, 207 | }; 208 | }; 209 | 210 | export default useTraverseTree; 211 | function a() { 212 | throw new Error("Function not implemented."); 213 | } 214 | 215 | function laura() { 216 | throw new Error("Function not implemented."); 217 | } 218 | 219 | -------------------------------------------------------------------------------- /src/components/left/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | /* color: white; */ 3 | /* font-size: 18px; */ 4 | 5 | } 6 | 7 | 8 | 9 | /* Styling for the folder element */ 10 | .folder { 11 | /* background-color:#bdc3c7; */ 12 | display: flex; 13 | align-items: center; 14 | justify-content: space-between; 15 | padding: 5px; 16 | width: 90%; 17 | cursor: pointer; 18 | border-radius: 4px; 19 | font-size: 1.5rem; 20 | /* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); */ 21 | margin-top: 5%; 22 | overflow-y: scroll; 23 | opacity: 0.9; 24 | 25 | } 26 | 27 | /* Styling for the folder text and buttons */ 28 | .folder span { 29 | font-family: Arial, sans-serif; 30 | font-size: 1.5rem; 31 | font-weight: normal; 32 | color: white; 33 | 34 | } 35 | 36 | /* Styling for the buttons inside the folder */ 37 | .folder button { 38 | background: none; 39 | border: none; 40 | cursor: pointer; 41 | font-size: 16px; 42 | color: #000046; 43 | margin-left: 10px; 44 | } 45 | 46 | /* Styling for the expanded folder contents */ 47 | .folder .folder-contents { 48 | display: block; 49 | padding-left: 25px; 50 | color: white; 51 | } 52 | 53 | /* Styles for the form container */ 54 | .cursor { 55 | margin-top: 3px; 56 | /* background-color: pink; */ 57 | } 58 | 59 | 60 | /* Styles for the form element */ 61 | form { 62 | display: flex; 63 | justify-content: space-between; 64 | align-items: center; 65 | /* border: 1px solid #e0e0e0; */ 66 | border-radius: 5px; 67 | padding: 10px; 68 | width: 90%; /* Set to 75% width */ 69 | max-width: 600px; 70 | box-sizing: border-box; /* Include padding and border in the width */ 71 | box-shadow: inset 2px -3px 10px rgba(0, 0, 0, 0.2); 72 | color: white; 73 | 74 | } 75 | 76 | form:hover{ 77 | cursor: text; 78 | color: white; 79 | 80 | } 81 | 82 | /* Styles for the input field */ 83 | input { 84 | flex: 1; 85 | padding: 10px; 86 | border: 1px solid #ccc; 87 | width: 80%; 88 | border-radius: 5px; 89 | font-size: 16px; 90 | margin-right: 5px; 91 | animation: blink 1s step-end 10; /* Apply the animation */ 92 | color: white; 93 | font-size: 3rem; 94 | } 95 | 96 | /* Styles for the submit button */ 97 | button[type="submit"] { 98 | background-color: transparent; 99 | color: rgba(229, 63, 115); 100 | border: 1px solid rgba(229, 63, 115); 101 | border-radius: 5px; 102 | /* padding: 10px 15px; */ 103 | padding: 2%; 104 | font-size: 18px; 105 | cursor: pointer; 106 | transition: background-color 0.2s; 107 | margin-left: 20px; 108 | box-sizing: border-box; /* Include padding and border in the width */ 109 | } 110 | 111 | /* Hover state for the submit button */ 112 | button[type="submit"]:hover { 113 | background-color: rgba(229, 63, 115); 114 | color: white; 115 | font-weight: 700; 116 | 117 | } 118 | 119 | /* Disabled state for the submit button */ 120 | button[type="submit"]:disabled { 121 | background-color: #ccc; 122 | cursor: not-allowed; 123 | } 124 | 125 | 126 | /* Example container styles */ 127 | #tree-container { 128 | width: 200%; /* Set the container width to 100% */ 129 | max-height: 1000px; /* Set a maximum height, adjust as needed */ 130 | overflow: auto; /* Add scrollbars if the content overflows */ 131 | position: relative; /* Ensure correct positioning */ 132 | /* Add other styles as needed */ 133 | } 134 | 135 | .logo { 136 | font-family: 'Roboto Mono', monospace; 137 | } 138 | 139 | 140 | 141 | 142 | 143 | /* YourComponent.css */ 144 | 145 | /* Styles for the input container */ 146 | .input-container { 147 | position: relative; 148 | height: 100%; 149 | background-color: transparent; 150 | } 151 | 152 | .inputContainer input{ 153 | 154 | background-color: transparent; 155 | } 156 | 157 | 158 | /* Styles for the input field */ 159 | #searchInput { 160 | border: none; /* Remove the default input border */ 161 | outline: none; /* Remove the default input focus outline */ 162 | padding: 0; /* Remove any padding or margins */ 163 | width: 100%; 164 | line-height: 100%; 165 | margin: 0; 166 | font-size: 1.5rem; 167 | background-color: transparent; 168 | 169 | } 170 | 171 | .page { 172 | justify-content: space-between; 173 | padding-right: 30%; 174 | } 175 | 176 | /* Styles for the blinking cursor (initially hidden) */ 177 | .text-cursor { 178 | position: absolute; 179 | top: 0; 180 | left: 0; 181 | height: 100%; 182 | width: 2px; /* Adjust the width as needed */ 183 | animation: none; /* Blinking animation */ 184 | color: rgba(135,215,243); 185 | z-index: 2 186 | } 187 | 188 | @keyframes cursor-blink { 189 | 0%, 100% { 190 | background: transparent; /* Make the cursor disappear */ 191 | } 192 | 50% { 193 | } 194 | 195 | } 196 | 197 | 198 | 199 | #searchInput::placeholder { 200 | color: #ccc; /* Customize the placeholder text color */ 201 | z-index: 1; 202 | } 203 | 204 | #searchInput:focus + .text-cursor { 205 | animation: cursor-blink 1s infinite; /* Start blinking animation when focused */ 206 | color: rgba(135,215,243); 207 | } 208 | 209 | .buttons{ 210 | display: flex; 211 | justify-content: right; 212 | } 213 | /* YourComponent.css */ 214 | @media (max-width: 768px) { 215 | .tree-container { 216 | width: 20%; /* Change as needed for smaller screens */ 217 | } 218 | } 219 | @media (max-width: 480px) { 220 | .tree-container { 221 | width: 80%; /* Change as needed for even smaller screens */ 222 | } 223 | } 224 | 225 | /* Styles for the blinking cursor */ 226 | 227 | .folder-node { 228 | fill: blue; 229 | } 230 | 231 | 232 | .jumpBtn { 233 | transition: transform 0.2s ease; 234 | } 235 | 236 | .jumpBtn.clicked { 237 | animation: jump 0.5s ease; 238 | } 239 | 240 | @keyframes jump { 241 | 0% { 242 | transform: translateY(0); 243 | } 244 | 50% { 245 | transform: translateY(-20px); 246 | } 247 | 100% { 248 | transform: translateY(0); 249 | } 250 | }; 251 | 252 | div .buttons { 253 | font-size: 100px; 254 | } 255 | 256 | 257 | @media (min-width: 2000px) { 258 | .folder { 259 | /* Define the styles to apply when the screen width is 2000px or larger */ 260 | /* For example, you can modify the background-color and box-shadow here */ 261 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 262 | /* Add any other styles you want to change */ 263 | } 264 | } -------------------------------------------------------------------------------- /src/components/middle/DragOverlayWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Active, DragOverlay, useDndMonitor } from '@dnd-kit/core'; 2 | import { useContext, useState } from 'react'; 3 | import { StaticTagOverlay } from './StaticTag'; 4 | import AppContext from '../../context/AppContext'; 5 | import { Box } from '@mui/material'; 6 | import { NonContainerTagOverlay } from '../right/NonContainerTag'; 7 | import { ContainerTagOverlay } from '../right/ContainerTag'; 8 | 9 | const DragOverlayWrapper = () => { 10 | const { tags } = useContext(AppContext); 11 | const [draggedItem, setDraggedItem] = useState(null); 12 | 13 | useDndMonitor({ 14 | onDragStart: (event) => { 15 | setDraggedItem(event.active); 16 | }, 17 | onDragCancel: () => { 18 | setDraggedItem(null); 19 | }, 20 | onDragEnd: () => { 21 | setDraggedItem(null); 22 | }, 23 | }); 24 | 25 | if (!draggedItem) return null; 26 | 27 | let node = no drag overlay!; 28 | 29 | const isStaticTag = draggedItem?.data?.current?.isStaticTag; 30 | 31 | if (isStaticTag) { 32 | const itemName = draggedItem?.data?.current?.name; 33 | node = {itemName}; 34 | } 35 | 36 | const isNonContainerTag = draggedItem?.data?.current?.isNonContainerTag; 37 | 38 | if (isNonContainerTag) { 39 | const tagId = draggedItem?.data?.current?.tagId; 40 | const tag = tags[tags.findIndex((tag) => tag.id === tagId)]; 41 | 42 | if (!tag) node = tag not found!; 43 | else { 44 | node = ; 45 | } 46 | } 47 | 48 | const isContainerTag = draggedItem?.data?.current?.isContainerTag; 49 | 50 | if (isContainerTag) { 51 | const tagId = draggedItem?.data?.current?.tagId; 52 | const tag = tags[tags.findIndex((tag) => tag.id === tagId)]; 53 | 54 | if (!tag) node = tag not found!; 55 | else { 56 | node = ; 57 | } 58 | } 59 | 60 | return {node}; 61 | }; 62 | 63 | export default DragOverlayWrapper; 64 | -------------------------------------------------------------------------------- /src/components/middle/StaticTag.tsx: -------------------------------------------------------------------------------- 1 | import { UniqueIdentifier, useDraggable } from '@dnd-kit/core'; 2 | import { Button } from '@mui/material'; 3 | import { Tag } from '../../utils/interfaces'; 4 | 5 | /** 6 | * @description - creates a draggable item 7 | * @parent - StaticTagsContainer.tsx 8 | */ 9 | 10 | interface StaticTagProps { 11 | id: UniqueIdentifier; 12 | children: Tag; 13 | } 14 | 15 | export const StaticTag = ({ id, children }: StaticTagProps) => { 16 | const { name, container, attribute } = children; 17 | const { attributes, listeners, setNodeRef } = useDraggable({ 18 | id: id, 19 | data: { 20 | name: name, 21 | container: container, 22 | isStaticTag: true, 23 | attribute: attribute, 24 | }, 25 | }); 26 | 27 | return ( 28 | 60 | ); 61 | }; 62 | 63 | interface StaticTagOverlayProps { 64 | children: string; 65 | } 66 | 67 | export const StaticTagOverlay = ({ children }: StaticTagOverlayProps) => { 68 | return ( 69 | 93 | ); 94 | }; 95 | -------------------------------------------------------------------------------- /src/components/middle/StaticTagsContainer.tsx: -------------------------------------------------------------------------------- 1 | import { StaticTag } from './StaticTag'; 2 | import { Tag } from '../../utils/interfaces'; 3 | import { Box, Typography } from '@mui/material'; 4 | import { generateId } from '../../utils/generateId'; 5 | 6 | /** 7 | * @description - container for draggable HTML tag elements 8 | * @parent - MiddleContainer.tsx (make this) 9 | * @children - StaticTag.tsx 10 | */ 11 | 12 | const StaticTagsContainer = (): JSX.Element => { 13 | const staticTags: Tag[] = [ 14 | { 15 | id: generateId(), 16 | name: 'div', 17 | container: true, 18 | }, 19 | { 20 | id: generateId(), 21 | name: 'img', 22 | container: false, 23 | attribute: `src=' '`, 24 | }, 25 | { 26 | id: generateId(), 27 | name: 'p', 28 | container: false, 29 | }, 30 | { 31 | id: generateId(), 32 | name: 'form', 33 | container: true, 34 | }, 35 | { 36 | id: generateId(), 37 | name: 'button', 38 | container: false, 39 | }, 40 | { 41 | id: generateId(), 42 | name: 'link', 43 | container: false, 44 | attribute: `href=' '`, 45 | }, 46 | { 47 | id: generateId(), 48 | name: 'input', 49 | container: false, 50 | }, 51 | { 52 | id: generateId(), 53 | name: 'label', 54 | container: true, 55 | }, 56 | { 57 | id: generateId(), 58 | name: 'h1', 59 | container: false, 60 | }, 61 | { 62 | id: generateId(), 63 | name: 'h2', 64 | container: false, 65 | }, 66 | { 67 | id: generateId(), 68 | name: 'span', 69 | container: true, 70 | }, 71 | { 72 | id: generateId(), 73 | name: 'ordered list', 74 | container: true, 75 | }, 76 | { 77 | id: generateId(), 78 | name: 'unordered list', 79 | container: true, 80 | }, 81 | { 82 | id: generateId(), 83 | name: 'list item', 84 | container: false, 85 | }, 86 | ]; 87 | 88 | return ( 89 | 105 | 108 | Add Elements 109 | 110 | 127 | {staticTags.map((staticTag) => ( 128 | 132 | {staticTag} 133 | 134 | ))} 135 | 136 | 137 | ); 138 | }; 139 | 140 | export default StaticTagsContainer; 141 | -------------------------------------------------------------------------------- /src/components/right/CodePreview.tsx: -------------------------------------------------------------------------------- 1 | import Prism from 'prismjs'; 2 | import { Box, Typography } from '@mui/material'; 3 | import './prism/prism.css'; // Use the path to the actual Prism.css file 4 | import 'prismjs/themes/prism-okaidia.css'; //okadia theme 5 | import 'prismjs/components/prism-jsx.js'; 6 | import 'prismjs/plugins/line-numbers/prism-line-numbers.js'; 7 | import 'prismjs/plugins/line-numbers/prism-line-numbers.css'; 8 | import 'prismjs/components/prism-javascript'; 9 | import 'prismjs/plugins/match-braces/prism-match-braces.min'; 10 | import 'prismjs/plugins/match-braces/prism-match-braces.css'; 11 | import { useContext, useEffect, useState } from 'react'; 12 | import { CodeContext, CodeSnippetContext } from '../../App'; 13 | import AppContext from '../../context/AppContext'; 14 | 15 | interface CodePreviewProps { 16 | treeData: object; 17 | } 18 | 19 | declare const prettier: any; 20 | declare const prettierPlugins: any; 21 | 22 | const CodePreview = ({ treeData }: CodePreviewProps) => { 23 | const [componentName, setComponentName] = useContext(CodeContext); 24 | const [codeSnippet, setCodeSnippet] = useContext(CodeSnippetContext); 25 | const { tags, setTags, currentId, setCurrentId, reset, setReset } = 26 | useContext(AppContext); 27 | 28 | useEffect(() => { 29 | console.log('inside codepreview useefffect'); 30 | if (reset === true) { 31 | setReset(false); 32 | return; 33 | } 34 | 35 | // Generate the code snippet 36 | renderCode(componentName); 37 | Prism.highlightAll(); 38 | }, [componentName, tags]); // Re-render and update the code when componentName and tags change 39 | 40 | const addingChildrenTags = (elements: Tag[]): Tag[] => { 41 | //check if the container property is true 42 | //add a property of children 43 | const tagsCopy = structuredClone(elements); 44 | 45 | const tagsDirectory = {}; 46 | 47 | //create a dictinary of tags for easy lookup based on id 48 | tagsCopy.forEach((tag) => { 49 | tagsDirectory[tag.id] = tag; 50 | if (tag.container) { 51 | tag.children = []; 52 | } 53 | }); 54 | 55 | //map through the tags to see if they have a parent 56 | tagsCopy.map((tag) => { 57 | if (tag.parent) { 58 | const parentId = tag.parent; 59 | const parentTag = tagsDirectory[parentId]; 60 | 61 | if (parentTag) { 62 | parentTag.children.push(tag); 63 | } 64 | } 65 | }); 66 | 67 | const rootNodes = tagsCopy.filter((tag) => !tag.parent); 68 | return rootNodes; 69 | }; 70 | 71 | const childrenTags = addingChildrenTags(tags); 72 | 73 | const generateCode = (elements: Tag[]): JSX.Element => { 74 | const renderElements = elements.map((element) => { 75 | if (element.name === 'unordered list') element.name = 'ul'; 76 | if (element.name === 'ordered list') element.name = 'ol'; 77 | if (element.name === 'list item') element.name = 'li'; 78 | 79 | let tagStart = `<${element.name}`; 80 | let tagEnd = ``; 81 | 82 | if (element.attribute) { 83 | tagStart += ` ${element.attribute}`; 84 | } 85 | 86 | if (element.name === 'img' || element.name === 'link') { 87 | tagStart += ' />'; 88 | tagEnd = ''; 89 | } else { 90 | tagStart += '>'; 91 | } 92 | 93 | if (element.children) { 94 | const children = element.children; 95 | const result = children.map((child) => generateCode([child])); 96 | return `${tagStart}${result.join('')}${tagEnd}`; 97 | } else { 98 | return `${tagStart}${tagEnd}`; 99 | } 100 | }); 101 | return renderElements.join(''); 102 | }; 103 | 104 | const additional = generateCode(childrenTags); 105 | 106 | const formatCode = (code: string) => { 107 | return prettier.format(code, { 108 | parser: 'babel', 109 | plugins: prettierPlugins, 110 | jsxBracketSameLine: true, 111 | singleQuote: true, 112 | }); 113 | }; 114 | 115 | function renderCode(name: string) { 116 | if (name === undefined) return; 117 | //Check if it has end .tsx 118 | if (name.slice(-4) === '.tsx') { 119 | name = name.slice(0, -4); 120 | } 121 | // Capitalize the component name 122 | name = name.charAt(0).toUpperCase() + name.slice(1); 123 | 124 | let codeSnippet = ''; 125 | if (name === 'NotFound') { 126 | codeSnippet = ` 127 | import React from 'react'; 128 | 129 | const ${name} = () => { 130 | return ( 131 |
132 |

404 - Page Not Found

133 | ${additional} 134 |
135 | ); 136 | }; 137 | 138 | export default ${name}; 139 | `; 140 | } else { 141 | codeSnippet = `import React from 'react'; 142 | 143 | const ${name} = () => { 144 | return ( 145 | <> 146 | ${additional} 147 | 148 | ); 149 | }; 150 | 151 | export default ${name}; 152 | `; 153 | } 154 | setCodeSnippet(formatCode(codeSnippet)); 155 | } 156 | 157 | return ( 158 | <> 159 | 170 | Code Preview 171 | 192 |
193 |             {codeSnippet}
194 |           
195 | 196 |
197 |
198 | 199 | ); 200 | }; 201 | 202 | export default CodePreview; 203 | -------------------------------------------------------------------------------- /src/components/right/ContainerTag.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/material'; 2 | import { Tag } from '../../utils/interfaces'; 3 | import { useDraggable, useDroppable } from '@dnd-kit/core'; 4 | import { Item, NonContainerTag } from './NonContainerTag'; 5 | import { useContext } from 'react'; 6 | import AppContext from '../../context/AppContext'; 7 | 8 | interface ContainerTagProps { 9 | tag: Tag; 10 | } 11 | 12 | export const ContainerTag = ({ tag }: ContainerTagProps) => { 13 | const { tags } = useContext(AppContext); 14 | 15 | const childrenTags = tags.filter((prev) => prev.parent === tag.id); 16 | 17 | // console.log('tags', tags); 18 | // console.log('childrenTags', childrenTags); 19 | 20 | const topArea = useDroppable({ 21 | id: tag.id + '-container-top', 22 | data: { 23 | tagId: tag.id, 24 | isTopAreaContainerTag: true, 25 | }, 26 | }); 27 | 28 | const middleArea = useDroppable({ 29 | id: tag.id + '-container-middle', 30 | data: { 31 | tagId: tag.id, 32 | isMiddleAreaContainerTag: true, 33 | }, 34 | }); 35 | 36 | const bottomArea = useDroppable({ 37 | id: tag.id + '-container-bottom', 38 | data: { 39 | tagId: tag.id, 40 | isBottomAreaContainerTag: true, 41 | }, 42 | }); 43 | 44 | const draggable = useDraggable({ 45 | id: tag.id + '-container-drag-handler', 46 | data: { 47 | tagId: tag.id, 48 | isContainerTag: true, 49 | }, 50 | }); 51 | 52 | if (draggable.isDragging) return null; 53 | 54 | return ( 55 | 77 | 88 | 99 | 110 | {topArea.isOver && ( 111 | 120 | )} 121 | {tag.name} 122 | {childrenTags.map((childTag) => { 123 | if (childTag.container) { 124 | return ; 125 | } 126 | return ; 127 | })} 128 | {middleArea.isOver && ( 129 | 138 | )} 139 | {bottomArea.isOver && ( 140 | 149 | )} 150 | 151 | ); 152 | }; 153 | 154 | interface ContainerTagOverlayProps { 155 | tag: Tag; 156 | } 157 | 158 | export const ContainerTagOverlay = ({ tag }: ContainerTagOverlayProps) => { 159 | const { tags } = useContext(AppContext); 160 | 161 | const childrenTags = tags.filter((prev) => prev.parent === tag.id); 162 | 163 | return ( 164 | 182 | {tag.name} 183 | {childrenTags.map((childTag) => { 184 | if (childTag.container) { 185 | return ; 186 | } 187 | return ; 188 | })} 189 | 190 | ); 191 | }; 192 | -------------------------------------------------------------------------------- /src/components/right/DisplayContainer.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Typography } from '@mui/material'; 2 | import { DragEndEvent, useDndMonitor, useDroppable } from '@dnd-kit/core'; 3 | import { useContext } from 'react'; 4 | import AppContext from '../../context/AppContext'; 5 | import { Tag } from '../../utils/interfaces'; 6 | import { NonContainerTag } from './NonContainerTag'; 7 | import { ContainerTag } from './ContainerTag'; 8 | 9 | /** 10 | * @description - container for displayed tag elements 11 | * @parent - App.tsx 12 | * @children - ContainerTag.tsx, NonContainerTag.tsx 13 | */ 14 | 15 | const DisplayContainer = () => { 16 | const { tags, setTags, setUpdate } = useContext(AppContext); 17 | 18 | const tagsWithoutParents = tags.filter((prev) => !prev.parent); 19 | 20 | const { setNodeRef, isOver } = useDroppable({ 21 | id: 'display-container-drop-area', 22 | data: { 23 | isDisplayContainerDropArea: true, 24 | }, 25 | }); 26 | 27 | useDndMonitor({ 28 | onDragEnd: async (event: DragEndEvent) => { 29 | console.log('drag end event', event); 30 | const { active, over } = event; 31 | 32 | const isStaticTag = active.data?.current?.isStaticTag; 33 | const isDroppingOverDisplayContainerDropArea = 34 | over?.data?.current?.isDisplayContainerDropArea; 35 | 36 | const droppingStaticTagOverDisplayContainerDropArea = 37 | isStaticTag && isDroppingOverDisplayContainerDropArea; 38 | 39 | // scenario 1: dropping static tag over display container drop area 40 | if (droppingStaticTagOverDisplayContainerDropArea) { 41 | const newTag: Tag = { 42 | id: active.id, 43 | name: active.data.current?.name, 44 | container: active.data.current?.container, 45 | attribute: active.data.current?.attribute, 46 | }; 47 | await setTags([...tags, newTag]); 48 | setUpdate(true); 49 | return; 50 | } 51 | 52 | const isDroppingOverNonContainerTagTopHalf = 53 | over?.data?.current?.isTopHalfNonContainerTag; 54 | const isDroppingOverNonContainerTagBottomHalf = 55 | over?.data?.current?.isBottomHalfNonContainerTag; 56 | 57 | const isDroppingOverNonContainerTag = 58 | isDroppingOverNonContainerTagTopHalf || 59 | isDroppingOverNonContainerTagBottomHalf; 60 | 61 | const isDroppingOverContainerTagTopArea = 62 | over?.data?.current?.isTopAreaContainerTag; 63 | const isDroppingOverContainerTagMiddleArea = 64 | over?.data?.current?.isMiddleAreaContainerTag; 65 | const isDroppingOverContainerTagBottomArea = 66 | over?.data?.current?.isBottomAreaContainerTag; 67 | 68 | const isDroppingOverContainerTag = 69 | isDroppingOverContainerTagTopArea || 70 | isDroppingOverContainerTagMiddleArea || 71 | isDroppingOverContainerTagBottomArea; 72 | 73 | const droppingStaticTagOverNonContainerTagOrContainerTag = 74 | isStaticTag && 75 | (isDroppingOverNonContainerTag || isDroppingOverContainerTag); 76 | 77 | // scenario 2: dropping static tag over a container or non container tag (either above or below it) 78 | if (droppingStaticTagOverNonContainerTagOrContainerTag) { 79 | const newTag: Tag = { 80 | id: active.id, 81 | name: active.data.current?.name, 82 | container: active.data.current?.container, 83 | attribute: active.data.current?.attribute, 84 | }; 85 | 86 | const overId = over.data?.current?.tagId; 87 | 88 | const overTagIndex = tags.findIndex((tag) => tag.id === overId); 89 | if (overTagIndex === -1) { 90 | throw new Error('tag not found'); 91 | } 92 | 93 | let indexForNewTag = overTagIndex; 94 | if ( 95 | isDroppingOverNonContainerTagBottomHalf || 96 | isDroppingOverContainerTagBottomArea 97 | ) { 98 | indexForNewTag = overTagIndex + 1; 99 | } 100 | // add tag 101 | await setTags((prev) => { 102 | const newTags = [...prev]; 103 | newTags.splice(indexForNewTag, 0, newTag); 104 | return newTags; 105 | }); 106 | 107 | setUpdate(true); 108 | } 109 | 110 | const isDraggingNonContainerTag = 111 | active?.data?.current?.isNonContainerTag; 112 | const isDraggingContainerTag = active?.data?.current?.isContainerTag; 113 | 114 | const isDraggingNonContainerTagOrContainerTag = 115 | isDraggingNonContainerTag || isDraggingContainerTag; 116 | 117 | const draggingNonContainerTagOrContainerTagOverAnotherNonContainerTagOrContainerTag = 118 | isDraggingNonContainerTagOrContainerTag && 119 | (isDroppingOverNonContainerTag || isDroppingOverContainerTag); 120 | 121 | // scenario 3: dragging container or non container tag over another container or non container tag (placement of container or non container tags once its in the display drop area) 122 | if ( 123 | draggingNonContainerTagOrContainerTagOverAnotherNonContainerTagOrContainerTag 124 | ) { 125 | const activeId = active.data?.current?.tagId; 126 | const overId = over.data?.current?.tagId; 127 | 128 | const activeTagIndex = tags.findIndex((tag) => tag.id === activeId); 129 | const overTagIndex = tags.findIndex((tag) => tag.id === overId); 130 | 131 | if (activeTagIndex === -1 || overTagIndex === -1) { 132 | throw new Error('tag not found'); 133 | } 134 | 135 | const activeTag = { ...tags[activeTagIndex] }; 136 | 137 | // remove tag 138 | await setTags((prev) => prev.filter((tag) => tag.id !== activeId)); 139 | 140 | // scenario 4: dragging container or non container tag into a container tag 141 | if (isDroppingOverContainerTagMiddleArea) { 142 | activeTag.parent = overId; 143 | } 144 | 145 | let indexForNewTag = overTagIndex; 146 | 147 | if (isDroppingOverContainerTagTopArea) { 148 | activeTag.parent = false; 149 | } 150 | 151 | if ( 152 | isDroppingOverNonContainerTagBottomHalf || 153 | isDroppingOverContainerTagBottomArea 154 | ) { 155 | indexForNewTag = overTagIndex + 1; 156 | activeTag.parent = false; 157 | } 158 | 159 | // add tag 160 | await setTags((prev) => { 161 | const newTags = [...prev]; 162 | newTags.splice(indexForNewTag, 0, activeTag); 163 | return newTags; 164 | }); 165 | setUpdate(true); 166 | } 167 | 168 | // scenario 5: dropping static tag into container tag middle area 169 | const droppingStaticTagOverContainerTagMiddleArea = 170 | isStaticTag && isDroppingOverContainerTagMiddleArea; 171 | 172 | if (droppingStaticTagOverContainerTagMiddleArea) { 173 | const newTag: Tag = { 174 | id: active.id, 175 | name: active.data.current?.name, 176 | container: active.data.current?.container, 177 | attribute: active.data.current?.attribute, 178 | parent: over?.data.current?.tagId, 179 | }; 180 | await setTags([...tags, newTag]); 181 | setUpdate(true); 182 | return; 183 | } 184 | }, 185 | }); 186 | 187 | return ( 188 | 189 | 193 | My Page 194 | 195 | 212 | {!isOver && tags.length === 0 && ( 213 | 222 | Drop Here 223 | 224 | )} 225 | 226 | {tags.length > 0 && 227 | tagsWithoutParents.map((tag) => { 228 | if (tag.container) { 229 | return ; 230 | } 231 | return ; 232 | })} 233 | 234 | 235 | ); 236 | }; 237 | 238 | export default DisplayContainer; 239 | -------------------------------------------------------------------------------- /src/components/right/ExportButton.tsx: -------------------------------------------------------------------------------- 1 | import {Button} from '@mui/material' 2 | 3 | const ExportButton = () => { 4 | async function handleClick() { 5 | try { 6 | const response = await fetch('http://localhost:3000/export'); 7 | 8 | if (!response.ok) { 9 | // Handle errors, e.g., show an error message 10 | console.error('Error exporting folder:', response.statusText); 11 | return; 12 | } 13 | 14 | const blob = await response.blob(); 15 | // console.log(blob); 16 | const url = window.URL.createObjectURL(blob); 17 | 18 | const a = document.createElement('a'); 19 | a.href = url; 20 | a.download = 'NextSketch.zip'; 21 | 22 | // Trigger a click event on the invisible "a" element to prompt the download 23 | a.click(); 24 | 25 | window.URL.revokeObjectURL(url); 26 | } catch (error) { 27 | console.error('An error occurred:', error); 28 | } 29 | } 30 | 31 | return ( 32 | 48 | ); 49 | }; 50 | 51 | export default ExportButton; 52 | -------------------------------------------------------------------------------- /src/components/right/NonContainerTag.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import { Tag } from '../../utils/interfaces'; 3 | import { useDraggable, useDroppable } from '@dnd-kit/core'; 4 | 5 | interface ItemProps { 6 | children: string; 7 | } 8 | 9 | export const Item = ({ children }: ItemProps) => { 10 | return {children}; 11 | }; 12 | 13 | interface NonContainerTagProps { 14 | tag: Tag; 15 | } 16 | 17 | export const NonContainerTag = ({ tag }: NonContainerTagProps) => { 18 | const topHalf = useDroppable({ 19 | id: tag.id + '-item-top', 20 | data: { 21 | tagId: tag.id, 22 | isTopHalfNonContainerTag: true, 23 | }, 24 | }); 25 | 26 | const bottomHalf = useDroppable({ 27 | id: tag.id + '-item-bottom', 28 | data: { 29 | tagId: tag.id, 30 | isBottomHalfNonContainerTag: true, 31 | }, 32 | }); 33 | 34 | const draggable = useDraggable({ 35 | id: tag.id + '-item-drag-handler', 36 | data: { 37 | tagId: tag.id, 38 | isNonContainerTag: true, 39 | }, 40 | }); 41 | 42 | if (draggable.isDragging) return null; 43 | 44 | return ( 45 | 67 | 76 | 77 | 86 | 87 | {topHalf.isOver && ( 88 | 97 | )} 98 | {tag.name} 99 | {bottomHalf.isOver && ( 100 | 109 | )} 110 | 111 | ); 112 | }; 113 | 114 | interface NonContainerTagOverlay { 115 | tag: Tag; 116 | } 117 | 118 | export const NonContainerTagOverlay = ({ tag }: NonContainerTagOverlay) => { 119 | return ( 120 | 133 | {tag.name} 134 | 135 | ); 136 | }; 137 | -------------------------------------------------------------------------------- /src/components/right/Tree.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { useRef, useEffect, useState, useCallback } from 'react'; 3 | import { 4 | select, 5 | tree, 6 | zoom, 7 | hierarchy, 8 | HierarchyPointNode, 9 | linkHorizontal, 10 | } from 'd3'; 11 | 12 | const Tree = ({ srcApp }) => { 13 | const svgRef = useRef(null); 14 | const [width, setWidth] = useState(0); 15 | const [height, setHeight] = useState(0); 16 | const [resetView, setResetView] = useState(false); 17 | const [position, setPosition] = useState({ x: 0, y: 0 }); 18 | 19 | const createTree = useCallback((data) => { 20 | if (data.isFolder) { 21 | return { 22 | name: data.name, 23 | isFolder: data.isFolder, 24 | children: data.items.map((item) => createTree(item)), 25 | }; 26 | } else { 27 | return { name: data.name }; 28 | } 29 | }, []); 30 | 31 | const setTreeNodePositions = useCallback((node) => { 32 | // let x = 0; 33 | // const y = 0; 34 | let { x, y } = position; 35 | 36 | if (node.children) { 37 | // Adjust the x position based on the children 38 | node.children.forEach((child) => { 39 | setTreeNodePositions(child); 40 | setPosition((prevPosition) => ({ 41 | ...prevPosition, 42 | x: prevPosition.x + child.x, 43 | })); 44 | }); 45 | x /= node.children.length; 46 | } 47 | 48 | // Update the x and y positions for the current node 49 | node.x = x; 50 | node.y = y; 51 | 52 | return node; 53 | }, []); 54 | 55 | useEffect(() => { 56 | const handleResize = () => { 57 | const maxWidth = window.innerWidth - 20; 58 | const maxHeight = window.innerHeight - 20; 59 | const newWidth = Math.min(maxWidth, 800); 60 | const newHeight = Math.min(maxHeight, 700); 61 | setWidth(newWidth); 62 | setHeight(newHeight); 63 | }; 64 | 65 | window.addEventListener('resize', handleResize); 66 | handleResize(); 67 | return () => window.removeEventListener('resize', handleResize); 68 | }, []); 69 | 70 | useEffect(() => { 71 | const svg = select(svgRef.current); 72 | svg.selectAll('*').remove(); 73 | 74 | const root = hierarchy(createTree(srcApp)); 75 | 76 | // Set the initial positions of tree nodes 77 | setTreeNodePositions(root); 78 | 79 | const treeLayout = tree().size([height, width]); 80 | 81 | const treeData: HierarchyPointNode = treeLayout(root); 82 | 83 | const pathGenerator = linkHorizontal() 84 | .x((d) => (d as any).y + 40) 85 | .y((d) => (d as any).x); 86 | 87 | // const pathGenerator = (d: any) => { 88 | // return `M${d.source.y + 40},${d.source.x}L${d.target.y + 40},${ 89 | // d.target.x 90 | // }`; 91 | // }; 92 | const g = svg.append('g'); 93 | 94 | g.selectAll('path') 95 | .data(treeData.links()) 96 | .enter() 97 | .append('path') 98 | .attr('stroke', 'white') 99 | .attr('fill', 'none') 100 | .attr('opacity', 1) 101 | .attr('d', pathGenerator); 102 | 103 | const nodes = g 104 | .selectAll('g') 105 | .data(treeData.descendants()) 106 | .enter() 107 | .append('g') 108 | .attr('transform', (d) => `translate(${d.y + 40},${d.x})`); 109 | 110 | nodes 111 | .append('circle') 112 | .attr('r', 5) 113 | .style('fill', (d) => (d.data.isFolder ? 'yellow' : 'white')); // Change node fill color based on isFolder property 114 | 115 | nodes 116 | .append('text') 117 | .text((d) => d.data.name) 118 | .attr('dy', 20) 119 | .attr('dx', -20) 120 | .attr('text-anchor', 'middle') 121 | .attr('fill', (d) => (d.data.isFolder ? 'yellow' : '#bdbdbd')) 122 | .style('font-size', '1.00rem'); 123 | 124 | const zoomBehavior = zoom() 125 | .scaleExtent([0.5, 10]) // Set the zoom scale extent 126 | .on('zoom', (event) => { 127 | g.attr('transform', event.transform); // Apply the zoom transform to the entire tree 128 | }); 129 | 130 | svg.call(zoomBehavior); 131 | }, [width, height, srcApp, resetView, createTree, setTreeNodePositions]); 132 | 133 | const treeStyles = { 134 | height: '100%', 135 | width: '100%', 136 | // overflowY:'hidden', 137 | }; 138 | 139 | return ( 140 |
141 | 158 | 159 | 160 |
161 | ); 162 | }; 163 | 164 | export default Tree; 165 | -------------------------------------------------------------------------------- /src/components/right/prism/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.29.0 2 | https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+jsx+tsx+typescript */ 3 | 4 | 5 | code[class*='language-'], 6 | pre[class*='language-'] { 7 | color: #f8f8f2; 8 | background: 0 0; 9 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 10 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 11 | font-size: 1em; 12 | text-align: left; 13 | white-space: pre; 14 | word-spacing: normal; 15 | word-break: normal; 16 | word-wrap: normal; 17 | line-height: 1.5; 18 | -moz-tab-size: 4; 19 | -o-tab-size: 4; 20 | tab-size: 4; 21 | -webkit-hyphens: none; 22 | -moz-hyphens: none; 23 | -ms-hyphens: none; 24 | hyphens: none; 25 | } 26 | pre[class*='language-'] { 27 | padding: 1em; 28 | margin: 0.5em 0; 29 | overflow: auto; 30 | border-radius: 0.3em; 31 | } 32 | :not(pre) > code[class*='language-'], 33 | pre[class*='language-'] { 34 | background: #272822; 35 | } 36 | :not(pre) > code[class*='language-'] { 37 | padding: 0.1em; 38 | border-radius: 0.3em; 39 | white-space: normal; 40 | } 41 | .token.cdata, 42 | .token.comment, 43 | .token.doctype, 44 | .token.prolog { 45 | color: #8292a2; 46 | } 47 | .token.punctuation { 48 | color: #f8f8f2; 49 | } 50 | .token.namespace { 51 | opacity: 0.7; 52 | } 53 | .token.constant, 54 | .token.deleted, 55 | .token.property, 56 | .token.symbol, 57 | .token.tag { 58 | color: #f92672; 59 | } 60 | .token.boolean, 61 | .token.number { 62 | color: #ae81ff; 63 | } 64 | .token.attr-name, 65 | .token.builtin, 66 | .token.char, 67 | .token.inserted, 68 | .token.selector, 69 | .token.string { 70 | color: #a6e22e; 71 | } 72 | .language-css .token.string, 73 | .style .token.string, 74 | .token.entity, 75 | .token.operator, 76 | .token.url, 77 | .token.variable { 78 | color: #f8f8f2; 79 | } 80 | .token.atrule, 81 | .token.attr-value, 82 | .token.class-name, 83 | .token.function { 84 | color: #e6db74; 85 | } 86 | .token.keyword { 87 | color: #66d9ef; 88 | } 89 | .token.important, 90 | .token.regex { 91 | color: #fd971f; 92 | } 93 | .token.bold, 94 | .token.important { 95 | font-weight: 700; 96 | } 97 | .token.italic { 98 | font-style: italic; 99 | } 100 | .token.entity { 101 | cursor: help; 102 | } 103 | -------------------------------------------------------------------------------- /src/context/AppContext.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch, SetStateAction, createContext } from 'react'; 2 | import { Tag } from '../utils/interfaces'; 3 | 4 | export interface AppContextType { 5 | componentName: string; 6 | setComponentName: Dispatch>; 7 | codeSnippet: string; 8 | setCodeSnippet: Dispatch>; 9 | tags: Tag[]; 10 | setTags: Dispatch>; 11 | currentId: number; 12 | setCurrentId: Dispatch>; 13 | update: boolean; 14 | setUpdate: Dispatch>; 15 | reset: boolean; 16 | setReset: Dispatch>; 17 | previewFolder: string; 18 | setPreviewFolder: Dispatch>; 19 | currentParent: string; 20 | setCurrentParent: Dispatch>; 21 | } 22 | 23 | export default createContext(null); 24 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client'; 2 | import App from './App'; 3 | 4 | ReactDOM.createRoot(document.getElementById('root')!).render(); 5 | -------------------------------------------------------------------------------- /src/utils/generateId.ts: -------------------------------------------------------------------------------- 1 | export const generateId = (() => { 2 | let id = 1; 3 | return () => id++; 4 | })(); 5 | -------------------------------------------------------------------------------- /src/utils/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { UniqueIdentifier } from "@dnd-kit/core"; 2 | import explorer from '../components/left/data/folderData'; 3 | import React, { SetStateAction } from "react"; 4 | 5 | export interface Tag { 6 | id: UniqueIdentifier; 7 | name: string; 8 | container: boolean; 9 | parent?: UniqueIdentifier | undefined | boolean; 10 | attribute?: string 11 | } 12 | 13 | export interface Elements { 14 | [key: string]: Tag; 15 | } 16 | 17 | 18 | export interface ContextTypes { 19 | tags: Tag[], 20 | } 21 | 22 | export interface ComponentNameType { 23 | componentName: string; 24 | setComponentName: React.Dispatch>; 25 | } 26 | 27 | export interface CodeSnippetType { 28 | codeSnippet: string; 29 | setCodeSnippet: React.Dispatch>; 30 | } 31 | 32 | 33 | export interface FolderProps{ 34 | handleInsertNode: (folderId: number, item: string, isFolder: boolean, preview?: string) => void, 35 | handleDeleteNode: (folderId?: number) => void, 36 | handleInputBoilerFiles: (folderId: number, item: string, folderName: string, preview?: string) => typeof explorer | void, 37 | explorer: typeof explorer, 38 | setFolder: React.Dispatch>, 39 | folder: string 40 | setFile: React.Dispatch>, 41 | file: string, 42 | setPostData: React.Dispatch>, 43 | postData: boolean 44 | } 45 | 46 | export interface CustomEndpointProps{ 47 | handleCreateCustomEndpoint: (folderId: number, item: string, isFolder?: boolean) => typeof explorer | void, 48 | handleInputBoilerFiles: (folderId: number, item: string, folderName: string, preview?: string) => typeof explorer | void, 49 | handleUpdatePreview: (fileId: number, preview: string, tags: Tag[]) => typeof explorer | void, 50 | handleInitialPreview: (folderName: string, fileName: string, preview: string, tags: Tag[]) => typeof explorer | void, 51 | explorer: typeof explorer, 52 | setFolder: React.Dispatch>, 53 | folder: string 54 | setFile: React.Dispatch>, 55 | file: string, 56 | setPostData: React.Dispatch>, 57 | postData: boolean 58 | } 59 | 60 | export interface Input { 61 | visible: boolean | undefined | null; 62 | isFolder: boolean | undefined | null; 63 | } 64 | 65 | 66 | export interface modalLayout { 67 | default: boolean; 68 | error: boolean; 69 | layout: boolean; 70 | loading: boolean; 71 | notFound: boolean; 72 | route: boolean; 73 | template: boolean; 74 | page: boolean 75 | } 76 | 77 | export interface RenderCodeProps { 78 | elements: Tag[]; 79 | } 80 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: { 9 | colors: { 10 | "mainBackgroundColor": '#edede9', 11 | "columnBackgroundColor": '#d6ccc2' 12 | } 13 | }, 14 | }, 15 | plugins: [], 16 | } 17 | 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | /* Projects */ 5 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 6 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 7 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 8 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 9 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 10 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 11 | /* Language and Environment */ 12 | "target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 13 | "lib": [ 14 | "ES2020", 15 | "DOM", 16 | "DOM.Iterable" 17 | ], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 18 | "jsx": "react-jsxdev", /* Specify what JSX code is generated. */ 19 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 20 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 21 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 22 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 23 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 24 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 25 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 26 | "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 27 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 28 | /* Modules */ 29 | "module": "ESNext", /* Specify what module code is generated. */ 30 | // "rootDir": "./", /* Specify the root folder within your source files. */ 31 | "moduleResolution": "bundler", /* Specify how TypeScript looks up a file from a given module specifier. */ 32 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 33 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 34 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 35 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 36 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 37 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 38 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 39 | "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 40 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 41 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 42 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 43 | "resolveJsonModule": true, /* Enable importing .json files. */ 44 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 45 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | /* Emit */ 51 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 52 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 53 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 54 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 55 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 56 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 57 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 58 | "removeComments": true, /* Disable emitting comments. */ 59 | "noEmit": true, /* Disable emitting files from a compilation. */ 60 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 61 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 62 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 63 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 64 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 65 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 66 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 67 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 68 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 69 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 70 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 71 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 72 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 73 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 74 | /* Interop Constraints */ 75 | "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 76 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 77 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 78 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 79 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 80 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 81 | /* Type Checking */ 82 | "strict": false, /* Enable all strict type-checking options. */ 83 | "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 84 | "strictNullChecks": false, /* When type checking, take into account 'null' and 'undefined'. */ 85 | "strictFunctionTypes": false, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 86 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 87 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 88 | "noImplicitThis": false, /* Enable error reporting when 'this' is given the type 'any'. */ 89 | "useUnknownInCatchVariables": false, /* Default catch clause variables as 'unknown' instead of 'any'. */ 90 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 91 | "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 92 | "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 93 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 94 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 95 | "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 96 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 97 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 98 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 99 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 100 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 101 | /* Completeness */ 102 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 103 | "skipLibCheck": true, /* Skip type checking all .d.ts files. */ 104 | "paths": { 105 | "@mui/material": [ 106 | "./node_modules/@mui/joy" /*To compile the icons from material ui */ 107 | ] 108 | } 109 | }, 110 | "include": [ 111 | "src", 112 | "electron", 113 | "ExportFolder", 114 | ], 115 | "references": [ 116 | { 117 | "path": "./tsconfig.node.json" 118 | } 119 | ] 120 | } -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "./server/server.js", 6 | "use": "@vercel/node" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "/" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import electron from 'vite-plugin-electron/simple'; 3 | import react from '@vitejs/plugin-react'; 4 | import tsconfigPaths from 'vite-tsconfig-paths'; 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | react(), 9 | tsconfigPaths(), 10 | electron({ 11 | main: { 12 | entry: 'electron/main.ts', 13 | }, 14 | }), 15 | ], 16 | 17 | build: { 18 | outDir: 'dist-electron', 19 | }, 20 | }); 21 | --------------------------------------------------------------------------------