├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── Dockerfile ├── README.md ├── build ├── icon.icns └── icon.ico ├── favicon.ico ├── index.html ├── jest.config.js ├── package.json ├── prevue-prod.zip ├── 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 │ ├── PreVueDemo.gif │ ├── PreVueDemo.mp4 │ ├── PreVueExportDemo.gif │ ├── background.jpg │ ├── componentdisplay.png │ ├── homeview.png │ ├── logo.png │ ├── logo.svg │ ├── newcomp.png │ ├── newcompvue.png │ ├── prevue-large-green.png │ ├── prevue-large.png │ ├── prevue-logo.png │ ├── prevue.png │ ├── prevue_color_white.png │ ├── robert-photo.jpeg │ ├── routecreator.png │ ├── sean-photo.jpeg │ ├── tree-demo.png │ ├── treeview.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 -------------------------------------------------------------------------------- /.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 |

PreVue

4 |

5 | 6 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/teamprevue/PreVue/pulls) 7 | ![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg) 8 | 9 |

10 | All in One Prototyping Tool 11 | For Vue Developers 12 | 13 |

14 | 15 |

16 | From Component Architecture to Code Exporting 17 |

18 | 19 | PreVue allows users to conceptualize and visualize component architecture by allowing them to : 20 | 21 | 1. Create components and preview their associated code 22 | 2. Set up different routes and views 23 | 3. Establish parent-child component relationships 24 | 4. View application hierarchy in tree format 25 | 5. Export the component architecture as a Vue application created with default Vite settings. 26 | 27 | Use PreVue to create projects in single sessions or sign in with GitHub to save projects and update them anytime. 28 | the component architecture as a Vue application created with the default Vue CLI settings. 29 | 30 |

31 | 32 | 33 |

34 | 35 | ## Getting Started 36 | 37 | 38 | ## How to use 39 | 40 | --- 41 | 42 | #### Adding Components 43 | 44 | - Double click on the application icon 45 | - Create components by entering a name and clicking the HTML elements you need 46 | - Clicked elements will be shown in the right sidebar 47 | - Drag the elements to change their order 48 | - Once you're satisfied, click the button to ‘add a component’ and it will show up in the working area. Resize and move components to fit the design you have in mind. 49 | 50 | 51 | 52 | 53 | #### Editing Components 54 | 55 | - Double click elements to bring up the modal view 56 | - Add additional elements to a component with a live preview of the component code 57 | - Drag elements on the right side bar to nest elements 58 | - Establish parent-child component relationships via the dropdown menu when creating or editing components 59 | 60 | 61 | 62 | 63 | #### Adding Routes 64 | 65 | - Create different routes that represent different Views for your app. 66 | - Any components created on a given route will be automatically saved to that route 67 | - See your application’s hierarchy by clicking the ‘Tree’ icon in the navigation bar 68 | 69 |

70 | 71 |

72 | 73 | #### Tree View of Application Architecture 74 | 75 |

76 | 77 | 78 |

79 | 80 | #### Saving/Opening/Exporting Projects 81 | 82 | - In order to utilize the saving and opening functionality of PreVue, please clone the repo to run on your local machine. 83 | - If you're signed in with GitHub, click the ‘Save Project’ icon to save it to PreVue’s database 84 | - Click ‘Open Project’ to retrieve past projects 85 | - Once you're satisfied, click the export project icon to export your awesome project as new Vue application! 86 | - Other users can use PreVue's playground to create and export projects in single sessions. 87 | 88 | 89 | 90 | 91 | ##### Code Exporting 92 | 93 | Below is the generated directory structure of the Vue application that is created when you export your design. 94 | 95 | ``` 96 | src/ 97 | assets/ 98 | App.vue 99 | components/ 100 | UserCreatedComponent1.vue 101 | UserCreatedComponent2.vue 102 | ... 103 | views/ 104 | HomeView.vue 105 | UserCreatedRouteComponent1.vue 106 | UserCreatedRouteComponent2.vue 107 | ... 108 | ``` 109 | ## Running your own local version 110 | 111 | 112 | ### Setup 113 | 114 | Clone this repo 115 | 116 | ``` 117 | git clone https://github.com/oslabs/PreVue.git 118 | ``` 119 | 120 | Install dependencies 121 | 122 | ``` 123 | npm i 124 | ``` 125 | Build the app 126 | 127 | ``` 128 | npm run build 129 | ``` 130 | Run the app 131 | 132 | ``` 133 | npm run server 134 | ``` 135 | 136 | Go to http://localhost:8080 to use PreVue! 137 | 138 | ## Built With 139 | 140 | --- 141 | 142 | - [Vue.js](https://vuejs.org/) 143 | - [Vue Router](https://router.vuejs.org/guide/#html) 144 | - [Vuex](https://vuex.vuejs.org/) 145 | - [Vite](https://vitejs.dev/) 146 | - [Vuetify](https://vuetifyjs.com/) 147 | - [Jest](https://jestjs.io/) 148 | - [SuperTest](https://www.npmjs.com/package/supertest) 149 | 150 | ## Contributing 151 | 152 | --- 153 | 154 | PreVue 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. Also follow PreVue on [LinkedIn](https://www.linkedin.com/company/prevue-live/) for more updates. 155 | 156 | ## Authors 157 | 158 | --- 159 | PreVue 2.0 160 | - **Jason Boo** [@jasonboo123](https://github.com/jasonboo123) 161 | - **Robert Drake** [@rmdrake8](https://github.com/rmdrake8) 162 | - **Sean Flynn** [@seanflynn5](http://github.com/seanflynn5) 163 | - **Zach Pestaina** [@zachpestaina](https://github.com/zachpestaina) 164 | 165 | PreVue 1.0 166 | - **Hubert Lin** [@hubelin](https://github.com/hubelin) 167 | - **Franklin Pinnock** [@pinnockf](https://github.com/pinnockf) 168 | - **Annette Lin** [@al2613](https://github.com/al2613) 169 | - **Daniel Shu** [@danshuu](https://github.com/danshuu) 170 | 171 | ## License 172 | 173 | --- 174 | 175 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 176 | -------------------------------------------------------------------------------- /build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/build/icon.icns -------------------------------------------------------------------------------- /build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/build/icon.ico -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/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": "2.0.0", 8 | "description": "Developer prototyping app built with Vue", 9 | "scripts": { 10 | "dev": "concurrently \"npm run vite\" \"npm run server\"", 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 | }, 18 | "license": "MIT", 19 | "dependencies": { 20 | "@he-tree/vue": "^2.2.7", 21 | "@progress/jszip-esm": "^1.0.3", 22 | "@ssthouse/vue3-tree-chart": "^0.2.6", 23 | "axios": "^1.3.4", 24 | "concurrently": "^7.6.0", 25 | "connect-history-api-fallback": "^2.0.0", 26 | "cookie-parser": "^1.4.6", 27 | "cors": "^2.8.5", 28 | "express": "^4.18.2", 29 | "file-saver": "^2.0.5", 30 | "fs-extra": "^7.0.1", 31 | "handlebars": "^4.7.7", 32 | "happy-dom": "^8.9.0", 33 | "he-tree-vue": "^3.1.2", 34 | "jsonwebtoken": "^9.0.0", 35 | "mongodb": "^5.1.0", 36 | "mongodb-connection-string-url": "^2.6.0", 37 | "mongoose": "^6.10.0", 38 | "mousetrap": "^1.6.3", 39 | "sass": "^1.58.3", 40 | "vue": "^3.2.47", 41 | "vue-multiselect": "^3.0.0-alpha.2", 42 | "vue-router": "^4.1.6", 43 | "vue-template-compiler": "^2.6.11", 44 | "vue3-draggable-resizable": "^1.6.5", 45 | "vued3tree": "^3.6.4", 46 | "vuedraggable": "^4.1.0", 47 | "vuetify": "^3.1.6" 48 | }, 49 | "devDependencies": { 50 | "@typescript-eslint/eslint-plugin": "^5.54.0", 51 | "@typescript-eslint/parser": "^5.54.0", 52 | "@vitejs/plugin-vue": "^4.0.0", 53 | "@vue/eslint-config-prettier": "^4.0.1", 54 | "@vue/test-utils": "^2.3.1", 55 | "dotenv": "^16.0.3", 56 | "eslint": "^8.35.0", 57 | "eslint-plugin-typescript": "^0.14.0", 58 | "eslint-plugin-vue": "^8.7.1", 59 | "jest": "^29.5.0", 60 | "jsdom": "^21.1.0", 61 | "sinon": "^15.0.1", 62 | "supertest": "^6.3.3", 63 | "typescript": "^4.9.5", 64 | "vite": "^4.1.4", 65 | "vitest": "^0.29.2", 66 | "vue-eslint-parser": "^9.1.0", 67 | "vue-template-compiler": "^2.5.21", 68 | "vue-tsc": "^1.0.24", 69 | "vuex": "^4.1.0" 70 | }, 71 | "main": "background.js", 72 | "directories": { 73 | "test": "tests" 74 | }, 75 | "repository": { 76 | "type": "git", 77 | "url": "git+https://github.com/oslabs-beta/PreVue.git" 78 | }, 79 | "bugs": { 80 | "url": "https://github.com/oslabs-beta/PreVue/issues" 81 | }, 82 | "homepage": "https://github.com/oslabs-beta/PreVue#readme" 83 | } 84 | -------------------------------------------------------------------------------- /prevue-prod.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/prevue-prod.zip -------------------------------------------------------------------------------- /server/controllers/accountController.js: -------------------------------------------------------------------------------- 1 | const Users = require('../models/accountModels'); 2 | const accountController = {}; 3 | 4 | // enters a user into the database after GitHub OAuth if an entry 5 | // does not already exist 6 | accountController.createUser = (req, res, next) => { 7 | const { username, id } = res.locals; 8 | //Making sure username does not exist 9 | Users.findOne({ username }) 10 | .then(data => { 11 | if (!data) { 12 | Users.create({ username, id }).then(data => { 13 | //data here is full entry, includes _id key 14 | res.locals.id = data._id; // sending ID for cookie auth 15 | return next(); 16 | }); 17 | } else { 18 | return next(); 19 | } 20 | }) 21 | .catch(err => { 22 | next({ 23 | log: `accountController.createUser failed: ${err}`, 24 | message: `User already exists!` 25 | }); 26 | }); 27 | }; 28 | 29 | // showing the logged in user's projects before the mounting 30 | // of the home page 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 | // write code here 48 | // const { username } = req.body; 49 | Users.find({}) 50 | .then(data => { 51 | res.locals.username = data; 52 | return next(); 53 | }) 54 | .catch(err => { 55 | // if (err.message === `Username Doesn't Exist`) res.redirect("/signup"); 56 | return next({ 57 | log: err, 58 | error: `error found in userController.findUser` 59 | }); 60 | }); 61 | }; 62 | 63 | module.exports = accountController; 64 | -------------------------------------------------------------------------------- /server/controllers/authController.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | require('dotenv').config(); 3 | 4 | // the secret key generated when integrating your app with Github OAuth 5 | const privateKey = process.env.SECRET_KEY; 6 | 7 | const authController = {}; 8 | 9 | // authenticates user base on info stored on the JWT 10 | authController.authenticate = (req, res, next) => { 11 | try { 12 | const { ssid } = req.cookies; 13 | //decoded becomes {id,username} 14 | const decoded = jwt.verify(ssid, privateKey); 15 | res.locals.username = decoded.username; 16 | res.locals.id = decoded.id; 17 | return next(); 18 | } catch (err) { 19 | return next({ 20 | log: `authController.authenticate failed: ${err}`, 21 | message: `An authentication error occured.` 22 | }); 23 | } 24 | }; 25 | 26 | // assigns a JWT to a user upon login 27 | authController.sign = (req, res, next) => { 28 | try { 29 | const { username, id } = res.locals; 30 | const token = jwt.sign( 31 | { 32 | username, 33 | id 34 | }, 35 | privateKey, 36 | { expiresIn: '6h' } 37 | ); 38 | res.locals.token = token; 39 | return next(); 40 | } catch (err) { 41 | return next(err); 42 | } 43 | }; 44 | 45 | module.exports = authController; 46 | -------------------------------------------------------------------------------- /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.toString(); 10 | let newStr = GITHUB_REDIRECT_URI.toString(); 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 | console.log('res.locals', res.locals.url); 25 | return next(); 26 | } catch (error) { 27 | return next({ 28 | log: 'Error occurred in the oauthController.oAuthLogin middleware', 29 | status: 400, // bad request 30 | err: { 31 | err: 'Error occurred in sending user to login to GitHub to login' 32 | } 33 | }); 34 | } 35 | }; 36 | 37 | // 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) 38 | oAuthController.requestGitHubIdentity = async (req, res, next) => { 39 | try { 40 | console.log('reached requestGitHubIdentity controller'); 41 | const { code } = req.query; 42 | const { data } = await axios.post( 43 | GITHUB_ACCESS_TOKEN_REQUEST_URL, 44 | { 45 | client_id: GITHUB_OAUTH_CLIENT_ID, 46 | client_secret: GITHUB_OAUTH_CLIENT_SECRET, 47 | code 48 | }, 49 | { 50 | headers: { 51 | Accept: 'application/json' 52 | } 53 | } 54 | ); 55 | console.log(data); 56 | 57 | // if all's good, attach access_token to res.locals and move on! 58 | res.locals.access_token = data.access_token; 59 | return next(); 60 | } catch (error) { 61 | console.log(error); 62 | return next({ 63 | log: `Error occurred in the oauthController.requestGitHubIdentity middleware\n Error: ${error.message}`, 64 | status: 400, // bad request 65 | err: { err: 'Error occurred in getting your Github user identity' } 66 | }); 67 | } 68 | }; 69 | 70 | // how a given user is actually authenticated 71 | // https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-the-authenticated-user 72 | oAuthController.queryGitHubAPIWithAccessToken = async (req, res, next) => { 73 | try { 74 | const auth = res.locals.access_token; 75 | const { data } = await axios.get('https://api.github.com/user', { 76 | headers: { Authorization: `Bearer ${auth}` } 77 | }); 78 | console.log('response from the api'); 79 | console.log(data); 80 | 81 | // set info from api to res.locals 82 | res.locals = { 83 | ...res.locals, 84 | ...processGitHubData(data) 85 | }; 86 | 87 | return next(); 88 | } catch (error) { 89 | return next({ 90 | log: `Error occurred in the oauthController.queryGitHubAPIWithAccessToken middleware\n Error: ${error.message}`, 91 | status: 400, // bad request 92 | err: { err: 'Error occurred in querying Github API with access token' } 93 | }); 94 | } 95 | }; 96 | 97 | // // Helper function for converting Github API data to fields for database input 98 | function processGitHubData(data) { 99 | const { login, id } = data; 100 | // only works with two names 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 | // we query the database to see if a project with the name on the req.body already exists 7 | // if not, create a project; if so, update the project under that name with req.body.projectObject 8 | projectController.saveProject = (req, res, next) => { 9 | const { project_name, projectObject } = req.body; 10 | Project.findOne({ project_name }).then(data => { 11 | if (!data) { 12 | Project.create({ 13 | project_name, 14 | projectObject, 15 | projectOwner: res.locals.username 16 | }) 17 | .then(data => { 18 | res.locals.newProject = data.projectObject; 19 | res.locals.projectName = data.project_name; 20 | return next(); 21 | }) 22 | .catch(err => { 23 | next({ 24 | log: `projectController.saveProject failed, ${err}`, 25 | message: `Can't save new project!` 26 | }); 27 | }); 28 | } else { 29 | // if the project already exists, it is updated with the new state 30 | Project.findOneAndUpdate( 31 | { project_name }, 32 | { projectObject: req.body.projectObject } 33 | ) 34 | .then(data => { 35 | res.locals.newProject = data.projectObject; 36 | res.locals.projectName = data.project_name; 37 | return next(); 38 | }) 39 | .catch(err => { 40 | next({ 41 | log: `projectController.saveProject failed, ${err}`, 42 | message: `Can't update project!` 43 | }); 44 | }); 45 | } 46 | }); 47 | }; 48 | 49 | // updates project_ids of of User who is saving a project 50 | // if it already exists in their projects array, it is not added 51 | projectController.userQuery = (req, res, next) => { 52 | User.findOneAndUpdate( 53 | { username: res.locals.username }, 54 | { $addToSet: { project_ids: res.locals.projectName } }, 55 | { new: true } 56 | ) 57 | .then(data => { 58 | res.locals.user = data; 59 | return next(); 60 | }) 61 | .catch(err => { 62 | next({ 63 | log: `projectController.userQuery failed, ${err}`, 64 | message: `user already exists!` 65 | }); 66 | }); 67 | }; 68 | 69 | // for retrieving projects ('open project' on frontend) 70 | projectController.getProject = (req, res, next) => { 71 | Project.findOne({ 72 | project_name: req.body.project_name, 73 | projectOwner: res.locals.username 74 | }) 75 | .then(data => { 76 | res.locals.project = data.projectObject; 77 | return next(); 78 | }) 79 | .catch(err => { 80 | next({ 81 | log: `projectController.getProject failed, ${err}`, 82 | message: `Can't find project!` 83 | }); 84 | }); 85 | }; 86 | 87 | // general query to find all projects; not used in app itself 88 | projectController.findProject = (req, res, next) => { 89 | Project.find({}) 90 | .then(data => { 91 | res.locals.username = data; 92 | return next(); 93 | }) 94 | .catch(err => { 95 | // if (err.message === `Username Doesn't Exist`) res.redirect("/signup"); 96 | return next({ 97 | log: err, 98 | error: `error found in userController.verifyUser` 99 | }); 100 | }); 101 | }; 102 | 103 | module.exports = projectController; 104 | -------------------------------------------------------------------------------- /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 | // route for GitHub OAuth 9 | accountRouter.get( 10 | '/oauth', 11 | oAuthController.oAuthLogin, 12 | // oAuthController.requestGitHubIdentity, 13 | (req, res) => { 14 | console.log('Oauth Router console log'); 15 | return res.status(200).json(res.locals.url); 16 | } 17 | ); 18 | 19 | // retrieves specific user projects 20 | accountRouter.get( 21 | '/userProjects', 22 | authController.authenticate, 23 | accountController.userProjects, 24 | (req, res) => { 25 | return res.status(200).json(res.locals.userProjects); 26 | } 27 | ); 28 | 29 | // github OAuth route 30 | accountRouter.get( 31 | '/oauth/access_token/redirect', 32 | oAuthController.requestGitHubIdentity, 33 | oAuthController.queryGitHubAPIWithAccessToken, 34 | accountController.createUser, 35 | authController.sign, 36 | cookieController.setSSIDCookie, 37 | (req, res) => { 38 | console.log('after requestGitHUbIdentity'), 39 | console.log('res.locals.access_token', res.locals.access_token), 40 | console.log('final redirect to homepage'); 41 | res.redirect('/'); 42 | } 43 | ); 44 | 45 | // validates user on login 46 | accountRouter.get( 47 | '/validateSession', 48 | authController.authenticate, 49 | (req, res) => { 50 | res.status(200).json(res.locals.username); 51 | } 52 | ); 53 | 54 | // logs out user by deleting cookie 55 | accountRouter.get('/logout', cookieController.deleteCookie, (req, res) => { 56 | return res.sendStatus(200); 57 | }); 58 | 59 | // general route for querying to find all users in database 60 | accountRouter.get( 61 | '/find', 62 | accountController.findUser, 63 | // oAuthController.requestGitHubIdentity, 64 | (req, res) => { 65 | return res.status(200).json(res.locals.username); 66 | } 67 | ); 68 | 69 | module.exports = accountRouter; 70 | -------------------------------------------------------------------------------- /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 | // endpoint : /projects/saveProject 8 | projectRouter.post( 9 | '/saveProject', 10 | authController.authenticate, 11 | projectController.saveProject, 12 | projectController.userQuery, 13 | (req, res) => { 14 | return res.status(201).json(res.locals.user); 15 | } 16 | ); 17 | 18 | projectRouter.post( 19 | '/getProject', 20 | authController.authenticate, 21 | projectController.getProject, 22 | (req, res) => { 23 | console.log('testing route'); 24 | return res.status(201).json(res.locals.project); 25 | } 26 | ); 27 | 28 | // used to test Supertest functionality; not used in actual app 29 | projectRouter.get( 30 | '/find', 31 | projectController.findProject, 32 | // oAuthController.requestGitHubIdentity, 33 | (req, res) => { 34 | return res 35 | .status(200) 36 | .json({ hello: test, 'res.locals.usename': res.locals.username }); 37 | } 38 | ); 39 | 40 | module.exports = projectRouter; 41 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const cookieParser = require('cookie-parser'); 4 | const app = express(); 5 | const PORT = 8080; 6 | 7 | const cors = require('cors'); 8 | const corsOptions = { 9 | origin: process.env.CORS_ORIGIN, 10 | methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', 11 | credentials: true, 12 | }; 13 | const accountRouter = require('./routes/accountRouter'); 14 | const projectRouter = require('./routes/projectRouter'); 15 | 16 | // connecting to MongoDB 17 | const mongoose = require('mongoose'); 18 | const myURI = process.env.MONGO_URI; 19 | const { MongoClient } = require('mongodb'); 20 | mongoose 21 | .connect(myURI, { 22 | // options for the connect method to parse the URI 23 | useNewUrlParser: true, 24 | useUnifiedTopology: true, 25 | // sets the name of the DB that our collections are part of 26 | dbName: 'prevueDB', 27 | }) 28 | .then(() => console.log('Connected to Mongo DB.')) 29 | .catch((err) => console.log(err)); 30 | 31 | // Global Middleware 32 | app.use(express.json()); 33 | app.use(cookieParser()); 34 | app.use(cors(corsOptions)); 35 | app.use(express.urlencoded({ extended: true })); 36 | 37 | app.use(express.static(path.join(__dirname, '..', '/dist'))); 38 | 39 | // Routers 40 | app.use('/users', accountRouter); 41 | app.use('/projects', projectRouter); 42 | 43 | // app.get('*', (req, res) => { 44 | // return res.sendFile(path.join(__dirname, '..', '/dist/index.html')); 45 | // }); 46 | 47 | app.use((req, res) => res.sendStatus(404)); 48 | 49 | // Global error handler 50 | app.use((err, req, res, next) => { 51 | const defaultErr = { 52 | log: 'Express error handler caught unknown middleware error', 53 | status: 400, 54 | message: { err: 'An error occurred' }, 55 | }; 56 | const errorObj = Object.assign({}, defaultErr, err); 57 | console.log(errorObj.log); 58 | return res.status(errorObj.status).json(errorObj.message); 59 | }); 60 | 61 | // starts server 62 | app.listen(PORT, () => { 63 | console.log(`Server listening on port: ${PORT}`); 64 | }); 65 | 66 | module.exports = app; 67 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | 25 | 36 | -------------------------------------------------------------------------------- /src/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/3.png -------------------------------------------------------------------------------- /src/assets/PreVueDemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/PreVueDemo.gif -------------------------------------------------------------------------------- /src/assets/PreVueDemo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/PreVueDemo.mp4 -------------------------------------------------------------------------------- /src/assets/PreVueExportDemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/PreVueExportDemo.gif -------------------------------------------------------------------------------- /src/assets/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/background.jpg -------------------------------------------------------------------------------- /src/assets/componentdisplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/componentdisplay.png -------------------------------------------------------------------------------- /src/assets/homeview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/homeview.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | Artboard 46 2 | -------------------------------------------------------------------------------- /src/assets/newcomp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/newcomp.png -------------------------------------------------------------------------------- /src/assets/newcompvue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/newcompvue.png -------------------------------------------------------------------------------- /src/assets/prevue-large-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/prevue-large-green.png -------------------------------------------------------------------------------- /src/assets/prevue-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/prevue-large.png -------------------------------------------------------------------------------- /src/assets/prevue-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/prevue-logo.png -------------------------------------------------------------------------------- /src/assets/prevue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/prevue.png -------------------------------------------------------------------------------- /src/assets/prevue_color_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/prevue_color_white.png -------------------------------------------------------------------------------- /src/assets/robert-photo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/robert-photo.jpeg -------------------------------------------------------------------------------- /src/assets/routecreator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/routecreator.png -------------------------------------------------------------------------------- /src/assets/sean-photo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/sean-photo.jpeg -------------------------------------------------------------------------------- /src/assets/tree-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/tree-demo.png -------------------------------------------------------------------------------- /src/assets/treeview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/treeview.png -------------------------------------------------------------------------------- /src/assets/zach-photo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PreVue/d5ebfce8e0ff71ab2413b6f806c8789937a7986d/src/assets/zach-photo.jpeg -------------------------------------------------------------------------------- /src/components/ChildrenMultiselect.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/components/Component.vue: -------------------------------------------------------------------------------- 1 | 29 | 53 | 58 | -------------------------------------------------------------------------------- /src/components/ComponentDisplay.vue: -------------------------------------------------------------------------------- 1 | 39 | 129 | 130 | 145 | -------------------------------------------------------------------------------- /src/components/ExportProjectComponent.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 211 | 212 | 221 | -------------------------------------------------------------------------------- /src/components/HomeQueue.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 86 | 87 | 127 | -------------------------------------------------------------------------------- /src/components/HomeSidebar.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 132 | 141 | -------------------------------------------------------------------------------- /src/components/Icons.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 37 | 38 | 59 | -------------------------------------------------------------------------------- /src/components/LogOutComponent.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 39 | -------------------------------------------------------------------------------- /src/components/Modal/ComponentCodeDisplay.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 78 | 79 | 94 | -------------------------------------------------------------------------------- /src/components/Modal/EditQueue.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 51 | 52 | 102 | -------------------------------------------------------------------------------- /src/components/Modal/EditSidebar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 34 | 47 | -------------------------------------------------------------------------------- /src/components/Modal/Modal.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 60 | 61 | 67 | -------------------------------------------------------------------------------- /src/components/NavBar.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 68 | 69 | 118 | -------------------------------------------------------------------------------- /src/components/NewProjectComponent.vue: -------------------------------------------------------------------------------- 1 | 36 | -------------------------------------------------------------------------------- /src/components/OpenProjectComponent.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 94 | 95 | 113 | -------------------------------------------------------------------------------- /src/components/ProjectTabs.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 30 | -------------------------------------------------------------------------------- /src/components/RouteDisplay.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 74 | 75 | 89 | -------------------------------------------------------------------------------- /src/components/Routes.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/components/SaveProjectComponent.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 84 | 85 | 98 | -------------------------------------------------------------------------------- /src/components/TreeGraph.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 94 | 95 | 107 | -------------------------------------------------------------------------------- /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('initializeStore'); 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; 34 | -------------------------------------------------------------------------------- /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.setLogin]: ({ commit }, payload) => { 9 | commit(types.SET_LOGIN, payload); 10 | }, 11 | [types.replaceState]: ({ commit }, payload) => { 12 | commit('replaceState', payload); 13 | }, 14 | [types.nameProject]: ({ commit }, payload) => { 15 | commit(types.NAME_PROJECT, payload); 16 | }, 17 | [types.initialiseStore]: ({ commit }) => { 18 | commit(types.INITIALISESTORE); 19 | }, 20 | [types.registerComponent]: ({ state, commit }, payload) => { 21 | const { componentName } = payload; 22 | if (!state.componentMap[componentName]) { 23 | commit(types.ADD_COMPONENT_TO_COMPONENT_MAP, payload); 24 | commit( 25 | types.ADD_COMPONENT_TO_ACTIVE_ROUTE_CHILDREN, 26 | payload.componentName 27 | ); 28 | commit(types.ADD_COMPONENT_TO_ACTIVE_ROUTE_IN_ROUTE_MAP, payload); 29 | 30 | const component = state.componentNameInputValue; 31 | const value = state.componentChildrenMultiselectValue.map( 32 | (component: string) => { 33 | return state.componentMap[component].componentName; 34 | } 35 | ); 36 | commit(types.UPDATE_COMPONENT_CHILDREN_VALUE, { component, value }); 37 | commit(types.UPDATE_COMPONENT_CHILDREN_MULTISELECT_VALUE, []); 38 | commit(types.UPDATE_COMPONENT_NAME_INPUT_VALUE, ''); 39 | commit(types.SET_SELECTED_ELEMENT_LIST, []); 40 | } 41 | }, 42 | [types.setSelectedElementList]: ({ commit }, payload) => { 43 | if (payload) { 44 | commit(types.SET_SELECTED_ELEMENT_LIST, payload); 45 | } 46 | }, 47 | [types.addToSelectedElementList]: ({ commit }, payload) => { 48 | commit(types.ADD_TO_SELECTED_ELEMENT_LIST, payload); 49 | }, 50 | [types.addToComponentElementList]: ({ commit }, payload) => { 51 | commit(types.ADD_TO_COMPONENT_HTML_LIST, payload); 52 | }, 53 | [types.setClickedElementList]: ({ commit }, payload) => { 54 | commit(types.SET_CLICKED_ELEMENT_LIST, payload); 55 | }, 56 | [types.deleteActiveComponent]: ({ state, commit }) => { 57 | commit(types.DELETE_ACTIVE_COMPONENT); 58 | const activeRouteArray = [...state.routes[state.activeRoute]]; 59 | const newActiveRouteArray = activeRouteArray.filter(componentData => { 60 | return state.activeComponent !== componentData.componentName; 61 | }); 62 | commit(types.SET_ACTIVE_ROUTE_ARRAY, newActiveRouteArray); 63 | commit(types.SET_ACTIVE_COMPONENT, ''); 64 | }, 65 | [types.deleteSelectedElement]: ({ commit }, payload) => { 66 | commit(types.DELETE_SELECTED_ELEMENT, payload); 67 | }, 68 | [types.setState]: ({ commit }, payload) => { 69 | commit(types.SET_STATE, payload); 70 | }, 71 | [types.addProject]: ({ commit }, payload) => { 72 | commit(types.ADD_PROJECT, payload); 73 | }, 74 | [types.deleteFromComponentHtmlList]: ({ commit }, payload) => { 75 | commit(types.DELETE_FROM_COMPONENT_HTML_LIST, payload); 76 | }, 77 | [types.changeActiveTab]: ({ commit }, payload) => { 78 | commit(types.CHANGE_ACTIVE_TAB, payload); 79 | }, 80 | [types.setComponentMap]: ({ commit }, payload) => { 81 | commit(types.SET_COMPONENT_MAP, payload); 82 | }, 83 | [types.addRouteToRouteMap]: ({ state, commit }, payload) => { 84 | commit(types.ADD_ROUTE, payload); 85 | commit(types.SET_ACTIVE_ROUTE, payload); 86 | const route = state.activeRoute; 87 | const children: string[] = []; 88 | commit(types.ADD_ROUTE_TO_COMPONENT_MAP, { route, children }); 89 | const component = 'App'; 90 | const value = state.componentMap[state.activeRoute].componentName; 91 | commit(types.ADD_COMPONENT_TO_COMPONENT_CHILDREN, { component, value }); 92 | }, 93 | [types.setActiveRoute]: ({ commit }, payload) => { 94 | commit(types.SET_ACTIVE_ROUTE, payload); 95 | }, 96 | [types.setActiveComponent]: ({ commit }, payload) => { 97 | console.log(payload); 98 | commit(types.SET_ACTIVE_COMPONENT, payload); 99 | }, 100 | [types.setRoutes]: ({ commit }, payload) => { 101 | commit(types.SET_ROUTES, payload); 102 | }, 103 | [types.deleteProjectTab]: ({ commit }, payload) => { 104 | commit(types.DELETE_PROJECT_TAB, payload); 105 | }, 106 | [types.updateComponentChildrenMultiselectValue]: ({ commit }, payload) => { 107 | commit(types.UPDATE_COMPONENT_CHILDREN_MULTISELECT_VALUE, payload); 108 | }, 109 | [types.updateActiveComponentChildrenValue]: ({ commit }, payload) => { 110 | console.log('payload', payload); 111 | commit(types.UPDATE_ACTIVE_COMPONENT_CHILDREN_VALUE, payload); 112 | }, 113 | [types.updateComponentNameInputValue]: ({ commit }, payload) => { 114 | commit(types.UPDATE_COMPONENT_NAME_INPUT_VALUE, payload); 115 | }, 116 | [types.updateOpenModal]: ({ commit }, payload) => { 117 | commit(types.UPDATE_OPEN_MODAL, payload); 118 | } 119 | }; 120 | 121 | export default actions; 122 | -------------------------------------------------------------------------------- /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; 14 | -------------------------------------------------------------------------------- /src/store/mutations.ts: -------------------------------------------------------------------------------- 1 | import * as types from './storeTypes'; 2 | import { State, Mutations, HtmlList, HtmlChild } from '../types'; 3 | 4 | const mutations: Mutations = { 5 | initializeStore(state: State) { 6 | if (localStorage.getItem('store')) { 7 | this.replaceState( 8 | Object.assign( 9 | state, 10 | JSON.parse(localStorage.getItem('store') || `${state}`) 11 | ) 12 | ); 13 | } 14 | }, 15 | replaceState(state: State, payload) { 16 | this.replaceState(payload); 17 | }, 18 | [types.INC_RERENDER_KEY]: (state: State) => { 19 | state.rerenderKey++; 20 | }, 21 | [types.SET_LOGIN]: (state: State, payload) => { 22 | state.loggedIn = payload; 23 | }, 24 | [types.NAME_PROJECT]: (state: State, payload) => { 25 | state.projectName = payload; 26 | }, 27 | [types.ADD_COMPONENT_TO_COMPONENT_MAP]: (state: State, payload) => { 28 | const { componentName, htmlList, children, isActive } = payload; 29 | state.componentMap = { 30 | ...state.componentMap, 31 | [componentName]: { 32 | componentName, 33 | x: 0, 34 | y: 0, 35 | w: 200, 36 | h: 200, 37 | children, 38 | htmlList, 39 | isActive 40 | } 41 | }; 42 | }, 43 | [types.ADD_TO_SELECTED_ELEMENT_LIST]: (state: State, payload) => { 44 | state.selectedElementList.push({ text: payload, children: [] }); 45 | }, 46 | [types.SET_SELECTED_ELEMENT_LIST]: (state: State, payload) => { 47 | state.selectedElementList = payload; 48 | }, 49 | [types.ADD_TO_COMPONENT_HTML_LIST]: (state: State, elementName) => { 50 | const componentName: string = state.activeComponent; 51 | 52 | state.componentMap[componentName].htmlList.push({ 53 | text: elementName, 54 | children: [] 55 | }); 56 | }, 57 | [types.DELETE_FROM_COMPONENT_HTML_LIST]: (state: State, id) => { 58 | const componentName = state.activeComponent; 59 | const htmlList = state.componentMap[componentName].htmlList; 60 | 61 | function parseAndDelete(htmlList: HtmlList) { 62 | htmlList.forEach((element, index) => { 63 | if (element.children.length > 0) { 64 | parseAndDelete(element.children); 65 | } 66 | if (id === element._id) { 67 | htmlList.splice(index, 1); 68 | } 69 | }); 70 | 71 | const copied = htmlList.slice(0); 72 | state.componentMap[componentName].htmlList = copied; 73 | } 74 | parseAndDelete(htmlList); 75 | }, 76 | 77 | [types.SET_CLICKED_ELEMENT_LIST]: (state: State, payload) => { 78 | const componentName = state.activeComponent; 79 | state.componentMap[componentName].htmlList = payload; 80 | }, 81 | [types.DELETE_ACTIVE_COMPONENT]: (state: State) => { 82 | const { componentMap, activeComponent } = state; 83 | 84 | const newObj = Object.assign({}, componentMap); 85 | 86 | delete newObj[activeComponent]; 87 | 88 | for (const compKey in newObj) { 89 | const children = newObj[compKey].children; 90 | children.forEach((child, index) => { 91 | if (activeComponent === child) children.splice(index, 1); 92 | }); 93 | } 94 | state.componentMap = newObj; 95 | }, 96 | [types.SET_COMPONENT_MAP]: (state: State, payload) => { 97 | console.log(payload); 98 | state.componentMap = payload; 99 | }, 100 | [types.DELETE_SELECTED_ELEMENT]: (state: State, payload) => { 101 | state.selectedElementList.splice(payload, 1); 102 | }, 103 | [types.SET_STATE]: (state: State, payload) => { 104 | Object.assign(state, payload); 105 | }, 106 | 107 | [types.ADD_ROUTE]: (state: State, payload) => { 108 | state.routes = { 109 | ...state.routes, 110 | [payload]: [] 111 | }; 112 | }, 113 | [types.ADD_ROUTE_TO_COMPONENT_MAP]: (state: State, payload) => { 114 | const { route, children } = payload; 115 | state.componentMap = { 116 | ...state.componentMap, 117 | [route]: { 118 | componentName: route, 119 | children 120 | } 121 | }; 122 | }, 123 | [types.SET_ACTIVE_ROUTE]: (state: State, payload) => { 124 | state.activeRoute = payload; 125 | }, 126 | [types.ADD_COMPONENT_TO_ACTIVE_ROUTE_IN_ROUTE_MAP]: ( 127 | state: State, 128 | payload 129 | ) => { 130 | state.routes[state.activeRoute].push(payload); 131 | }, 132 | [types.SET_ACTIVE_COMPONENT]: (state: State, payload) => { 133 | state.activeComponent = payload; 134 | }, 135 | [types.SET_ROUTES]: (state: State, payload) => { 136 | state.routes = Object.assign({}, payload); 137 | }, 138 | [types.SET_ACTIVE_ROUTE_ARRAY]: (state: State, payload) => { 139 | state.routes[state.activeRoute] = payload; 140 | }, 141 | [types.ADD_COMPONENT_TO_ACTIVE_ROUTE_CHILDREN]: ( 142 | state: State, 143 | payload: string 144 | ) => { 145 | state.componentMap[state.activeRoute].children.push(payload); 146 | }, 147 | [types.DELETE_PROJECT_TAB]: (state: State, payload) => { 148 | // delete project tab functionality yet to be implemented 149 | }, 150 | [types.UPDATE_COMPONENT_CHILDREN_MULTISELECT_VALUE]: ( 151 | state: State, 152 | payload 153 | ) => { 154 | console.log('payload', payload); 155 | state.componentChildrenMultiselectValue = payload; 156 | }, 157 | [types.UPDATE_COMPONENT_CHILDREN_VALUE]: (state: State, payload) => { 158 | const { component, value } = payload; 159 | state.componentMap[component].children = value; 160 | }, 161 | [types.UPDATE_ACTIVE_COMPONENT_CHILDREN_VALUE]: (state: State, payload) => { 162 | state.componentMap[state.activeComponent].children = payload; 163 | }, 164 | [types.UPDATE_COMPONENT_NAME_INPUT_VALUE]: (state: State, payload) => { 165 | state.componentNameInputValue = payload; 166 | }, 167 | [types.ADD_COMPONENT_TO_COMPONENT_CHILDREN]: (state: State, payload) => { 168 | const { component, value } = payload; 169 | state.componentMap[component].children.push(value); 170 | }, 171 | [types.UPDATE_OPEN_MODAL]: (state: State, payload) => { 172 | state.modalOpen = payload; 173 | } 174 | }; 175 | 176 | export default mutations; 177 | -------------------------------------------------------------------------------- /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: ['
  • ', '
  • '], 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: '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 | selectedElementList: [], 30 | projectName: 'Untitled-1', 31 | componentChildrenMultiselectValue: [], 32 | modalOpen: false, 33 | htmlElements: [], 34 | saved: false, 35 | loggedIn: false, 36 | rerenderKey: 0 37 | }; 38 | 39 | export default newState; 40 | -------------------------------------------------------------------------------- /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 | export const ADD_COMPONENT_TO_COMPONENT_MAP: Type = 11 | 'ADD_COMPONENT_TO_COMPONENT_MAP'; 12 | export const SET_SELECTED_ELEMENT_LIST: Type = 'SET_SELECTED_ELEMENT_LIST'; 13 | export const ADD_TO_SELECTED_ELEMENT_LIST: Type = 14 | 'ADD_TO_SELECTED_ELEMENT_LIST'; 15 | export const ADD_TO_COMPONENT_HTML_LIST: Type = 'ADD_TO_COMPONENT_HTML_LIST'; 16 | export const SET_CLICKED_ELEMENT_LIST: Type = 'SET_CLICKED_ELEMENT_LIST'; 17 | export const DELETE_ACTIVE_COMPONENT: Type = 'DELETE_ACTIVE_COMPONENT'; 18 | export const SET_COMPONENT_MAP: Type = 'SET_COMPONENT_MAP'; 19 | export const DELETE_FROM_QUEUE: Type = 'DELETE_FROM_QUEUE'; 20 | export const DELETE_SELECTED_ELEMENT: Type = 'DELETE_SELECTED_ELEMENT'; 21 | export const SET_STATE: Type = 'SET_STATE'; 22 | export const ADD_PROJECT: Type = 'ADD_PROJECT'; 23 | export const DELETE_FROM_COMPONENT_HTML_LIST: Type = 24 | 'DELETE_FROM_COMPONENT_HTML_LIST'; 25 | export const CHANGE_ACTIVE_TAB: Type = 'CHANGE_ACTIVE_TAB'; 26 | export const ADD_COMPONENT_TO_ACTIVE_ROUTE_IN_ROUTE_MAP: Type = 27 | 'ADD_COMPONENT_TO_ACTIVE_ROUTE_IN_ROUTE_MAP'; 28 | export const ADD_ROUTE: Type = 'ADD_ROUTE'; 29 | export const SET_ACTIVE_COMPONENT: Type = 'SET_ACTIVE_COMPONENT'; 30 | export const SET_ACTIVE_PROJECT: Type = 'SET_ACTIVE_PROJECT'; 31 | export const SET_ACTIVE_ROUTE: Type = 'SET_ACTIVE_ROUTE'; 32 | export const INCREMENT_PROJECT_ID: Type = 'INCREMENT_PROJECT_ID'; 33 | export const SET_ROUTES: Type = 'SET_ROUTES'; 34 | export const SET_COMPONENT_HTML_LIST: Type = 'SET_COMPONENT_HTML_LIST'; 35 | export const SET_ACTIVE_ROUTE_ARRAY: Type = 'SET_ACTIVE_ROUTE_ARRAY'; 36 | export const ADD_COMPONENT_TO_ACTIVE_ROUTE_CHILDREN: Type = 37 | 'ADD_COMPONENT_TO_ACTIVE_ROUTE_CHILDREN'; 38 | export const ADD_ROUTE_TO_COMPONENT_MAP: Type = 'ADD_ROUTE_TO_COMPONENT_MAP'; 39 | export const DELETE_PROJECT_TAB: Type = 'DELETE_PROJECT_TAB'; 40 | export const UPDATE_COMPONENT_CHILDREN_MULTISELECT_VALUE: Type = 41 | 'UPDATE_COMPONENT_CHILDREN_MULTISELECT_VALUE'; 42 | export const UPDATE_COMPONENT_NAME_INPUT_VALUE: Type = 43 | 'UPDATE_COMPONENT_NAME_INPUT_VALUE'; 44 | export const UPDATE_COMPONENT_CHILDREN_VALUE: Type = 45 | 'UPDATE_COMPONENT_CHILDREN_VALUE'; 46 | export const UPDATE_ACTIVE_COMPONENT_CHILDREN_VALUE: Type = 47 | 'UPDATE_ACTIVE_COMPONENT_CHILDREN_VALUE'; 48 | export const ADD_COMPONENT_TO_COMPONENT_CHILDREN: Type = 49 | 'ADD_COMPONENT_TO_COMPONENT_CHILDREN'; 50 | export const UPDATE_OPEN_MODAL: Type = 'UPDATE_OPEN_MODAL'; 51 | 52 | //Actions 53 | export const registerComponent: Type = 'registerComponent'; 54 | export const setSelectedElementList: Type = 'setSelectedElementList'; 55 | export const addToSelectedElementList: Type = 'addToSelectedElementList'; 56 | export const addToComponentElementList: Type = 'addToComponentElementList'; 57 | export const setClickedElementList: Type = 'setClickedElementList'; 58 | export const deleteActiveComponent: Type = 'deleteActiveComponent'; 59 | export const setComponentMap: Type = 'setComponentMap'; 60 | export const deleteFromQueue: Type = 'deleteFromQueue'; 61 | export const deleteSelectedElement: Type = 'deleteSelectedElement'; 62 | export const setState: Type = 'setState'; 63 | export const addProject: Type = 'addProject'; 64 | export const deleteFromComponentHtmlList: Type = 'deleteFromComponentHtmlList'; 65 | export const changeActiveTab: Type = 'changeActiveTab'; 66 | export const addRouteToRouteMap: Type = 'addRouteToRouteMap'; 67 | export const setActiveComponent: Type = 'setActiveComponent'; 68 | export const setActiveRoute: Type = 'setActiveRoute'; 69 | export const incrementProjectId: Type = 'incrementProjectId'; 70 | export const setRoutes: Type = 'setRoutes'; 71 | export const setComponentHtmlList: Type = 'setComponentHtmlList'; 72 | export const deleteProjectTab: Type = 'deleteProjectTab'; 73 | export const updateComponentChildrenMultiselectValue: Type = 74 | 'updateComponentChildrenMultiselectValue'; 75 | export const updateActiveComponentChildrenValue: Type = 76 | 'updateActiveComponentChildrenValue'; 77 | export const updateComponentChildrenValue: Type = 78 | 'updateComponentChildrenValue'; 79 | export const updateComponentNameInputValue: Type = 80 | 'updateComponentNameInputValue'; 81 | export const updateOpenModal: Type = 'updateOpenModal'; 82 | export const addElement: Type = 'addElement'; 83 | export const initialiseStore: Type = 'initialiseStore'; 84 | export const nameProject: Type = 'nameProject'; 85 | export const replaceState: Type = 'replaceState'; 86 | export const setLogin: Type = 'setLogin'; 87 | export const incRerenderKey: Type = 'incRerenderKey'; 88 | 89 | export const resetComponentChildrenMultiselectValue: Type = 90 | 'resetComponentChildrenMultiselectValue'; 91 | -------------------------------------------------------------------------------- /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: 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: 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 | }; 41 | export type ComponentMap = { 42 | [k: string]: Component; 43 | }; 44 | 45 | export type Routes = { 46 | [k: string]: string[]; 47 | }; 48 | 49 | export type Project = { 50 | filename: string; 51 | lastSavedLocation: string; 52 | }; 53 | export type Type = string; 54 | 55 | export type State = { 56 | icons: Icons; 57 | htmlElementMap: HtmlElementMap; 58 | componentMap: ComponentMap; 59 | routes: Routes; 60 | componentNameInputValue: string; 61 | activeRoute: string; 62 | activeComponent: string; 63 | projectName: string; 64 | selectedElementList: object[]; 65 | componentChildrenMultiselectValue: string[]; 66 | modalOpen: boolean; 67 | htmlElements: any[]; 68 | saved: boolean; 69 | loggedIn: boolean; 70 | rerenderKey: number; 71 | }; 72 | 73 | export type Mutations = { 74 | [k: Type]: ( 75 | state: State, 76 | payload?: any, 77 | elementName?: string, 78 | id?: number 79 | ) => void; 80 | }; 81 | export type Actions = { 82 | [k: Type]: (context: any, payload?: any) => void; 83 | }; 84 | 85 | export type HtmlChild = { 86 | text: string; 87 | children: HtmlChild[]; 88 | _id?: number; 89 | }; 90 | 91 | export type HtmlList = HtmlChild[]; 92 | -------------------------------------------------------------------------------- /src/views/HomeView.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 75 | 76 | 81 | -------------------------------------------------------------------------------- /src/views/SplashView.vue: -------------------------------------------------------------------------------- 1 | 173 | 174 | 211 | 212 | 319 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------