61 |
62 | Protract is an accessible developer tool built from the ground up to assist with the blueprinting of Angular applications, simplifying the process of creating a hierarchy of components and allowing developers to quickly create the structure of their app so they can start coding faster.
63 |
64 | [Here](https://medium.com/@protract-app/protract-a-visual-guide-for-angular-fd80cbcc32ba) is a medium article describing the philosophy behind Protract.
65 |
66 | Visit our website Protract.dev!
67 |
68 |
69 |
70 |
71 |
72 | ##
Features
73 |
74 | ### Component Manipulation
75 |
76 | Drag and drop functionality to create, reorder, and delete HTML tags. Create custom components declared by the user that can be nested.
77 |
78 |
79 |
80 |
81 |
82 |
83 | ### Live Updates
84 |
85 | Real-time visualization of code for each custom component, file structure of directory, and hierarchy of components.
86 |
87 |
88 |
89 |
90 |
91 |
92 | ### Context Switch
93 |
94 | Change current component canvas by clicking on the component in file directory or component tree.
95 |
96 |
97 |
98 | ### Cloud Storage
99 |
100 | Create, save, load, and delete projects.
101 |
102 |
103 |
104 |
105 |
106 |
107 | ### Access Anywhere
108 |
109 | Fully online in-browser functionality.
110 |
111 |
112 |
113 | ### Easy Export
114 |
115 | Export projects to use in a newly created Angular project.
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | ##
Getting Started
124 |
125 | ###
Running Online
126 |
127 | You can start using Protract by visiting the website here.
128 | To save and load projects you will need to make an account and login. Once your blueprint is completed you can hit the export button on the canvas.
129 |
130 | ###
Running Locally
131 |
132 | If you would like to run with Docker,
133 |
134 | ```
135 | docker pull protractors/protract-prod:latest
136 | ```
137 |
138 | ```
139 | docker run -p :3000 protractors/protract-prod
140 | ```
141 |
142 | If you would like to use the app by forking and cloning:
143 |
144 | Fork this repository to your own GitHub account.
145 | Clone the forked repository to your machine
146 |
147 | ```
148 | git clone https://github.com//protract.git
149 | ```
150 |
151 | Create a .env in the root directory that contains 2 variables,
152 |
153 | ```
154 | MONGO_URI=
155 | mode=production
156 | ```
157 |
158 | Navigate to the root project directory and install dependencies.
159 |
160 | ```
161 | cd protract
162 | npm install
163 | ```
164 |
165 | If you would like to run in development mode, `npm run dev` and visit localhost:3000.
166 |
167 | If you would like to run in production mode, `npm run build` and then `npm start` and visit localhost:3000.
168 |
169 | ##
Run Exported Project
170 |
171 | In your terminal,
172 |
173 | ```
174 | npm install -g @angular/cli
175 | ```
176 |
177 | To install the Angular CLI if it has not already been installed.
178 |
179 | ```
180 | ng new
181 | ```
182 |
183 | To start your new project.
184 |
185 | In your file explorer, extract the zip file and replace the directory’s app folder with the one contained in the zip file.
186 |
187 | ##
Contributions
188 |
189 | We welcome contributions from the community. If you are interested in contributing to this project, please refer to our Contributing Guidelines for more information.
190 |
191 | ##
Contributors
192 |
193 | | Developed By | | |
194 | | :-----------: | :--------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------: |
195 | | Don Do | [](https://github.com/Donlebon) | [](www.linkedin.com/in/don-do-26822a281) |
196 | | Vander Harris | [](https://github.com/vdharris/) | [](https://www.linkedin.com/in/vanderharris/) |
197 | | Peter Tran | [](https://github.com/tranpeter95) | [](https://www.linkedin.com/in/peter-tran-6574b81b9/) |
198 | | Steven Vaughn | [](https://github.com/Svaughn4418) | [](www.linkedin.com/in/steven-vaughn-126a69280) |
199 | | Douglas Yao | [](https://github.com/douglas-yao) | [](https://www.linkedin.com/in/douglas-yao/) |
200 |
201 | ##
License
202 |
203 |
Protract is licensed under the terms of the MIT license.
2 |
3 | The Protract Team would like to thank you for your interest in helping to maintain and improve Protract!
4 |
5 |
6 | Reporting Bugs:
7 | All code changes happen through Github Pull Requests and we actively welcome them. To submit your pull request, follow the steps below:
8 |
9 |
10 | Pull Requests:
11 |
12 |
Fork the repo and create your branch from main.
13 |
If you've added code that should be tested, add tests.
14 |
If you've changed APIs, update the documentation.
15 |
Ensure the test suite passes.
16 |
Make sure your code lints.
17 |
Issue that pull request!
18 |
19 |
20 |
21 | Note:
22 | Any contributions you make will be under the MIT Software License and your submissions are understood to be under the same that covers the project. Please reach out to the team if you have any questions.
23 |
24 |
25 | Issues:
26 | We use GitHub issues to track public bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue.
27 |
28 |
29 | License:
30 | By contributing, you agree that your contributions will be licensed under Protract’s MIT License.
31 |
32 |
--------------------------------------------------------------------------------
/cypress.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "cypress";
2 |
3 | export default defineConfig({
4 | component: {
5 | devServer: {
6 | framework: "react",
7 | bundler: "vite",
8 | },
9 | },
10 |
11 | e2e: {
12 | setupNodeEvents(on, config) {
13 | // implement node event listeners here
14 | },
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/cypress/component/BankEl.cy.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import BankEl from '../../src/client/components/BankEl';
3 |
4 | describe('', () => {
5 | it('renders', () => {
6 | cy.mount();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/cypress/component/Canvas.cy.tsx:
--------------------------------------------------------------------------------
1 | describe('Canvas.cy.tsx', () => {
2 | it('playground', () => {
3 | // cy.mount()
4 | });
5 | });
6 |
7 | export {};
8 |
--------------------------------------------------------------------------------
/cypress/e2e/canvas.cy.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | describe('canvas functionality', () => {
4 | beforeEach(() => {
5 | cy.visit('http://localhost:3000/')
6 | })
7 |
8 | it('can have items dragged in and deleted', () => {
9 | cy.contains('div').trigger('mousedown', {button: 0}).wait(200).trigger('mousemove', {clientX: 500, clientY: 300}).wait(200).trigger('mouseup')
10 | cy.get('[aria-label="elements"]').within(() => {
11 | cy.contains('div').within(() => {
12 | cy.contains('X').click()
13 | })
14 | })
15 | cy.contains('Yes').click()
16 | cy.get('[aria-label="elements"]').should('not.have.descendants', 'button')
17 | })
18 |
19 | it('can add custom components', () => {
20 | cy.get('[placeholder="Component Name"]').click().type('Cypress is so amazing{enter}')
21 | cy.get('[aria-label="elements"]').should('have.descendants', 'button').contains('Cypress is so amazing')
22 | })
23 |
24 | //TODO : make sorting order test work. some issue at line 28-30
25 | // it('can handle components changing order', () => {
26 | // cy.contains('div').trigger('mousedown', {button: 0}).trigger('mousemove', {clientX: 500, clientY: 300}).trigger('mouseup')
27 | // cy.wait(500)
28 | // cy.contains('form').trigger('mousedown', {button: 0}).trigger('mousemove', {clientX: 500, clientY: 300}).trigger('mouseup')
29 | // cy.wait(500)
30 | // cy.get('[aria-label="elements"]').within(() => {
31 | // cy.contains('form').trigger('mousedown', {button: 0}).trigger('mousemove', {clientX: 550, clientY: 300}).wait(1000).trigger('mouseup');
32 | // })
33 | // })
34 | })
--------------------------------------------------------------------------------
/cypress/e2e/codepreview.cy.ts:
--------------------------------------------------------------------------------
1 | describe('Code preview functionality', () => {
2 |
3 | beforeEach(() => {
4 | cy.visit('http://localhost:3000/')
5 | })
6 |
7 | it('canvas item populates code preview', () => {
8 | cy.contains('img').trigger('mousedown', { button: 0 }).wait(200).trigger('mousemove', { clientX: 500, clientY: 300 }).wait(200).trigger('mouseup')
9 | cy.get('#codePreview').within(() => {
10 | cy.contains('').should('be.visible')
11 | })
12 | })
13 | it('deleting item from canvas should remove from code preview', () => {
14 | cy.contains('form').trigger('mousedown', { button: 0 }).wait(200).trigger('mousemove', { clientX: 500, clientY: 300 }).wait(200).trigger('mouseup')
15 | cy.get('#codePreview').within(() => {
16 | cy.contains('').should('be.visible')
17 | })
18 | cy.contains('X').click()
19 | cy.contains('Yes').click()
20 | cy.get('#codePreview').within(() => {
21 | cy.contains('').should('not.exist')
22 | })
23 | })
24 | })
25 |
26 | export {}
--------------------------------------------------------------------------------
/cypress/e2e/fileDir.cy.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | describe('fileDir func', () => {
4 | beforeEach(() => {
5 | cy.visit('http://localhost:3000/')
6 | })
7 |
8 | it('populates based on custom components made and not on draggables', () => {
9 | cy.get('[placeholder="Component Name"]').click().type('Cypress is so amazing{enter}')
10 | cy.contains('ol').trigger('mousedown', {button: 0}).trigger('mousemove', {clientX: 500, clientY: 300}).trigger('mouseup')
11 | cy.get('#fileDir').should('contain', 'Cypress is so amazing');
12 | cy.get('#fileDir').should('not.contain', 'ol');
13 | })
14 |
15 | it ('can enter the instance of a custom component', () => {
16 | cy.get('[placeholder="Component Name"]').click().type('I LOVE TESTING{enter}')
17 | cy.get('[placeholder="Component Name"]').click().type('I LOVE TESTING2{enter}')
18 | cy.get('[placeholder="Component Name"]').click().type('I LOVE TESTING3{enter}')
19 | cy.get('[placeholder="Component Name"]').click().type('I LOVE TESTING4{enter}')
20 | cy.get('#leftCol').within(() => {
21 | cy.contains('I LOVE TESTING4').click();
22 | })
23 | cy.get('h2').should('contain', 'I LOVE TESTING4');
24 | })
25 | })
--------------------------------------------------------------------------------
/cypress/e2e/flowtree.cy.ts:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | describe('flowtree functionality', () => {
4 | beforeEach(() => {
5 | cy.visit('http://localhost:3000/')
6 | })
7 |
8 | it('contains only one node upon app start', () => {
9 | // click component tree tab
10 | cy.contains('Component Tree').click()
11 | // look inside component tree div / container, check for one single node called app
12 | cy.get('#flowTree').contains('app').should('be.visible')
13 | })
14 |
15 | it('adds a custom component that populates in the tree, clicking on the new node populates it in canvas', () => {
16 | // click on component tree tab
17 | cy.contains('Component Tree').click()
18 | // select custom component input field
19 | // enter custom component, either click add or press enter
20 | cy.get('[placeholder="Component Name"]').click().type('Comp1{enter}')
21 | // check if new node populated
22 | cy.get('#flowTree').within(() => {
23 | cy.contains('Comp1').should('be.visible')
24 | // click on new node
25 | cy.contains('Comp1').click()
26 | })
27 | // check if canvas updates to new node
28 | cy.get('#currCompTitle').contains('Comp1').should('be.visible')
29 | })
30 |
31 | it('deletes appropriate tree node when corresponding node is deleted on canvas', () => {
32 | cy.contains('Component Tree').click();
33 | cy.get('[placeholder="Component Name"]').click().type('Comp1{enter}')
34 |
35 | cy.get('#canvas').within(() => {
36 | cy.contains('Comp1').within(() => {
37 | cy.contains('X').click();
38 | })
39 | })
40 | cy.get('#deleteModal').contains('Yes').click();
41 | cy.get('#flowTree').should('not.contain', 'Comp1');
42 |
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/cypress/e2e/login.cy.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | describe('login functionality', () => {
3 | it('allows a user to login and logout', () => {
4 | cy.visit('http://localhost:3000/')
5 | cy.contains('Login').should('be.visible').click();
6 | cy.get('#loginModal').within(() => {
7 | cy.get('input[name="username"]').type('hulk');
8 | cy.get('input[name="password"]').click().type('1234');
9 | cy.get('button').first().click();
10 | })
11 | cy.contains('hulk').should('be.visible');
12 |
13 | cy.contains('Projects').click();
14 | cy.get('#loadModal').within(() => {
15 | cy.contains('Protract Blueprint').click();
16 | cy.contains('Load').click();
17 | })
18 | cy.contains('div').trigger('mousedown', {button: 0}).wait(200).trigger('mousemove', {clientX: 400, clientY: 300}).wait(200).trigger('mouseup')
19 | cy.contains('ol').trigger('mousedown', {button: 0}).wait(200).trigger('mousemove', {clientX: 400, clientY: 300}).wait(200).trigger('mouseup')
20 | cy.contains('img').trigger('mousedown', {button: 0}).wait(200).trigger('mousemove', {clientX: 400, clientY: 300}).wait(200).trigger('mouseup')
21 | cy.get('[placeholder="Component Name"]').click().type('Cypress is so amazing{enter}')
22 |
23 | cy.get('[aria-label="elements"]').within(() => {
24 | cy.contains('amazing').within(() => {
25 | cy.contains('X').click()
26 | })
27 | })
28 | cy.contains('Yes').click()
29 | cy.get('[aria-label="elements"]').within(() => {
30 | cy.contains('div').within(() => {
31 | cy.contains('X').click()
32 | })
33 | })
34 | cy.contains('Yes').click()
35 | cy.get('[aria-label="elements"]').within(() => {
36 | cy.contains('img').within(() => {
37 | cy.contains('X').click()
38 | })
39 | })
40 | cy.contains('Yes').click()
41 | cy.get('[aria-label="elements"]').within(() => {
42 | cy.contains('ol').within(() => {
43 | cy.contains('X').click()
44 | })
45 | })
46 | cy.contains('Yes').click()
47 |
48 | cy.contains('Save').click();
49 | cy.contains('New').click();
50 | cy.contains('Yes').click();
51 | cy.contains('Save').click();
52 | cy.get('#saveModal').within(() => {
53 | cy.get('input').click().type('Super Cool Awesome Project also my WPM is so crazy fast holy moly');
54 | cy.contains('Save').click();
55 | })
56 | cy.contains('Projects').click();
57 | cy.get('#loadModal').within(() => {
58 | cy.contains('also').click();
59 | cy.contains('Delete').click();
60 | })
61 |
62 | cy.contains('Logout').click();
63 |
64 | cy.contains('Login').should('be.visible');
65 | })
66 | })
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/cypress/support/commands.ts:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************
3 | // This example commands.ts shows you how to
4 | // create various custom commands and overwrite
5 | // existing commands.
6 | //
7 | // For more comprehensive examples of custom
8 | // commands please read more here:
9 | // https://on.cypress.io/custom-commands
10 | // ***********************************************
11 | //
12 | //
13 | // -- This is a parent command --
14 | // Cypress.Commands.add('login', (email, password) => { ... })
15 | //
16 | //
17 | // -- This is a child command --
18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
19 | //
20 | //
21 | // -- This is a dual command --
22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
23 | //
24 | //
25 | // -- This will overwrite an existing command --
26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
27 | //
28 | // declare global {
29 | // namespace Cypress {
30 | // interface Chainable {
31 | // login(email: string, password: string): Chainable
32 | // drag(subject: string, options?: Partial): Chainable
33 | // dismiss(subject: string, options?: Partial): Chainable
34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable
35 | // }
36 | // }
37 | // }
38 |
39 | export {}
--------------------------------------------------------------------------------
/cypress/support/component-index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Components App
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/cypress/support/component.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/component.ts is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
22 | import { mount } from 'cypress/react18'
23 |
24 | // Augment the Cypress namespace to include type definitions for
25 | // your custom command.
26 | // Alternatively, can be defined in cypress/support/component.d.ts
27 | // with a at the top of your spec.
28 | declare global {
29 | namespace Cypress {
30 | interface Chainable {
31 | mount: typeof mount
32 | }
33 | }
34 | }
35 |
36 | Cypress.Commands.add('mount', mount)
37 |
38 | // Example use:
39 | // cy.mount()
--------------------------------------------------------------------------------
/cypress/support/e2e.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/e2e.ts is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
--------------------------------------------------------------------------------
/docker-compose-test.yml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/docker-compose-test.yml
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Protract
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/jest-setup.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom'
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-react-typescript-starter",
3 | "private": true,
4 | "version": "0.0.0",
5 | "scripts": {
6 | "dev": "mode=development nodemon src/server/main.ts -w src/server",
7 | "start": "ts-node src/server/main.ts",
8 | "build": "tsc --project tsconfig.json && vite build",
9 | "preview": "vite preview",
10 | "ts-coverage": "typescript-coverage-report",
11 | "test": "jest"
12 | },
13 | "dependencies": {
14 | "@dnd-kit/core": "^6.0.8",
15 | "@dnd-kit/sortable": "^7.0.2",
16 | "@monaco-editor/react": "^4.5.1",
17 | "bcrypt": "^5.1.0",
18 | "bcryptjs": "^2.4.3",
19 | "cookie-parser": "^1.4.6",
20 | "d3": "^7.8.5",
21 | "dotenv": "^16.1.4",
22 | "express": "^4.18.2",
23 | "file-saver": "^2.0.5",
24 | "jszip": "^3.10.1",
25 | "monaco-editor": "^0.39.0",
26 | "mongodb": "^5.6.0",
27 | "mongoose": "^7.2.4",
28 | "react": "^18.2.0",
29 | "react-d3-tree": "^3.6.1",
30 | "react-dom": "^18.2.0",
31 | "ts-node": "^10.9.1",
32 | "typescript": "^4.9.3",
33 | "vite-express": "*"
34 | },
35 | "devDependencies": {
36 | "@babel/preset-env": "^7.22.5",
37 | "@babel/preset-react": "^7.22.5",
38 | "@babel/preset-typescript": "^7.22.5",
39 | "@cypress/code-coverage": "^3.10.7",
40 | "@testing-library/jest-dom": "^5.16.5",
41 | "@testing-library/react": "^14.0.0",
42 | "@testing-library/user-event": "^14.4.3",
43 | "@types/d3": "^7.4.0",
44 | "@types/express": "^4.17.15",
45 | "@types/file-saver": "^2.0.5",
46 | "@types/node": "^18.11.18",
47 | "@types/react": "^18.0.26",
48 | "@types/react-dom": "^18.0.10",
49 | "@vitejs/plugin-react": "^3.0.1",
50 | "autoprefixer": "^10.4.14",
51 | "cypress": "^12.15.0",
52 | "esbuild": "^0.18.9",
53 | "identity-obj-proxy": "^3.0.0",
54 | "jest": "^29.5.0",
55 | "jest-environment-jsdom": "^29.5.0",
56 | "nodemon": "^2.0.20",
57 | "path": "^0.12.7",
58 | "postcss": "^8.4.24",
59 | "prettier": "^2.8.8",
60 | "prettier-plugin-tailwindcss": "^0.3.0",
61 | "tailwind-scrollbar": "^3.0.4",
62 | "tailwindcss": "^3.3.2",
63 | "typescript-coverage-report": "^0.7.0",
64 | "vite": "^4.3.9"
65 | },
66 | "jest": {
67 | "testEnvironment": "jsdom",
68 | "setupFilesAfterEnv": [
69 | "/jest-setup.ts"
70 | ],
71 | "collectCoverageFrom": [
72 | "src/**/*.{ts,tsx,js.jsx}",
73 | "!src/**/*.d.ts"
74 | ],
75 | "moduleNameMapper": {
76 | "\\.(css|less)$": "identity-obj-proxy",
77 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/assetsTransformer.js"
78 | },
79 | "transformIgnorePatterns": [
80 | "/node_modules/(?!d3)/"
81 | ]
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/protract-icon-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/public/protract-icon-white.png
--------------------------------------------------------------------------------
/public/protract-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/public/protract-icon.png
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/client/App.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/src/client/App.css
--------------------------------------------------------------------------------
/src/client/App.tsx:
--------------------------------------------------------------------------------
1 | import MainContainer from './components/Containers/MainContainer';
2 | import React from 'react';
3 |
4 | export default function App() {
5 | return (
6 | <>
7 |
8 | >
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/src/client/assets/gifs/Create-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/src/client/assets/gifs/Create-demo.gif
--------------------------------------------------------------------------------
/src/client/assets/gifs/Export-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/src/client/assets/gifs/Export-demo.gif
--------------------------------------------------------------------------------
/src/client/assets/gifs/Projects-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/src/client/assets/gifs/Projects-demo.gif
--------------------------------------------------------------------------------
/src/client/assets/gifs/Protract-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/src/client/assets/gifs/Protract-demo.gif
--------------------------------------------------------------------------------
/src/client/assets/gifs/Tree-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/src/client/assets/gifs/Tree-demo.gif
--------------------------------------------------------------------------------
/src/client/assets/github-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/src/client/assets/github-dark.png
--------------------------------------------------------------------------------
/src/client/assets/github-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/src/client/assets/github-light.png
--------------------------------------------------------------------------------
/src/client/assets/logo-bg-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/src/client/assets/logo-bg-white.png
--------------------------------------------------------------------------------
/src/client/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/src/client/assets/logo.png
--------------------------------------------------------------------------------
/src/client/assets/logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/src/client/assets/logo2.png
--------------------------------------------------------------------------------
/src/client/assets/logo3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/src/client/assets/logo3.png
--------------------------------------------------------------------------------
/src/client/assets/protract-favicon-color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/src/client/assets/protract-favicon-color.png
--------------------------------------------------------------------------------
/src/client/assets/protract-readme-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Protract/a4fe7dbfbe5d13bd237d62186c7d68a164c5cf1a/src/client/assets/protract-readme-logo.png
--------------------------------------------------------------------------------
/src/client/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/client/components/BankEl.tsx:
--------------------------------------------------------------------------------
1 | import { UniqueIdentifier, useDraggable } from '@dnd-kit/core';
2 | import React from 'react';
3 |
4 | export default function BankEl(props: {
5 | key: UniqueIdentifier;
6 | id: UniqueIdentifier;
7 | }) {
8 | const { id } = props;
9 |
10 | const { attributes, listeners, setNodeRef } = useDraggable({ id });
11 |
12 | return (
13 |
19 | {id}
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/src/client/components/Canvas.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | SortableContext,
3 | arrayMove,
4 | verticalListSortingStrategy,
5 | } from '@dnd-kit/sortable';
6 | import SortableBankEl from './SortableBankEl';
7 | import { DndContext, DragEndEvent } from '@dnd-kit/core';
8 | import React, { useEffect, useState, useContext } from 'react';
9 | import { useDroppable } from '@dnd-kit/core';
10 | import { Item, Project } from '../../types';
11 | import { PlaygroundContext } from './Playground';
12 | import WarningModal from './Modals/WarningModal';
13 | import SaveModal from './Modals/SaveModal';
14 | import LoadModal from './Modals/LoadModal';
15 | import zipFiles from '../helperFunctions/zipFiles';
16 |
17 | export default function Canvas(props: {
18 | user: string;
19 | currComp: Item;
20 | handleCanvasUpdate: (arr: Item[]) => void;
21 | setChildren: React.Dispatch>;
22 | }) {
23 | const { user, currComp, setChildren, handleCanvasUpdate } = props;
24 | const { setComps, comps, setCurrComp } = useContext(PlaygroundContext);
25 |
26 | // used for dndkit, notifies this component as a droppable area
27 | const { setNodeRef } = useDroppable({
28 | id: 'canvas',
29 | });
30 |
31 | // list is the display of the canvas, originally set to currComp.children
32 | const [list, setList] = useState(currComp.children);
33 |
34 | // different modals show up depending on which buttons were clicked
35 | const [modal, setModal] = useState('');
36 |
37 | // project title set if user decides to save a project
38 | const [project, setProject] = useState('');
39 |
40 | // array of projects available to user when they choose to load projects on an account
41 | const [projects, setProjects] = useState([]);
42 |
43 | // updates order of children for the current component in the object that contains all components
44 | function handleAppReorder(
45 | comps: Item[],
46 | currComp: Item,
47 | list: Item[]
48 | ): Item[] {
49 | return comps.map((comp) => {
50 | if (comp.id === currComp.id && comp.children) {
51 | comp.children = list;
52 | } else {
53 | comp.children = handleAppReorder(comp.children, currComp, list);
54 | }
55 | return comp;
56 | });
57 | }
58 |
59 | // switches list when new currComp selected
60 | useEffect(() => {
61 | setList(currComp.children);
62 | }, [currComp]);
63 |
64 | // whenever list updates, tell parent what the order is now, and also call handleAppReorder
65 | useEffect(() => {
66 | handleCanvasUpdate(list);
67 | const updateApp = handleAppReorder(comps, currComp, list);
68 | setComps(updateApp);
69 | }, [list]);
70 |
71 | // function to reorder list items in the canvas
72 | function handleDragEnd(e: DragEndEvent) {
73 | const { active, over } = e;
74 | if (over === null) {
75 | return;
76 | }
77 | if (active.id !== over.id) {
78 | setList((list) => {
79 | const oldIndex = list.findIndex((item) => active.id === item.id);
80 | const newIndex = list.findIndex((item) => over.id === item.id);
81 | const updated = arrayMove(list, oldIndex, newIndex);
82 | // whenever order changes in an instance, update the children array
83 | // useeffect hooks for updating currComp and comps will run everytime children arr is updated as well
84 | setChildren(updated);
85 | return updated;
86 | });
87 | }
88 | }
89 |
90 | function handleCancel() {
91 | setModal('');
92 | }
93 |
94 | // returns state of app back to default when user first visits site
95 | function handleReset() {
96 | setComps([
97 | {
98 | value: 'app',
99 | id: 'app',
100 | codeStart: '',
101 | codeEnd: '',
102 | canEnter: true,
103 | children: [],
104 | },
105 | ]);
106 | setCurrComp({
107 | value: 'app',
108 | id: 'app',
109 | codeStart: '',
110 | codeEnd: '',
111 | canEnter: true,
112 | children: [],
113 | });
114 | setChildren([]);
115 | setModal('');
116 | setProject('');
117 | }
118 |
119 | async function saveProj(project: string) {
120 | try {
121 | await fetch('/proj', {
122 | method: 'PATCH',
123 | headers: {
124 | 'Content-Type': 'application/json',
125 | },
126 | body: JSON.stringify({
127 | username: user,
128 | project,
129 | root: comps,
130 | }),
131 | });
132 | } catch (err) {
133 | console.log(err);
134 | }
135 | }
136 |
137 | // asks for a project name if the project doesnt have one yet, otherwise patches the project
138 | function checkIfNewProj() {
139 | if (project === '') {
140 | showModal('save');
141 | } else {
142 | saveProj(project);
143 | }
144 | }
145 |
146 | function showModal(string: string) {
147 | setModal(string);
148 | }
149 |
150 | // loads the projects associated with the user
151 | async function handleLoad() {
152 | try {
153 | const response = await fetch(`/proj/${user}`, {
154 | method: 'GET',
155 | headers: {
156 | 'Content-Type': 'application/json',
157 | },
158 | });
159 | if (response) {
160 | const data = await response.json();
161 | setProjects(data);
162 | } else {
163 | throw new Error('Request failed');
164 | }
165 | } catch (error) {
166 | console.error('Failed to load projects');
167 | }
168 | setModal('load');
169 | }
170 |
171 | function handleExport() {
172 | zipFiles(comps[0]);
173 | }
174 |
175 | return (
176 |