├── .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 | 
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 |
29 |
30 |
31 |
32 |
40 |
41 |
42 |
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 |
29 |
30 |
31 |
32 |
40 |
41 |
42 |
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 |
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 = `${element.name}>`;
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 |
--------------------------------------------------------------------------------