├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── Dockerfile ├── README.md ├── build ├── icon.icns └── icon.ico ├── favicon.ico ├── index.html ├── jest.config.js ├── package.json ├── scrapcode.txt ├── server ├── controllers │ ├── accountController.js │ ├── authController.js │ ├── cookieController.js │ ├── oAuthController.js │ └── projectController.js ├── models │ ├── accountModels.js │ └── projectModels.js ├── routes │ ├── accountRouter.js │ └── projectRouter.js └── server.js ├── src ├── App.vue ├── assets │ ├── 3.png │ ├── ComponentMp4.mp4 │ ├── ModalMp4.mp4 │ ├── PreVueDemo.gif │ ├── PreVueDemo.mp4 │ ├── PreVueDemo2.mp4 │ ├── PreVueExportDemo.gif │ ├── SaveMp4.mp4 │ ├── april-photo.jpg │ ├── background.jpg │ ├── button.svg │ ├── cole-photo.jpg │ ├── componentdisplay.png │ ├── form.svg │ ├── github-icon-white.svg │ ├── homeview.png │ ├── ilay-photo.jpg │ ├── img.png │ ├── img.svg │ ├── input.svg │ ├── jason-photo.jpg │ ├── link.svg │ ├── linkedin-svg.svg │ ├── list-item.svg │ ├── list-ol.svg │ ├── list-ul.svg │ ├── logo.png │ ├── logo.svg │ ├── modal-image.png │ ├── nathan-photo.jpg │ ├── navbar.png │ ├── navbar.svg │ ├── new-banner.png │ ├── newcomp.png │ ├── newcompvue.png │ ├── p1.png │ ├── paragraph.svg │ ├── prevue-large-green-bottom.png │ ├── prevue-large-green.png │ ├── prevue-large.png │ ├── prevue-logo.png │ ├── prevue-recording.gif │ ├── prevue-recording.mp4 │ ├── prevue.png │ ├── prevue_color_white.png │ ├── pvv.png │ ├── robert-photo.jpeg │ ├── routecreator.png │ ├── sean-photo.jpeg │ ├── tree-demo.png │ ├── treeview.png │ ├── viewcreator.png │ └── zach-photo.jpeg ├── components │ ├── ChildrenMultiselect.vue │ ├── Component.vue │ ├── ComponentDisplay.vue │ ├── ExportProjectComponent.vue │ ├── HomeQueue.vue │ ├── HomeSidebar.vue │ ├── Icons.vue │ ├── LogOutComponent.vue │ ├── Modal │ │ ├── ComponentCodeDisplay.vue │ │ ├── EditQueue.vue │ │ ├── EditSidebar.vue │ │ └── Modal.vue │ ├── NavBar.vue │ ├── NewProjectComponent.vue │ ├── OpenProjectComponent.vue │ ├── ProjectTabs.vue │ ├── RouteDisplay.vue │ ├── Routes.vue │ ├── SaveProjectComponent.vue │ └── TreeGraph.vue ├── main.ts ├── router.ts ├── store │ ├── actions.ts │ ├── index.ts │ ├── mutations.ts │ ├── state │ │ ├── htmlElementMap.ts │ │ ├── icons.ts │ │ └── stateIndex.ts │ └── storeTypes.ts ├── types.ts └── views │ ├── HomeView.vue │ ├── SplashView.vue │ └── TreeView.vue ├── tests ├── integration │ └── supertest.spec.js └── unit │ ├── .eslintrc.js │ ├── App.spec.js │ ├── ChildrenMultiselect.spec.js │ ├── Component.spec.js │ ├── ComponentDisplay.spec.js │ ├── HomeQueue.spec.js │ ├── HomeSidebar.spec.js │ ├── Icons.spec.js │ ├── RouteDisplay.spec.js │ ├── TreeGraph.spec.js │ └── __snapshots__ │ └── App.spec.js.snap ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | es2021: true, 5 | node: true 6 | }, 7 | extends: [ 8 | 'plugin:vue/essential', 9 | 'plugin:prettier/recommended', 10 | '@vue/prettier', 11 | 'eslint:recommended', 12 | 'plugin:@typescript-eslint/recommended' 13 | ], 14 | parser: 'vue-eslint-parser', 15 | plugins: ['@typescript-eslint, vue'], 16 | rules: { 17 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn', 18 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # dotenv environment variables files (local env files) 6 | .env.local 7 | .env.*.local 8 | .env 9 | .env.test 10 | 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw* 26 | 27 | 28 | package-lock.json 29 | # Elastic Beanstalk Files 30 | .elasticbeanstalk/* 31 | !.elasticbeanstalk/*.cfg.yml 32 | !.elasticbeanstalk/*.global.yml 33 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true 3 | }; 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # base image that provides runtime environment for the application 2 | FROM node:16.13 3 | # where the application code will be copied to in the docker container 4 | WORKDIR /usr/src/app 5 | # copies all files from the current directory (where the Dockerfile is located) to the working directory in the docker image (which we set on line 4) 6 | COPY . . 7 | RUN npm install 8 | RUN npm run build 9 | # Exposes port 4173 to the host machine, so that it can access the Node.js application running inside the Docker container 10 | EXPOSE 8080 11 | # Specifies the command to run when the Docker container starts 12 | ENTRYPOINT npm run server 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | --- 6 | 7 |

8 | 9 |

PreVue

10 |

11 | 12 |

13 | All-in-One Prototyping Tool 14 | For Vue Developers 15 |

16 | 17 |

18 | From Component Architecture to Code Exporting 19 |

20 | 21 | 24 | 25 | PreVue allows users to conceptualize and visualize component architecture by making it possible to : 26 | 27 | - Build components, visualize UI and preview the associated code 28 | - Set up different routes and views for each project 29 | - Establish parent-child component relationships 30 | - View application hierarchy in tree format 31 | - Save and open projects that are currently in progress, ensuring that completed work is not lost and can be revisited at any time 32 | - Export component architecture as a Vue application created with default Vite settings 33 | 34 | Use PreVue to create projects in single sessions or sign in with GitHub to save projects and update them at your convenience! 35 | 36 | 39 | 40 | ## Getting Started 41 | 42 | --- 43 | 44 | ### Adding Views 45 | 46 | - Select an existing view from the View Creator dropdown, or enter a new view name, then select your custom view from the View Creator dropdown 47 | - Any components created on a given view will be automatically saved to that specific view 48 | - See your application’s hierarchy by clicking the ‘Tree’ icon in the navigation bar 49 | 50 |

51 | 52 |

53 | 54 |
Tree View of Application Architecture
55 | 56 |

57 | 58 |

59 | 60 | ### Adding Components 61 | 62 | - Enter a component name in the Component Creator field and select HTML elements 63 | - Clicked elements will be shown in the right sidebar -- drag elements to change their order 64 | - Once you're satisfied, click ‘add component’ button and it will show up in the working area -- resize and move components to fit the design you have in mind 65 | 66 | 67 | 68 | ### Editing Components 69 | 70 | - Double click elements to bring up the modal view 71 | - Add additional elements to a component with a live preview of the component code 72 | - Drag selected elements to the right to nest elements 73 | - Establish parent-child component relationships via the dropdown menu when creating or editing components 74 | 75 | 76 | 77 | ### Saving / Opening / Exporting Projects 78 | 79 | - If you're signed in with GitHub, click the ‘Save Project’ icon to save it to PreVue’s database 80 | - Click ‘Open Project’ to retrieve past projects 81 | - Once you're satisfied, click the export project icon to export your awesome project as new Vue application 82 | - Other users can use PreVue's playground to create and export projects in single sessions without signing in 83 | 84 | 86 | 87 | ### Code Exporting 88 | 89 | Below is the generated directory structure of the Vue application that is created when you export your design. 90 | 91 | ``` 92 | src/ 93 | assets/ 94 | App.vue 95 | components/ 96 | UserCreatedComponent1.vue 97 | UserCreatedComponent2.vue 98 | ... 99 | views/ 100 | HomeView.vue 101 | UserCreatedRouteComponent1.vue 102 | UserCreatedRouteComponent2.vue 103 | ... 104 | ``` 105 | 106 | 111 | 112 | ## Built With 113 | 114 | --- 115 | 116 | - [Express](https://expressjs.com/) 117 | - [Jest](https://jestjs.io/) 118 | - [MongoDB](https://www.mongodb.com/) 119 | - [Mongoose](https://mongoosejs.com/) 120 | - [Node.js](https://nodejs.org/en) 121 | - [SuperTest](https://www.npmjs.com/package/supertest) 122 | - [Vite](https://vitejs.dev/) 123 | - [Vue Router](https://router.vuejs.org/guide/#html) 124 | - [Vue Test Utils](https://test-utils.vuejs.org/) 125 | - [Vue.js](https://vuejs.org/) 126 | - [Vuex](https://vuex.vuejs.org/) 127 | - [Vuetify](https://vuetifyjs.com/) 128 | 129 | ## Changelog 130 | 131 | --- 132 | 133 | PreVue 3.0 Updates: 134 | 135 | - OAuth integration with GitHub for secure authentication 136 | - Full CRUD functionality for prototype creation 137 | - Implementation of appropriate hierarchical relationships reflected in UI 138 | - Website and Homepage redesign for seamless user experience 139 | - Realistic rendering of elements to Component Display 140 | - Delete and Undo functionality for individual Components 141 | - Project saving, loading & export ability 142 | 143 | PreVue 2.0 Updates: 144 | 145 | - Implementation of PreVue as a web application 146 | - TypeScript integration 147 | - Backend infrastructure built with Node/Express 148 | - General UI/UX enhancements 149 | - Testing with Vitest and Supertest (and Jest) 150 | 151 | ## Contributing to PreVue 152 | 153 | --- 154 | 155 | We encourage you to submit issues for any bugs or ideas for enhancements. Please feel free to fork this repo and submit pull requests to contribute as well. Follow PreVue on [LinkedIn](https://www.linkedin.com/company/prevue-live/) for more updates. 156 | 157 | Ideas for additional features include: 158 | 159 | - Project livesharing for collaborative sessions (via Websockets) 160 | - Migrate state management from Vuex to Pinia 161 | - More thorough testing with Jest 162 | - Ability to rename and add styling to individual components 163 | - Containerization of PreVue App 164 | - User Authentication updates via OAuth 165 | 166 | ## Authors 167 | 168 | Prevue 3.0 169 | 170 | - **April Sanders** [@algorithmrhythm](https://github.com/algorithmrhythm) 171 | - **Cole Jaeger** [@colejaeger0](https://github.com/colejaeger0) 172 | - **Ilay Eskinazi** [@Pixolino](https://github.com/Pixolino) 173 | - **Nathan Bornstein** [@greenteaisgreat](https://github.com/greenteaisgreat) 174 | 175 | PreVue 2.0 176 | 177 | - **Jason Boo** [@jasonboo123](https://github.com/jasonboo123) 178 | - **Robert Drake** [@rmdrake8](https://github.com/rmdrake8) 179 | - **Sean Flynn** [@seanflynn5](http://github.com/seanflynn5) 180 | - **Zach Pestaina** [@zachpestaina](https://github.com/zachpestaina) 181 | 182 | PreVue 1.0 183 | 184 | - **Hubert Lin** [@hubelin](https://github.com/hubelin) 185 | - **Franklin Pinnock** [@pinnockf](https://github.com/pinnockf) 186 | - **Annette Lin** [@al2613](https://github.com/al2613) 187 | - **Daniel Shu** [@danshuu](https://github.com/danshuu) 188 | 189 | ## License 190 | 191 | --- 192 | 193 |

This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details

194 | 195 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/teamprevue/PreVue/pulls) 196 | ![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg) 197 | -------------------------------------------------------------------------------- /build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/build/icon.icns -------------------------------------------------------------------------------- /build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/build/icon.ico -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 16 | PreVue 17 | 18 | 19 | 20 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 6 | 'jest-transform-stub', 7 | '^.+\\.jsx?$': 'babel-jest', 8 | }, 9 | transformIgnorePatterns: ['/node_modules/'], 10 | moduleNameMapper: { 11 | '^@/(.*)$': '/src/$1', 12 | }, 13 | snapshotSerializers: ['jest-serializer-vue'], 14 | testMatch: [ 15 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)', 16 | ], 17 | testEnvironmentOptions: { 18 | url: 'http://localhost:5174', // replace with your test URL 19 | }, 20 | testEnvironment: 'node', 21 | }; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "type": "commonjs" 4 | }, 5 | "name": "prevue", 6 | "author": "teamprevue (www.prevue.io)", 7 | "version": "3.0.0", 8 | "description": "Developer prototyping app built with Vue", 9 | "scripts": { 10 | "dev": "vite", 11 | "build": "vite build", 12 | "serve": "vite preview", 13 | "vite": "npm run server", 14 | "server": "node server/server.js", 15 | "test": "NODE_ENV=test vitest", 16 | "coverage": "vitest run --coverage", 17 | "start": "node server/server.js" 18 | }, 19 | "license": "MIT", 20 | "dependencies": { 21 | "@he-tree/vue": "^2.2.7", 22 | "@progress/jszip-esm": "^1.0.3", 23 | "@ssthouse/vue3-tree-chart": "^0.2.6", 24 | "axios": "^1.3.4", 25 | "concurrently": "^7.6.0", 26 | "connect-history-api-fallback": "^2.0.0", 27 | "cookie-parser": "^1.4.6", 28 | "cors": "^2.8.5", 29 | "dotenv": "^16.3.1", 30 | "express": "^4.18.2", 31 | "file-saver": "^2.0.5", 32 | "fs-extra": "^7.0.1", 33 | "handlebars": "^4.7.7", 34 | "happy-dom": "^8.9.0", 35 | "he-tree-vue": "^3.1.2", 36 | "jsonwebtoken": "^9.0.2", 37 | "mongodb": "^5.1.0", 38 | "mongodb-connection-string-url": "^2.6.0", 39 | "mongoose": "^6.10.0", 40 | "mousetrap": "^1.6.3", 41 | "sass": "^1.58.3", 42 | "vue": "^3.2.47", 43 | "vue-multiselect": "^3.0.0-alpha.2", 44 | "vue-router": "^4.1.6", 45 | "vue-template-compiler": "^2.6.11", 46 | "vue3-draggable-resizable": "^1.6.5", 47 | "vued3tree": "^3.6.4", 48 | "vuedraggable": "^4.1.0", 49 | "vuetify": "^3.1.6" 50 | }, 51 | "devDependencies": { 52 | "@typescript-eslint/eslint-plugin": "^5.54.0", 53 | "@typescript-eslint/parser": "^5.54.0", 54 | "@vitejs/plugin-vue": "^4.0.0", 55 | "@vue/eslint-config-prettier": "^4.0.1", 56 | "@vue/test-utils": "^2.3.1", 57 | "eslint": "^8.35.0", 58 | "eslint-plugin-typescript": "^0.14.0", 59 | "eslint-plugin-vue": "^8.7.1", 60 | "jest": "^29.5.0", 61 | "jsdom": "^21.1.0", 62 | "sinon": "^15.0.1", 63 | "supertest": "^6.3.3", 64 | "typescript": "^4.9.5", 65 | "vite": "^4.1.4", 66 | "vitest": "^0.29.2", 67 | "vue-eslint-parser": "^9.1.0", 68 | "vue-template-compiler": "^2.5.21", 69 | "vue-tsc": "^1.0.24", 70 | "vuex": "^4.1.0" 71 | }, 72 | "main": "background.js", 73 | "directories": { 74 | "test": "tests" 75 | }, 76 | "repository": { 77 | "type": "git", 78 | "url": "git+https://github.com/oslabs-beta/PreVue.git" 79 | }, 80 | "bugs": { 81 | "url": "https://github.com/oslabs-beta/PreVue/issues" 82 | }, 83 | "homepage": "https://github.com/oslabs-beta/PreVue#readme" 84 | } -------------------------------------------------------------------------------- /scrapcode.txt: -------------------------------------------------------------------------------- 1 | 19 | 20 | 54 | 55 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /server/controllers/accountController.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const Users = require('../models/accountModels'); 3 | const jwt = require('jsonwebtoken'); 4 | const accountController = {}; 5 | 6 | // enters a user into the database after GitHub OAuth if an entry does not already exist 7 | accountController.createUser = (req, res, next) => { 8 | const { username, id } = res.locals; 9 | //Making sure username exists 10 | Users.findOne({ username }) 11 | .then(data => { 12 | if (!data) { 13 | Users.create({ username, id }).then(data => { 14 | //data here is full entry, includes _id key 15 | res.locals.id = data._id; // sending ID for cookie auth 16 | return next(); 17 | }); 18 | } else { 19 | return next(); 20 | } 21 | }) 22 | .catch(err => { 23 | next({ 24 | log: `accountController.createUser failed: ${err}`, 25 | message: `User already exists!` 26 | }); 27 | }); 28 | }; 29 | 30 | // showing logged in user's projects before mounting 31 | accountController.userProjects = (req, res, next) => { 32 | Users.findOne({ username: res.locals.username }) 33 | .then(data => { 34 | res.locals.userProjects = data.project_ids; 35 | return next(); 36 | }) 37 | .catch(err => { 38 | next({ 39 | log: 'accountController.userProjects failed', 40 | message: `could not find projects for user` 41 | }); 42 | }); 43 | }; 44 | 45 | // just for test purposes; returns all users in the database 46 | accountController.findUser = (req, res, next) => { 47 | Users.find({}) 48 | .then(data => { 49 | res.locals.username = data; 50 | return next(); 51 | }) 52 | .catch(err => { 53 | // if (err.message === `Username Doesn't Exist`) res.redirect("/signup"); 54 | return next({ 55 | log: err, 56 | error: `error found in userController.findUser` 57 | }); 58 | }); 59 | }; 60 | 61 | module.exports = accountController; 62 | -------------------------------------------------------------------------------- /server/controllers/authController.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const jwt = require('jsonwebtoken'); 3 | 4 | //secret key generated when integrating your app with Github OAuth (must create .env file in root directory) 5 | const privateKey = process.env.SECRET_KEY; 6 | const authController = {}; 7 | 8 | // assigns a JWT to a user upon login 9 | authController.sign = (req, res, next) => { 10 | try { 11 | const { username, id } = res.locals; 12 | console.log(`username: ${username}`); 13 | const token = jwt.sign( 14 | { 15 | username, 16 | id 17 | }, 18 | privateKey, 19 | { expiresIn: '6h' } 20 | ); 21 | res.locals.token = token; 22 | return next(); 23 | } catch (err) { 24 | return next(err); 25 | } 26 | }; 27 | 28 | // authenticates user base on info stored on the JWT 29 | authController.authenticate = (req, res, next) => { 30 | const token = req.cookies.ssid; 31 | // Check if token is present 32 | if (!token) { 33 | // If no token, respond with 401 Unauthorized status 34 | return res.status(401).send('Authentication required'); 35 | } 36 | try { 37 | // If token present, verify it 38 | const decoded = jwt.verify(token, privateKey); 39 | res.locals.username = decoded.username; 40 | res.locals.id = decoded.id; 41 | return next(); 42 | } catch (err) { 43 | // If token verification fails, handle error 44 | return next({ 45 | log: `authController.authenticate failed: ${err}`, 46 | message: `Authentication error: Invalid token` 47 | }); 48 | } 49 | }; 50 | 51 | module.exports = authController; 52 | -------------------------------------------------------------------------------- /server/controllers/cookieController.js: -------------------------------------------------------------------------------- 1 | const cookieController = {}; 2 | 3 | // sets cookie with key of 'ssid' and value of generated JWT 4 | cookieController.setSSIDCookie = (req, res, next) => { 5 | res.cookie('ssid', res.locals.token, { httpOnly: true }); 6 | return next(); 7 | }; 8 | 9 | // used to log out a user 10 | cookieController.deleteCookie = (req, res, next) => { 11 | res.clearCookie('ssid'); 12 | return next(); 13 | }; 14 | 15 | module.exports = cookieController; 16 | -------------------------------------------------------------------------------- /server/controllers/oAuthController.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const axios = require('axios'); 3 | 4 | const oAuthController = {}; 5 | const GITHUB_OAUTH_CLIENT_ID = process.env.GITHUB_OAUTH_CLIENT_ID; 6 | const GITHUB_OAUTH_CLIENT_SECRET = process.env.GITHUB_OAUTH_CLIENT_SECRET; 7 | const GITHUB_ACCESS_TOKEN_REQUEST_URL = `https://github.com/login/oauth/access_token`; 8 | const GITHUB_REDIRECT_URI = process.env.GITHUB_REDIRECT_URI; 9 | let str = GITHUB_OAUTH_CLIENT_ID 10 | let newStr = GITHUB_REDIRECT_URI 11 | 12 | // first step of OAuth: redirects user to github with specific client id and redirect uri's concatenated 13 | oAuthController.oAuthLogin = async (req, res, next) => { 14 | try { 15 | console.log('sending get request to github '); 16 | let redirectStr = 17 | `https://github.com/login/oauth/authorize?` + 18 | 'client_id=' + 19 | str + 20 | '&redirect_uri=' + 21 | newStr; 22 | let redirectURL = new URL(redirectStr); 23 | res.locals.url = redirectURL; 24 | return next(); 25 | } catch (error) { 26 | return next({ 27 | log: 'Error occurred in the oauthController.oAuthLogin middleware', 28 | status: 400, // bad request 29 | err: { 30 | err: 'Error occurred in sending user to login to GitHub to login' 31 | } 32 | }); 33 | } 34 | }; 35 | 36 | // Get temporary "code" from Github (in req.query) in oauthController and AWAIT post request it back to exchange it for an access token (to Github API for user data) 37 | oAuthController.requestGitHubIdentity = async (req, res, next) => { 38 | try { 39 | const { code } = req.query; 40 | const { data } = await axios.post( 41 | GITHUB_ACCESS_TOKEN_REQUEST_URL, 42 | { 43 | client_id: GITHUB_OAUTH_CLIENT_ID, 44 | client_secret: GITHUB_OAUTH_CLIENT_SECRET, 45 | code 46 | }, 47 | { 48 | headers: { 49 | Accept: 'application/json' 50 | } 51 | } 52 | ); 53 | // if all is good, attach access_token to res.locals 54 | res.locals.access_token = data.access_token; 55 | console.log(`access_token aquired`); 56 | return next(); 57 | } catch (error) { 58 | console.log(error); 59 | return next({ 60 | log: `Error occurred in the oauthController.requestGitHubIdentity middleware\n Error: ${error.message}`, 61 | status: 400, // bad request 62 | err: { err: 'Error occurred in getting your Github user identity' } 63 | }); 64 | } 65 | }; 66 | 67 | // how a given user is actually authenticated 68 | // https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-the-authenticated-user 69 | oAuthController.queryGitHubAPIWithAccessToken = async (req, res, next) => { 70 | try { 71 | const auth = res.locals.access_token; 72 | const { data } = await axios.get('https://api.github.com/user', { 73 | headers: { Authorization: `Bearer ${auth}` } 74 | }); 75 | 76 | // Check if necessary fields are present 77 | if (!data || !data.login || !data.id) { 78 | throw new Error('Required GitHub user data not found'); 79 | } 80 | 81 | // set info from api to res.locals. -> Process GitHub Data 82 | const processedData = processGitHubData(data); 83 | res.locals = { 84 | ...res.locals, 85 | ...processedData 86 | }; 87 | 88 | return next(); 89 | } catch (error) { 90 | return next({ 91 | log: `Error occurred in the oauthController.queryGitHubAPIWithAccessToken middleware\n Error: ${error.message}`, 92 | status: 400, // bad request 93 | err: { err: 'Error occurred in querying Github API with access token' } 94 | }); 95 | } 96 | }; 97 | 98 | // Helper function for converting Github API data to fields for database input 99 | function processGitHubData(data) { 100 | const { login, id } = data; 101 | return { 102 | username: login, 103 | id: id 104 | }; 105 | } 106 | 107 | module.exports = oAuthController; 108 | -------------------------------------------------------------------------------- /server/controllers/projectController.js: -------------------------------------------------------------------------------- 1 | const Project = require('../models/projectModels'); 2 | const User = require('../models/accountModels'); 3 | const projectController = {}; 4 | 5 | // saving a project: 6 | projectController.saveProject = (req, res, next) => { 7 | const { project_name, projectObject } = req.body; 8 | Project.findOne({ project_name }).then(data => { 9 | console.log(`Saving project...`); 10 | if (!data) { 11 | Project.create({ 12 | project_name, 13 | projectObject, 14 | projectOwner: res.locals.username 15 | }) 16 | .then(data => { 17 | res.locals.newProject = data.projectObject; 18 | res.locals.projectName = data.project_name; 19 | return next(); 20 | }) 21 | .catch(err => { 22 | next({ 23 | log: `projectController.saveProject failed, ${err}`, 24 | message: `Can't save new project!` 25 | }); 26 | }); 27 | } else { 28 | // if project already exists, update with new state 29 | Project.findOneAndUpdate( 30 | { project_name }, 31 | { projectObject: req.body.projectObject } 32 | ) 33 | .then(data => { 34 | res.locals.newProject = data.projectObject; 35 | res.locals.projectName = data.project_name; 36 | return next(); 37 | }) 38 | .catch(err => { 39 | next({ 40 | log: `projectController.saveProject failed, ${err}`, 41 | message: `Can't update project!` 42 | }); 43 | }); 44 | } 45 | }); 46 | }; 47 | 48 | // Updates project_ids of User who's saving a project. If it already exists, it's not added. 49 | projectController.userQuery = (req, res, next) => { 50 | User.findOneAndUpdate( 51 | { username: res.locals.username }, 52 | { $addToSet: { project_ids: res.locals.projectName } }, 53 | { new: true } 54 | ) 55 | .then(data => { 56 | res.locals.user = data; 57 | return next(); 58 | }) 59 | .catch(err => { 60 | next({ 61 | log: `projectController.userQuery failed, ${err}`, 62 | message: `user already exists!` 63 | }); 64 | }); 65 | }; 66 | 67 | //For retrieving projects ('open project' on frontend) 68 | projectController.getProject = (req, res, next) => { 69 | Project.findOne({ 70 | project_name: req.body.project_name, 71 | projectOwner: res.locals.username 72 | }) 73 | .then(data => { 74 | res.locals.project = data.projectObject; 75 | return next(); 76 | }) 77 | .catch(err => { 78 | next({ 79 | log: `projectController.getProject failed, ${err}`, 80 | message: `Can't find project!` 81 | }); 82 | }); 83 | }; 84 | 85 | //general query to find all projects; not used in app itself 86 | projectController.findProject = (req, res, next) => { 87 | Project.find({}) 88 | .then(data => { 89 | res.locals.username = data; 90 | return next(); 91 | }) 92 | .catch(err => { 93 | // if (err.message === `Username Doesn't Exist`) res.redirect("/signup"); 94 | return next({ 95 | log: err, 96 | error: `error found in userController.verifyUser` 97 | }); 98 | }); 99 | }; 100 | 101 | module.exports = projectController; 102 | -------------------------------------------------------------------------------- /server/models/accountModels.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | // schema for users stored in database 6 | const userSchema = new Schema({ 7 | username: { type: String, required: true }, 8 | id: { type: String, required: true }, 9 | project_ids: { type: Array } 10 | }); 11 | 12 | const Users = mongoose.model('Users', userSchema); 13 | module.exports = Users; 14 | -------------------------------------------------------------------------------- /server/models/projectModels.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | // schema for projects stored in database 6 | const projectSchema = new Schema({ 7 | project_name: { type: String, required: true }, 8 | projectObject: { type: Object, required: true }, 9 | projectOwner: { type: String, required: true } 10 | }); 11 | 12 | const Project = mongoose.model('Project', projectSchema); 13 | module.exports = Project; 14 | -------------------------------------------------------------------------------- /server/routes/accountRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const accountController = require('../controllers/accountController'); 3 | const cookieController = require('../controllers/cookieController'); 4 | const oAuthController = require('../controllers/oAuthController'); 5 | const authController = require('../controllers/authController'); 6 | const accountRouter = express.Router(); 7 | 8 | accountRouter.get('/oauth', oAuthController.oAuthLogin, (req, res) => { 9 | // Instead of sending the URL back in the response, redirect the client to it 10 | res.redirect(res.locals.url.toString()); 11 | }); 12 | 13 | // retrieves specific user projects 14 | accountRouter.get( 15 | '/userProjects', 16 | authController.authenticate, 17 | accountController.userProjects, 18 | (req, res) => { 19 | return res.status(200).json(res.locals.userProjects); 20 | } 21 | ); 22 | 23 | // github OAuth route 24 | accountRouter.get( 25 | '/oauth/access_token/redirect', 26 | oAuthController.requestGitHubIdentity, 27 | oAuthController.queryGitHubAPIWithAccessToken, 28 | accountController.createUser, 29 | authController.sign, 30 | cookieController.setSSIDCookie, 31 | (req, res) => { 32 | console.log('Succesful login'); 33 | res.redirect('/home'); 34 | } 35 | ); 36 | 37 | // validates user on login 38 | accountRouter.get( 39 | '/validateSession', 40 | authController.authenticate, 41 | (req, res) => { 42 | res.status(200).json(res.locals.username); 43 | } 44 | ); 45 | 46 | // logs out user by deleting cookie 47 | accountRouter.get('/logout', cookieController.deleteCookie, (req, res) => { 48 | return res.sendStatus(200); 49 | }); 50 | 51 | // general route for querying users in database 52 | accountRouter.get( 53 | '/find', 54 | accountController.findUser, 55 | (req, res) => { 56 | return res.status(200).json(res.locals.username); 57 | } 58 | ); 59 | 60 | module.exports = accountRouter; 61 | -------------------------------------------------------------------------------- /server/routes/projectRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const projectController = require('../controllers/projectController'); 3 | const authController = require('../controllers/authController'); 4 | const projectRouter = express.Router(); 5 | 6 | // signup route 7 | projectRouter.post( 8 | '/saveProject', 9 | authController.authenticate, 10 | projectController.saveProject, 11 | projectController.userQuery, 12 | (req, res) => { 13 | return res.status(201).json(res.locals.user); 14 | } 15 | ); 16 | 17 | projectRouter.post( 18 | '/getProject', 19 | authController.authenticate, 20 | projectController.getProject, 21 | (req, res) => { 22 | console.log('testing route'); 23 | return res.status(201).json(res.locals.project); 24 | } 25 | ); 26 | 27 | // used to test Supertest functionality; not used in actual app 28 | projectRouter.get( 29 | '/find', 30 | projectController.findProject, 31 | (req, res) => { 32 | return res 33 | .status(200) 34 | .json({ hello: test, 'res.locals.usename': res.locals.username }); 35 | } 36 | ); 37 | 38 | module.exports = projectRouter; 39 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const express = require('express'); 3 | const path = require('path'); 4 | const cookieParser = require('cookie-parser'); 5 | const app = express(); 6 | const PORT = process.env.PORT; 7 | 8 | const cors = require('cors'); 9 | const corsOptions = { 10 | origin: process.env.CORS_ORIGIN, 11 | methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', 12 | credentials: true, 13 | }; 14 | const accountRouter = require('./routes/accountRouter'); 15 | const projectRouter = require('./routes/projectRouter'); 16 | 17 | // connecting to MongoDB 18 | const mongoose = require('mongoose'); 19 | const myURI = process.env.MONGO_URI; 20 | const { MongoClient } = require('mongodb'); 21 | mongoose 22 | .connect(myURI, { 23 | // options for the connect method to parse the URI 24 | useNewUrlParser: true, 25 | useUnifiedTopology: true, 26 | // sets the name of the DB that our collections are part of 27 | dbName: 'prevueDB', 28 | }) 29 | .then(() => { 30 | console.log('Connected to Mongo DB.'); 31 | }) 32 | .catch((err) => console.log(err)); 33 | 34 | // Global Middleware 35 | app.use(express.json()); 36 | app.use(cookieParser()); 37 | app.use(cors(corsOptions)); 38 | app.use(express.urlencoded({ extended: true })); 39 | 40 | app.use(express.static(path.join(__dirname, '..', '.'))); 41 | 42 | app.get('/', (req, res) => { 43 | res.status(200).sendFile(path.resolve(__dirname, 'index.html')); 44 | }); 45 | 46 | // Routers 47 | app.use('/users', accountRouter); 48 | app.use('/projects', projectRouter); 49 | 50 | app.use((req, res) => res.sendStatus(404)); 51 | 52 | // Global error handler 53 | app.use((err, req, res, next) => { 54 | const defaultErr = { 55 | log: 'Express error handler caught unknown middleware error', 56 | status: 400, 57 | message: { err: 'An error occurred' }, 58 | }; 59 | const errorObj = Object.assign({}, defaultErr, err); 60 | console.log(errorObj.log); 61 | return res.status(errorObj.status).json(errorObj.message); 62 | }); 63 | 64 | // starts server 65 | app.listen(PORT, () => { 66 | console.log(`Server listening on port: ${PORT}`); 67 | }); 68 | 69 | module.exports = app; 70 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | 25 | 36 | -------------------------------------------------------------------------------- /src/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/3.png -------------------------------------------------------------------------------- /src/assets/ComponentMp4.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/ComponentMp4.mp4 -------------------------------------------------------------------------------- /src/assets/ModalMp4.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/ModalMp4.mp4 -------------------------------------------------------------------------------- /src/assets/PreVueDemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/PreVueDemo.gif -------------------------------------------------------------------------------- /src/assets/PreVueDemo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/PreVueDemo.mp4 -------------------------------------------------------------------------------- /src/assets/PreVueDemo2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/PreVueDemo2.mp4 -------------------------------------------------------------------------------- /src/assets/PreVueExportDemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/PreVueExportDemo.gif -------------------------------------------------------------------------------- /src/assets/SaveMp4.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/SaveMp4.mp4 -------------------------------------------------------------------------------- /src/assets/april-photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/april-photo.jpg -------------------------------------------------------------------------------- /src/assets/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/background.jpg -------------------------------------------------------------------------------- /src/assets/button.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/cole-photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/cole-photo.jpg -------------------------------------------------------------------------------- /src/assets/componentdisplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/componentdisplay.png -------------------------------------------------------------------------------- /src/assets/form.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/github-icon-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/homeview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/homeview.png -------------------------------------------------------------------------------- /src/assets/ilay-photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/ilay-photo.jpg -------------------------------------------------------------------------------- /src/assets/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/img.png -------------------------------------------------------------------------------- /src/assets/img.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/input.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/jason-photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/jason-photo.jpg -------------------------------------------------------------------------------- /src/assets/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/linkedin-svg.svg: -------------------------------------------------------------------------------- 1 | LinkedIn icon -------------------------------------------------------------------------------- /src/assets/list-item.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/list-ol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/list-ul.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | Artboard 46 2 | -------------------------------------------------------------------------------- /src/assets/modal-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/modal-image.png -------------------------------------------------------------------------------- /src/assets/nathan-photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/nathan-photo.jpg -------------------------------------------------------------------------------- /src/assets/navbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/navbar.png -------------------------------------------------------------------------------- /src/assets/navbar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/new-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/new-banner.png -------------------------------------------------------------------------------- /src/assets/newcomp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/newcomp.png -------------------------------------------------------------------------------- /src/assets/newcompvue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/newcompvue.png -------------------------------------------------------------------------------- /src/assets/p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/p1.png -------------------------------------------------------------------------------- /src/assets/paragraph.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/prevue-large-green-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/prevue-large-green-bottom.png -------------------------------------------------------------------------------- /src/assets/prevue-large-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/prevue-large-green.png -------------------------------------------------------------------------------- /src/assets/prevue-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/prevue-large.png -------------------------------------------------------------------------------- /src/assets/prevue-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/prevue-logo.png -------------------------------------------------------------------------------- /src/assets/prevue-recording.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/prevue-recording.gif -------------------------------------------------------------------------------- /src/assets/prevue-recording.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/prevue-recording.mp4 -------------------------------------------------------------------------------- /src/assets/prevue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/prevue.png -------------------------------------------------------------------------------- /src/assets/prevue_color_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/prevue_color_white.png -------------------------------------------------------------------------------- /src/assets/pvv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/pvv.png -------------------------------------------------------------------------------- /src/assets/robert-photo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/robert-photo.jpeg -------------------------------------------------------------------------------- /src/assets/routecreator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/routecreator.png -------------------------------------------------------------------------------- /src/assets/sean-photo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/sean-photo.jpeg -------------------------------------------------------------------------------- /src/assets/tree-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/tree-demo.png -------------------------------------------------------------------------------- /src/assets/treeview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/treeview.png -------------------------------------------------------------------------------- /src/assets/viewcreator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/viewcreator.png -------------------------------------------------------------------------------- /src/assets/zach-photo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/PreVue/deedda8b44e97f51b06ad6e2da17baf4997bf281/src/assets/zach-photo.jpeg -------------------------------------------------------------------------------- /src/components/ChildrenMultiselect.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/components/Component.vue: -------------------------------------------------------------------------------- 1 | 29 | 53 | 58 | 59 | -------------------------------------------------------------------------------- /src/components/ComponentDisplay.vue: -------------------------------------------------------------------------------- 1 | 86 | 87 | 278 | 359 | -------------------------------------------------------------------------------- /src/components/ExportProjectComponent.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 212 | 213 | 222 | -------------------------------------------------------------------------------- /src/components/HomeQueue.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 86 | 87 | -------------------------------------------------------------------------------- /src/components/HomeSidebar.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 133 | 134 | 143 | -------------------------------------------------------------------------------- /src/components/Icons.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 38 | 39 | 60 | -------------------------------------------------------------------------------- /src/components/LogOutComponent.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 39 | -------------------------------------------------------------------------------- /src/components/Modal/ComponentCodeDisplay.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 82 | 83 | 96 | -------------------------------------------------------------------------------- /src/components/Modal/EditQueue.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 64 | 65 | 115 | -------------------------------------------------------------------------------- /src/components/Modal/EditSidebar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 34 | 47 | -------------------------------------------------------------------------------- /src/components/Modal/Modal.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 53 | 54 | 60 | -------------------------------------------------------------------------------- /src/components/NavBar.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 72 | 73 | 124 | 125 | -------------------------------------------------------------------------------- /src/components/NewProjectComponent.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 58 | 59 | 69 | -------------------------------------------------------------------------------- /src/components/OpenProjectComponent.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 79 | 80 | 95 | -------------------------------------------------------------------------------- /src/components/ProjectTabs.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 70 | 71 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/components/RouteDisplay.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 73 | 74 | 88 | -------------------------------------------------------------------------------- /src/components/Routes.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/components/SaveProjectComponent.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 82 | 83 | 96 | -------------------------------------------------------------------------------- /src/components/TreeGraph.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 98 | 99 | 119 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | import router from './router'; 4 | import store from './store'; 5 | 6 | // vuetify imports 7 | import 'vuetify/styles'; 8 | import { createVuetify } from 'vuetify'; 9 | import * as components from 'vuetify/components'; 10 | import * as directives from 'vuetify/directives'; 11 | 12 | // vuetify config 13 | const vuetify = createVuetify({ 14 | components, 15 | directives 16 | }); 17 | 18 | // Creation and mounting of the app 19 | const app = createApp({ 20 | extends: App, 21 | beforeCreate() { 22 | store.commit('initialiseStore'); 23 | } 24 | }); 25 | 26 | app.use(vuetify); 27 | app.use(router); 28 | app.use(store); 29 | app.mount('#app'); 30 | -------------------------------------------------------------------------------- /src/router.ts: -------------------------------------------------------------------------------- 1 | import * as VueRouter from 'vue-router'; 2 | import Home from './views/HomeView.vue'; 3 | import Splash from './views/SplashView.vue'; 4 | // flagged due to linting configuration, remains functional 5 | const router = VueRouter.createRouter({ 6 | history: VueRouter.createWebHistory(), 7 | base: import.meta.env.BASE_URL, 8 | routes: [ 9 | { 10 | path: '/', 11 | name: 'login', 12 | component: Splash, 13 | meta: { 14 | hideNavbar: true 15 | } 16 | }, 17 | { 18 | path: '/tree', 19 | name: 'tree', 20 | // route level code-splitting 21 | // this generates a separate chunk (about.[hash].js) for this route 22 | // which is lazy-loaded when the route is visited. 23 | component: () => import('./views/TreeView.vue') 24 | }, 25 | { 26 | path: '/home', 27 | name: 'home', 28 | component: Home 29 | } 30 | ] 31 | }); 32 | 33 | export default router; -------------------------------------------------------------------------------- /src/store/actions.ts: -------------------------------------------------------------------------------- 1 | import * as types from './storeTypes'; 2 | import { Actions } from '../types'; 3 | 4 | const actions: Actions = { 5 | [types.incRerenderKey]: ({ commit }) => { 6 | commit(types.INC_RERENDER_KEY); 7 | }, 8 | [types.updateProjectName]: ({ commit }, payload) => { 9 | commit(types.UPDATE_PROJECT_NAME, payload); 10 | }, 11 | [types.setLogin]: ({ commit }, payload) => { 12 | commit(types.SET_LOGIN, payload); 13 | }, 14 | [types.replaceState]: ({ commit }, payload) => { 15 | commit('replaceState', payload); 16 | }, 17 | [types.nameProject]: ({ commit }, payload) => { 18 | commit(types.NAME_PROJECT, payload); 19 | }, 20 | [types.initialiseStore]: ({ commit }) => { 21 | commit(types.INITIALISESTORE); 22 | }, 23 | 24 | [types.registerComponent]: ({ state, commit }, payload) => { 25 | const { componentName } = payload; 26 | 27 | if (!state.componentMap[componentName]) { 28 | commit(types.ADD_COMPONENT_TO_COMPONENT_MAP, payload); 29 | 30 | commit( 31 | types.ADD_COMPONENT_TO_ACTIVE_ROUTE_CHILDREN, 32 | payload.componentName 33 | ); 34 | commit(types.ADD_COMPONENT_TO_ACTIVE_ROUTE_IN_ROUTE_MAP, payload); 35 | 36 | const component = state.componentNameInputValue; 37 | const value = state.componentChildrenMultiselectValue.map( 38 | (component: string) => { 39 | return state.componentMap[component].componentName; 40 | } 41 | ); 42 | commit(types.UPDATE_COMPONENT_CHILDREN_VALUE, { component, value }); 43 | commit(types.UPDATE_COMPONENT_CHILDREN_MULTISELECT_VALUE, []); 44 | commit(types.UPDATE_COMPONENT_NAME_INPUT_VALUE, ''); 45 | commit(types.SET_SELECTED_ELEMENT_LIST, []); 46 | } 47 | }, 48 | [types.setSelectedElementList]: ({ commit }, payload) => { 49 | if (payload) { 50 | 51 | commit(types.SET_SELECTED_ELEMENT_LIST, payload); 52 | } 53 | }, 54 | [types.addToSelectedElementList]: ({ commit }, payload) => { 55 | //console.log('action payload is', payload); 56 | commit(types.ADD_TO_SELECTED_ELEMENT_LIST, payload); 57 | }, 58 | [types.addToComponentElementList]: ({ commit }, payload) => { 59 | commit(types.ADD_TO_COMPONENT_HTML_LIST, payload); 60 | }, 61 | [types.setClickedElementList]: ({ commit }, payload) => { 62 | commit(types.SET_CLICKED_ELEMENT_LIST, payload); 63 | }, 64 | [types.deleteActiveComponent]: ({ state, commit }) => { 65 | commit(types.DELETE_ACTIVE_COMPONENT); 66 | const activeRouteArray = [...state.routes[state.activeRoute]]; 67 | const newActiveRouteArray = activeRouteArray.filter(componentData => { 68 | return state.activeComponent !== componentData.componentName; 69 | }); 70 | commit(types.SET_ACTIVE_ROUTE_ARRAY, newActiveRouteArray); 71 | commit(types.SET_ACTIVE_COMPONENT, ''); 72 | }, 73 | [types.deleteSelectedElement]: ({ commit }, payload) => { 74 | //console.log('this is the payload', payload) 75 | commit(types.DELETE_SELECTED_ELEMENT, payload); 76 | }, 77 | [types.setState]: ({ commit }, payload) => { 78 | commit(types.SET_STATE, payload); 79 | }, 80 | [types.addProject]: ({ commit }, payload) => { 81 | commit(types.ADD_PROJECT, payload); 82 | }, 83 | [types.deleteFromComponentHtmlList]: ({ commit }, payload) => { 84 | commit(types.DELETE_FROM_COMPONENT_HTML_LIST, payload); 85 | }, 86 | [types.changeActiveTab]: ({ commit }, payload) => { 87 | commit(types.CHANGE_ACTIVE_TAB, payload); 88 | }, 89 | [types.setComponentMap]: ({ commit }, payload) => { 90 | commit(types.SET_COMPONENT_MAP, payload); 91 | }, 92 | [types.addRouteToRouteMap]: ({ state, commit }, payload) => { 93 | commit(types.ADD_ROUTE, payload); 94 | commit(types.SET_ACTIVE_ROUTE, payload); 95 | const route = state.activeRoute; 96 | const children: string[] = []; 97 | commit(types.ADD_ROUTE_TO_COMPONENT_MAP, { route, children }); 98 | const component = 'App'; 99 | const value = state.componentMap[state.activeRoute].componentName; 100 | commit(types.ADD_COMPONENT_TO_COMPONENT_CHILDREN, { component, value }); 101 | }, 102 | [types.setActiveRoute]: ({ commit }, payload) => { 103 | commit(types.SET_ACTIVE_ROUTE, payload); 104 | }, 105 | [types.setActiveComponent]: ({ commit }, payload) => { 106 | commit(types.SET_ACTIVE_COMPONENT, payload); 107 | }, 108 | 109 | [types.setActiveElement]: ({ commit }, payload) => { //new 110 | commit(types.SET_ACTIVE_ELEMENT, payload); 111 | }, 112 | [types.deleteActiveElement]: ({ commit }) => { 113 | commit(types.DELETE_ACTIVE_ELEMENT) 114 | }, 115 | [types.setComponentIndex]: ({ commit }, payload) => { 116 | commit(types.SET_COMPONENT_INDEX, payload) 117 | }, 118 | [types.setElementIndex]: ({ commit }, payload) => { 119 | commit(types.SET_ELEMENT_INDEX, payload) 120 | }, 121 | [types.saveState]: ({ commit }) => { 122 | commit(types.SAVE_STATE) 123 | }, 124 | [types.restoreState]: ({ commit }) => { 125 | commit(types.RESTORE_STATE) 126 | }, 127 | 128 | [types.setRoutes]: ({ commit }, payload) => { 129 | commit(types.SET_ROUTES, payload); 130 | }, 131 | [types.deleteProjectTab]: ({ commit }, payload) => { 132 | commit(types.DELETE_PROJECT_TAB, payload); 133 | }, 134 | [types.updateComponentChildrenMultiselectValue]: ({ commit }, payload) => { 135 | commit(types.UPDATE_COMPONENT_CHILDREN_MULTISELECT_VALUE, payload); 136 | }, 137 | [types.updateActiveComponentChildrenValue]: ({ commit }, payload) => { 138 | console.log('payload', payload); 139 | commit(types.UPDATE_ACTIVE_COMPONENT_CHILDREN_VALUE, payload); 140 | }, 141 | [types.updateComponentNameInputValue]: ({ commit }, payload) => { 142 | commit(types.UPDATE_COMPONENT_NAME_INPUT_VALUE, payload); 143 | }, 144 | [types.updateOpenModal]: ({ commit }, payload) => { 145 | commit(types.UPDATE_OPEN_MODAL, payload); 146 | } 147 | }; 148 | 149 | export default actions; 150 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex'; 2 | 3 | import state from './state/stateIndex'; 4 | import actions from './actions'; 5 | import mutations from './mutations'; 6 | 7 | const store = createStore({ state, mutations, actions }); 8 | 9 | store.subscribe((mutation, state) => { 10 | localStorage.setItem('store', JSON.stringify(state)); 11 | }); 12 | 13 | export default store; -------------------------------------------------------------------------------- /src/store/mutations.ts: -------------------------------------------------------------------------------- 1 | import * as types from './storeTypes'; 2 | import { MutationTree } from 'vuex'; 3 | import { State, Mutations, HtmlList, HtmlChild } from '../types'; 4 | 5 | const mutations: Mutations = { 6 | initialiseStore(state: State) { 7 | 8 | if (localStorage.getItem('store')) { 9 | this.replaceState( 10 | Object.assign( 11 | state, 12 | JSON.parse(localStorage.getItem('store') || `${state}`) 13 | ) 14 | ); 15 | } 16 | }, 17 | replaceState(state: State, payload) { 18 | this.replaceState(payload); 19 | }, 20 | [types.INC_RERENDER_KEY]: (state: State) => { 21 | state.rerenderKey++; 22 | }, 23 | [types.UPDATE_PROJECT_NAME]: (state: State, payload) => { 24 | state.editedProjectName = payload; 25 | }, 26 | [types.SET_LOGIN]: (state: State, payload) => { 27 | state.loggedIn = payload; 28 | }, 29 | [types.NAME_PROJECT]: (state: State, payload) => { 30 | state.projectName = payload; 31 | }, 32 | [types.ADD_COMPONENT_TO_COMPONENT_MAP]: (state: State, payload) => { 33 | const { componentName, htmlList, children, isActive } = payload; 34 | state.componentMap = { 35 | ...state.componentMap, 36 | [componentName]: { 37 | componentName, 38 | x: 0, 39 | y: 0, 40 | w: 200, 41 | h: 200, 42 | children, 43 | htmlList, 44 | isActive 45 | } 46 | }; 47 | }, 48 | [types.ADD_TO_SELECTED_ELEMENT_LIST]: (state: State, payload) => { 49 | state.selectedElementList.push({ 50 | text: payload, 51 | children: [], 52 | id: Date.now(), 53 | x: 20, 54 | y: 20, 55 | w: 100, 56 | h: 100, 57 | isActive: Boolean 58 | }); 59 | }, 60 | [types.SET_SELECTED_ELEMENT_LIST]: (state: State, payload) => { 61 | state.selectedElementList = payload; 62 | }, 63 | [types.ADD_TO_COMPONENT_HTML_LIST]: (state: State, elementName) => { //and this 64 | const componentName: string = state.activeComponent; 65 | 66 | // state.componentMap[componentName].htmlList.push({ 67 | // text: elementName, 68 | // children: [], 69 | // x: 20, 70 | // y: 20, 71 | // w: 100, 72 | // h: 100 73 | // }) 74 | 75 | //find the active component and save the index 76 | const findIndex = function(obj){ 77 | for(const num in obj){ 78 | if(obj[num].componentName === componentName){ 79 | return num 80 | } 81 | } 82 | } 83 | let index = findIndex(state.routes[state.activeRoute]) 84 | console.log("index", index) 85 | 86 | //also adds to routes 87 | console.log("COMPONENT HTML LIST FUNCTION", state.routes[state.activeRoute]) 88 | state.routes[state.activeRoute][index].htmlList.push({ 89 | text: elementName, 90 | children: [], 91 | id: Date.now(), 92 | x: 20, 93 | y: 20, 94 | w: 100, 95 | h: 100, 96 | isActive: Boolean 97 | }) 98 | }, 99 | 100 | [types.DELETE_FROM_COMPONENT_HTML_LIST]: (state: State, id) => { 101 | const componentName = state.activeComponent; 102 | const htmlList = state.componentMap[componentName].htmlList; 103 | 104 | function parseAndDelete(htmlList: HtmlList) { 105 | htmlList.forEach((element, index) => { 106 | if (element.children.length > 0) { 107 | parseAndDelete(element.children); 108 | } 109 | if (id === element._id) { 110 | htmlList.splice(index, 1); 111 | } 112 | }); 113 | 114 | const copied = htmlList.slice(0); 115 | state.componentMap[componentName].htmlList = copied; 116 | } 117 | parseAndDelete(htmlList); 118 | }, 119 | 120 | [types.SET_CLICKED_ELEMENT_LIST]: (state: State, payload) => { 121 | const componentName = state.activeComponent; 122 | state.componentMap[componentName].htmlList = payload; 123 | }, 124 | 125 | // 126 | [types.DELETE_ACTIVE_COMPONENT]: (state: State) => { 127 | const { routes, activeRoute, componentMap, activeComponent, arrayOfStates } = state; 128 | 129 | const newObj = Object.assign({}, componentMap); 130 | 131 | delete newObj[activeComponent]; 132 | 133 | for (const compKey in newObj) { 134 | const children = newObj[compKey].children; 135 | children.forEach((child, index) => { 136 | if (activeComponent === child) children.splice(index, 1); 137 | }); 138 | } 139 | state.componentMap = newObj; 140 | }, 141 | 142 | // gets the component, traverses the component document and performs a splice on the element when it finds it 143 | [types.DELETE_ACTIVE_ELEMENT]: (state: State) => {//new 144 | let { routes, activeElement, activeRoute, componentIndex } = state; 145 | // routes, activeRoute, arrayOfStates 146 | const component = routes[activeRoute][componentIndex]; 147 | 148 | let newList 149 | let oldIndex = [] 150 | 151 | function findAndDelete(arr, id) { 152 | for (const [i, el] of arr.entries()) { 153 | console.log("EL", el) 154 | if (el.id === id) { 155 | newList = arr.slice(); // create a shallow copy 156 | newList.splice(i, 1); // delete the id'd element 157 | if(!oldIndex.length){ 158 | component.htmlList = newList 159 | } else { 160 | if(oldIndex.length === 1){ 161 | component.htmlList[oldIndex[0]].children = newList 162 | } else if (oldIndex.length === 2){ 163 | component.htmlList[oldIndex[0]].children[oldIndex[1]].children = newList 164 | } else if (oldIndex.length === 3){ 165 | component.htmlList[oldIndex[0]].children[oldIndex[1]].children[oldIndex[2]].children = newList 166 | } else if (oldIndex.length === 4){ 167 | component.htmlList[oldIndex[0]].children[oldIndex[1]].children[oldIndex[2]].children[oldIndex[3]].children = newList 168 | } else if (oldIndex.length === 5){ 169 | component.htmlList[oldIndex[0]].children[oldIndex[1]].children[oldIndex[2]].children[oldIndex[3]].children[oldIndex[4]].children = newList 170 | } else if (oldIndex.length === 6){ 171 | component.htmlList[oldIndex[0]].children[oldIndex[1]].children[oldIndex[2]].children[oldIndex[3]].children[oldIndex[4]].children[oldIndex[5]].children = newList 172 | } else if (oldIndex.length === 7){ 173 | component.htmlList[oldIndex[0]].children[oldIndex[1]].children[oldIndex[2]].children[oldIndex[3]].children[oldIndex[4]].children[oldIndex[5]].children[oldIndex[6]].children = newList 174 | } 175 | } 176 | } else if (el.children.length > 0) { 177 | console.log("CHILD") 178 | oldIndex.push(i) 179 | findAndDelete(el.children, id); 180 | } 181 | oldIndex = [] 182 | } 183 | } 184 | findAndDelete(component.htmlList, activeElement.id); 185 | }, 186 | 187 | [types.SET_ACTIVE_ELEMENT]: (state: State, payload) => {//new 188 | state.activeElement = payload; 189 | }, 190 | [types.SET_COMPONENT_INDEX]: (state: State, payload) => {//new 191 | state.componentIndex = payload; 192 | }, 193 | [types.SET_ELEMENT_INDEX]: (state: State, payload) => {//new 194 | state.elementIndex = payload; 195 | }, 196 | 197 | // pushes new state to arrayOfStates 198 | [types.SAVE_STATE]: (state: State) => {//new 199 | const { routes, activeRoute, arrayOfStates } = state; 200 | const cloneOfActiveRoute = JSON.parse(JSON.stringify(routes[activeRoute])) 201 | state.arrayOfStates = [...state.arrayOfStates, cloneOfActiveRoute] 202 | if(arrayOfStates.length > 120){ 203 | state.arrayOfStates = arrayOfStates.slice(20, arrayOfStates.length) 204 | } 205 | // console.log("pt 1", arrayOfStates) 206 | }, 207 | // removing the top element of arrayOfStates, aka, the current state 208 | // assigning routes[activeRoute] to state we just removed 209 | [types.RESTORE_STATE]: (state: State) => {//new 210 | const { routes, activeRoute, arrayOfStates } = state; 211 | const prevRoute = arrayOfStates.pop(); 212 | console.log('prevRoute',prevRoute) 213 | // console.log("pt 2", arrayOfStates[arrayOfStates.length - 1]) 214 | routes[activeRoute] = prevRoute; 215 | console.log("pt 2", arrayOfStates) 216 | // console.log("pt 3", routes[activeRoute]) 217 | }, 218 | 219 | [types.SET_COMPONENT_MAP]: (state: State, payload) => { 220 | console.log(payload); 221 | state.componentMap = payload; 222 | }, 223 | [types.DELETE_SELECTED_ELEMENT]: (state: State, payload) => { 224 | console.log(state.selectedElementList) 225 | state.selectedElementList.splice(payload, 1); 226 | }, 227 | [types.SET_STATE]: (state: State, payload) => { 228 | Object.assign(state, payload); 229 | }, 230 | 231 | [types.ADD_ROUTE]: (state: State, payload) => { 232 | state.routes = { 233 | ...state.routes, 234 | [payload]: [] 235 | }; 236 | }, 237 | [types.ADD_ROUTE_TO_COMPONENT_MAP]: (state: State, payload) => { 238 | const { route, children } = payload; 239 | state.componentMap = { 240 | ...state.componentMap, 241 | [route]: { 242 | componentName: route, 243 | children 244 | } 245 | }; 246 | }, 247 | [types.SET_ACTIVE_ROUTE]: (state: State, payload) => { 248 | state.activeRoute = payload; 249 | }, 250 | [types.ADD_COMPONENT_TO_ACTIVE_ROUTE_IN_ROUTE_MAP]: ( 251 | state: State, 252 | payload 253 | ) => { 254 | state.routes[state.activeRoute].push(payload); 255 | }, 256 | [types.SET_ACTIVE_COMPONENT]: (state: State, payload) => { 257 | state.activeComponent = payload; 258 | }, 259 | [types.SET_ROUTES]: (state: State, payload) => { 260 | state.routes = Object.assign({}, payload); 261 | }, 262 | [types.SET_ACTIVE_ROUTE_ARRAY]: (state: State, payload) => { 263 | state.routes[state.activeRoute] = payload; 264 | }, 265 | [types.ADD_COMPONENT_TO_ACTIVE_ROUTE_CHILDREN]: ( 266 | state: State, 267 | payload: string 268 | ) => { 269 | state.componentMap[state.activeRoute].children.push(payload); 270 | }, 271 | [types.DELETE_PROJECT_TAB]: (state: State, payload) => { 272 | // delete project tab functionality yet to be implemented 273 | }, 274 | [types.UPDATE_COMPONENT_CHILDREN_MULTISELECT_VALUE]: ( 275 | state: State, 276 | payload 277 | ) => { 278 | console.log('payload', payload); 279 | state.componentChildrenMultiselectValue = payload; 280 | }, 281 | [types.UPDATE_COMPONENT_CHILDREN_VALUE]: (state: State, payload) => { 282 | const { component, value } = payload; 283 | state.componentMap[component].children = value; 284 | }, 285 | [types.UPDATE_ACTIVE_COMPONENT_CHILDREN_VALUE]: (state: State, payload) => { 286 | state.componentMap[state.activeComponent].children = payload; 287 | }, 288 | [types.UPDATE_COMPONENT_NAME_INPUT_VALUE]: (state: State, payload) => { 289 | state.componentNameInputValue = payload; 290 | }, 291 | [types.ADD_COMPONENT_TO_COMPONENT_CHILDREN]: (state: State, payload) => { 292 | const { component, value } = payload; 293 | state.componentMap[component].children.push(value); 294 | }, 295 | [types.UPDATE_OPEN_MODAL]: (state: State, payload) => { 296 | state.modalOpen = payload; 297 | } 298 | }; 299 | 300 | export default mutations; 301 | -------------------------------------------------------------------------------- /src/store/state/htmlElementMap.ts: -------------------------------------------------------------------------------- 1 | // Map of html element opening and closing tags 2 | 3 | import { HtmlElementMap } from '../../types'; 4 | 5 | const htmlElementMap: HtmlElementMap = { 6 | div: ['
', '
'], 7 | button: [''], 8 | form: ['
', '
'], 9 | img: ['', ''], 10 | link: ['', ''], 11 | 'list-item': ['
  • ', '
  • '], 12 | paragraph: ['

    ', '

    '], 13 | 'list-ol': ['
      ', '
    '], 14 | 'list-ul': ['
      ', '
    '], 15 | input: ['', ''], 16 | navbar: [''] 17 | }; 18 | 19 | export default htmlElementMap; 20 | -------------------------------------------------------------------------------- /src/store/state/icons.ts: -------------------------------------------------------------------------------- 1 | // Font awesome icons for each html tag 2 | 3 | import { Icons } from '../../types'; 4 | 5 | const icons: Icons = { 6 | div: 'far fa-square fa-lg', 7 | button: 'fas fa-toggle-off fa-lg', 8 | form: 'fab fa-wpforms fa-lg', 9 | img: 'far fa-image fa-lg', 10 | link: 'fas fa-link fa-lg', 11 | 'list-item': 'fas fa-circle fa-lg', 12 | paragraph: 'fas fa-paragraph fa-lg', 13 | 'list-ol': 'fas fa-list-ol fa-lg', 14 | 'list-ul': 'fas fa-list-ul fa-lg', 15 | input: 'fas fa-pen fa-lg', 16 | navbar: 'fas fa-window-maximize fa-lg' 17 | }; 18 | 19 | export default icons; 20 | -------------------------------------------------------------------------------- /src/store/state/stateIndex.ts: -------------------------------------------------------------------------------- 1 | // Vuex state, single source of truth 2 | 3 | import icons from './icons'; 4 | import htmlElementMap from './htmlElementMap'; 5 | import { State } from '../../types'; 6 | 7 | const newState: State = { 8 | icons, 9 | htmlElementMap, 10 | componentMap: { 11 | App: { 12 | componentName: 'App', 13 | children: ['HomeView'], 14 | htmlList: [] 15 | }, 16 | HomeView: { 17 | componentName: 'HomeView', 18 | children: [], 19 | htmlList: [] 20 | } 21 | }, 22 | routes: { 23 | HomeView: [] 24 | }, 25 | 26 | componentNameInputValue: '', 27 | activeRoute: 'HomeView', 28 | activeComponent: '', 29 | 30 | activeElement: '', 31 | componentIndex: 0, 32 | elementIndex: 0, 33 | 34 | selectedElementList: [], 35 | projectName: 'Project-Name', 36 | editedProjectName: 'Project-Name', 37 | componentChildrenMultiselectValue: [], 38 | modalOpen: false, 39 | htmlElements: [], 40 | saved: false, 41 | loggedIn: false, 42 | rerenderKey: 0, 43 | 44 | arrayOfStates: [], 45 | }; 46 | 47 | console.log('newState',newState) 48 | 49 | export default newState; 50 | -------------------------------------------------------------------------------- /src/store/storeTypes.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '../types'; 2 | 3 | //Mutations 4 | export const INC_RERENDER_KEY: Type = 'INC_RERENDER_KEY'; 5 | export const SET_LOGIN: Type = 'SET_LOGIN'; 6 | export const REPLACE_STATE: Type = 'REPLACE_STATE'; 7 | export const NAME_PROJECT: Type = 'NAME_PROJECT'; 8 | export const INITIALISESTORE: Type = 'INITIALISESTORE'; 9 | export const ADD_COMPONENT: Type = 'ADD_COMPONENT'; 10 | 11 | export const UPDATE_PROJECT_NAME = 'UPDATE_PROJECT_NAME'; //new 12 | export const SET_ACTIVE_ELEMENT: Type = 'SET_ACTIVE_ELEMENT';//new 13 | export const DELETE_ACTIVE_ELEMENT: Type = 'DELETE_ACTIVE_ELEMENT'//new 14 | export const SET_ELEMENT_INDEX: Type = 'SET_ELEMENT_INDEX';//new 15 | export const SET_COMPONENT_INDEX: Type = 'SET_COMPONENT_INDEX';//new 16 | export const SAVE_STATE: Type = 'SAVE_STATE'; //new 17 | export const RESTORE_STATE: Type = 'RESTORE_STATE'; //new 18 | 19 | export const ADD_COMPONENT_TO_COMPONENT_MAP: Type = 20 | 'ADD_COMPONENT_TO_COMPONENT_MAP'; 21 | export const SET_SELECTED_ELEMENT_LIST: Type = 'SET_SELECTED_ELEMENT_LIST'; 22 | export const ADD_TO_SELECTED_ELEMENT_LIST: Type = 23 | 'ADD_TO_SELECTED_ELEMENT_LIST'; 24 | export const ADD_TO_COMPONENT_HTML_LIST: Type = 'ADD_TO_COMPONENT_HTML_LIST'; 25 | export const SET_CLICKED_ELEMENT_LIST: Type = 'SET_CLICKED_ELEMENT_LIST'; 26 | export const DELETE_ACTIVE_COMPONENT: Type = 'DELETE_ACTIVE_COMPONENT'; 27 | export const SET_COMPONENT_MAP: Type = 'SET_COMPONENT_MAP'; 28 | export const DELETE_FROM_QUEUE: Type = 'DELETE_FROM_QUEUE'; 29 | export const DELETE_SELECTED_ELEMENT: Type = 'DELETE_SELECTED_ELEMENT'; 30 | export const SET_STATE: Type = 'SET_STATE'; 31 | export const ADD_PROJECT: Type = 'ADD_PROJECT'; 32 | export const DELETE_FROM_COMPONENT_HTML_LIST: Type = 33 | 'DELETE_FROM_COMPONENT_HTML_LIST'; 34 | export const CHANGE_ACTIVE_TAB: Type = 'CHANGE_ACTIVE_TAB'; 35 | export const ADD_COMPONENT_TO_ACTIVE_ROUTE_IN_ROUTE_MAP: Type = 36 | 'ADD_COMPONENT_TO_ACTIVE_ROUTE_IN_ROUTE_MAP'; 37 | export const ADD_ROUTE: Type = 'ADD_ROUTE'; 38 | export const SET_ACTIVE_COMPONENT: Type = 'SET_ACTIVE_COMPONENT'; 39 | export const SET_ACTIVE_PROJECT: Type = 'SET_ACTIVE_PROJECT'; 40 | export const SET_ACTIVE_ROUTE: Type = 'SET_ACTIVE_ROUTE'; 41 | export const INCREMENT_PROJECT_ID: Type = 'INCREMENT_PROJECT_ID'; 42 | export const SET_ROUTES: Type = 'SET_ROUTES'; 43 | export const SET_COMPONENT_HTML_LIST: Type = 'SET_COMPONENT_HTML_LIST'; 44 | export const SET_ACTIVE_ROUTE_ARRAY: Type = 'SET_ACTIVE_ROUTE_ARRAY'; 45 | export const ADD_COMPONENT_TO_ACTIVE_ROUTE_CHILDREN: Type = 46 | 'ADD_COMPONENT_TO_ACTIVE_ROUTE_CHILDREN'; 47 | export const ADD_ROUTE_TO_COMPONENT_MAP: Type = 'ADD_ROUTE_TO_COMPONENT_MAP'; 48 | export const DELETE_PROJECT_TAB: Type = 'DELETE_PROJECT_TAB'; 49 | export const UPDATE_COMPONENT_CHILDREN_MULTISELECT_VALUE: Type = 50 | 'UPDATE_COMPONENT_CHILDREN_MULTISELECT_VALUE'; 51 | export const UPDATE_COMPONENT_NAME_INPUT_VALUE: Type = 52 | 'UPDATE_COMPONENT_NAME_INPUT_VALUE'; 53 | export const UPDATE_COMPONENT_CHILDREN_VALUE: Type = 54 | 'UPDATE_COMPONENT_CHILDREN_VALUE'; 55 | export const UPDATE_ACTIVE_COMPONENT_CHILDREN_VALUE: Type = 56 | 'UPDATE_ACTIVE_COMPONENT_CHILDREN_VALUE'; 57 | export const ADD_COMPONENT_TO_COMPONENT_CHILDREN: Type = 58 | 'ADD_COMPONENT_TO_COMPONENT_CHILDREN'; 59 | export const UPDATE_OPEN_MODAL: Type = 'UPDATE_OPEN_MODAL'; 60 | 61 | //Actions 62 | export const registerComponent: Type = 'registerComponent'; 63 | export const setSelectedElementList: Type = 'setSelectedElementList'; 64 | export const addToSelectedElementList: Type = 'addToSelectedElementList'; 65 | export const addToComponentElementList: Type = 'addToComponentElementList'; 66 | export const setClickedElementList: Type = 'setClickedElementList'; 67 | export const deleteActiveComponent: Type = 'deleteActiveComponent'; 68 | export const setComponentMap: Type = 'setComponentMap'; 69 | export const deleteFromQueue: Type = 'deleteFromQueue'; 70 | export const deleteSelectedElement: Type = 'deleteSelectedElement'; 71 | export const setState: Type = 'setState'; 72 | export const addProject: Type = 'addProject'; 73 | export const deleteFromComponentHtmlList: Type = 'deleteFromComponentHtmlList'; 74 | export const changeActiveTab: Type = 'changeActiveTab'; 75 | export const addRouteToRouteMap: Type = 'addRouteToRouteMap'; 76 | export const setActiveComponent: Type = 'setActiveComponent'; 77 | 78 | export const updateProjectName: Type = 'updateProjectName'; //new 79 | export const setActiveElement: Type = 'setActiveElement'; //new 80 | export const deleteActiveElement: Type = 'deleteActiveElement'; //new 81 | export const setComponentIndex: Type = 'setComponentIndex'; //new 82 | export const setElementIndex: Type = 'setElementIndex'; //new 83 | export const saveState: Type = 'saveState'; //new 84 | export const restoreState: Type = 'restoreState'; //new 85 | 86 | 87 | export const setActiveRoute: Type = 'setActiveRoute'; 88 | export const incrementProjectId: Type = 'incrementProjectId'; 89 | export const setRoutes: Type = 'setRoutes'; 90 | export const setComponentHtmlList: Type = 'setComponentHtmlList'; 91 | export const deleteProjectTab: Type = 'deleteProjectTab'; 92 | export const updateComponentChildrenMultiselectValue: Type = 93 | 'updateComponentChildrenMultiselectValue'; 94 | export const updateActiveComponentChildrenValue: Type = 95 | 'updateActiveComponentChildrenValue'; 96 | export const updateComponentChildrenValue: Type = 97 | 'updateComponentChildrenValue'; 98 | export const updateComponentNameInputValue: Type = 99 | 'updateComponentNameInputValue'; 100 | export const updateOpenModal: Type = 'updateOpenModal'; 101 | export const addElement: Type = 'addElement'; 102 | export const initialiseStore: Type = 'initialiseStore'; 103 | export const nameProject: Type = 'nameProject'; 104 | export const replaceState: Type = 'replaceState'; 105 | export const setLogin: Type = 'setLogin'; 106 | export const incRerenderKey: Type = 'incRerenderKey'; 107 | 108 | export const resetComponentChildrenMultiselectValue: Type = 109 | 'resetComponentChildrenMultiselectValue'; 110 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | // TypeScript - type declaration file 2 | 3 | export type Icons = { 4 | div: string; 5 | button: string; 6 | form: string; 7 | img: string; 8 | link: string; 9 | 'list-item': string; 10 | paragraph: string; 11 | 'list-ol': string; 12 | 'list-ul': string; 13 | input: string; 14 | navbar: string; 15 | }; 16 | 17 | export type HtmlElementMap = { 18 | div: string[]; 19 | button: string[]; 20 | form: string[]; 21 | img: string[]; 22 | link: string[]; 23 | 'list-item': string[]; 24 | paragraph: string[]; 25 | 'list-ol': string[]; 26 | 'list-ul': string[]; 27 | input: string[]; 28 | navbar: string[]; 29 | }; 30 | 31 | export type Component = { 32 | componentName: string; 33 | children: string[]; 34 | htmlList: HtmlList; 35 | isActive?: boolean; 36 | x?: number; 37 | y?: number; 38 | h?: number; 39 | w?: number; 40 | id?: number; 41 | }; 42 | export type ComponentMap = { 43 | [k: string]: Component; 44 | }; 45 | 46 | export type Routes = { 47 | [k: string]: string[]; 48 | }; 49 | 50 | export type Project = { 51 | filename: string; 52 | lastSavedLocation: string; 53 | }; 54 | export type Type = string; 55 | 56 | export type State = { 57 | icons: Icons; 58 | htmlElementMap: HtmlElementMap; 59 | componentMap: ComponentMap; 60 | routes: Routes; 61 | componentNameInputValue: string; 62 | activeRoute: string; 63 | activeComponent: string; 64 | 65 | activeElement: string; //new 66 | componentIndex: number; //new 67 | elementIndex: number; //new 68 | 69 | projectName: string; 70 | editedProjectName: string; 71 | selectedElementList: object[]; 72 | componentChildrenMultiselectValue: string[]; 73 | modalOpen: boolean; 74 | htmlElements: any[]; 75 | saved: boolean; 76 | loggedIn: boolean; 77 | rerenderKey: number; 78 | 79 | arrayOfStates: Array; 80 | }; 81 | 82 | // export type StateQueue = State[] 83 | export type Mutations = { 84 | [k: Type]: ( 85 | state: State, 86 | payload?: any, 87 | elementName?: string, 88 | id?: number 89 | ) => void; 90 | }; 91 | export type Actions = { 92 | [k: Type]: (context: any, payload?: any) => void; 93 | }; 94 | 95 | export type HtmlChild = { 96 | text: string; 97 | children: HtmlChild[]; 98 | _id?: number; 99 | x?: number; 100 | y?: number; 101 | w?: number; 102 | h?: number; 103 | }; 104 | 105 | export type HtmlList = HtmlChild[]; 106 | -------------------------------------------------------------------------------- /src/views/HomeView.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 76 | 77 | 84 | -------------------------------------------------------------------------------- /src/views/SplashView.vue: -------------------------------------------------------------------------------- 1 | 218 | 219 | 279 | 280 | -------------------------------------------------------------------------------- /src/views/TreeView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 23 | -------------------------------------------------------------------------------- /tests/integration/supertest.spec.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const mongoose = require('mongoose'); 3 | let server; 4 | // const server = require('../../server/server'); 5 | const accountRouter = require('../../server/routes/accountRouter'); 6 | const projectRouter = require('../../server/routes/projectRouter'); 7 | const authController = require('../../server/controllers/authController'); 8 | const sinon = require('sinon'); 9 | 10 | beforeAll(async () => { 11 | // connect to MongoDB before all tests 12 | await mongoose.connect(process.env.MONGO_URI, { 13 | useNewUrlParser: true, 14 | useUnifiedTopology: true 15 | }); 16 | }); 17 | 18 | afterAll(async () => { 19 | // disconnect from MongoDB after all tests 20 | await mongoose.disconnect(); 21 | }); 22 | 23 | let sandbox; 24 | let authenticateStub; 25 | 26 | beforeEach(() => { 27 | // create sinon sandbox to simplify working with fakes that need to be restored, eases cleanup 28 | sandbox = sinon.createSandbox(); 29 | 30 | // create sinon stub (object) for the authenticate middleware 31 | authenticateStub = sandbox.stub(authController, 'authenticate'); 32 | 33 | // in mock auth middleware, mock res.locals.username and res.locals.id to be the values from testAccount 34 | authenticateStub.callsFake((req, res, next) => { 35 | res.locals.username = testAccount.username; 36 | res.locals.id = testAccount.id; 37 | res.cookie('ssid', 'test token', { httpOnly: true }); 38 | next(); 39 | }); 40 | 41 | // use the mock middleware by replacing the original middleware in the middleware chain 42 | // server.use(authController.authenticate); 43 | 44 | // create server/app AFTER stubbing authenticate middleware (so stub is made before a reference to authController.authenticate is set up when creating app) 45 | server = require('../../server/server'); 46 | }); 47 | 48 | afterEach(() => { 49 | // restore original middleware function after each test 50 | sandbox.restore(); 51 | }); 52 | 53 | describe('/projects projectRouter', () => { 54 | // make a testAccount 55 | const testAccount = { 56 | username: 'TestProjectOwner', 57 | id: '12345678', 58 | project_ids: ['1'] 59 | }; 60 | 61 | // make a testProject 62 | const testProject = { 63 | project_name: 'Test Project', 64 | projectObject: { state: 'state' }, 65 | projectOwner: 'TestProjectOwner' 66 | }; 67 | 68 | describe('find a project, GET to /find', () => { 69 | it('responds with 200 status', () => { 70 | return request(server) 71 | .get('/projects/find') 72 | .expect(200); 73 | }); 74 | 75 | it('should retrieve all projects from the database', () => {}); 76 | }); 77 | 78 | describe('saving a project, POST to /saveProject', () => { 79 | it('responds with 201 status', () => { 80 | return request(server) 81 | .post('/projects/saveProject') 82 | .send(testProject) // sends the testProject in req.body 83 | .expect(201); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } -------------------------------------------------------------------------------- /tests/unit/App.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount, mount } from '@vue/test-utils'; 2 | import { describe, it, expect } from 'vitest'; 3 | import { createRouter, createWebHistory } from 'vue-router'; 4 | import { createStore } from 'vuex'; 5 | import App from '@/App.vue'; 6 | import NavBar from '@/components/NavBar.vue'; 7 | 8 | describe('App.vue', () => { 9 | const router = createRouter({ 10 | history: createWebHistory(), 11 | routes: [ 12 | { 13 | path: '/', 14 | component: App, 15 | meta: { 16 | hideNavbar: true 17 | } 18 | } 19 | ] 20 | }); 21 | 22 | const store = createStore({ 23 | state() { 24 | return {}; 25 | } 26 | }); 27 | 28 | it('contains v-main element that wraps router-view', () => { 29 | const wrapper = shallowMount(App, { 30 | global: { 31 | plugins: [router, store] 32 | } 33 | }); 34 | expect(wrapper.find('v-main')).toBeTruthy(); 35 | expect(wrapper.find('v-main > router-view')).toBeTruthy(); 36 | }); 37 | }); 38 | 39 | describe('App.vue', () => { 40 | // create store with fake properties 41 | const store = createStore({ 42 | state() { 43 | return { 44 | store1: '', 45 | store2: '', 46 | componentNameInputValue: '' 47 | }; 48 | } 49 | }); 50 | 51 | const router = createRouter({ 52 | history: createWebHistory(), 53 | routes: [ 54 | { 55 | path: '/', 56 | component: App, 57 | meta: { 58 | hideNavbar: true 59 | } 60 | } 61 | ] 62 | }); 63 | it('should renders elements found in children', () => { 64 | const wrapper = mount(App, { 65 | components: { 66 | NavBar 67 | }, 68 | global: { 69 | plugins: [store, router] 70 | } 71 | }); 72 | expect(wrapper.find('h1').exists()).toBeTruthy(); 73 | }); 74 | 75 | it('should renders elements found in children', () => { 76 | const wrapper = mount(App, { 77 | components: { 78 | NavBar 79 | }, 80 | global: { 81 | plugins: [store, router] 82 | } 83 | }); 84 | expect(wrapper.findComponent({ name: 'NavBar' }).exists()).toBeTruthy(); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /tests/unit/ChildrenMultiselect.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount, mount, createVuexStore } from '@vue/test-utils'; 2 | import { createStore } from 'vuex'; 3 | import { describe, it, expect, vi } from 'vitest'; 4 | import ChildrenMultiselect from '@/components/ChildrenMultiselect.vue'; 5 | import VueMultiselect from 'vue-multiselect'; 6 | 7 | describe('ChildrenMultiselect', () => { 8 | const store = createStore({ 9 | state() { 10 | return { 11 | routes: {}, 12 | componentMap: {} 13 | }; 14 | } 15 | }); 16 | it('properly renders div container', () => { 17 | const wrapper = mount(ChildrenMultiselect, { 18 | global: { plugins: [store] } 19 | }); 20 | expect(wrapper.find('div').exists()).toBeTruthy(); 21 | }); 22 | 23 | it('properly renders children component', () => { 24 | const wrapper = mount(ChildrenMultiselect, { 25 | components: { VueMultiselect }, 26 | global: { plugins: [store] } 27 | }); 28 | expect( 29 | wrapper.findComponent({ name: 'VueMultiselect' }).exists() 30 | ).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /tests/unit/Component.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount, mount, createVuexStore } from '@vue/test-utils'; 2 | import { createStore } from 'vuex'; 3 | import { describe, it, expect, vi } from 'vitest'; 4 | import Vue3DraggableResizable from 'vue3-draggable-resizable'; 5 | import Component from '@/components/Component.vue'; 6 | 7 | describe('Component.vue', () => { 8 | it('properly renders the html element of children', () => { 9 | const wrapper = mount(Component); 10 | expect(wrapper.find('h3').exists()).toBeTruthy(); 11 | expect(wrapper.find('h1').exists()).toBeFalsy(); 12 | expect(wrapper.find('div').exists()).toBeTruthy(); 13 | expect(wrapper.find('h5').exists()).toBeFalsy(); 14 | }); 15 | }); 16 | 17 | describe('Component.vue', () => { 18 | it('properly imports Vue3Draggable and renders the component', () => { 19 | const store = createStore({ 20 | state() { 21 | return { 22 | store1: '', 23 | store2: '' 24 | }; 25 | } 26 | }); 27 | 28 | const wrapper = mount(Component, { 29 | components: { 30 | Vue3DraggableResizable 31 | }, 32 | global: { 33 | plugins: [store] 34 | } 35 | }); 36 | expect( 37 | wrapper.findComponent({ name: 'Vue3DraggableResizable' }).exists() 38 | ).toBeTruthy(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /tests/unit/ComponentDisplay.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount, mount } from '@vue/test-utils'; 2 | import { createStore } from 'vuex'; 3 | import { describe, it, expect } from 'vitest'; 4 | import ComponentDisplay from '@/components/ComponentDisplay.vue'; 5 | import Vue3DraggableResizable from 'vue3-draggable-resizable'; 6 | 7 | describe('ComponentDisplay', () => { 8 | const store = createStore({ 9 | state() { 10 | return { 11 | activeComponent: 'active', 12 | activeRoute: 'test', 13 | routes: { 14 | test: ["test"], 15 | }, 16 | componentMap: { 17 | active: { 18 | children: [], 19 | }, 20 | }, 21 | }; 22 | }, 23 | }); 24 | it('properly renders HTML element', () => { 25 | const wrapper = shallowMount(ComponentDisplay, { 26 | global: { plugins: [store] }, 27 | }); 28 | 29 | expect(wrapper.find('div').exists()).toBeTruthy(); 30 | }); 31 | 32 | it('properly renders children component', () => { 33 | const wrapper = mount(ComponentDisplay, { 34 | components: { Vue3DraggableResizable }, 35 | global: { plugins: [store] }, 36 | }); 37 | expect( 38 | wrapper.findComponent({ name: 'Vue3DraggableResizable' }).exists() 39 | ).toBeTruthy(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /tests/unit/HomeQueue.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount, mount } from '@vue/test-utils'; 2 | import { createStore } from 'vuex'; 3 | import { describe, it, expect } from 'vitest'; 4 | import HomeQueue from '@/components/HomeQueue.vue'; 5 | import draggable from 'vuedraggable'; 6 | 7 | describe('HomeQueue', () => { 8 | const store = createStore({ 9 | state() { 10 | return {}; 11 | }, 12 | }); 13 | it('properly renders children component', () => { 14 | const wrapper = mount(HomeQueue, { 15 | components: { draggable }, 16 | global: { plugins: [store] }, 17 | }); 18 | expect(wrapper.findComponent({ name: 'draggable' }).exists()).toBeTruthy(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/unit/HomeSidebar.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount, mount, createVuexStore } from '@vue/test-utils'; 2 | import { createStore } from 'vuex'; 3 | import HomeSidebar from '@/components/HomeSidebar.vue'; 4 | import Icons from '@/components/Icons.vue'; 5 | import ChildrenMultiselect from '@/components/ChildrenMultiselect.vue'; 6 | 7 | describe('HomeSidebar.vue', () => { 8 | const store = createStore({ 9 | state() { 10 | return { 11 | store1: '', 12 | store2: '', 13 | componentNameInputValue: '' 14 | }; 15 | } 16 | }); 17 | it('properly renders elements and checks to see proper wrapping', () => { 18 | const wrapper = shallowMount(HomeSidebar, { 19 | components: { 20 | ChildrenMultiselect, 21 | Icons 22 | }, 23 | global: { 24 | plugins: [store] 25 | } 26 | }); 27 | expect(wrapper.find('v-card')).toBeTruthy(); 28 | expect(wrapper.find('v-card-actions > router-view')).toBeTruthy(); 29 | }); 30 | 31 | it('renders the ChildrenMultiselect component', () => { 32 | const wrapper = shallowMount(HomeSidebar, { 33 | components: { 34 | ChildrenMultiselect, 35 | Icons 36 | }, 37 | global: { 38 | plugins: [store] 39 | } 40 | }); 41 | expect( 42 | wrapper.findComponent({ name: 'ChildrenMultiselect' }).exists() 43 | ).toBeTruthy(); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /tests/unit/Icons.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils'; 2 | import { createStore } from 'vuex'; 3 | import { describe, it, expect, vi } from 'vitest'; 4 | import Icons from '@/components/Icons.vue'; 5 | 6 | describe('Icons', () => { 7 | const store = createStore({ 8 | state() { 9 | return { 10 | icons: { elementName: 'previousElement' } 11 | }; 12 | } 13 | }); 14 | 15 | it('after button, elementName is changed to newElement', () => { 16 | const wrapper = shallowMount(Icons, { 17 | global: { plugins: [store] } 18 | }); 19 | 20 | // wrapper.vm is Vue instance of mounted component, containing changeState function 21 | // spy will be object of changeState's metadata 22 | const spy = vi.spyOn(wrapper.vm, 'changeState'); 23 | const button = wrapper.find('button'); 24 | button.trigger('click'); 25 | 26 | expect(spy.called).toBeTruthy(); 27 | expect(spy.callCount).toEqual(1); 28 | 29 | button.trigger('click'); 30 | expect(spy.callCount).toEqual(2); 31 | 32 | vi.restoreAllMocks(); 33 | 34 | expect(spy.callCount).toEqual(0); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /tests/unit/RouteDisplay.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount, mount } from '@vue/test-utils'; 2 | import { createStore } from 'vuex'; 3 | import { describe, it, expect } from 'vitest'; 4 | import RouteDisplay from '@/components/RouteDisplay.vue'; 5 | import Routes from '@/components/Routes.vue'; 6 | 7 | describe('RouteDisplay', () => { 8 | const store = createStore({ 9 | state() { 10 | return { 11 | routes: { 12 | test: ["test"], 13 | }, 14 | }; 15 | }, 16 | }); 17 | it('properly renders HTML elements', () => { 18 | const wrapper = shallowMount(RouteDisplay, { 19 | global: { plugins: [store] }, 20 | }); 21 | 22 | expect(wrapper.find('strong').exists()).toBeTruthy(); 23 | }); 24 | 25 | it('properly renders children component', () => { 26 | const wrapper = mount(RouteDisplay, { 27 | components: { Routes }, 28 | global: { plugins: [store] }, 29 | }); 30 | expect( 31 | wrapper.findComponent({ name: 'Routes' }).exists() 32 | ).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /tests/unit/TreeGraph.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount, mount } from '@vue/test-utils'; 2 | import { createStore } from 'vuex'; 3 | import { describe, it, expect } from 'vitest'; 4 | import TreeGraph from '@/components/TreeGraph.vue'; 5 | import VueTree from '@ssthouse/vue3-tree-chart'; 6 | 7 | describe('TreeGraph', () => { 8 | const store = createStore({ 9 | state() { 10 | return { 11 | componentMap: {}, 12 | }; 13 | }, 14 | }); 15 | it('properly renders HTML element', () => { 16 | const wrapper = shallowMount(TreeGraph, { 17 | global: { plugins: [store] }, 18 | }); 19 | 20 | expect(wrapper.find('div').exists()).toBeTruthy(); 21 | }); 22 | 23 | it('properly renders children component', () => { 24 | const wrapper = mount(TreeGraph, { 25 | components: { VueTree }, 26 | global: { plugins: [store] }, 27 | }); 28 | expect(wrapper.findComponent({ name: 'VueTree' }).exists()).toBeTruthy(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/unit/__snapshots__/App.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`App.vue > renders App correctly 1`] = ` 4 |
    7 | 8 | 11 | 12 | 16 | 19 | 20 | 21 | 22 | 23 | 32 | 33 |
    34 | `; 35 | 36 | exports[`App.vue > renders App correctly 2`] = ` 37 |
    40 | 41 | 44 | 45 | 49 | 52 | 53 | 54 | 55 | 56 | 65 | 66 |
    67 | `; 68 | 69 | exports[`App.vue > renders html correct 1`] = ` 70 |
    73 | 74 | 77 | 78 | 82 | 85 | 86 | 87 | 88 | 89 | 98 | 99 |
    100 | `; 101 | 102 | exports[`App.vue > renders html correctly 1`] = ` 103 |
    106 | 107 | 110 | 111 | 115 | 118 | 119 | 120 | 121 | 122 | 131 | 132 |
    133 | `; 134 | 135 | exports[`App.vue renders correct 1`] = ` 136 |
    140 | 141 | 142 |
    145 | 146 |
    147 |
    148 | `; 149 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "jsx": "preserve", 8 | "resolveJsonModule": true, 9 | "isolatedModules": true, 10 | "esModuleInterop": true, 11 | "lib": ["ESNext", "DOM"], 12 | "skipLibCheck": true, 13 | "allowJs": true 14 | }, 15 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], 16 | "references": [{ "path": "./tsconfig.node.json" }] 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vite'; 3 | import vue from '@vitejs/plugin-vue'; 4 | import checker from 'vite-plugin-checker'; 5 | const path = require('path'); 6 | export default defineConfig({ 7 | plugins: [vue()], 8 | test: { 9 | globals: true, 10 | environment: 'jsdom' 11 | }, 12 | resolve: { 13 | alias: { 14 | '@': path.resolve(__dirname, './src') 15 | } 16 | }, 17 | server: { 18 | host: '0.0.0.0', 19 | port: 4173, 20 | proxy: { 21 | '/users': { 22 | target: 'http://localhost:8080', 23 | changeOrigin: true, 24 | secure: false 25 | } 26 | } 27 | } 28 | }); 29 | --------------------------------------------------------------------------------