├── Procfile ├── .vscode └── settings.json ├── public ├── favicon.ico ├── abstractlogo.png ├── images │ ├── logo-192x192.png │ ├── logo-512x512.png │ ├── abLogo-192x192.png │ ├── abLogo-512x512.png │ └── Asset_11.svg ├── offline.html ├── manifest.json ├── serviceworker.js ├── logo.svg └── index.html ├── readme-assets ├── export-code.GIF ├── review-code.GIF ├── switch-canvas.GIF ├── render-components.GIF ├── Asset_2.svg └── Asset_10.svg ├── src ├── components │ ├── RightContainer │ │ ├── Preview.jsx │ │ ├── NavButtons.jsx │ │ ├── Ipad.jsx │ │ ├── Phone.jsx │ │ ├── CanvasButton.jsx │ │ ├── PreviewButton.jsx │ │ ├── SizeButton.jsx │ │ ├── Canvas.jsx │ │ ├── RightContainer.jsx │ │ ├── ExportButton.jsx │ │ ├── UserProfileButton.jsx │ │ └── BodyContainer.jsx │ ├── ExportModalBackdrop.jsx │ ├── ComponentMenu │ │ ├── ReactRouterLibrary.jsx │ │ ├── AddButton.jsx │ │ ├── ReactRouterComponent.jsx │ │ ├── HTMLComponent.jsx │ │ ├── BootstrapComponent.jsx │ │ ├── HTMLLibrary.jsx │ │ ├── BootstrapLibrary.jsx │ │ ├── ComponentMenu.jsx │ │ └── ComSettings.jsx │ └── ExportCodeModal.jsx ├── reducers │ ├── index.js │ └── reducer.js ├── store.js ├── index.js ├── constants │ ├── actionTypes.js │ └── componentTypes.js ├── stylesheets │ ├── index.css │ ├── Login.css │ └── Dashboard.css ├── __tests__ │ ├── reducer.js │ └── enzyme.js ├── App.js ├── Pages │ ├── Dashboard.jsx │ └── Login.jsx └── actions │ └── actions.js ├── server ├── db │ ├── abstract.sql │ └── db.js ├── server.js └── controllers │ └── accountController.js ├── .gitignore ├── LICENSE.md ├── package.json └── README.md /Procfile: -------------------------------------------------------------------------------- 1 | web: node ./server/server.js -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.updateImportsOnFileMove.enabled": "always" 3 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Abstract/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/abstractlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Abstract/HEAD/public/abstractlogo.png -------------------------------------------------------------------------------- /public/images/logo-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Abstract/HEAD/public/images/logo-192x192.png -------------------------------------------------------------------------------- /public/images/logo-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Abstract/HEAD/public/images/logo-512x512.png -------------------------------------------------------------------------------- /readme-assets/export-code.GIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Abstract/HEAD/readme-assets/export-code.GIF -------------------------------------------------------------------------------- /readme-assets/review-code.GIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Abstract/HEAD/readme-assets/review-code.GIF -------------------------------------------------------------------------------- /readme-assets/switch-canvas.GIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Abstract/HEAD/readme-assets/switch-canvas.GIF -------------------------------------------------------------------------------- /public/images/abLogo-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Abstract/HEAD/public/images/abLogo-192x192.png -------------------------------------------------------------------------------- /public/images/abLogo-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Abstract/HEAD/public/images/abLogo-512x512.png -------------------------------------------------------------------------------- /readme-assets/render-components.GIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Abstract/HEAD/readme-assets/render-components.GIF -------------------------------------------------------------------------------- /src/components/RightContainer/Preview.jsx: -------------------------------------------------------------------------------- 1 | function Preview({ children }) { 2 | return ( 3 |
4 | { children } 5 |
6 | ) 7 | }; 8 | 9 | export default Preview 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import reducer from './reducer.js'; 4 | 5 | const reducers = combineReducers({ 6 | main : reducer, 7 | }); 8 | 9 | export default reducers; 10 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, } from 'redux'; 2 | import { composeWithDevTools } from 'redux-devtools-extension'; 3 | import reducers from './reducers/index'; 4 | 5 | const store = createStore( 6 | reducers, 7 | composeWithDevTools(), 8 | ); 9 | 10 | export default store; -------------------------------------------------------------------------------- /server/db/abstract.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS public.user_sessions; 2 | CREATE TABLE public.user_sessions ( 3 | "_id" varchar NOT NULL, 4 | "session_id" varchar NOT NULL, 5 | "username" varchar NOT NULL, 6 | PRIMARY KEY ("_id") 7 | ); 8 | 9 | INSERT INTO user_sessions ("_id", "session_id", "username") VALUES ('123456789', '987654321', 'test user'); -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './stylesheets/index.css'; 4 | import App from './App'; 5 | import store from './store'; 6 | import { Provider } from 'react-redux'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ); 14 | -------------------------------------------------------------------------------- /server/db/db.js: -------------------------------------------------------------------------------- 1 | import pg from 'pg' 2 | import dotenv from 'dotenv'; 3 | 4 | dotenv.config(); 5 | 6 | const PG_URI = process.env.SQLURL; 7 | 8 | const pool = new pg.Pool({ 9 | connectionString: PG_URI 10 | }); 11 | 12 | const db = { 13 | query: (text, params, callback) => { 14 | return pool.query(text, params, callback); 15 | } 16 | }; 17 | 18 | export default db; 19 | -------------------------------------------------------------------------------- /src/components/RightContainer/NavButtons.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CanvasButton from './CanvasButton.jsx'; 3 | import PreviewButton from './PreviewButton.jsx'; 4 | 5 | function NavButtons() { 6 | return ( 7 | 11 | ) 12 | }; 13 | 14 | export default NavButtons; 15 | -------------------------------------------------------------------------------- /public/offline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Abstract 7 | 10 | 11 | 12 |

Please go online to access the application.

13 | 14 | -------------------------------------------------------------------------------- /src/components/ExportModalBackdrop.jsx: -------------------------------------------------------------------------------- 1 | import { motion } from 'framer-motion'; 2 | 3 | const ExportModalBackdrop = ({ children, toggleExportModal }) => { 4 | return ( 5 | 11 | {children} 12 | 13 | ); 14 | }; 15 | 16 | export default ExportModalBackdrop; -------------------------------------------------------------------------------- /src/components/ComponentMenu/ReactRouterLibrary.jsx: -------------------------------------------------------------------------------- 1 | import ReactRouterComponent from "./ReactRouterComponent"; 2 | 3 | function ReactRouterLibrary (props) { 4 | let reactRcomponents = []; 5 | 6 | for (let x = 0; x < 2; x++) { 7 | reactRcomponents.push() 8 | } 9 | 10 | return ( 11 |
12 | {reactRcomponents} 13 |
14 | ) 15 | }; 16 | 17 | export default ReactRouterLibrary; 18 | -------------------------------------------------------------------------------- /src/constants/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const SET_ACTIVE_USER = 'SET_ACTIVE_USER'; 2 | export const ADD_COMPONENT = 'ADD_COMPONENT'; 3 | export const DELETE_COMPONENT = 'DELETE_COMPONENT'; 4 | export const TOGGLE_BODY_VIEW = 'TOGGLE_BODY_VIEW'; 5 | export const TOGGLE_CANVAS_SIZE = 'TOGGLE_CANVAS_SIZE'; 6 | export const TOGGLE_COMPONENT_MENU = 'TOGGLE_COMPONENT_MENU'; 7 | export const SELECT_COMPONENT = 'SELECT_COMPONENT'; 8 | export const TOGGLE_EXPORT_MODAL = 'TOGGLE_EXPORT_MODAL'; -------------------------------------------------------------------------------- /src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | background: black; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .gitignore 16 | package-lock.json 17 | .DS_Store 18 | .env 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | .env 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | -------------------------------------------------------------------------------- /src/components/ComponentMenu/AddButton.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import * as actions from '../../actions/actions.js'; 3 | 4 | const mapDispatchToProps = (dispatch) => ({ 5 | addComponent: (component) => dispatch(actions.addComponent(component)) 6 | }); 7 | 8 | function AddButton({ addComponent, component }) { 9 | return ( 10 |
11 | 12 |
13 | ) 14 | }; 15 | 16 | export default connect(null, mapDispatchToProps)(AddButton); 17 | -------------------------------------------------------------------------------- /src/components/RightContainer/Ipad.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { Icon } from '@iconify/react'; 3 | import * as actions from '../../actions/actions.js'; 4 | 5 | const mapDispatchToProps = (dispatch) => ({ 6 | toggleCanvasSize: (canvasSize) => dispatch(actions.toggleCanvasSize(canvasSize)) 7 | }); 8 | 9 | function Ipad(props) { 10 | return ( 11 | 14 | ); 15 | }; 16 | 17 | export default connect(null, mapDispatchToProps)(Ipad); -------------------------------------------------------------------------------- /src/components/RightContainer/Phone.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { Icon } from '@iconify/react'; 3 | import * as actions from '../../actions/actions.js'; 4 | 5 | const mapDispatchToProps = (dispatch) => ({ 6 | toggleCanvasSize: (canvasSize) => dispatch(actions.toggleCanvasSize(canvasSize)) 7 | }); 8 | 9 | function Phone(props) { 10 | return ( 11 | 14 | ); 15 | }; 16 | 17 | export default connect(null, mapDispatchToProps)(Phone); 18 | -------------------------------------------------------------------------------- /src/__tests__/reducer.js: -------------------------------------------------------------------------------- 1 | // import subject from '../src/reducers/reducer' 2 | 3 | //test redux states 4 | 5 | // describe('Abstract reducer'), () => { 6 | // let state; 7 | 8 | // beforeEach(() => { 9 | // state = { 10 | // username: "oakj", 11 | // renderedComponents: [], 12 | // bodyView: "Canvas", 13 | // canvasSize: "iPad Pro", 14 | // componentMenu: true, 15 | // selectedComponent: ' ', 16 | // exportModal: false, 17 | // prototypeCode: ``, 18 | // }; 19 | // }); 20 | 21 | // describe('default state', () => { 22 | 23 | // }) 24 | 25 | 26 | 27 | 28 | 29 | // }; -------------------------------------------------------------------------------- /src/components/RightContainer/CanvasButton.jsx: -------------------------------------------------------------------------------- 1 | import { React } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Icon } from '@iconify/react'; 4 | import * as actions from '../../actions/actions.js'; 5 | 6 | const mapDispatchToProps = (dispatch) => ({ 7 | toggleBodyView: (bodyView) => dispatch(actions.toggleBodyView(bodyView)) 8 | }); 9 | 10 | function CanvasButton(props) { 11 | return ( 12 | 15 | ) 16 | }; 17 | 18 | export default connect(null, mapDispatchToProps)(CanvasButton); -------------------------------------------------------------------------------- /src/components/RightContainer/PreviewButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Icon } from '@iconify/react'; 4 | import * as actions from '../../actions/actions.js'; 5 | 6 | const mapDispatchToProps = (dispatch) => ({ 7 | toggleBodyView: (bodyView) => dispatch(actions.toggleBodyView(bodyView)) 8 | }); 9 | 10 | function PreviewButton(props) { 11 | return ( 12 | 15 | ); 16 | }; 17 | 18 | export default connect(null, mapDispatchToProps)(PreviewButton); 19 | -------------------------------------------------------------------------------- /src/components/ComponentMenu/ReactRouterComponent.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import * as actions from '../../actions/actions.js'; 3 | import { Icon } from '@iconify/react'; 4 | 5 | 6 | const mapDispatchToProps = (dispatch) => ({ 7 | selectComponent: (component) => dispatch(actions.selectComponent(component)) 8 | }); 9 | 10 | 11 | function ReactRouterComponent() { 12 | return ( 13 |
14 | 17 |

IN-PROGRESS

18 |
19 | ); 20 | } 21 | export default connect (null, mapDispatchToProps)(ReactRouterComponent); -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Abstract", 3 | "name": "Abstract", 4 | "icons": [ 5 | { 6 | "src": "/images/Asset_11.svg", 7 | "type":"image/png", 8 | "sizes": "192x192" 9 | }, 10 | { 11 | "src": "/images/Asset_11.svg", 12 | "type":"image/png", 13 | "sizes": "192x192", 14 | "purpose":"maskable" 15 | }, 16 | { 17 | "src": "/images/Asset_11.svg", 18 | "type":"image/png", 19 | "sizes": "512x512" 20 | }, 21 | { 22 | "src": "/images/Asset_11.svg", 23 | "type":"image/png", 24 | "sizes": "512x512", 25 | "purpose":"maskable" 26 | } 27 | 28 | ], 29 | "start_url": ".", 30 | "display": "Standalone", 31 | "theme_color": "#61dafb", 32 | "background_color": "#61dafb" 33 | } 34 | -------------------------------------------------------------------------------- /src/components/ComponentMenu/HTMLComponent.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import * as actions from '../../actions/actions.js'; 3 | 4 | const mapDispatchToProps = (dispatch) => ({ 5 | selectComponent: (component) => dispatch(actions.selectComponent(component)), 6 | }); 7 | 8 | function HTMLComponent({ selectComponent, name, key, pic, disabled }) { 9 | if (disabled) { 10 | return ( 11 |
12 | 13 |

{name}

14 |
15 | ); 16 | } 17 | return ( 18 |
19 | 20 |

{name}

21 |
22 | ); 23 | } 24 | 25 | export default connect(null, mapDispatchToProps)(HTMLComponent); 26 | -------------------------------------------------------------------------------- /src/components/ComponentMenu/BootstrapComponent.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import * as actions from '../../actions/actions.js'; 3 | 4 | const mapDispatchToProps = (dispatch) => ({ 5 | selectComponent: (component) => dispatch(actions.selectComponent(component)) 6 | }); 7 | 8 | function BootstrapComponent( {selectComponent, name, key, pic, disabled}) { 9 | if (disabled) { 10 | return ( 11 |
12 | 13 |

{name}

14 |
15 | ) 16 | } 17 | return ( 18 |
19 | 20 |

{name}

21 |
22 | ); 23 | } 24 | 25 | export default connect(null, mapDispatchToProps)(BootstrapComponent); -------------------------------------------------------------------------------- /src/components/RightContainer/SizeButton.jsx: -------------------------------------------------------------------------------- 1 | import { React, useState } from 'react'; 2 | import { Icon } from '@iconify/react'; 3 | import Ipad from './Ipad.jsx'; 4 | import Phone from './Phone.jsx'; 5 | 6 | function SizeButton(props) { 7 | // declaring state to toggle state of dropdown 8 | const [dropdown, setDropdown] = useState(false); 9 | 10 | return( 11 |
12 | 15 | { 16 | // if dropdown is true, render IPad component and Phone component 17 | dropdown ? 18 | <> 19 | 20 | 21 | 22 | : 23 | null 24 | } 25 |
26 | ) 27 | }; 28 | 29 | export default SizeButton; 30 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { lazy, Suspense } from 'react'; 2 | import { BrowserRouter as Router, Switch, Route, } from 'react-router-dom'; 3 | const Login = lazy(() => import('./Pages/Login.jsx')) 4 | const Dashboard = lazy(() => import('./Pages/Dashboard.jsx')) 5 | 6 | function App() { 7 | return ( 8 | 9 | 10 | 11 | Loading...}> 12 | 13 | 14 | 15 | 16 | Loading...}> 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | }; 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /src/__tests__/enzyme.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { configure, shallow } from 'enzyme'; 3 | import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; 4 | 5 | import AddButton from '../components/ComponentMenu/AddButton.jsx'; 6 | import BootstrapComponent from '../components/ComponentMenu/BootstrapComponent.jsx'; 7 | 8 | configure({ adapter: new Adapter() }); 9 | 10 | // describe('React unit tests', () => { 11 | 12 | describe('Test Add Button', () => { 13 | it('should render a button', () => { 14 | const wrapper = shallow(); 15 | expect(wrapper.find('button').text()).toEqual('Add'); 16 | // expect(wrapper.text()).toEqual('Add'); 17 | }); 18 | }); 19 | describe('Test bootstrap buttons', () => { 20 | it('should render a click', () => { 21 | const wrapper = shallow(< BootstrapComponent/>); 22 | // expect(wrapper.find('button').simulate('click')); 23 | }) 24 | }); 25 | // }); 26 | 27 | -------------------------------------------------------------------------------- /src/components/RightContainer/Canvas.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | const mapStateToProps = (state) => ({ 5 | canvasSize: state.main.canvasSize 6 | }); 7 | 8 | function Canvas({ children, ...props }) { 9 | const [canvasStyle, setCanvasStyle] = useState({ width: '683px', height: '512px' }); 10 | 11 | useEffect(() => { 12 | if (props.canvasSize === 'iPad Pro') { // 75% of actual iPad Pro resolution 13 | setCanvasStyle({ width: '768px', height: '1024.5px' }) 14 | } else if (props.canvasSize === 'iPhone X') { // 100% of actual iPhone X resolution 15 | setCanvasStyle({ width: '375px', height: '812px' }) 16 | } 17 | }, [props.canvasSize]) 18 | 19 | return ( 20 |
21 | { children } 22 |
23 | ) 24 | }; 25 | 26 | export default connect(mapStateToProps, null)(Canvas); 27 | -------------------------------------------------------------------------------- /src/components/RightContainer/RightContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '@iconify/react'; 3 | import NavButtons from './NavButtons.jsx'; 4 | import BodyContainer from './BodyContainer.jsx'; 5 | import UserProfileButton from './UserProfileButton.jsx'; 6 | import SizeButton from './SizeButton.jsx'; 7 | 8 | 9 | function RightContainer() { 10 | return ( 11 |
12 | {/*
13 | 14 |
*/} 15 | 16 | {/* right-side buttons */} 17 | 18 | 19 | {/* main body */} 20 | 21 |
22 | ) 23 | }; 24 | 25 | 26 | 27 | 28 | export default RightContainer; 29 | -------------------------------------------------------------------------------- /src/components/RightContainer/ExportButton.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { React } from 'react'; 3 | import { Icon } from '@iconify/react'; 4 | import * as actions from '../../actions/actions.js'; 5 | 6 | const mapStateToProps = (state) => ({ 7 | exportModal: state.main.exportModal, 8 | bodyView: state.main.bodyView 9 | }) 10 | 11 | const mapDispatchToProps = (dispatch) => ({ 12 | toggleExportModal: (toggle) => dispatch(actions.toggleExportModal(toggle)), 13 | toggleBodyView: (toggle) => dispatch(actions.toggleBodyView(toggle)) 14 | }); 15 | 16 | function ExportButton(props) { 17 | return ( 18 | 21 | ) 22 | }; 23 | 24 | export default connect(mapStateToProps, mapDispatchToProps)(ExportButton); -------------------------------------------------------------------------------- /src/components/ComponentMenu/HTMLLibrary.jsx: -------------------------------------------------------------------------------- 1 | import HTMLComponent from './HTMLComponent.jsx'; 2 | import { Icon } from '@iconify/react'; 3 | 4 | function HTMLLibrary (props) { 5 | const HTMLcomponents = []; 6 | const names = ['div', 'link', 'image', 'paragraph'] 7 | const pictures = [,,,] 8 | 9 | for (let x = 0; x < names.length; x++) { 10 | if (names[x] === 'div' || names[x] === 'link' || names[x] === 'paragraph') { 11 | HTMLcomponents.push() 12 | } else { 13 | HTMLcomponents.push() 14 | } 15 | } 16 | 17 | return ( 18 |
19 | {HTMLcomponents} 20 |
21 | ) 22 | }; 23 | 24 | export default HTMLLibrary; 25 | -------------------------------------------------------------------------------- /src/components/RightContainer/UserProfileButton.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import ExportButton from './ExportButton'; 4 | 5 | const mapStateToProps = (state) => ({ 6 | username: state.main.username 7 | }); 8 | 9 | function UserProfileButton(props) { 10 | const [dropdown, setDropdown] = useState(false); 11 | 12 | function handleDropdown() { 13 | setDropdown(!dropdown); 14 | } 15 | 16 | return ( 17 |
18 | { 19 | 20 | } 21 | 22 | {/* */} 25 |

{ props.username ? props.username : "user"}

26 |
27 | ) 28 | }; 29 | 30 | export default connect(mapStateToProps, null)(UserProfileButton); 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Abstract 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/components/ComponentMenu/BootstrapLibrary.jsx: -------------------------------------------------------------------------------- 1 | import BootstrapComponent from './BootstrapComponent'; 2 | import { Icon } from '@iconify/react'; 3 | 4 | function BootstrapLibrary (props) { 5 | let BootstrapComponents = [] 6 | let name = ['button', 'form', 'navbar', 'nav', 'list'] 7 | let pictures = [, , , , ] 8 | 9 | for (let x = 0; x < name.length; x++){ 10 | if (name[x] === 'button' || name[x] === 'form' || name[x] === 'navbar' || name[x] === 'nav' || name[x] === 'list') { 11 | BootstrapComponents.push() 12 | } else { 13 | BootstrapComponents.push() 14 | } 15 | } 16 | 17 | return ( 18 |
19 | {BootstrapComponents} 20 |
21 | ) 22 | }; 23 | 24 | export default BootstrapLibrary; -------------------------------------------------------------------------------- /public/serviceworker.js: -------------------------------------------------------------------------------- 1 | const CACHE_NAME = "version-1"; 2 | const urlsToCache = ['index.html', 'offline.html']; 3 | 4 | const self = this; 5 | // Install SW 6 | self.addEventListener('install', (event) => { 7 | event.waitUntil( 8 | caches.open(CACHE_NAME) 9 | .then(cache => { 10 | console.log('Opened cache'); 11 | return cache.addAll(urlsToCache); 12 | }) 13 | ) 14 | }); 15 | 16 | // Listen for requests 17 | self.addEventListener('fetch', (event) => { 18 | event.respondWith( 19 | caches.match(event.request) 20 | .then(() => { 21 | return fetch(event.request) 22 | // if cannot fetch a cached url, send offline.html 23 | .catch(() => caches.match('offline.html')) 24 | }) 25 | ) 26 | }); 27 | 28 | // Actiate the SW 29 | self.addEventListener('activate', (event) => { 30 | const cacheWhiteList = []; 31 | cacheWhiteList.push(CACHE_NAME); 32 | 33 | event.waitUntil( 34 | caches.keys() 35 | .then(cacheNames => Promise.all( 36 | cacheNames.map(cacheName => { 37 | if(!cacheWhiteList.includes(cacheName)) { 38 | return caches.delete(cacheName); 39 | } 40 | }) 41 | )) 42 | ) 43 | }); -------------------------------------------------------------------------------- /src/components/RightContainer/BodyContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import * as actions from '../../actions/actions.js'; 3 | import Canvas from './Canvas.jsx'; 4 | import Preview from './Preview.jsx'; 5 | import { LiveProvider, LiveEditor, LiveError, LivePreview } from 'react-live' 6 | import { Form, Button, Navbar, Container, Nav, NavDropdown, ListGroup } from 'react-bootstrap'; 7 | 8 | const mapStateToProps = (state) => ({ 9 | bodyView: state.main.bodyView, 10 | renderedComponents: state.main.renderedComponents, 11 | prototypeCode: state.main.prototypeCode 12 | }); 13 | 14 | const mapDispatchToProps = (dispatch) => ({ 15 | selectComponent: (component) => dispatch(actions.selectComponent(component)) 16 | }); 17 | 18 | function BodyContainer(props) { 19 | const scope = { Form, Button, Navbar, Container, Nav, NavDropdown, ListGroup }; 20 | 21 | // code string is intentially tabbed this way for formatting on render 22 | const code = ` 23 | render ( 24 | <> 25 | ${props.prototypeCode} 26 | 27 | ); 28 | `; 29 | 30 | return ( 31 |
32 | 33 | { props.bodyView === 'Code Preview' ? : null } 34 |
35 | { props.bodyView === 'Canvas' ? : null } 36 |
37 |
38 | ) 39 | }; 40 | 41 | export default connect(mapStateToProps, mapDispatchToProps)(BodyContainer); -------------------------------------------------------------------------------- /src/Pages/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useParams } from 'react-router-dom'; 3 | import { connect } from 'react-redux'; 4 | import * as actions from '../actions/actions.js'; 5 | import '../stylesheets/Dashboard.css'; 6 | import RightContainer from '../components/RightContainer/RightContainer'; 7 | import ComponentMenu from '../components/ComponentMenu/ComponentMenu'; 8 | import ExportCodeModal from '../components/ExportCodeModal'; 9 | 10 | const mapStateToProps = (state) => ({ 11 | exportModal: state.main.exportModal 12 | }) 13 | 14 | const mapDispatchToProps = (dispatch) => ({ 15 | setUser: (username) => dispatch(actions.setUser(username)), 16 | }); 17 | 18 | function Dashboard(props) { 19 | // useParams parses the url and outputs the params. Using slice on username to extract just the username 20 | const username = useParams().username.slice(9); 21 | // destructure setUser action from props to use inside useEffect (so that useEffect doesn't render on every prop change 22 | const { setUser } = props; 23 | 24 | // invoke setUser action whenever the user changes 25 | useEffect(() => { 26 | setUser(username) 27 | }, [username, setUser]) 28 | 29 | return ( 30 | <> 31 | { 32 | props.exportModal ? : null 33 | } 34 |
35 |
36 | 37 | 38 |
39 |
40 | 41 | ) 42 | }; 43 | 44 | export default connect(mapStateToProps, mapDispatchToProps)(Dashboard); -------------------------------------------------------------------------------- /src/Pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import '../stylesheets/Login.css'; 2 | import { Icon } from '@iconify/react'; 3 | import { useEffect } from 'react'; 4 | 5 | function Login() { 6 | return ( 7 | useEffect(() => { 8 | document.body.style.overflow = "hidden"; 9 | return () => { 10 | document.body.style.overflow = "auto"; 11 | }; 12 | }, []), 13 | 14 |
15 | {/*

Abstract

*/} 16 |
17 |
18 |
19 | 20 |
21 |
22 |
32 |

Don't have a GitHub account?

33 | 34 |
35 | 36 |
37 |
38 |
39 |
40 |
41 | ); 42 | } 43 | 44 | export default Login; 45 | -------------------------------------------------------------------------------- /src/components/ComponentMenu/ComponentMenu.jsx: -------------------------------------------------------------------------------- 1 | import { React, useState } from 'react'; 2 | import HTMLLibrary from './HTMLLibrary.jsx'; 3 | import ReactRouterLibrary from './ReactRouterLibrary.jsx'; 4 | import BootstrapLibrary from './BootstrapLibrary.jsx'; 5 | import ComSettings from './ComSettings.jsx'; 6 | import { connect } from 'react-redux'; 7 | import * as actions from '../../actions/actions.js'; 8 | 9 | const mapStateToProps = (state) => ({ 10 | componentMenu: state.main.componentMenu 11 | }); 12 | 13 | const mapDispatchToProps = (dispatch) => ({ 14 | toggleComponentMenu: (componentMenu) => dispatch(actions.toggleComponentMenu(componentMenu)) 15 | }); 16 | 17 | function ComponentMenu (props) { 18 | // declaring library state to decide which dropdown (HTML, Bootstrap) to render 19 | const [library, setLibrary] = useState('') 20 | 21 | return ( 22 |
23 |
Component Menu
24 | { 25 | props.componentMenu ? 26 | <> 27 | { library === 'BootstrapLibrary' ? : null } 28 | { library === 'HTMLLibrary' ? : null } 29 | { library === 'ReactRouterLibrary' ? : null } 30 | 31 | 32 | : null 33 | } 34 |
35 | ) 36 | }; 37 | 38 | export default connect(mapStateToProps, mapDispatchToProps)(ComponentMenu); 39 | -------------------------------------------------------------------------------- /src/reducers/reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/actionTypes'; 2 | 3 | const initialState = { 4 | username: "oakj", 5 | renderedComponents: [], 6 | bodyView: "Canvas", // Canvas, Code Preview 7 | canvasSize: "iPad Pro", // iPhone X, iPad Pro 8 | componentMenu: true, 9 | selectedComponent: "", 10 | exportModal: false, 11 | // prototype code is used as an input to React-Live inside BodyContainer and as export code to Github 12 | prototypeCode: ``, 13 | }; 14 | 15 | const reducer = (state = initialState, action) => { 16 | switch (action.type) { 17 | case types.SET_ACTIVE_USER: { 18 | return { 19 | ...state, 20 | username: action.payload 21 | }; 22 | } 23 | case types.ADD_COMPONENT: { 24 | return { 25 | ...state, 26 | prototypeCode: state.prototypeCode + action.payload 27 | }; 28 | } 29 | case types.DELETE_COMPONENT: { 30 | return state; 31 | } 32 | case types.TOGGLE_BODY_VIEW: { 33 | return { 34 | ...state, 35 | bodyView: action.payload 36 | }; 37 | } 38 | case types.TOGGLE_CANVAS_SIZE: { 39 | return { 40 | ...state, 41 | canvasSize: action.payload 42 | }; 43 | } 44 | case types.TOGGLE_COMPONENT_MENU: { 45 | return { 46 | ...state, 47 | componentMenu: !state.componentMenu, 48 | } 49 | } 50 | case types.SELECT_COMPONENT: { 51 | return { 52 | ...state, 53 | selectedComponent: action.payload 54 | }; 55 | } 56 | case types.TOGGLE_EXPORT_MODAL: { 57 | return { 58 | ...state, 59 | exportModal: action.payload 60 | } 61 | } 62 | default: { 63 | return state; 64 | } 65 | } 66 | } 67 | 68 | export default reducer -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "abstract", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@iconify/react": "^3.0.1", 7 | "@octokit/core": "^3.5.1", 8 | "@testing-library/jest-dom": "^5.14.1", 9 | "@testing-library/react": "^11.2.7", 10 | "@testing-library/user-event": "^12.8.3", 11 | "bcrypt": "^5.0.1", 12 | "bootstrap": "^5.1.0", 13 | "buffer": "^6.0.3", 14 | "cookie-parser": "^1.4.5", 15 | "cors": "^2.8.5", 16 | "dotenv": "^10.0.0", 17 | "framer-motion": "^4.1.17", 18 | "jsonwebtoken": "^8.5.1", 19 | "jwt-decode": "^3.1.2", 20 | "node-fetch": "^3.0.0", 21 | "nodemon": "^2.0.12", 22 | "octokit": "^1.5.0", 23 | "path": "^0.12.7", 24 | "pg": "^8.7.1", 25 | "react": "^17.0.2", 26 | "react-bootstrap": "^2.0.0-beta.6", 27 | "react-dom": "^17.0.2", 28 | "react-live": "^2.2.3", 29 | "react-redux": "^7.2.5", 30 | "react-router": "^5.2.1", 31 | "react-router-dom": "^5.3.0", 32 | "react-scripts": "4.0.3", 33 | "redux": "^4.1.1", 34 | "redux-devtools-extension": "^2.13.9", 35 | "uuid": "^8.3.2", 36 | "web-vitals": "^1.1.2" 37 | }, 38 | "scripts": { 39 | "start": "react-scripts start", 40 | "build": "react-scripts build", 41 | "test": "react-scripts test", 42 | "eject": "react-scripts eject", 43 | "server-dev": "nodemon ./server/server.js " 44 | }, 45 | "eslintConfig": { 46 | "extends": [ 47 | "react-app", 48 | "react-app/jest" 49 | ] 50 | }, 51 | "browserslist": { 52 | "production": [ 53 | ">0.2%", 54 | "not dead", 55 | "not op_mini all" 56 | ], 57 | "development": [ 58 | "last 1 chrome version", 59 | "last 1 firefox version", 60 | "last 1 safari version" 61 | ] 62 | }, 63 | "type": "module", 64 | "devDependencies": { 65 | "@wojtekmaj/enzyme-adapter-react-17": "^0.6.3", 66 | "enzyme": "^3.11.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | import accountController from './controllers/accountController.js'; 2 | import dotenv from 'dotenv'; 3 | import cors from 'cors'; 4 | import express from 'express'; 5 | import cookieParser from 'cookie-parser'; 6 | import path from 'path'; 7 | 8 | // intiate __dirname to root path 9 | const __dirname = path.resolve(); 10 | 11 | const app = express(); 12 | const PORT = process.env.PORT || 5000; 13 | 14 | dotenv.config(); 15 | 16 | app.use(express.json()); 17 | app.use(express.urlencoded()); 18 | app.use(cors()); 19 | app.use(cookieParser()); 20 | 21 | console.log('testing heroku logs: entered server.js'); 22 | 23 | //oauth login 24 | app.get('/oauth', 25 | accountController.handleOAuth); 26 | 27 | // github API to create a repo 28 | app.post('/export', 29 | accountController.createRepo, 30 | (req, res) => { 31 | return res.sendStatus(200) 32 | } 33 | ) 34 | 35 | //github API submit files to existing repo 36 | app.put('/export', 37 | accountController.updateRepo, 38 | (req, res) => { 39 | return res.sendStatus(200) 40 | } 41 | ) 42 | 43 | // to deploy 44 | app.use(express.static(path.join(__dirname, 'build'))); 45 | // enpoint '/*' is needed to cover client routes for '/' and '/dashboard' 46 | app.get('/*', function (req, res) { 47 | res.sendFile(path.join(__dirname, 'build', 'index.html')); 48 | }); 49 | 50 | // catch-all route handler for any requests to an unknown route 51 | app.use((req, res) => res.status(404).send('Page not Found')); 52 | 53 | //global err handler 54 | app.use((err, req, res, next) => { 55 | const defaultErr = { 56 | log: 'Express error handler caught unknown middleware error', 57 | status: 500, 58 | message: { err: 'An error occurred' }, 59 | }; 60 | const errorObj = Object.assign({}, defaultErr, err); 61 | console.log(errorObj.log); 62 | return res.status(errorObj.status).json(errorObj.message); 63 | }); 64 | 65 | //server listening 66 | app.listen(PORT, () => { 67 | console.log(`Server listening on port: ${PORT}...`); 68 | }); -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | A B S T R C T -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 30 | 39 | Abstract 40 | 41 | 42 | 43 |
44 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/stylesheets/Login.css: -------------------------------------------------------------------------------- 1 | .logInContainer { 2 | text-align: center; 3 | background-color: #F0F8FF; 4 | 5 | } 6 | 7 | #Abstract { 8 | color: white; 9 | position: relative; 10 | bottom: -30px; 11 | } 12 | 13 | #abstractLogo { 14 | opacity: 100%; 15 | width: 50px; 16 | height: 50px; 17 | } 18 | 19 | .logInHeader { 20 | min-height: 100vh; 21 | display: flex; 22 | flex-direction: column; 23 | justify-content: center; 24 | align-items: center; 25 | font-size: calc(6px + 2vmin); 26 | color: black; 27 | } 28 | 29 | .App-link { 30 | color: #61dafb; 31 | } 32 | 33 | #welcomeBorderBox { 34 | box-shadow: deepskyblue 0px 3px 8px; 35 | border-radius: 4px; 36 | background-color: white; 37 | width: 40%; 38 | padding: 70px;; 39 | } 40 | 41 | #loginButtonDiv { 42 | display:flex; 43 | justify-content: center; 44 | align-items: center; 45 | } 46 | #frontPageLogInButton { 47 | border-radius: 5px; 48 | font-size: 1.25rem; 49 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; 50 | height: 90px; 51 | display:flex; 52 | justify-content: center; 53 | align-items: center; 54 | background-color: #f5fbff; 55 | border: 1px solid #093A7B; 56 | margin:0px; 57 | } 58 | 59 | #frontPageLogInButton:hover { 60 | box-shadow: 0 0 11px rgba(33,33,33,.2); 61 | } 62 | 63 | #loginText { 64 | margin: 20px; 65 | } 66 | 67 | #frontPageSignUpButton { 68 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; 69 | border-radius: 6px; 70 | height: 60px; 71 | width: auto; 72 | font-size: 1.25rem; 73 | background-color: #f5fbff; 74 | border: 1px solid #093A7B; 75 | } 76 | 77 | 78 | #frontPageSignUpButton:hover { 79 | box-shadow: 0 0 11px rgba(33,33,33,.2); 80 | } 81 | 82 | #logIn { 83 | border-radius: 15px; 84 | width: 270px; 85 | height: 70px; 86 | font-size: 30px; 87 | word-wrap: break-word; 88 | } 89 | 90 | #gitHubLogo { 91 | width: 50px; 92 | height: 50px; 93 | padding: 1px; 94 | 95 | } 96 | 97 | #hexagon { 98 | color: white; 99 | } 100 | 101 | #welcomeMessage { 102 | font-size: larger; 103 | font-weight: 350; 104 | } 105 | #noAccount { 106 | font-size: large; 107 | font-weight: 400; 108 | } 109 | 110 | #logo { 111 | position: absolute; 112 | top: 0; 113 | width: 40%; 114 | height: 40px; 115 | /* display: flex; 116 | flex-direction: column; 117 | justify-content: center; 118 | align-items: center; */ 119 | /* position: absolute; 120 | top: 0; */ 121 | /* margin: 0%; */ 122 | /* width: 100px; 123 | height: 100px; */ 124 | } 125 | 126 | 127 | @media (max-width: 820px) { 128 | #welcomeBorderBox { 129 | width:100%; 130 | height:fit-content; 131 | padding: 0; 132 | } 133 | #loginButtonDiv { 134 | width: 100% 135 | } 136 | #frontPageLogInButton { 137 | width: 100%; 138 | /* font-size: 1rem; */ 139 | } 140 | #frontPageSignUpButton { 141 | width:auto; 142 | } 143 | #abstractlogo { 144 | width: 80%; 145 | height: auto; 146 | } 147 | } -------------------------------------------------------------------------------- /src/actions/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/actionTypes'; 2 | import * as componentTypes from '../constants/componentTypes'; 3 | 4 | // action for SET_ACTIVE_USER action type 5 | export const setUser = (username) => { 6 | return { 7 | type: types.SET_ACTIVE_USER, 8 | payload: username 9 | } 10 | }; 11 | 12 | // action for ADD_COMPONENT action type 13 | export const addComponent = (selectedComponent) => { 14 | switch (selectedComponent) { 15 | case 'bootstrapForm': 16 | return { 17 | type: types.ADD_COMPONENT, 18 | payload: componentTypes.bootstrapForm 19 | } 20 | case 'bootstrapButton': 21 | return { 22 | type: types.ADD_COMPONENT, 23 | payload: componentTypes.bootstrapButton 24 | } 25 | case 'bootstrapNavbar': 26 | return { 27 | type: types.ADD_COMPONENT, 28 | payload: componentTypes.bootstrapNavbar 29 | } 30 | case 'bootstrapNav': 31 | return { 32 | type: types.ADD_COMPONENT, 33 | payload: componentTypes.bootstrapNav 34 | } 35 | case 'bootstrapList': 36 | return { 37 | type: types.ADD_COMPONENT, 38 | payload: componentTypes.bootstrapList 39 | } 40 | case 'htmlDiv': 41 | return { 42 | type: types.ADD_COMPONENT, 43 | payload: componentTypes.htmlDiv 44 | } 45 | case 'htmlLink': 46 | return { 47 | type: types.ADD_COMPONENT, 48 | payload: componentTypes.htmlLink 49 | } 50 | case 'htmlImage': 51 | return { 52 | type: types.ADD_COMPONENT, 53 | payload: componentTypes.htmlImage 54 | } 55 | case 'htmlParagraph': 56 | return { 57 | type: types.ADD_COMPONENT, 58 | payload: componentTypes.htmlParagraph 59 | } 60 | default: 61 | return { 62 | type: types.ADD_COMPONENT, 63 | payload: 'Not a component type.' 64 | } 65 | } 66 | } 67 | 68 | //action for DELETE_COMPONENT action type 69 | export const deleteComponent = (selectedComponent) => ({ 70 | type: types.DELETE_COMPONENT, 71 | payload: selectedComponent 72 | }) 73 | 74 | // action for TOGGLE_BODY_VIEW action type 75 | export const toggleBodyView = (bodyView) => ({ 76 | type: types.TOGGLE_BODY_VIEW, 77 | payload: bodyView 78 | }) 79 | 80 | //action for TOGGLE_CANVAS_SIZE action type 81 | export const toggleCanvasSize = (canvasSize) => ({ 82 | type: types.TOGGLE_CANVAS_SIZE, 83 | payload: canvasSize 84 | }) 85 | 86 | //action for TOGGLE_COMPONENT_MENU action type 87 | export const toggleComponentMenu = (componentMenu) => { 88 | return { 89 | type: types.TOGGLE_COMPONENT_MENU, 90 | payload: componentMenu 91 | } 92 | } 93 | 94 | //action for SELECT_COMPONENT action type 95 | export const selectComponent = (component) => ({ 96 | type: types.SELECT_COMPONENT, 97 | payload: component 98 | }) 99 | 100 | //action for TOGGLE EXPORT MODAL 101 | export const toggleExportModal = (toggle) => ({ 102 | type: types.TOGGLE_EXPORT_MODAL, 103 | payload: toggle 104 | }) 105 | -------------------------------------------------------------------------------- /src/components/ExportCodeModal.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { motion } from 'framer-motion'; 3 | import * as actions from '../actions/actions.js'; 4 | import ExportModalBackdrop from "./ExportModalBackdrop"; 5 | 6 | 7 | const mapStateToProps = (state) => ({ 8 | exportModal: state.main.exportModal, 9 | prototypeCode: state.main.prototypeCode, 10 | username: state.main.username, 11 | bodyView: state.main.bodyView 12 | }) 13 | 14 | const mapDispatchToProps = (dispatch) => ({ 15 | toggleExportModal: (toggle) => dispatch(actions.toggleExportModal(toggle)), 16 | }); 17 | 18 | function ExportCodeModal ({ toggleExportModal, exportModal, ...props}) { 19 | const handleFormSubmit = async (e) => { 20 | e.preventDefault(); 21 | const { repository_name, commit_message } = e.target.elements 22 | // create github repo 23 | const createRepo = await fetch('https://abstractreact.herokuapp.com/export', { 24 | method: 'POST', 25 | mode: 'cors', 26 | body: JSON.stringify({ 27 | "repository_name": repository_name.value, 28 | }), 29 | headers: { 30 | 'Accept': 'application/json', 31 | 'Content-Type': 'application/json' 32 | }, 33 | }) 34 | .catch(e => console.log('create err :', e)); 35 | 36 | //update files 37 | const updateRepo = await fetch('https://abstractreact.herokuapp.com/export', { 38 | method: 'PUT', 39 | mode: 'cors', 40 | body: JSON.stringify({ 41 | "prototypeCode": props.prototypeCode, 42 | "repository_name": repository_name.value, 43 | "commit_message": commit_message.value, 44 | "username": props.username, 45 | }), 46 | headers: { 47 | 'Accept' : 'application/json', 48 | 'Content-Type': 'application/json' } 49 | }) 50 | .then(toggleExportModal(!exportModal)) 51 | .then(alert('Code successfully exported! Please check your Github repos.')) 52 | .catch(e => console.log('update err: ', e)); 53 | } 54 | 55 | return ( 56 | toggleExportModal(!exportModal)}> 57 | e.stopPropagation()} 59 | className="abstract_modal orange-gradient" 60 | initial='hidden' 61 | animate='visible' 62 | exit='exit' 63 | > 64 | 65 |
66 |

Export Code

67 |

Create a Repository

68 |
69 | 70 | 71 | 72 | 73 | 74 | 75 |
76 |
77 |
78 |
79 | ) 80 | } 81 | 82 | export default connect(mapStateToProps, mapDispatchToProps)(ExportCodeModal); -------------------------------------------------------------------------------- /src/constants/componentTypes.js: -------------------------------------------------------------------------------- 1 | export const bootstrapForm = 2 | ` 3 |
4 | 5 | Email address 6 | 7 | 8 | We'll never share your email with anyone else. 9 | 10 | 11 | 12 | 13 | Password 14 | 15 | 16 | 17 | 18 | 19 | 22 |
23 | `; 24 | 25 | export const bootstrapButton = 26 | ` 27 | 33 | `; 34 | 35 | export const bootstrapNavbar = 36 | ` 37 | 38 | 39 | React-Bootstrap 40 | 41 | 42 | 53 | 54 | 55 | 56 | `; 57 | 58 | export const bootstrapNav = 59 | ` 60 | 79 | `; 80 | 81 | export const bootstrapList = 82 | ` 83 | 84 | Cras justo odio 85 | Dapibus ac facilisis in 86 | Morbi leo risus 87 | Porta ac consectetur ac 88 | Vestibulum at eros 89 | 90 | `; 91 | 92 | export const htmlDiv = 93 | ` 94 |
html div
95 | `; 96 | 97 | export const htmlLink = 98 | ` 99 | html anchor link 100 | `; 101 | 102 | export const htmlImage = 103 | ` 104 | logo 105 | `; 106 | 107 | export const htmlParagraph = 108 | ` 109 |

html paragraph

110 | `; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | logo 3 |

4 | 5 |

6 | PRs Welcome 7 | 8 | License: MIT 9 | 10 | Version Beta 1.0.0 11 |

12 | 13 |

Abstract Beta is a mobile first application focused on improving developer experience. Create mobile first application prototypes in real-time with proven UI frameworks such as React Bootstrap.

14 | 15 |
16 | 17 | ## Table of Contents 18 | * [Demo](#demo) 19 | + [Select Device Resolution for the Canvas](#select-device-resolution-for-the-canvas) 20 | + [Render Components on the Canvas](#render-components-on-the-canvas) 21 | + [Review Code](#review-code) 22 | + [Export Code](#export-code) 23 | * [Live Features](#live-features) 24 | * [How To Use](#how-to-use) 25 | + [Web Based](#web-based) 26 | + [Running Local](#running-local) 27 | * [Contribute](#how-to-use) 28 | + [Contributors](#contributors) 29 | + [How to contribute](#how-to-contribute) 30 | + [New feature log](#new-feature-log) 31 | * [License](#license) 32 | 33 | ## Demo 34 | ### Select Device Resolution for the Canvas 35 | canvas-size-gif 36 | 37 | ### Render Components on the Canvas 38 | render-components-gif 39 | 40 | ### Review Code 41 | review-code-gif 42 | 43 | ### Export Code 44 | export-code-gif 45 | 46 | ## Live Features 47 | 1. Real-time live preview of your prototype including rendered components and styling. 48 | 2. Prototype on multiple canvas sizes (e.g. iPad Pro and iPhone X). 49 | 3. Code preview formatted and synchronized with the prototype render view. 50 | 4. React-Bootstrap component integration. 51 | 5. Export your prototype code to your Github account. 52 | 6. Secure signup and login using Github OAuth. 53 | 7. Installable progressive web application 54 | 55 | ## How To Use 56 | ### Web-based 57 | 1. Visit [abstractreact.herokuapp.com](https://abstractreact.herokuapp.com) 58 | 2. Login with a GitHub account 59 | ### Running local 60 | 1. Clone this repo 61 | ``` 62 | git clone https://github.com/oslabs-beta/Abstract.git 63 | ``` 64 | 2. Navigate to your local directory 65 | 3. Install dependencies 66 | ``` 67 | npm install 68 | ``` 69 | 4. Run the client (verify that localhost port 3000 is not being used) 70 | ``` 71 | npm start 72 | ``` 73 | 74 | ## Contribute 75 | Abstract is an open-source product supported via the tech accelerator OS Labs/OS Labs-beta. We encourge all feedback and welcome all contributions. Note that this product does not have any full-time contributors. Expect that there will be a wait time before your pull request can be reviewed. 76 | 77 | #### Contributors 78 | - Jonnie Oak [@Github](https://github.com/oakj) [@Linkedin](https://www.linkedin.com/in/oakj28/) 79 | - Brian Cheng [@Github](https://github.com/chengbrian9) [@Linkedin](https://www.linkedin.com/in/brian-cheng24/) 80 | - Raymond Hu [@Github](https://github.com/rhu0) [@Linkedin](https://www.linkedin.com/in/raymond-hu-3b18231a2/) 81 | - Omar Brown [@Github](https://github.com/rashadhndrxx) [@Linkedin](https://www.linkedin.com/in/omar-b-76892521b/) 82 | 83 | #### How to contribute 84 | 1. Fork the repo and create a new feature branch from dev. 85 | 2. Any changes to the code base should include unit and/or integration tests. 86 | 3. Ensure your code follows proper formatting recommendations. 87 | 4. Create a pull request to the dev branch. 88 | 89 | #### New feature log 90 | 1. Drag and drop feature from the component menu onto the canvas 91 | 2. Refactor to TypeScript 92 | 3. Support TypeScript exports 93 | 4. Add support for additional component libraries (Material UI, Chakra UI, etc) 94 | 95 | ## License 96 | This project is licensed under the MIT License - see the LICENSE.md file for details. -------------------------------------------------------------------------------- /server/controllers/accountController.js: -------------------------------------------------------------------------------- 1 | import db from '../db/db.js'; 2 | import fetch from 'node-fetch'; 3 | import { v4 as uuid } from 'uuid'; 4 | import jwt from 'jsonwebtoken'; 5 | import { Octokit } from "@octokit/core"; 6 | import jwt_decode from 'jwt-decode'; 7 | import { Buffer } from 'buffer'; 8 | import bcrypt from 'bcrypt'; 9 | 10 | const salt = 11; // bcrypt salt work factor 11 | const accountController = {}; 12 | 13 | // OAuth 14 | accountController.handleOAuth = async (req, res, next) => { 15 | console.log('entered server.js/OAuth endpoint and handleOAuth controller'); 16 | // deconstruct req.query to get code from first Github GET request 17 | const { code } = req.query; 18 | // handle edge case if code is not provided 19 | if (!code) { 20 | throw new Error("Missing code from Github!"); 21 | } 22 | 23 | // once we get code back from Github, we need to make a POST to: https://github.com/login/oauth/access_token/ 24 | let accessToken = await fetch(`https://github.com/login/oauth/access_token/?client_id=${process.env.GITHUB_OAUTH_CLIENT_ID}&client_secret=${process.env.GITHUB_OAUTH_CLIENT_SECRET}&code=${code}`, { 25 | method: 'POST', 26 | headers: { 27 | 'Accept': 'application/json', 28 | } 29 | }) 30 | .then(response => response.json()) 31 | .then(data => data) 32 | .catch(error => { 33 | return next({ 34 | status: 500, 35 | message: error 36 | }); 37 | }); 38 | 39 | // use access token from POST request above to access user 40 | let userData = await fetch('https://api.github.com/user', { 41 | method: 'GET', 42 | // pass in access token into authorization header 43 | headers: { 44 | Authorization: `token ${accessToken.access_token}` 45 | } 46 | }) 47 | .then(response => response.json()) 48 | .then(data => data) 49 | .catch(error => { 50 | return next({ 51 | status: 500, 52 | message: error 53 | }) 54 | }); 55 | 56 | // db query: store access token in database under unique user _id and username 57 | const id = uuid(); 58 | const query = ` 59 | INSERT INTO user_sessions ("_id", "session_id", "username" ) 60 | VALUES ($1, $2, $3);`; 61 | const params = [id, accessToken, userData.login]; 62 | const dbResponse = await db.query(query, params) 63 | .then(response => { 64 | return response; 65 | }) 66 | .catch(error => { 67 | return next({ 68 | status: 500, 69 | message: error 70 | }) 71 | }); 72 | 73 | // bcrypt access token before storing in db or in a jwt 74 | accessToken = await bcrypt.hash(accessToken.access_token, salt); 75 | 76 | const token = jwt.sign(JSON.stringify(accessToken), process.env.USER_JWT_SECRET) 77 | 78 | res.cookie("github-token-jwt", token, { 79 | httpOnly: true, 80 | secure: true 81 | }) 82 | 83 | // store access token in a jwt cookie to send back to server on Github API request 84 | 85 | // store user data in a jwt cookie to send back to server on Github API request 86 | const user = jwt.sign(JSON.stringify(userData), process.env.USER_JWT_SECRET) 87 | res.cookie("github-user-jwt", user, { 88 | httpOnly: true, 89 | secure: true 90 | }) 91 | 92 | // redirect to dashboard with the username as a query paramater (to modify Redux store) 93 | return res.redirect(`https://abstractreact.herokuapp.com/dashboard/username=${userData.login}`); 94 | } 95 | 96 | //create github repo 97 | accountController.createRepo = async (req, res, next) => { 98 | // console.log('got to create Repo') 99 | 100 | //get access token for octokit 101 | const cookie = req.cookies["github-token-jwt"]; 102 | const decodedCookie = jwt_decode(cookie); 103 | 104 | // get user data cookie 105 | const userData = req.cookies["github-user-jwt"]; 106 | const decodeduserData = jwt_decode(userData); 107 | 108 | // get access token for user from database 109 | const query = `SELECT session_id FROM user_sessions WHERE username = $1;` 110 | const db_result = await db.query(query, [decodeduserData.login]) 111 | .then(response => response) 112 | .catch(err => next(err)); 113 | 114 | // extract most recent access_token from db 115 | // compare decodedCookie with access token from db (hashed) 116 | const userToken = JSON.parse(db_result.rows[db_result.rows.length - 1].session_id).access_token; 117 | const isUserValid = await bcrypt.compare(userToken, decodedCookie); 118 | 119 | 120 | // start here after lunch 121 | // if access token is valid, do every thing below 122 | if (isUserValid) { 123 | const repo_name = req.body.repository_name; 124 | const octokit = new Octokit({ auth: `${userToken}` }); 125 | const createResponse = await octokit.request(`POST /user/repos`, { 126 | name: `${repo_name}`, 127 | private: true, 128 | auto_init: true, 129 | }) 130 | .then(response => response) 131 | .catch(error => next({ 132 | status: 500, 133 | message: error 134 | })); 135 | 136 | // console.log('create Response', createResponse); 137 | return next(); 138 | } else { 139 | // if access token is not valid, console log to server the error, redirect to login page 140 | return res.redirect('https://abstractreact.herokuapp.com'); 141 | } 142 | } 143 | 144 | //update repo with files 145 | accountController.updateRepo = async (req, res, next) => { 146 | try { 147 | const username = req.body.username; 148 | const commit_msg = req.body.commit_message; 149 | const repo_name = req.body.repository_name.replace(' ', '-'); 150 | 151 | // decoded token 152 | const cookie = req.cookies["github-token-jwt"]; 153 | const decodedCookie = jwt_decode(cookie); 154 | const prototypeCode = Buffer.from(`${req.body.prototypeCode}`, 'binary').toString('base64'); 155 | 156 | // decoded user data 157 | const userData = req.cookies["github-user-jwt"]; 158 | const decodeduserData = jwt_decode(userData); 159 | 160 | // get access token for user from database 161 | const query = `SELECT session_id FROM user_sessions WHERE username = $1;` 162 | const db_result = await db.query(query, [decodeduserData.login]) 163 | .then(response => response) 164 | .catch(err => next(err)); 165 | 166 | // extract most recent access_token from db 167 | // compare decodedCookie with access token from db (hashed) 168 | const userToken = JSON.parse(db_result.rows[db_result.rows.length - 1].session_id).access_token; 169 | const isUserValid = await bcrypt.compare(userToken, decodedCookie); 170 | 171 | if (isUserValid) { 172 | const octokit = new Octokit({ auth: `${userToken}` }); 173 | const updateResponse = await octokit.request('PUT /repos/{owner}/{repo}/contents/{path}', { 174 | owner: `${username}`, 175 | repo: `${repo_name}`, 176 | message: `${commit_msg}`, 177 | content: `${prototypeCode}`, 178 | path: `App.jsx`, 179 | }) 180 | return next(); 181 | } else { 182 | return res.redirect('https://abstractreact.herokuapp.com/'); 183 | } 184 | 185 | } 186 | catch (error) { 187 | return next({ 188 | status: 500, 189 | message: error 190 | }) 191 | } 192 | } 193 | 194 | 195 | export default accountController; -------------------------------------------------------------------------------- /src/stylesheets/Dashboard.css: -------------------------------------------------------------------------------- 1 | /* Dashboard Container - only for Desktop */ 2 | @media screen and (min-width: 1367px) { 3 | body { 4 | overflow: auto; 5 | } 6 | 7 | #dashboard_container { 8 | display: flex; 9 | flex-direction: column; 10 | justify-content: center; 11 | align-items: center; 12 | 13 | background-color: #ACC6E3; 14 | height: 100vh; 15 | } 16 | 17 | #dashboard { 18 | transform: scale(0.8); 19 | } 20 | 21 | /* Hide scrollbar for Chrome, Safari and Opera */ 22 | #component_menu::-webkit-scrollbar { 23 | display: none; 24 | } 25 | 26 | /* Hide scrollbar for IE, Edge and Firefox */ 27 | #component_menu { 28 | -ms-overflow-style: none; /* IE and Edge */ 29 | scrollbar-width: none; /* Firefox */ 30 | } 31 | 32 | .backdrop { 33 | z-index: 9001; 34 | } 35 | 36 | .abstract_modal { 37 | z-index: 9002; 38 | } 39 | 40 | } 41 | 42 | /* Export Modal Styling*/ 43 | .backdrop { 44 | display: flex; 45 | align-items: center; 46 | justify-content: center; 47 | position: fixed; 48 | top: 0; 49 | left: 0; 50 | height: 100%; 51 | width: 100%; 52 | background: #000000e1; 53 | } 54 | 55 | #canvas_backdrop { 56 | background-color: white; 57 | /* border-style: solid; */ 58 | display: flex; 59 | flex-direction: column; 60 | align-items: center; 61 | justify-content: center; 62 | background: linear-gradient(90deg, black 50%, transparent 50%), 63 | linear-gradient(90deg, black 50%, transparent 50%), 64 | linear-gradient(0deg, black 50%, transparent 50%), 65 | linear-gradient(0deg, black 50%, transparent 50%); 66 | background-repeat: repeat-x, repeat-x, repeat-y, repeat-y; 67 | background-size: 16px 4px, 16px 4px, 4px 16px, 4px 16px; 68 | background-position: 0% 0%, 100% 100%, 0% 100%, 100% 0px; 69 | border-radius: 5px; 70 | padding: 10px; 71 | animation: dash 15s linear infinite; 72 | } 73 | @keyframes dash { 74 | to { 75 | background-position: 100% 0%, 0% 100%, 0% 0%, 100% 100%; 76 | } 77 | } 78 | .abstract_modal { 79 | width: clamp(50%, 800px, 90%); 80 | /* width:50%; */ 81 | height: min(70%, 700px); 82 | margin: auto; 83 | padding: 0 1rem 0 5rem; 84 | border-radius: 12px; 85 | display: flex; 86 | flex-direction: column; 87 | align-items: center; 88 | justify-content: flex-start; 89 | background: #f5fbff; 90 | } 91 | 92 | #export_form { 93 | width: 90%; 94 | display: flex; 95 | flex-direction: column; 96 | justify-content: flex-start; 97 | align-content: flex-start; 98 | align-items: flex-start; 99 | } 100 | 101 | #repository_name, 102 | #commit_message { 103 | height: fit-content; 104 | } 105 | 106 | #close_modal { 107 | display: flex; 108 | justify-content: right; 109 | align-self: flex-end; 110 | align-items: flex-end; 111 | padding: 15px; 112 | border: none; 113 | background: none; 114 | font-size: 2rem; 115 | } 116 | 117 | #export_form input { 118 | width: 100%; 119 | } 120 | 121 | .export_form_container { 122 | width: 100%; 123 | display: flex; 124 | flex-direction: column; 125 | justify-content: left; 126 | align-self: flex-start; 127 | } 128 | 129 | .export_form_container h2, 130 | .export_form_container h3 { 131 | margin: .5rem 0 .5rem 0; 132 | } 133 | 134 | #component_menu h3 { 135 | padding-top: 25px; 136 | } 137 | .export_form_container input { 138 | margin: .5rem 0 .5rem 0; 139 | height: 1.25rem; 140 | } 141 | 142 | #submit_export_button { 143 | display: flex; 144 | justify-content: center; 145 | align-self: center; 146 | /* padding: 10px; */ 147 | margin: 30px; 148 | background-color: #f5fbff; 149 | border: 2px solid #093A7B; 150 | 151 | } 152 | 153 | /* Dashboard Styling */ 154 | #dashboard { 155 | display: grid; 156 | grid-template-columns: 1fr 3fr; 157 | } 158 | 159 | /* Component Menu Styling*/ 160 | #component_menu { 161 | display: flex; 162 | flex-direction: column; 163 | align-items: center; 164 | overflow: scroll; 165 | height: 1024px; 166 | width: 306px; 167 | border: 0.25px solid; 168 | background-color: #f5fbff 169 | } 170 | #component_menu_banner { 171 | width: 100%; 172 | display:flex; 173 | flex-direction: column; 174 | align-items: center; 175 | justify-content: center; 176 | background-color: #456DAA; 177 | font-size: 20px; 178 | font-weight: bold; 179 | color: whitesmoke; 180 | height: 60px; 181 | } 182 | 183 | /* Dashboard-Right Styling */ 184 | #dashboard_right { 185 | display: grid; 186 | grid-template-columns: 60% auto 100px; 187 | grid-template-rows: 124px 420px auto; 188 | grid-template-areas: 189 | "profile_button_container profile_button_container profile_button_container" 190 | "body_container body_container size_button" 191 | "body_container body_container nav_button"; 192 | 193 | height: 1024px; 194 | width: 1060px; 195 | background-color: white; 196 | } 197 | 198 | #profile_button_container { 199 | grid-area: profile_button_container; 200 | grid-column-end: four; 201 | width: 100%; 202 | display: flex; 203 | flex-direction: row; 204 | align-items: center; 205 | justify-content: space-between; 206 | /* border: 2px solid #093A7B; */ 207 | /* margin-right: 20px; */ 208 | height: 124px; 209 | } 210 | 211 | #profile_button { 212 | display: flex; 213 | flex-direction: row; 214 | align-items: center; 215 | justify-content: center; 216 | 217 | height: 85px; 218 | width: auto; 219 | font-size: 1.25rem; 220 | font-weight: lighter; 221 | 222 | /* background-color: #f5fbff; */ 223 | border-top: 1px solid #093A7B; 224 | border-bottom: 1px solid #093A7B; 225 | 226 | padding-left: 20px; 227 | padding-right: 20px; 228 | margin-bottom: 0px; 229 | margin-right: 12px; 230 | } 231 | 232 | #export_modal_button { 233 | height: 85px; 234 | width: auto; 235 | padding-left: 10px; 236 | padding-right: 10px; 237 | background-color: #f5fbff; 238 | border: 1px solid #093A7B; 239 | font-size: 1.25rem; 240 | } 241 | 242 | #body_container { 243 | grid-area: body_container; 244 | 245 | display: flex; 246 | flex-direction: column; 247 | align-items: center; 248 | justify-content: flex-start; 249 | height: 878px; 250 | width: 100%; 251 | background-color: white; 252 | } 253 | 254 | #code_preview { 255 | background-color: #2E4053; 256 | height: 700px; 257 | width: 700px; 258 | overflow-y: scroll; 259 | padding: 0px 25px 0px 25px; 260 | } 261 | 262 | #size_button_container { 263 | grid-area: size_button; 264 | grid-column-end: one; 265 | display: flex; 266 | flex-direction: column; 267 | align-items: flex-end; 268 | justify-content: flex-start; 269 | margin-top: 48px; 270 | margin-right: 12px; 271 | } 272 | 273 | #size_button_container button { 274 | width: 85px; 275 | height: 85px; 276 | 277 | background-color: #f5fbff; 278 | border: 1px solid #093A7B; 279 | 280 | margin-bottom: 24px; 281 | } 282 | 283 | #size_button:active { 284 | transform: scale(1.1); 285 | } 286 | 287 | #size_button_container svg { 288 | width: 48px; 289 | height: 48px; 290 | } 291 | 292 | #nav_buttons { 293 | grid-area: nav_button; 294 | grid-column-end: one; 295 | display: flex; 296 | flex-direction: column; 297 | align-items: flex-end; 298 | justify-content: flex-start; 299 | margin-right: 12px; 300 | } 301 | 302 | #canvas_button, 303 | #preview_button { 304 | width: 85px; 305 | height: 85px; 306 | 307 | background-color: #f5fbff; 308 | border: 1px solid #093A7B; 309 | 310 | margin-bottom: 24px; 311 | } 312 | 313 | #canvas_button:active, 314 | #preview_button:active { 315 | transform: scale(1.1); 316 | } 317 | 318 | #nav_buttons svg { 319 | width: 48px; 320 | height: 48px; 321 | } 322 | 323 | #gitHubLogo { 324 | width: 40px; 325 | height: 40px; 326 | padding-right: 10px; 327 | } 328 | 329 | .compMenuBtn { 330 | width: 100%; 331 | margin-top: 15px; 332 | height: 40px; 333 | border-top: 2px solid #093A78; 334 | border-bottom: 2px solid #093A78;; 335 | border-left: none; 336 | border-right: none; 337 | font-size: 1.25rem; 338 | font-weight: lighter; 339 | } 340 | 341 | .compMenuBtn:hover { 342 | color: #3e3eff; 343 | 344 | } 345 | 346 | #library-div { 347 | margin-top: 25px; 348 | display: grid; 349 | grid-template-columns: repeat(2, 90%); 350 | grid-template-rows: repeat(3, auto); 351 | justify-items: center; 352 | justify-content: center; 353 | text-align: center; 354 | } 355 | 356 | #componentIcon { 357 | width: 50px; 358 | height: 50px; 359 | } 360 | 361 | #conditionalForm { 362 | padding-top: 25px; 363 | display: grid; 364 | justify-items: center 365 | } 366 | 367 | .componentButton:hover { 368 | color: #3e3eff; 369 | } 370 | .componentButton:focus { 371 | color: #3e3eff; 372 | border-color: #456DAA; 373 | } 374 | #addButton { 375 | padding: 7px 10px 7px 10px; 376 | margin-top: 40px; 377 | width: 150px; 378 | color: white; 379 | background-color: #2B2929; 380 | } 381 | 382 | #h2 { 383 | font-size: 1rem; 384 | } 385 | 386 | @media (min-width: 0px) { 387 | #dashboard_right { 388 | width: auto; 389 | height: auto 390 | } 391 | 392 | #body_container { 393 | width: auto; 394 | } 395 | 396 | } -------------------------------------------------------------------------------- /readme-assets/Asset_2.svg: -------------------------------------------------------------------------------- 1 | A B S T R A C T -------------------------------------------------------------------------------- /readme-assets/Asset_10.svg: -------------------------------------------------------------------------------- 1 | BETAA B S T R A C T -------------------------------------------------------------------------------- /public/images/Asset_11.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ComponentMenu/ComSettings.jsx: -------------------------------------------------------------------------------- 1 | import AddButton from './AddButton.jsx'; 2 | import { connect } from 'react-redux'; 3 | import { Form, Dropdown, DropdownButton } from 'react-bootstrap'; 4 | import { useState } from 'react'; 5 | import 'bootstrap/dist/css/bootstrap.min.css' 6 | 7 | const mapStateToProps = (state) => ({ 8 | selectedComponent: state.main.selectedComponent 9 | }); 10 | 11 | 12 | function ComSettings(props) { 13 | const [ methods, setMethods ] = useState('') 14 | 15 | switch (props.selectedComponent) { 16 | case 'div': 17 | return ( 18 |
19 |

Div

20 |
21 | 22 | Parent Component 23 | 24 | 25 | 26 | 27 | Component ID 28 | 29 | Class Name 30 | 31 | 32 |
33 | 34 |
35 | ) 36 | case 'link': 37 | return ( 38 |
39 |

Link

40 |
41 | 42 | Parent Component 43 | 44 | 45 | 46 | 47 | 48 | Source Link 49 | 50 | 51 |
52 | 53 |
54 | ) 55 | case 'image': 56 | return ( 57 |
58 |

Image

59 |
60 | 61 | Parent Component 62 | 63 | 64 | 65 | Class Name 66 | 67 | Component ID 68 | 69 | Source Link 70 | 71 | Alt Text 72 | 73 | 74 |
75 | 76 |
77 | ) 78 | case 'paragraph': 79 | return ( 80 |
81 |

Paragraph

82 |
83 | 84 | Parent Component 85 | 86 | 87 | 88 | Font Size 89 | 90 | 91 |
92 | 93 |
94 | ) 95 | case 'button': 96 | return ( 97 |
98 |

Button

99 |
100 | 101 | Parent Component 102 | 103 | 104 | 105 | Class Name 106 | 107 | Component ID 108 | 109 | Font Size 110 | 111 | 112 |
113 | 114 |
115 | ) 116 | case 'form': 117 | function renderFormSettings() { 118 | // remove X in if statement methods when functionality works 119 | if (methods === 'XForm.Control') { 120 | return ( 121 |
122 |

Form.Control

123 |
124 | 125 | Parent Component 126 | 127 | 128 | 129 | Class Name 130 | 131 | Component ID 132 | 133 | Type 134 | 135 | Value 136 | 137 | Placeholder Text 138 | 139 | Size 140 | 141 | 142 | 143 |
144 | 145 |
146 | ) 147 | // remove X in if statement methods when functionality works 148 | } else if (methods === 'XForm.Group') { 149 | return ( 150 |
151 |

Form.Group

152 |
153 | 154 | Parent Component 155 | 156 | 157 | 158 | Class Name 159 | 160 | Component ID 161 | 162 | 163 |
164 | 165 |
166 | ) 167 | // remove X in if statement methods when functionality works 168 | } else if (methods === 'XForm.Label') { 169 | return ( 170 |
171 |

Form.Label

172 |
173 | 174 | Parent Component 175 | 176 | 177 | 178 | Class Name 179 | 180 | Component ID 181 | 182 | 183 |
184 | 185 |
186 | ); 187 | } else if (methods === 'Form') { 188 | return ( 189 |
190 |

Form Component

191 |
192 | 193 | Parent Component 194 | 195 | 196 | 197 | 198 |
199 | 200 |
201 | ) 202 | } 203 | } return ( 204 |
205 | 206 | setMethods('Form')}>Form 207 | setMethods('Form.Control')}>Form.Control 208 | setMethods('Form.Group')}>Form.Group 209 | setMethods('Form.Label')}>Form.Label 210 | 211 | { renderFormSettings() } 212 |
213 | ) 214 | 215 | case 'navbar': 216 | function renderNavbarSettings() { 217 | if (methods === 'Navbar') { 218 | return ( 219 |
220 |

Navbar Component

221 |
222 | 223 | Parent Component 224 | 225 | 226 | 227 | Class Name 228 | 229 | Component ID 230 | 231 | Fixed 232 | 233 | 234 | 235 | 236 | 237 | Variant 238 | 239 | 240 | 241 | 242 | href 243 | 244 | Background Color 245 | 246 | 247 | 248 | 249 | Expand 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 |
259 | 260 |
261 | ) 262 | } else if (methods === 'Navbar.Brand') { 263 | return ( 264 |
265 |

Navbar.Brand

266 |
267 | 268 | Parent Component 269 | 270 | 271 | 272 | href 273 | 274 | Navbar Brand Text 275 | 276 | 277 |
278 | 279 |
280 | ) 281 | } else if (methods === 'Navbar.Toggle') { 282 | return ( 283 |
284 |

Navbar.Toggle

285 |
286 | 287 | Parent Component 288 | 289 | 290 | 291 | Aria Controls 292 | 293 | 294 | 295 | 296 | 297 |
298 | 299 |
300 | ) 301 | } else if (methods === 'Navbar.Collapse') { 302 | return ( 303 |
304 |

Navbar.Collapse

305 |
306 | 307 | Parent Component 308 | 309 | 310 | 311 | Class Name 312 | 313 | 314 |
315 | 316 |
317 | ) 318 | } 319 | } 320 | return ( 321 |
322 | 323 | setMethods('Navbar')}>Navbar 324 | setMethods('Navbar.Brand')}>Navbar.Brand 325 | setMethods('Navbar.Toggle')}>Navbar.Toggle 326 | setMethods('Navbar.Collapse')}>Navbar.Collapse 327 | 328 | { renderNavbarSettings() } 329 |
330 | ) 331 | case 'nav': 332 | function renderNavSettings() { 333 | if (methods === 'Nav') { 334 | return ( 335 |
336 |

Nav Component

337 |
338 | 339 | Parent Component 340 | 341 | 342 | 343 | Class Name 344 | 345 | Nav Text 346 | 347 | 348 |
349 | 350 |
351 | ) 352 | } else if (methods === 'Nav.Link') { 353 | return ( 354 |
355 |

Navbar.Collapse

356 |
357 | 358 | Parent Component 359 | 360 | 361 | 362 | Class Name 363 | 364 | href 365 | 366 | 367 |
368 | 369 |
370 | ) 371 | } else if (methods === 'Nav.DropDown') { 372 | return ( 373 |
374 |

Navbar.Dropdown

375 |
376 | 377 | Parent Component 378 | 379 | 380 | 381 | Class Name 382 | 383 | title 384 | 385 | 386 |
387 | 388 |
389 | ) 390 | } else if (methods === 'Nav.DropDownItem') { 391 | return ( 392 |
393 |

Navbar.DropdownItem

394 |
395 | 396 | Parent Component 397 | 398 | 399 | 400 | Class Name 401 | 402 | href 403 | 404 | 405 |
406 | 407 |
408 | ) 409 | } 410 | } 411 | return ( 412 |
413 | 414 | setMethods('Nav')}>Nav 415 | setMethods('Nav.Link')}>Nav.Link 416 | setMethods('Nav.DropDown')}>Navbar.DropDown 417 | setMethods('Nav.DropDownItem')}>Navbar.DropDownItem 418 | 419 | { renderNavSettings() } 420 |
421 | ) 422 | case 'list': 423 | function renderListSettings() { 424 | if (methods === 'List') { 425 | return ( 426 |
427 |

List Component

428 |
429 | 430 | Parent Component 431 | 432 | 433 | 434 | Class Name 435 | 436 | Nav Text 437 | 438 | 439 |
440 | 441 |
442 | ) 443 | } else if (methods === 'List.Group') { 444 | return ( 445 |
446 |

List.Group

447 |
448 | 449 | Parent Component 450 | 451 | 452 | 453 | Class Name 454 | 455 | 456 |
457 |
458 | ) 459 | } 460 | } 461 | return ( 462 |
463 | 464 | setMethods('List')}>List 465 | setMethods('List.Group')}>List.Group 466 | 467 | 468 | { renderListSettings() } 469 |
470 | ) 471 | default: 472 | return ( 473 |
474 |
475 | ) 476 | 477 | } 478 | } 479 | 480 | 481 | export default connect(mapStateToProps, null)(ComSettings); 482 | --------------------------------------------------------------------------------