├── .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 | [](https://github.com/teamprevue/PreVue/pulls)
7 | 
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 |
21 | We're sorry but prevue doesn't work properly without JavaScript
23 | enabled. Please enable it to continue.
25 |
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 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
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 |
2 |
3 |
4 |
21 |
22 |
23 |
24 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/src/components/Component.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
25 | {{ compName }}
26 |
27 |
28 |
29 |
53 |
58 |
--------------------------------------------------------------------------------
/src/components/ComponentDisplay.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
27 | {{ componentData.componentName }}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
129 |
130 |
145 |
--------------------------------------------------------------------------------
/src/components/ExportProjectComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Export Project
6 |
7 |
8 |
9 |
211 |
212 |
221 |
--------------------------------------------------------------------------------
/src/components/HomeQueue.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
19 | Selected Elements
20 |
21 |
30 |
31 |
32 | {{ element.text }}
33 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
86 |
87 |
127 |
--------------------------------------------------------------------------------
/src/components/HomeSidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
67 |
68 |
69 |
132 |
141 |
--------------------------------------------------------------------------------
/src/components/Icons.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
14 |
15 |
16 | {{ elementName }}
17 |
18 |
19 |
20 |
21 |
37 |
38 |
59 |
--------------------------------------------------------------------------------
/src/components/LogOutComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Logout
6 |
7 |
8 |
9 |
28 |
29 |
39 |
--------------------------------------------------------------------------------
/src/components/Modal/ComponentCodeDisplay.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ activeComponent }} Code Display
4 |
5 |
6 |
7 |
8 |
78 |
79 |
94 |
--------------------------------------------------------------------------------
/src/components/Modal/EditQueue.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Selected Elements:
4 |
5 |
6 |
7 |
8 |
51 |
52 |
102 |
--------------------------------------------------------------------------------
/src/components/Modal/EditSidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
34 |
47 |
--------------------------------------------------------------------------------
/src/components/Modal/Modal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
60 |
61 |
67 |
--------------------------------------------------------------------------------
/src/components/NavBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 | PreVue
12 |
13 |
14 |
15 | Tree
16 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
68 |
69 |
118 |
--------------------------------------------------------------------------------
/src/components/NewProjectComponent.vue:
--------------------------------------------------------------------------------
1 |
36 |
--------------------------------------------------------------------------------
/src/components/OpenProjectComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Open Project
9 |
10 |
11 |
12 |
18 |
19 |
26 |
27 |
28 |
34 | OPEN
35 |
36 | Close
37 |
38 |
39 |
40 |
41 |
42 |
43 |
94 |
95 |
113 |
--------------------------------------------------------------------------------
/src/components/ProjectTabs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ this.project }}
5 |
6 |
7 |
8 |
24 |
25 |
30 |
--------------------------------------------------------------------------------
/src/components/RouteDisplay.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
42 |
43 |
44 |
74 |
75 |
89 |
--------------------------------------------------------------------------------
/src/components/Routes.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
22 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/components/SaveProjectComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Save Project
8 |
9 |
10 |
11 |
16 |
17 |
26 |
28 |
29 |
30 |
36 | Save
37 |
38 | Close
39 |
40 |
41 |
42 |
43 |
44 |
45 |
84 |
85 |
98 |
--------------------------------------------------------------------------------
/src/components/TreeGraph.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
Back to workspace
12 |
13 |
14 |
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 |
2 |
3 |
4 |
12 |
13 |
14 |
15 |
16 |
17 |
24 |
25 |
26 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
75 |
76 |
81 |
--------------------------------------------------------------------------------
/src/views/SplashView.vue:
--------------------------------------------------------------------------------
1 |
2 |
172 |
173 |
174 |
211 |
212 |
319 |
--------------------------------------------------------------------------------
/src/views/TreeView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
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 |
--------------------------------------------------------------------------------