diff --git a/.gitignore b/.gitignore index f4fea82c3..2a0bed492 100644 --- a/.gitignore +++ b/.gitignore @@ -108,6 +108,9 @@ typings/ # dotenv environment variables file .env +# config file for db URI +config.js + # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/LICENSE.md b/LICENSE.md index 2d347a54a..d176f7680 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 ReacType +Copyright (c) 2021 ReacType 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: diff --git a/README.md b/README.md index 3c0dea74c..899dea3c2 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,49 @@

- +

ReacType

[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/team-reactype/ReacType/pulls) ![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg) -![Release: 4.0](https://img.shields.io/badge/Release-4.0-white) +![Version 5.0](https://img.shields.io/badge/Release-5.0-lightgrey.svg) -**ReacType** is a visual prototyping tool for developers employing **React** component architecture alongside the comprehensive type checking of **TypeScript**. -In other words, **you can draw prototypes and export React / Typescript code!** +**ReacType** is a visual prototyping tool for developers employing **React** component architecture alongside the comprehensive type-checking of **TypeScript**. +In other words, **you can draw prototypes and export React / TypeScript code!** -**ReacType** allows the user to _visualize_ their application architecture dynamically, employing a _drag-and-drop canvas display_ and a _real-time component code preview_. The user can create components and drag _instances_ of these components, as well as HTML elements, onto the canvas. This architecture can then be _exported_ as TypeScript application files to be used as a starter template for any repository. +**ReacType** allows users to _visualize_ their application architecture dynamically, employing a _drag-and-drop canvas display_ and a _real-time component code preview_. Users can create components and drag _instances_ of these components, as well as HTML elements, onto the canvas. This architecture can then be _exported_ as TypeScript application files to be used as a starter template for any repository. -**New with version 4.0:** -- View dynamically created components/HTML elements in the component tree -- View Typescript syntax for React -- Code preview is fully editable (make changes before exporting project) -- Create custom HTML elements -- Improved UI experience -- Implemented a comprehensive tutorial page with images +**New with version 5.0:** + +- Elements may be added to components in any location, rather than only at the bottom +- Compatibility with Gatsby.js +- Modernized and cleaner UI, including enhanced dark mode +- Tutorial has been updated to reflect other modifications Download for [MacOS](https://github.com/team-reactype/ReacType/releases), [Windows](https://github.com/team-reactype/ReacType/releases/), [Linux](https://github.com/team-reactype/ReacType/releases/). - **Mac users**: After opening the dmg and dragging ReacType into your Applications folder, ctrl+click the icon and select 'Open' from the context menu to run the app. This extra step is necessary since we don't have an Apple developer license yet. -- **Windows users**: Install the application by running ReacType Setup 4.0.0.exe. +- **Windows users**: Install the application by running ReacType Setup 5.0.0.exe. - **Linux users**: Run the application as a super user in order to read and write files. -![Gif of adding](https://i.imgur.com/Ioqkr00.gif) +![Gif of adding](https://i.imgur.com/d1oHiTm.gif) ### How to use -- **Sign-in page**: Sign up for an account, authenticate via Github/Facebook, or just continue as a guest. -- **Tutorial**: Click ‘Tutorial’ from the help tab’s drop-down menu of the help tab at the top left of the application to view a tutorial page. -- **Start a project (only after authenticating)**: After you authenticate via Github/Facebook, create a new project, and select whether you want your project to be a Next.js or a classic React project. Also, save your project so that you can return to it at a later time. -- **Add Components**: Create components on the left panel. Components can be associated with a route, or they can be used within other components. -- **Delete Components**: Delete components after focusing on them from the right panel. Be careful when deleting components because all instances of the component will be deleted within the application/project. -- **Add Custom Elements**: Create custom elements or add HTML elements that you are more familiar with into the application. Once the project is exported, the HTML tags generated in the code preview will function the way the label is supposed to work. You can create functionality for custom elements in a new file. The tutorial on HTML Elements explains more on how to do this. -- **Delete Elements**: Delete elements by clicking on the ‘X’ button next to the element. Be careful when deleting elements because all elements will be deleted within the application/project. -- **Create instances on the canvas**: Each component has its canvas. Create an example of an element or HTML element by dragging it onto the canvas. Div components are arbitrarily nestable and useful for complex layouts. Next.js projects have Link components to enable client-side navigation to other routes. -- **Component Tree**: Click on the component tree tab next to the code preview tab to view the component tree hierarchy. -- **Update styling**: Click on an element on the canvas to update basic styling using the right functions. As you create new instances and add styling, watch as your code dynamically generates in the bottom panel. -- **User preference features**: Select a theme for the code preview to your liking and change the application’s lighting. -- **Export project**: Click the “Export Project’ button to export the project’s application files into a Typescript file. The exported project is fully functional with Webpack, Express server, routing, etc., and will match what’s mocked on the canvas. +- **Sign-in page**: Sign up for an account, or just continue as a guest. Registered users enjoy additional project-saving functionality. +- **Tutorial**: Click ‘Tutorial’ from the Help tab’s dropdown menu (at the top left of the application) to view a tutorial. +- **Start a project (only after registration)**: Registered users can create a new project and select whether they want their project to be a Next.js, Gatsby.js, or classic React project. Also, registered users can save projects to return to them at a later time. +- **Add Components**: Create components on the right panel. Components can be associated with a route, or they can be used within other components. +- **Delete Components**: Delete components after focusing on them in the right panel. Be careful when deleting components: Upon deletion, all instances of the component will be removed within the application/project. +- **Add Custom Elements**: Create custom elements or add provided HTML elements into the application. Once the project is exported, the HTML tags generated in the code preview will function as expected. You can specify functionality for custom elements in the code preview. The tutorial on HTML Elements explains more on how to do this. +- **Delete Custom HTML Elements**: Delete custom HTML elements by clicking on the ‘X’ button adjacent to the element. Be careful when deleting custom elements: All instances of the element will be deleted within the application/project. +- **Create Instances on the Canvas**: Each component has its own canvas. Add an element to a component by dragging it onto the canvas. Div components are arbitrarily nestable and useful for complex layouts. Next.js and Gatsby.js projects have Link components to enable client-side navigation to other routes. +- **Component Tree**: Click on the Component Tree tab next to the Code Preview tab to view the component tree hierarchy. +- **Update Styling**: Select an element on the canvas to update its basic style attributes on the right panel. As you create new instances and add styling, watch as your code dynamically generates in the code preview in the bottom panel. +- **User Preference Features**: With the click of a button, toggle between light mode and dark mode, depending on your preference. +- **Export project**: Click the “Export Project’ button to export the project’s application files into a TypeScript file. The exported project is fully functional with Webpack, Express server, routing, etc., and will match what is mocked on the canvas. #### Contributors @@ -52,6 +51,8 @@ Download for [MacOS](https://github.com/team-reactype/ReacType/releases), [Windo [Adam Singer](https://linkedin.com/in/adsing) [@spincycle01](https://github.com/spincycle01) +[Alex Wolinsky](https://www.linkedin.com/in/alex-wolinsky-80ab591b2/) [@aw2934](https://github.com/aw2934/) + [Andrew Cho](https://www.linkedin.com/in/andrewjcho84/) [@andrewjcho84](https://github.com/andrewjcho84) [Brian Han](https://www.linkedin.com/in/brianjisoohan/) [@brianjshan](https://github.com/brianjshan) @@ -72,6 +73,12 @@ Download for [MacOS](https://github.com/team-reactype/ReacType/releases), [Windo [Jin Soo Lim](https://www.linkedin.com/in/jin-soo-lim-3a567b1b3/) [@jinsoolim](https://github.com/jinsoolim) +[Julie Wu](https://www.linkedin.com/in/jwuarchitect/) [@yutingwu4](https://github.com/yutingwu4) + +[Linh Tran](https://www.linkedin.com/in/linhtran51/) [@Linhatran](https://github.com/Linhatran) + +[Luke Madden](https://www.linkedin.com/in/lukemadden/) [@lukemadden](https://github.com/lukemadden) + [Mitchel Severe](https://www.linkedin.com/in/misevere/) [@mitchelsevere](https://github.com/mitchelsevere) [Natalie Vick](https://www.linkedin.com/in/vicknatalie/) [@natattackvick](https://github.com/natattackvick) @@ -114,9 +121,9 @@ npm run prod npm run dev ``` -- Please note that the development build is not connected to the production server. `npm run dev` should spin up the development server from the server folder of this repo. For additional information, the readme is [here](https://github.com/open-source-labs/ReacType/blob/master/server/README.md). Alternatively, you can also select "Continue as guest" on the log-in page of the app to not use any features that rely on the server (authentication and saving project data.) +- Please note that the development build is not connected to the production server. `npm run dev` should spin up the development server from the server folder of this repo. For additional information, the readme is [here](https://github.com/open-source-labs/ReacType/blob/master/server/README.md). Alternatively, you can select "Continue as guest" on the login page of the app, which will not use any features that rely on the server (authentication and saving project data.) -## To Run Your Exported Next.js Project +## To Run Your Exported Next.js or Gatsby.js Project - Open exported project directory - Install dependencies diff --git a/__tests__/BottomTabs.test.tsx b/__tests__/BottomTabs.test.tsx index 4976e1525..311217f6b 100644 --- a/__tests__/BottomTabs.test.tsx +++ b/__tests__/BottomTabs.test.tsx @@ -1,6 +1,6 @@ import React, { useReducer} from 'react'; import '@testing-library/jest-dom'; -import { render, fireEvent, cleanup, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import BottomTabs from '../app/src/components/bottom/BottomTabs'; import StateContext from '../app/src/context/context'; diff --git a/__tests__/HTMLPanel.test.tsx b/__tests__/HTMLPanel.test.tsx index 211f3a331..35784512c 100644 --- a/__tests__/HTMLPanel.test.tsx +++ b/__tests__/HTMLPanel.test.tsx @@ -22,7 +22,7 @@ function Test() { } -test('Renders HTMLPanel component', () => { +test('Renders HTMLPanel component properly', () => { render( ); @@ -37,7 +37,8 @@ test('Renders HTMLPanel component', () => { expect(screen.getByText('Header 1')).toBeInTheDocument(); expect(screen.getByText('Header 2')).toBeInTheDocument(); expect(screen.getByText('Span')).toBeInTheDocument(); -}) + expect(screen.queryByText('separator')).toBe(null); +}); test('Adds new custom element', () => { render( @@ -54,5 +55,4 @@ test('Adds new custom element', () => { fireEvent.click(screen.getByDisplayValue('Add Element')); expect(screen.getByText('Testing')).toBeInTheDocument(); -}) - +}); diff --git a/__tests__/__snapshots__/enzyme.test.tsx.snap b/__tests__/__snapshots__/enzyme.test.tsx.snap index 058674934..e69de29bb 100644 --- a/__tests__/__snapshots__/enzyme.test.tsx.snap +++ b/__tests__/__snapshots__/enzyme.test.tsx.snap @@ -1,502 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Test All 10 default HTML elements have rendered Matches snapshot 1`] = ` - - -
  • - item 1 -
  • -
  • - item 2 -
  • -
  • - item 3 -
  • - , - "style": Object { - "color": "purple", - }, - "tag": "li", - }, - Object { - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "displayName": "EditAttributesIcon", - "muiName": "SvgIcon", - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "id": 4, - "name": "Button", - "placeHolderLong": "", - "placeHolderShort": , - "style": Object { - "border": "none", - "textAlign": "center", - }, - "tag": "button", - }, - Object { - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "displayName": "LinkIcon", - "muiName": "SvgIcon", - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "id": 6, - "name": "Link", - "placeHolderLong": "", - "placeHolderShort": - Link - , - "style": Object { - "border": "none", - }, - "tag": "a", - }, - Object { - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "displayName": "LocalParkingIcon", - "muiName": "SvgIcon", - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "id": 8, - "name": "Paragraph", - "placeHolderLong": "", - "placeHolderShort": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatu", - "style": Object {}, - "tag": "p", - }, - Object { - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "displayName": "TextFormatIcon", - "muiName": "SvgIcon", - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "id": 9, - "name": "Header 1", - "placeHolderLong": "", - "placeHolderShort": "Header 1", - "style": Object { - "fontSize": "2em", - }, - "tag": "h1", - }, - Object { - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "displayName": "TextFormatIcon", - "muiName": "SvgIcon", - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "id": 10, - "name": "Header 2", - "placeHolderLong": "", - "placeHolderShort": "Header 2", - "style": Object { - "fontSize": "1.5em", - }, - "tag": "h2", - }, - Object { - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "displayName": "TextFormatIcon", - "muiName": "SvgIcon", - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, - "id": 5, - "name": "Span", - "placeHolderLong": "", - "placeHolderShort": "Span", - "style": Object { - "fontSize": "1.5em", - }, - "tag": "span", - }, - ], - "canvasFocus": Object { - "childId": null, - "componentId": 1, - }, - "components": Array [ - Object { - "children": Array [], - "code": "
    Drag in a component or HTML element into the canvas!
    ", - "id": 1, - "isPage": true, - "name": "index", - "style": Object {}, - }, - ], - "isLoggedIn": false, - "name": "", - "nextChildId": 1, - "nextComponentId": 2, - "projectType": "Next.js", - "rootComponents": Array [ - 1, - ], - } - } - > - -
    -
    -`; - -exports[`Test the BottomTabs component Matches snapshot 1`] = ` -
    - - - - - - -
    -
    - Change Theme: -
    - - - - - - - -
    -
    -
    - -
    -`; - -exports[`Test the CanvasContainer component Matches snapshot 1`] = ` -
    - -
    -`; diff --git a/__tests__/componentReducer.test.ts b/__tests__/componentReducer.test.ts index 326ef7cb0..31e501785 100644 --- a/__tests__/componentReducer.test.ts +++ b/__tests__/componentReducer.test.ts @@ -1,5 +1,5 @@ import reducer from '../app/src/reducers/componentReducer'; -import { State, Action, Component, ChildElement } from '../app/src/interfaces/InterfacesNew'; +import { State, Action } from '../app/src/interfaces/InterfacesNew'; import initialState from '../app/src/context/initialState'; @@ -27,7 +27,7 @@ describe('Testing componentReducer functionality', function () { // TEST 'ADD CHILD' describe('ADD CHILD reducer', () => { - it('should add child component to top-level component', () => { + it('should add child component and separator to top-level component', () => { const action: Action = { type: 'ADD CHILD', payload: { @@ -40,11 +40,13 @@ describe('Testing componentReducer functionality', function () { state.canvasFocus = { componentId: 1, childId: null }; state = reducer(state, action); const newParent = state.components[0]; - // expect new parent's children array to have length 1 - expect(newParent.children.length).toEqual(1); + // expect new parent's children array to have length 2 (component + separator) + expect(newParent.children.length).toEqual(2); + // expect first element in children array to be separator + expect(newParent.children[0].name).toEqual('separator'); // expect new child to have type 'Component' - expect(newParent.children[0].type).toEqual('Component'); - const addedChild = state.components.find(comp => comp.id === newParent.children[0].typeId); + expect(newParent.children[1].type).toEqual('Component'); + const addedChild = state.components.find(comp => comp.id === newParent.children[1].typeId); // expect new child typeId to correspond to component with name 'TestRegular' expect(addedChild.name).toEqual('TestRegular'); }) @@ -89,9 +91,10 @@ describe('Testing componentReducer functionality', function () { state = reducer(state, action); // expect only one remaining child const delParent = state.components.find(comp => comp.id === state.canvasFocus.componentId); - // expect remaining child to have type 'Component' - expect(delParent.children.length).toEqual(1); + // expect remaining child to have type 'Component' and to be preceded by separator + expect(delParent.children.length).toEqual(2); expect(delParent.children[delParent.children.length -1].type).toEqual('Component'); + expect(delParent.children[delParent.children.length -2].name).toEqual('separator'); }) }) diff --git a/__tests__/enzyme.test.tsx b/__tests__/enzyme.test.tsx index 135aa49f2..3b11589ed 100644 --- a/__tests__/enzyme.test.tsx +++ b/__tests__/enzyme.test.tsx @@ -1,5 +1,5 @@ -import { shallow, mount } from 'enzyme'; -import React, { useState, useContext } from 'react'; +import { shallow } from 'enzyme'; +import React from 'react'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import StateContext from '../app/src/context/context'; @@ -13,14 +13,20 @@ import Canvas from '../app/src/components/main/Canvas'; import HTMLPanel from '../app/src/components/left/HTMLPanel'; import HTMLItem from '../app/src/components/left/HTMLItem'; import LeftContainer from '../app/src/containers/LeftContainer'; +import AppContainer from '../app/src/containers/AppContainer'; +import NavBar from '../app/src/components/top/NavBar'; +import MenuItem from '@material-ui/core/MenuItem'; +import Tab from '@material-ui/core/Tab'; +import RightContainer from '../app/src/containers/RightContainer'; -// npm test -- -u +/* If there is an error with unmatched snapshots because of intentionally modified codes, delete the contents in enzyme.test.tsx.snap to record new codes as blueprints */ describe('Test the CanvasContainer component', () => { const target = shallow(); it('Matches snapshot', () => { expect(target).toMatchSnapshot(); }); + // test if Canvas component is rendered it('Contains Canvas component', () => { expect(target.contains()).toBe(true); }); @@ -28,9 +34,11 @@ describe('Test the CanvasContainer component', () => { describe('Test the MainContainer component', () => { const target = shallow(); + // test it canvas container is rendered it('Contains CanvasContainer component', () => { expect(target.contains()).toBe(true); }); + // test if bottom panel is rendered it('Contains BottomPanel component', () => { expect(target.contains()).toBe(true); }); @@ -41,9 +49,22 @@ describe('Test the BottomTabs component', () => { it('Matches snapshot', () => { expect(target).toMatchSnapshot(); }); + // test if bottom tab has a Code Preview and a Component Tree button + it('Has two tabs called "Code Preview" and "Component Tree" ', () => { + expect(target.find(Tab)).toHaveLength(2); + expect(target.find(Tab).at(0).prop('label')).toEqual('Code Preview'); + expect(target.find(Tab).at(1).prop('label')).toEqual('Component Tree'); + }); + // test if the dropdown menu exists on the bottom tab + it('Has a dropdown selection menu for Classic React, Gatsby.js, and Next.js', () => { + expect(target.find(MenuItem)).toHaveLength(3); + expect(target.find(MenuItem).at(0).text()).toEqual('Classic React'); + expect(target.find(MenuItem).at(1).text()).toEqual('Gatsby.js'); + expect(target.find(MenuItem).at(2).text()).toEqual('Next.js'); + }); }); - -describe('Test All 10 default HTML elements have rendered', () => { +// test the drag and drop component in the left panel +describe('Test HTMLPanel Component', () => { const target = shallow( @@ -52,7 +73,93 @@ describe('Test All 10 default HTML elements have rendered', () => { ); + const props = { + name: 'abc', + key:'html-abc', + id:1, + Icon:'icon', + handleDelete: jest.fn() + }; + it('Matches snapshot', () => { expect(target).toMatchSnapshot(); }); + // test if there are html items such as form, img, etc. on the left side + it('Should render HTMLItem', () => { + expect(target.find()).toBeDefined(); +}); + +// testing for AppContainer +describe('Test AppContainer container', () => { + const target = shallow(); + + const props = { + setTheme: jest.fn(), + isThemeLight: jest.fn(), + }; + + // testing if there is a NavBar + it('Should render NavBar', () => { + expect( + target.find( + + ) + ).toBeDefined(); + }); + // testing for a RightContainer + it('Should render RightContainer', () => { + expect( + target.contains( + , + ), + ).toBe(true); }); + +// testing for NavBar component +describe('Test NavBar component', () => { + const props = { + setTheme: jest.fn(), + isThemeLight: jest.fn(), + }; + const target = shallow( + + ); + // testing for 4 generic buttons in NavBar + it('Should render 4 buttons: "Clear Canvas", "Export", "Dark Mode", "Login"', () => { + expect(target.find('.navbarButton')).toHaveLength(4); + expect( + target + .find('.navbarButton') + .at(0) + .text(), + ).toEqual('Clear Canvas'); + expect( + target + .find('.navbarButton') + .at(1) + .text(), + ).toEqual('Export'); + expect( + target + .find('.navbarButton') + .at(2) + .text(), + ).toEqual('Dark Mode'); + expect( + target + .find('.navbarButton') + .at(3) + .text(), + ).toEqual('Login'); + }); +}); + +describe('Test LeftContainer container', () => { + const target = shallow(); + // test for the HTML panel (with all the html elements) on the left panel + it('Should render HTMLPanel', () => { + expect(target.find()).toBeDefined(); + }); +}); + + diff --git a/__tests__/spec.js b/__tests__/spec.js new file mode 100644 index 000000000..92ee61689 --- /dev/null +++ b/__tests__/spec.js @@ -0,0 +1,32 @@ +import 'regenerator-runtime/runtime'; // if there is an error with moduleNameMapper, npm -S install regenerator-runtime + +const { Application } = require('spectron'); +const electronPath = require('electron'); +const path = require('path'); + +let app; + +beforeAll(() => { + // create a new app to test with setTimeout to be 15000 because the app takes a few seconds to spin up + app = new Application({ + path: electronPath, + chromeDriverArgs: ['--disable-extensions'], + args: [path.join(__dirname, '../app/electron/main.js')] // this is the path from this test file to main.js inside electron folder + }); + return app.start(); +}, 15000); + +// getWindowsCount() will return 2 instead of 1 in dev mode (one for the actual app, one in the browser at localhost:8080 in dev mode) +test('Displays App window', async () => { + const windowCount = await app.client.getWindowCount(); + // expect(windowCount).toBe(1); // this returns true/passed if in production mode, change mode in script "test" to 'production' instead of 'test' + expect(windowCount).toBe(2); // 'dev' or 'test' mode results in 2 windows (one for the app and one for the browser) +}); + +/* we want to test other functionalities of app.client such as text, title, etc. but even the examples from the official spectron website +or github repo did not yield the same outcomes as demonstrated. So we stopped testing Electron app here */ +afterAll(() => { + if (app && app.isRunning()) { + return app.stop(); + } +}); diff --git a/__tests__/tree.test.tsx b/__tests__/tree.test.tsx index 5aa655586..e0200a664 100644 --- a/__tests__/tree.test.tsx +++ b/__tests__/tree.test.tsx @@ -3,17 +3,23 @@ import React, { useReducer } from 'react'; import '@testing-library/jest-dom'; import { render, fireEvent, cleanup, screen } from '@testing-library/react'; import StateContext from '../app/src/context/context'; -import { State, Action, Component, ChildElement } from '../app/src/interfaces/Interfaces'; +import { + State, + Action, + Component, + ChildElement +} from '../app/src/interfaces/Interfaces'; import initialState from '../app/src/context/initialState'; import reducer from '../app/src/reducers/componentReducer'; import 'd3'; -const tester = [ - { - id: 1, - name: 'index', - style: {}, - code: `import React, { useState } from 'react'; +// tester populates the components array used for this testing suite +const tester = [ + { + id: 1, + name: 'index', + style: {}, + code: `import React, { useState } from 'react'; import A from '../components/A'; import B from '../components/B'; import Head from 'next/head'; @@ -37,79 +43,81 @@ const tester = [ }; export default index; `, - children: [ - { - childId: 1, - children: [ - { - childId: 2, - children: [], - name: 'A', - style: {}, - type: "Component", - typeId: 2 - } - ], - name: 'div', - style: {}, - type: "HTML Element", - typeId: 11 - }, - { - childId: 3, - children: [ - { - childId: 4, - children: [], - name: 'B', - style: {}, - type: "Component", - typeId: 3 - } - ], - name: 'div', - style: {}, - type: "HTML Element", - typeId: 11 - } - ], - isPage: true - }, - { - id: 2, - nextChildId: 1, - name: 'A', - style: {}, - code: '', - children: [], - isPage: false - }, - { - id: 3, - nextChildId: 1, - name: 'B', - style: {}, - code: '', - children: [], - isPage: false + children: [ + { + childId: 1, + children: [ + { + childId: 2, + children: [], + name: 'A', + style: {}, + type: 'Component', + typeId: 2 + } + ], + name: 'div', + style: {}, + type: 'HTML Element', + typeId: 11 + }, + { + childId: 3, + children: [ + { + childId: 4, + children: [], + name: 'B', + style: {}, + type: 'Component', + typeId: 3 + } + ], + name: 'div', + style: {}, + type: 'HTML Element', + typeId: 11 + } + ], + isPage: true + }, + { + id: 2, + nextChildId: 1, + name: 'A', + style: {}, + code: '', + children: [], + isPage: false + }, + { + id: 3, + nextChildId: 1, + name: 'B', + style: {}, + code: '', + children: [], + isPage: false } - ] +]; +// renders a tree of the components in tester function Test() { const [state, dispatch] = useReducer(reducer, initialState); state.components = tester; return ( - + ); } -test('Test the tree functionality', function () { - render(); - +test('Test the tree functionality', function() { + render(); + // elements that are not separators should appear in the tree expect(screen.getByText('index')).toBeInTheDocument(); expect(screen.getByText('A')).toBeInTheDocument(); expect(screen.getByText('B')).toBeInTheDocument(); - -}) \ No newline at end of file + // tree should not include separators + expect(screen.queryByText('separator')).toBe(null); +}); diff --git a/__tests__/userAuth.test.ts b/__tests__/userAuth.test.ts index e6f842aea..517b47312 100644 --- a/__tests__/userAuth.test.ts +++ b/__tests__/userAuth.test.ts @@ -1,44 +1,52 @@ import { sessionIsCreated, newUserIsCreated } from '../app/src/helperFunctions/auth'; +// tests auth.ts helper function and associated server routes describe('Login Tests', () => { jest.setTimeout(10000); let username; let password; + let isFbOauth; // whether OAuth is used // Called under SignIn.tsx describe('sessionIsCreated', () => { it('returns the message \'No Username Input\' when no username is entered', async () => { username = ''; - password = 'codesmith1!' - const result = await sessionIsCreated(username, password).then((loginStatus) => loginStatus); + password = 'Reactype123!@#'; + isFbOauth = false; + const result = await sessionIsCreated(username, password, isFbOauth).then((loginStatus) => loginStatus); expect(result).toEqual('No Username Input'); }) it('returns the message \'No Password Input\' when no password is entered', async () => { - username = 'reactyp3test'; - password = '' - const result = await sessionIsCreated(username, password).then((loginStatus) => loginStatus); + username = 'reactype123'; + password = ''; + isFbOauth = false; + const result = await sessionIsCreated(username, password, isFbOauth).then((loginStatus) => loginStatus); expect(result).toEqual('No Password Input'); }) it('returns the message \'Invalid Username\' when username does not exist', async () => { - username = 'l!b'; //breaks the 4 character minimum and no special characters + username = 'l!b'; //breaks the 4 character minimum and no special characters restriction password = 'test'; - const result = await sessionIsCreated(username, password).then((loginStatus) => loginStatus); + isFbOauth = false; + const result = await sessionIsCreated(username, password, isFbOauth).then((loginStatus) => loginStatus); expect(result).toEqual('Invalid Username'); }) it('returns the message \'Incorrect Password\' when password does not match', async () => { username = 'reactyp3test'; password = 'incorrect'; - const result = await sessionIsCreated(username, password).then((loginStatus) => loginStatus); + isFbOauth = false; + const result = await sessionIsCreated(username, password, isFbOauth).then((loginStatus) => loginStatus); expect(result).toEqual('Incorrect Password'); }) - + // note that the username and password in this test are kept in the heroku database + // DO NOT CHANGE unless you have access to the heroku database it('returns the message \'Success\' when the user passes all auth checks', async () => { - username = 'reactyp3test'; + username = 'testing'; password = 'codesmith1!'; - const result = await sessionIsCreated(username, password).then((loginStatus) => loginStatus); + isFbOauth = false; + const result = await sessionIsCreated(username, password, isFbOauth).then((loginStatus) => loginStatus); expect(result).toEqual('Success'); }) }) diff --git a/__tests__/users.test.js b/__tests__/users.test.js index 39e76ec71..58915a206 100644 --- a/__tests__/users.test.js +++ b/__tests__/users.test.js @@ -1,18 +1,25 @@ const request = require('supertest'); -//let server = 'https://reactype.herokuapp.com'; -let server = 'http://localhost:5000'; -const isDev = process.env.NODE_ENV === 'development'; -if (isDev) { - server = 'http://localhost:5000'; -} +// let server = 'https://reactype.herokuapp.com'; /* This is for production mode */ -console.log('is Dev====???', process.env.NODE_ENV); +const server = 'http://localhost:5000'; +const browser = 'http://localhost:8080'; // for checking endpoints accessed with hash router // tests user signup and login routes describe('User authentication tests', () => { - let num = Math.floor(Math.random() * 1000); + const num = Math.floor(Math.random() * 1000); + // tests whether signup page is returned on navigation to /#/signup endpoint + // note that /#/ is required in endpoint because it is accessed via hash router describe('/signup', () => { + describe('GET', () => { + it('respond with status 200 and load signup file', () => { + return request(browser) + .get('/#/signup') + .expect('Content-Type', /text\/html/) + .expect(200); + }); + }); + // tests whether new user can sign up describe('POST', () => { it('responds with status 200 and json object on valid new user signup', () => { return request(server) @@ -20,38 +27,51 @@ describe('User authentication tests', () => { .send({ username: `supertest${num}`, email: `test${num}@test.com`, - password: `${num}`, + password: `${num}` }) - .expect('Content-Type', /json/) + .set('Content-Type', 'application/json') .expect(200) - .then((res) => expect(typeof res.body).toBe('object')); + .then(res => expect(typeof res.body).toBe('object')); }); + // if invalid signup input, should respond with status 400 it('responds with status 400 and json string on invalid new user signup', () => { return request(server) .post('/signup') .send({ - username: 'reactyp3test', - email: 'testaccount@gmail.com', - password: 'password', + username: 'reactype123', + email: 'reactype@gmail.com', + password: 'Reactype123!@#' }) + .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(400) - .then((res) => expect(typeof res.body).toBe('string')); + .then(res => expect(typeof res.body).toBe('string')); }); }); }); + // tests whether login page is returned on navigation to /#/login endpoint describe('/login', () => { + describe('GET', () => { + it('respond with status 200 and load login file', () => { + return request(browser) + .get('/#/login') + .expect('Content-Type', /text\/html/) + .expect(200); + }); + }); + // tests whether existing login information permits user to log in describe('POST', () => { it('responds with status 200 and json object on verified user login', () => { return request(server) .post('/login') - .send({ username: 'testing', password: 'codesmith1!' }) + .send({ username: 'reactype123', password: 'Reactype123!@#' }) .expect(200) .expect('Content-Type', /json/) - .then((res) => - expect(res.body.sessionId).toEqual('5fa99d1930e67b513c17ba61') + .then(res => + expect(res.body.sessionId).toEqual('60123800e51f92e14363d97e') ); }); + // if invalid username/password, should respond with status 400 it('responds with status 400 and json string on invalid user login', () => { return request(server) .post('/login') @@ -63,10 +83,11 @@ describe('User authentication tests', () => { }); }); }); +// OAuth tests (currently inoperative) describe('Github oauth tests', () => { describe('/github/callback?code=', () => { describe('GET', () => { - it('responds with status 400 and error message if no code received', () => { + xit('responds with status 400 and error message if no code received', () => { return request(server) .get('/github/callback?code=') .expect(400) @@ -74,7 +95,7 @@ describe('Github oauth tests', () => { return expect(res.text).toEqual('\"Undefined or no code received from github.com\"'); }); }); - it('responds with status 400 if invalid code received', () => { + xit('responds with status 400 if invalid code received', () => { return request(server) .get('/github/callback?code=123456') .expect(400) diff --git a/app/electron/main.js b/app/electron/main.js index dafc3d7c1..9df1600a1 100644 --- a/app/electron/main.js +++ b/app/electron/main.js @@ -1,3 +1,9 @@ +/* +@description: main.js is what controls the lifecycle of the electron application from initialization to termination. +@actions: codes for Github Oauth has been commented out because of lack of functionality. +*/ +require('dotenv').config(); +const path = require('path'); const { app, protocol, @@ -5,34 +11,30 @@ const { BrowserWindow, session, ipcMain, - webContents } = require('electron'); // The splash screen is what appears while the app is loading const { initSplashScreen, OfficeTemplate } = require('electron-splashscreen'); const { resolve } = require('app-root-path'); +// to install react dev tool extension const { default: installExtension, REACT_DEVELOPER_TOOLS } = require('electron-devtools-installer'); const debug = require('electron-debug'); +// import custom protocol in protocol.js const Protocol = require('./protocol'); // menu from another file to modularize the code const MenuBuilder = require('./menu'); -const path = require('path'); -// const fs = require('fs'); -require('dotenv').config(); - +// mode that the app is running in const isDev = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test'; const port = 8080; const selfHost = `http://localhost:${port}`; -// main.js is what controls the lifecycle of the electron application - // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let win; @@ -41,14 +43,13 @@ let menuBuilder; // function to create a new browser window // this function will be called when Electron has initialized itself async function createWindow() { + // install react dev tools if we are in development mode if (isDev) { await installExtension([REACT_DEVELOPER_TOOLS]) .then(name => console.log(`Added Extension: ${name}`)) .catch(err => console.log('An error occurred: ', err)); } else { - // Needs to happen before creating/loading the browser window; - // not necessarily instead of extensions, just using this code block - // so I don't have to write another 'if' statement + // this will happen before creating the browser window. it returns a Boolean whether the protocol of scheme 'app://' was successfully registered and a file (index-prod.html) was sent as the response protocol.registerBufferProtocol(Protocol.scheme, Protocol.requestHandler); } @@ -196,19 +197,10 @@ app.on('ready', createWindow); // Quit when all windows are closed. app.on('window-all-closed', () => { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - // if (process.platform !== 'darwin') { - // app.quit(); - // } else { - // ContextMenu.clearMainBindings(ipcMain); - // } app.quit(); }); app.on('activate', () => { - // On macOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. if (win === null) { createWindow(); } @@ -360,155 +352,154 @@ if (isDev) { serverUrl = 'http://localhost:5000'; } -// for github oauth login in production, since cookies are not accessible through document.cookie on local filesystem, we need electron to grab the cookie that is set from oauth, this listens for an set cookie event from the renderer process then sends back the cookie -ipcMain.on('set_cookie', event => { - session.defaultSession.cookies - .get({ url: serverUrl }) - .then(cookie => { - // this if statement is necessary or the setInterval on main app will constantly run and will emit this event.reply, causing a memory leak - // checking for a cookie inside array will only emit reply when a cookie exists - if (cookie[0]) { - //console.log(cookie); - event.reply('give_cookie', cookie); - } - }) - .catch(error => { - console.log('Error giving cookies in set_cookie:', error); - }); -}); - -// again for production, document.cookie is not accessible so we need this listener on main to delete the cookie on logout -ipcMain.on('delete_cookie', event => { - session.defaultSession.cookies - .remove(serverUrl, 'ssid') - // .then(removed => { - // console.log('Cookies deleted', removed); - // }) - .catch(err => console.log('Error deleting cookie:', err)); -}); - -// opens new window for github oauth when button on sign in page is clicked -ipcMain.on('github', event => { - // your applications credentials - const githubUrl = 'https://github.com/login/oauth/authorize?'; - const options = { - client_id: process.env.GITHUB_ID, - client_secret: process.env.GITHUB_SECRET, - scopes: ['user:email', 'notifications'] - }; - // create new browser window object with size, title, security options - const github = new BrowserWindow({ - width: 800, - height: 600, - title: 'Github Oauth', - webPreferences: { - nodeIntegration: false, - nodeIntegrationInWorker: false, - nodeIntegrationInSubFrames: false, - contextIsolation: true, - enableRemoteModule: false, - zoomFactor: 1.0 - } - }); - const authUrl = - `${githubUrl}client_id=${process.env.GITHUB_ID}`; - github.loadURL(authUrl); - github.show(); - const handleCallback = url => { - const raw_code = /code=([^&]\*)/.exec(url) || null; - const code = raw_code && raw_code.length > 1 ? raw_code[1] : null; - const error = /\?error=(.+)\$/.exec(url); - - if (code || error) { - // Close the browser if code found or error - authWindow.destroy(); - } - - // If there is a code, proceed to get token from github - if (code) { - self.requestGithubToken(options, code); - } else if (error) { - alert( - "Oops! Something went wrong and we couldn't" + - 'log you in using Github. Please try again.' - ); - } - }; - - github.webContents.on('will-navigate', (e, url) => handleCallback(url)); - - github.webContents.on('did-finish-load', (e, url, a, b) => { - github.webContents.selectAll(); - }); - - github.webContents.on('did-get-redirect-request', (e, oldUrl, newUrl) => - handleCallback(newUrl) - ); - - // Reset the authWindow on close - github.on('close', () => (authWindow = null), false); - - // if final callback is reached and we get a redirect from server back to our app, close oauth window - github.webContents.on('will-redirect', (e, callbackUrl) => { - const matches = callbackUrl.match(/(?<=\?=).*/); - const ssid = matches ? matches[0] : ''; - callbackUrl = callbackUrl.replace(/\?=.*/, ''); - let redirectUrl = 'app://rse/'; - if (isDev) { - redirectUrl = 'http://localhost:8080/'; - } - if (callbackUrl === redirectUrl) { - dialog.showMessageBox({ - type: 'info', - title: 'ReacType', - icon: resolve('app/src/public/icons/png/256x256.png'), - message: 'Github Oauth Successful!' - }); - github.close(); - win.webContents - .executeJavaScript(`window.localStorage.setItem('ssid', '${ssid}')`) - .then(result => win.loadURL(selfHost)) - .catch(err => console.log(err)); - } - }); -}); - -ipcMain.on('tutorial', event => { - // create new browser window object with size, title, security options - const tutorial = new BrowserWindow({ - width: 800, - height: 600, - minWidth: 661, - title: 'Tutorial', - webPreferences: { - nodeIntegration: false, - nodeIntegrationInWorker: false, - nodeIntegrationInSubFrames: false, - contextIsolation: true, - enableRemoteModule: false, - zoomFactor: 1.0 - } - }); - // redirects to relevant server endpoint - //github.loadURL(`${serverUrl}/github`); - // show window - tutorial.show(); - // if final callback is reached and we get a redirect from server back to our app, close oauth window - // github.webContents.on('will-redirect', (e, callbackUrl) => { - // let redirectUrl = 'app://rse/'; - // if (isDev) { - // redirectUrl = 'http://localhost:8080/'; - // } - // if (callbackUrl === redirectUrl) { - // dialog.showMessageBox({ - // type: 'info', - // title: 'ReacType', - // icon: resolve('app/src/public/icons/png/256x256.png'), - // message: 'Github Oauth Successful!' - // }); - // github.close(); - // } - // }); -}); - -//module.exports = dialog; +// // for github oauth login in production, since cookies are not accessible through document.cookie on local filesystem, we need electron to grab the cookie that is set from oauth, this listens for an set cookie event from the renderer process then sends back the cookie +// ipcMain.on('set_cookie', event => { +// session.defaultSession.cookies +// .get({ url: serverUrl }) +// .then(cookie => { +// // this if statement is necessary or the setInterval on main app will constantly run and will emit this event.reply, causing a memory leak +// // checking for a cookie inside array will only emit reply when a cookie exists +// if (cookie[0]) { +// //console.log(cookie); +// event.reply('give_cookie', cookie); +// } +// }) +// .catch(error => { +// console.log('Error giving cookies in set_cookie:', error); +// }); +// }); + +// // again for production, document.cookie is not accessible so we need this listener on main to delete the cookie on logout +// ipcMain.on('delete_cookie', event => { +// session.defaultSession.cookies +// .remove(serverUrl, 'ssid') +// // .then(removed => { +// // console.log('Cookies deleted', removed); +// // }) +// .catch(err => console.log('Error deleting cookie:', err)); +// }); + +// // opens new window for github oauth when button on sign in page is clicked +// ipcMain.on('github', event => { +// // your applications credentials +// const githubUrl = 'https://github.com/login/oauth/authorize?'; +// const options = { +// client_id: process.env.GITHUB_ID, +// client_secret: process.env.GITHUB_SECRET, +// scopes: ['user:email', 'notifications'] +// }; +// // create new browser window object with size, title, security options +// const github = new BrowserWindow({ +// width: 800, +// height: 600, +// title: 'Github Oauth', +// webPreferences: { +// nodeIntegration: false, +// nodeIntegrationInWorker: false, +// nodeIntegrationInSubFrames: false, +// contextIsolation: true, +// enableRemoteModule: false, +// zoomFactor: 1.0 +// } +// }); +// const authUrl = `${githubUrl}client_id=${process.env.GITHUB_ID}`; +// github.loadURL(authUrl); +// github.show(); +// const handleCallback = url => { +// const raw_code = /code=([^&]\*)/.exec(url) || null; +// const code = raw_code && raw_code.length > 1 ? raw_code[1] : null; +// const error = /\?error=(.+)\$/.exec(url); + +// if (code || error) { +// // Close the browser if code found or error +// authWindow.destroy(); +// } + +// // If there is a code, proceed to get token from github +// if (code) { +// self.requestGithubToken(options, code); +// } else if (error) { +// alert( +// "Oops! Something went wrong and we couldn't" + +// 'log you in using Github. Please try again.' +// ); +// } +// }; + +// github.webContents.on('will-navigate', (e, url) => handleCallback(url)); + +// github.webContents.on('did-finish-load', (e, url, a, b) => { +// github.webContents.selectAll(); +// }); + +// github.webContents.on('did-get-redirect-request', (e, oldUrl, newUrl) => +// handleCallback(newUrl) +// ); + +// // Reset the authWindow on close +// github.on('close', () => (authWindow = null), false); + +// // if final callback is reached and we get a redirect from server back to our app, close oauth window +// github.webContents.on('will-redirect', (e, callbackUrl) => { +// const matches = callbackUrl.match(/(?<=\?=).*/); +// const ssid = matches ? matches[0] : ''; +// callbackUrl = callbackUrl.replace(/\?=.*/, ''); +// let redirectUrl = 'app://rse/'; +// if (isDev) { +// redirectUrl = 'http://localhost:8080/'; +// } +// if (callbackUrl === redirectUrl) { +// dialog.showMessageBox({ +// type: 'info', +// title: 'ReacType', +// icon: resolve('app/src/public/icons/png/256x256.png'), +// message: 'Github Oauth Successful!' +// }); +// github.close(); +// win.webContents +// .executeJavaScript(`window.localStorage.setItem('ssid', '${ssid}')`) +// .then(result => win.loadURL(selfHost)) +// .catch(err => console.log(err)); +// } +// }); +// }); + +// ipcMain.on('tutorial', event => { +// // create new browser window object with size, title, security options +// const tutorial = new BrowserWindow({ +// width: 800, +// height: 600, +// minWidth: 661, +// title: 'Tutorial', +// webPreferences: { +// nodeIntegration: false, +// nodeIntegrationInWorker: false, +// nodeIntegrationInSubFrames: false, +// contextIsolation: true, +// enableRemoteModule: false, +// zoomFactor: 1.0 +// } +// }); +// // redirects to relevant server endpoint +// //github.loadURL(`${serverUrl}/github`); +// // show window +// tutorial.show(); +// // if final callback is reached and we get a redirect from server back to our app, close oauth window +// // github.webContents.on('will-redirect', (e, callbackUrl) => { +// // let redirectUrl = 'app://rse/'; +// // if (isDev) { +// // redirectUrl = 'http://localhost:8080/'; +// // } +// // if (callbackUrl === redirectUrl) { +// // dialog.showMessageBox({ +// // type: 'info', +// // title: 'ReacType', +// // icon: resolve('app/src/public/icons/png/256x256.png'), +// // message: 'Github Oauth Successful!' +// // }); +// // github.close(); +// // } +// // }); +// }); + +module.exports = dialog; diff --git a/app/electron/menu.js b/app/electron/menu.js index dbb31370c..85ab23511 100644 --- a/app/electron/menu.js +++ b/app/electron/menu.js @@ -7,6 +7,29 @@ const port = 5000; const Protocol = require('./protocol'); const tutorialRoute = `http://localhost:${port}/tutorial`; +/* +DESCRIPTION: This file generates an array containing a menu based on the operating system the user is running. + +menuBuilder: The entire file is encompassed in menuBuilder. Ultimately, menuBuilder returns a function called + buildMenu that uses defaultTemplate to construct a menu at the top of the application (as invoked in main.js) + + Standard menu roles (e.g., undo, redo, quit, paste, etc.) come from Electron API and need not be separately coded + +openTutorial: opens browser window containing tutorial on how to use the app + -Creates a browser window + -Tutorial is invoked within the "Help" menu + +defaultTemplate: returns an array of submenus (each an array) + -First, checks whether user is on a Mac (node returns 'darwin' for process.platform) + -Then generates a dropdown menu at the top of the screen (e.g., "File") accordingly + -The Mac check is necessary primarily for the first menu column, which is the name of the app + -If user is not on a Mac, alternative menus are generated + -Each menu: + -"label" is the field at the top of each menu (e.g., "File", "Edit", "View", etc.) + -"role" is a subitem within each menu (e.g., under "File," "Quit") + -"type: separator" creates a horizontal line in a menu (e.g., under "Redo" in the "Edit" menu) +*/ + // Create a template for a menu and create menu using that template var MenuBuilder = function(mainWindow, appName) { // https://electronjs.org/docs/api/menu#main-process @@ -151,7 +174,6 @@ var MenuBuilder = function(mainWindow, appName) { ]) ] }, - // { role: "viewMenu" } { label: 'View', submenu: [ diff --git a/app/electron/preload.js b/app/electron/preload.js index 4656b06a9..0c30dde2f 100644 --- a/app/electron/preload.js +++ b/app/electron/preload.js @@ -15,6 +15,20 @@ const { tutorial } = require('./preloadFunctions/cookies'); +/* +DESCRIPTION: This file appears to limit the node methods the Electron app can access. + +Per the docs: + -Main World is the JavaScript context in which the renderer code runs (that is, the page) + -Isolated World is where preload scripts run + -contextBridge is a module that safely exposes APIs from the isolated context in which preload scripts run + to the context in which the website or application runs (i.e., from Isolated World to Main World) + +We likely should not change this file unless we determine additional methods are necessary +or some methods are not used. + +*/ + // Expose protected methods that allow the renderer process to use select node methods // without exposing all node functionality. This is a critical security feature // 'mainWorld" is the context that the main renderer runs in diff --git a/app/electron/protocol.js b/app/electron/protocol.js index ffebb2cde..a7014f8b4 100644 --- a/app/electron/protocol.js +++ b/app/electron/protocol.js @@ -1,13 +1,15 @@ - -// Implementing a custom protocol achieves two goals: -// 1) Allows us to use ES6 modules/targets for Angular -// 2) Avoids running the app in a file:// origin +/* + @desc: register a custom protocol and specify file that will be served on request to the origin '/'. our app will be served from 'app://...' instead of the default 'file://...' + @exports: scheme, requestHandler + @usage: is used in main.js + */ const fs = require('fs'); const path = require('path'); const DIST_PATH = path.join(__dirname, '../../app/dist'); -const scheme = 'app'; + +const scheme = 'app'; // it will serve resources like app://..... instead of default file://... const mimeTypes = { '.js': 'text/javascript', @@ -28,26 +30,38 @@ function charset(mimeType) { ? 'utf-8' : null; } - +// return the file type function mime(filename) { const type = mimeTypes[path.extname(`${filename || ''}`).toLowerCase()]; - return type ? type : null; + return type || null; } +/* requestHandler + servers index-prod.html when we access the root endpoint '/' + read the file above and pass on an object includes mimeType, charset, and exisiting data read from the file +*/ function requestHandler(req, next) { + // The URL() constructor returns a newly created URL object representing the URL defined by the parameters. const reqUrl = new URL(req.url); + // path.normalize resolves '..' and '.' segments in sequential path segments + // url.pathname: an initial '/' followed by the path of the URL not including the query string or fragment (or the empty string if there is no path). let reqPath = path.normalize(reqUrl.pathname); + + // when app opens, serve index-prod.html if (reqPath === '/') { reqPath = '/index-prod.html'; } + // path.basename returns the last portion of a path which includes filename we want to serve const reqFilename = path.basename(reqPath); + // use fs module to read index-prod.html (reqPath) in dist folder fs.readFile(path.join(DIST_PATH, reqPath), (err, data) => { - const mimeType = mime(reqFilename); + const mimeType = mime(reqFilename); // returns the file type + // check if there is no error and file type is valid, pass on the info to the next middleware if (!err && mimeType !== null) { next({ - mimeType: mimeType, + mimeType, charset: charset(mimeType), - data: data + data }); } else { console.error(err); diff --git a/app/src/components/bottom/BottomPanel.tsx b/app/src/components/bottom/BottomPanel.tsx index a3c2815cf..997c726a6 100644 --- a/app/src/components/bottom/BottomPanel.tsx +++ b/app/src/components/bottom/BottomPanel.tsx @@ -1,20 +1,17 @@ import React, { useContext } from 'react'; -// import StateContext from '../../context/context'; import BottomTabs from './BottomTabs'; import { Resizable } from 're-resizable'; -// const IPC = require('electron').ipcRenderer; - const BottomPanel = () => { return ( -
    +
    diff --git a/app/src/components/bottom/BottomTabs.tsx b/app/src/components/bottom/BottomTabs.tsx index f415dad0b..58e6c7c4c 100644 --- a/app/src/components/bottom/BottomTabs.tsx +++ b/app/src/components/bottom/BottomTabs.tsx @@ -10,6 +10,8 @@ import { emitKeypressEvents } from 'readline'; import NativeSelect from '@material-ui/core/NativeSelect'; import FormControl from '@material-ui/core/FormControl'; import { styleContext } from '../../containers/AppContainer'; +import MenuItem from '@material-ui/core/MenuItem'; +import Select from '@material-ui/core/Select'; const BottomTabs = () => { // state that controls which tab the user is on @@ -17,14 +19,20 @@ const BottomTabs = () => { const [tab, setTab] = useState(0); const classes = useStyles(); treeWrapper: HTMLDivElement; - const [theme, setTheme] = useState('monokai'); + const [theme, setTheme] = useState('solarized_light'); const { style } = useContext(styleContext); - // method changes the + + // breaks if handleChange is commented out const handleChange = (event: React.ChangeEvent, value: number) => { setTab(value); }; - + // Allows users to toggle project between "next.js" and "Classic React" + // When a user changes the project type, the code of all components is rerendered + const handleProjectChange = event => { + const projectType = event.target.value; + dispatch({ type: 'CHANGE PROJECT TYPE', payload: { projectType } }); + }; const { components, HTMLTypes } = state; const changeTheme = e => { @@ -33,7 +41,7 @@ const BottomTabs = () => { return (
    - + { label="Component Tree" /> - -
    -
    Change Theme:
    - + + + +
    {tab === 0 && } {tab === 1 && } @@ -96,10 +87,11 @@ const BottomTabs = () => { const useStyles = makeStyles(theme => ({ root: { flexGrow: 1, - backgroundColor: '#333333', + backgroundColor: '#186BB4', height: '100%', - color: '#fff', - boxShadow: '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)' + color: '#E8E8E8', + boxShadow: '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)', + }, bottomHeader: { flex: 1, @@ -108,11 +100,10 @@ const useStyles = makeStyles(theme => ({ Width: '200px' }, tabsRoot: { - borderBottom: '0.5px solid #424242', minHeight: '50%' }, tabsIndicator: { - backgroundColor: '#1de9b6' + backgroundColor: 'white', }, tabRoot: { textTransform: 'initial', @@ -133,15 +124,15 @@ const useStyles = makeStyles(theme => ({ '"Segoe UI Symbol"' ].join(','), '&:hover': { - color: '#1de9b6', + color: 'white', opacity: 1 }, '&$tabSelected': { - color: '#33eb91', + color: 'white', fontWeight: theme.typography.fontWeightMedium }, '&:focus': { - color: '#4aedc4' + color: 'white', } }, tabSelected: {}, @@ -154,6 +145,14 @@ const useStyles = makeStyles(theme => ({ switch: { marginRight: '10px', marginTop: '2px' + }, + projectTypeWrapper: { + marginTop: '10px', + marginBotton: '10px' + }, + projectSelector: { + backgroundColor: 'rgba(255,255,255,0.15)', + color: 'white' } })); diff --git a/app/src/components/bottom/CodePreview.tsx b/app/src/components/bottom/CodePreview.tsx index ef446ddb1..285905c73 100644 --- a/app/src/components/bottom/CodePreview.tsx +++ b/app/src/components/bottom/CodePreview.tsx @@ -29,11 +29,6 @@ const CodePreview: React.FC<{ const handleCodeSnipChange = val => { currentComponent.code = val; }; - - const changeTheme = e => { - setTheme(e.target.value); - }; - useEffect(() => { setDivHeight(height); }, [height]) @@ -42,9 +37,10 @@ const CodePreview: React.FC<{
    diff --git a/app/src/components/left/ComponentPanelItem.tsx b/app/src/components/left/ComponentPanelItem.tsx deleted file mode 100644 index 11713b95a..000000000 --- a/app/src/components/left/ComponentPanelItem.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useContext } from 'react'; -import Grid from '@material-ui/core/Grid'; -import { makeStyles } from '@material-ui/core/styles'; -import StateContext from '../../context/context'; -import { useDrag } from 'react-dnd'; -import { ItemTypes } from '../../constants/ItemTypes'; - -// ComponentPanelItem is a tile that represents a single component -const ComponentPanelItem: React.FC<{ - name: string; - id: number; - root: boolean; - isFocus: boolean; -}> = ({ name, id, root, isFocus }) => { - const classes = useStyles(); - const [state, dispatch] = useContext(StateContext); - - // useDrag hook allows components in left panel to be drag source - const [{ isDragging }, drag] = useDrag({ - item: { - type: ItemTypes.INSTANCE, - newInstance: true, - instanceType: 'Component', - instanceTypeId: id - }, - canDrag: !root && !isFocus, - collect: (monitor: any) => ({ - isDragging: !!monitor.isDragging() - }) - }); - - // when a component is clicked in the left panel, change canvas focus to that component - const handleClick = () => { - dispatch({ - type: 'CHANGE FOCUS', - payload: { componentId: id, childId: null } - }); - }; - return ( - -
    - {isFocus &&
    } -

    {name}

    -
    -
    - ); -}; - -const useStyles = makeStyles({ - activeFocus: { - backgroundColor: 'rgba(1,212,109,0.3)' - }, - focusMark: { - backgroundColor: '#01d46d', - position: 'absolute', - width: '12px', - height: '12px', - borderRadius: '12px', - left: '-35px', - top: '30px' - } -}); - -export default ComponentPanelItem; diff --git a/app/src/components/left/HTMLItem.tsx b/app/src/components/left/HTMLItem.tsx index 9e84ff039..a14a8e2cb 100644 --- a/app/src/components/left/HTMLItem.tsx +++ b/app/src/components/left/HTMLItem.tsx @@ -13,31 +13,41 @@ const buttonClasses = const useStyles = makeStyles({ HTMLPanelItem: { - color: 'white', - // this is experimental for version: BLADERUNNER THEME - backgroundColor: 'transparent', - // minWidth: '340px', - minHeight: '60px', - marginBottom: '10px', - marginRight: '5px', - marginLeft: '5px', - border: '2px dotted rgba(255,255,255, 0.45)', - borderRadius: '8px', + color: '#186BB4', + height: '35px', + width: '90px', + fontSize: '80%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + textAlign: 'center', + margin: '7px auto', + marginLeft: '30px', + borderRadius: '25px', cursor: 'grab', '& > h3': { display: 'inline-block', - paddingTop: '18px' } + }, + lightThemeFontColor: { + color: '#186BB4' + }, + darkThemeFontColor: { + color: '#fff' } + }); -const HTMLItem: React.FC<{ +const HTMLItem : React.FC<{ name: string; id: number; Icon: any; handleDelete: (id: number) => void; -}> = ({ name, id, Icon, handleDelete }) => { + isThemeLight: boolean; +}> = ({ name, id, Icon, handleDelete, isThemeLight }) => { + const classes = useStyles(); + const [modal, setModal] = useState(null); const [{ isDragging }, drag] = useDrag({ item: { @@ -110,22 +120,20 @@ const HTMLItem: React.FC<{ ); }; - return ( + return ( // HTML Elements -
    + { id <= 11 && +
    +

    {name}

    +
    } + {id > 11 && + +

    {name}

    - - {Icon && } - - {id > 11 && - }
    + +
    + } {modal} ); diff --git a/app/src/components/left/HTMLPanel.tsx b/app/src/components/left/HTMLPanel.tsx index 82fe984ce..d676ff4be 100644 --- a/app/src/components/left/HTMLPanel.tsx +++ b/app/src/components/left/HTMLPanel.tsx @@ -4,14 +4,27 @@ import StateContext from '../../context/context'; import HTMLItem from './HTMLItem'; import { makeStyles } from '@material-ui/core/styles'; -const HTMLPanel = (): JSX.Element => { +/* +DESCRIPTION: This is the bottom half of the left panel, starting from the 'HTML + Elements' header. The boxes containing each HTML element are rendered in + HTMLItem, which itself is rendered by this component. + +Central state contains all available HTML elements (stored in the HTMLTypes property). + The data for HTMLTypes is stored in HTMLTypes.tsx and is added to central state in + initialState.tsx. + +Hook state: + -tag: +*/ + +const HTMLPanel = (props): JSX.Element => { const classes = useStyles(); const [tag, setTag] = useState(''); const [name, setName] = useState(''); const [errorMsg, setErrorMsg] = useState(''); const [errorStatus, setErrorStatus] = useState(false); const [state, dispatch] = useContext(StateContext); - + const {isThemeLight} = props; let startingID = 0; state.HTMLTypes.forEach(element => { if (element.id >= startingID) startingID = element.id; @@ -52,13 +65,15 @@ const HTMLPanel = (): JSX.Element => { const triggerError = (type: String) => { setErrorStatus(true); if (type === 'empty') { - setErrorMsg('Tag/ Tag name cannot be blank.'); + setErrorMsg('* Input cannot be blank. *'); } else if (type === 'dupe') { - setErrorMsg('Tag/ Tag name already exists.'); + setErrorMsg('* Input already exists. *'); } else if (type === 'letters') { - setErrorMsg('Tag/ Tag name must start with a letter.'); + setErrorMsg('* Input must start with a letter. *'); } else if (type === 'symbolsDetected') { - setErrorMsg('Tag/ Tag name must not contain symbols.'); + setErrorMsg('* Input must not contain symbols. *'); + } else if (type === 'length') { + setErrorMsg('* Input cannot exceed 10 characters. *'); } }; @@ -111,6 +126,9 @@ const HTMLPanel = (): JSX.Element => { } else if (checkNameDupe(tag) || checkNameDupe(name)) { triggerError('dupe'); return; + } else if (name.length > 10) { + triggerError('length'); + return; } createOption(tag, name); resetError(); @@ -122,103 +140,154 @@ const HTMLPanel = (): JSX.Element => { payload: id }); }; - + // filter out separator so that it will not appear on the html panel + const htmlTypesToRender = state.HTMLTypes.filter(type => type.name !== 'separator') return ( -
    -

    HTML Elements

    +
    +
    + + {htmlTypesToRender.map(option => ( + + ))} + +
    +
    +
    +
    -
    -

    Create New Element:

    - + + {(!tag.charAt(0).match(/[A-Za-z]/) || !alphanumeric(tag) || tag.trim() === '' || checkNameDupe(tag)) + && + {errorMsg} + } +

    -
    - - {state.HTMLTypes.map(option => ( - - ))} - +
    ); }; const useStyles = makeStyles({ inputWrapper: { - // height: '115px', textAlign: 'center', display: 'flex', alignItems: 'center', justifyContent: 'space-between', - // paddingLeft: '35px', - marginBottom: '15px' + marginBottom: '15px', + width: '100%' }, addComponentWrapper: { - border: '1px solid rgba(70,131,83)', - padding: '20px', - margin: '20px' + width: '100%', + margin: '5px 0px 0px 0px' }, input: { - color: '#fff', borderRadius: '5px', - paddingLeft: '15px', - paddingRight: '10px', whiteSpace: 'nowrap', overflowX: 'hidden', textOverflow: 'ellipsis', - border: '1px solid rgba(51,235,145,0.75)', backgroundColor: 'rgba(255,255,255,0.15)', - marginLeft: '10px' + margin: '0px 0px 0px 10px', + width: '140px', + height: '30px', }, inputLabel: { - fontSize: '16px', + fontSize: '85%', zIndex: 20, - color: '#fff', - marginTop: '-10px' + margin: '-10px 0px -10px 0px', + width: '125%' + }, + addElementButton: { + backgroundColor: 'transparent', + height: '40px', + width: '105px', + fontFamily: '"Raleway", sans-serif', + fontSize: '85%', + textAlign: 'center', + marginLeft: '75px', + borderStyle: 'none', + transition: '0.3s', + borderRadius: '25px', + }, + lightThemeFontColor: { + color: '#186BB4' + }, + darkThemeFontColor: { + color: '#ffffff' + }, + errorMessage: { + fontSize:"11px", + marginTop: "10px", + width: "150px", + marginLeft: "-15px" + }, + errorMessageLight: { + color: '#6B6B6B' + }, + errorMessageDark: { + color: 'white' } }); diff --git a/app/src/components/login/FBPassWord.tsx b/app/src/components/login/FBPassWord.tsx index 178d43528..308cc02d8 100644 --- a/app/src/components/login/FBPassWord.tsx +++ b/app/src/components/login/FBPassWord.tsx @@ -60,8 +60,6 @@ const useStyles = makeStyles(theme => ({ const SignUp: React.FC = props => { const classes = useStyles(); - //const email = 'email'; - //console.log(props.location.state); const [password, setPassword] = useState(''); const [passwordVerify, setPasswordVerify] = useState(''); @@ -131,7 +129,6 @@ const SignUp: React.FC = props => { } // get username and email from FB - newUserIsCreated(email, email, password).then(userCreated => { if (userCreated === 'Success') { props.history.push('/'); @@ -139,7 +136,6 @@ const SignUp: React.FC = props => { console.log(userCreated); } }); - // } }; return ( diff --git a/app/src/components/login/SignIn.tsx b/app/src/components/login/SignIn.tsx index 8742400cc..155b0fdd5 100644 --- a/app/src/components/login/SignIn.tsx +++ b/app/src/components/login/SignIn.tsx @@ -161,8 +161,10 @@ const SignIn: React.FC = props => { ); } }; + const classBtn = 'MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-fullWidth'; + return ( @@ -205,11 +207,6 @@ const SignIn: React.FC = props => { helperText={invalidPassMsg} error={invalidPass} /> - {/* **TODO** Make 'Remember Me' functional - } - label="Remember me" - /> */} - {/* - */} + -
    + Create + +
    +
    +
    +
    {/* Display all root components */} + {/* Font size for 'index' in root components in .compPanelItem h3 style.css */}
    -

    {state.projectType === 'Next.js' ? 'Pages' : 'Root components'}

    + {/* Heading just below ADD button */} +

    {state.projectType === 'Next.js' || state.projectType === 'Gatsby.js' ? 'Pages' : 'Root Components'}

    {state.components .filter(comp => state.rootComponents.includes(comp.id)) .map(comp => { return ( - + /> + ); })} {/* Display all reusable components */} -

    Reusable components

    +

    Reusable Components

    {state.components .filter(comp => !state.rootComponents.includes(comp.id)) @@ -186,14 +212,15 @@ const ComponentPanel = (): JSX.Element => { name={comp.name} id={comp.id} root={false} + isThemeLight={isThemeLight} /> ); })} - {/* Display navigation components - (only applies to next.js which has routing built in) */} - {state.projectType === 'Next.js' ? ( + {/* Display routing components - (only applies to next.js or gatsby.js which has routing built in) */} + {state.projectType === 'Next.js' || state.projectType === 'Gatsby.js'? ( -

    Navigation

    +

    Routing

    { const useStyles = makeStyles({ inputField: { - marginTop: '15px' + marginTop: '10px', + borderRadius: '5px', + whiteSpace: 'nowrap', + overflowX: 'hidden', + textOverflow: 'ellipsis', + backgroundColor: 'rgba(255,255,255,0.15)', + margin: '0px 0px 0px 10px', + width: '140px', + height: '30px', + borderColor: 'white' }, inputWrapper: { - // height: '115px', textAlign: 'center', display: 'flex', + flexDirection: 'column', alignItems: 'center', justifyContent: 'space-between', - // paddingLeft: '35px', - marginBottom: '15px' + marginBottom: '15px', }, addComponentWrapper: { - border: '1px solid rgba(70,131,83)', - padding: '20px', - margin: '20px' + padding: 'auto', + marginLeft: '21px', + display: 'inline-block', + width: '100%', + }, + rootCheckBox: { + borderColor: '#186BB4', + padding: '0px' }, - rootCheckBox: {}, rootCheckBoxLabel: { - color: 'white' + borderColor: '#186BB4' }, panelWrapper: { width: '100%', - marginTop: '15px' + marginTop: '15px', + display: 'flex', + flexDirection:'column', + alignItems:'center' }, panelWrapperList: { - // maxHeight: '400px', minHeight: '120px', - // overflowY: 'auto', marginLeft: '-15px', - marginRight: '-15px' + marginRight: '-15px', + width: '300px', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }, + dragComponents: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + textAlign: 'center', + width: '500px', + backgroundColor: '#186BB4', + border: '5px solid #186BB4' }, panelSubheader: { textAlign: 'center', color: '#fff' }, input: { - color: '#fff', - borderRadius: '5px', - paddingLeft: '15px', - paddingRight: '10px', - whiteSpace: 'nowrap', - overflowX: 'hidden', - textOverflow: 'ellipsis', - border: '1px solid rgba(51,235,145,0.75)', - backgroundColor: 'rgba(255,255,255,0.15)' + + }, + newComponent: { + color: '#155084', + fontSize: '95%', + marginBottom: '20px' }, inputLabel: { - fontSize: '14px', - zIndex: 20, - color: '#fff', - marginTop: '-10px' + fontSize: '1em', + marginLeft: '10px' }, btnGroup: { display: 'flex', flexDirection: 'column', - paddingTop: '10px', - marginLeft: '10px' }, - button: { - fontSize: '1rem', + addComponentButton: { + backgroundColor: 'transparent', height: '40px', - maginTop: '10px', - width: '100%', - // border: '1px solid rgba(70,131,83)', - backgroundColor: 'rgba(1,212,109,0.1)' + width: '100px', + fontFamily: '"Raleway", sans-serif', + fontSize: '90%', + textAlign: 'center', + margin: '-20px 0px 5px 150px', + borderStyle: 'none', + transition: '0.3s', + borderRadius: '25px', }, rootToggle: { - color: '#01d46d', + color: '#696969', fontSize: '0.85rem' + }, + lightThemeFontColor: { + color: '#186BB4' + }, + darkThemeFontColor: { + color: '#fff' } }); diff --git a/app/src/components/right/ComponentPanelItem.tsx b/app/src/components/right/ComponentPanelItem.tsx new file mode 100644 index 000000000..9c22f7c64 --- /dev/null +++ b/app/src/components/right/ComponentPanelItem.tsx @@ -0,0 +1,107 @@ +import React, { useContext } from 'react'; +import Grid from '@material-ui/core/Grid'; +import { makeStyles } from '@material-ui/core/styles'; +import StateContext from '../../context/context'; +import { useDrag } from 'react-dnd'; +import { ItemTypes } from '../../constants/ItemTypes'; + +/* +DESCRIPTION: This component is each box beneath the 'root components' and + 'reusable components' (in classic React mode) headings. Drag-and-drop + functionality works only for reusable components. + + -root is a boolean reflecting whether a component is a root component (that is, a container) + -isFocus is boolean reflecting whether a component is the one currently displayed on the canvas +*/ + +/* +SUMMARY: REACT DND + -In React DnD, "items" of a certain "type" are dragged + -Dragged items are objects + -A type is a string that uniquely identifies a class of items (e.g., "html components") + + -useDrag is a hook from the React DnD API + -Takes a specification object that includes: + -Item (required): object describing data being dragged - only info the drop target (canvas) gets + -type is required - must be a string or a symbol + -Only drop targets registered for same type will allow drop to occur and react accordingly + -In Canvas.tsx, see useDrop method - on line 47, canvas has type item.instanceType, allowing drop/interaction + -canDrag: specifies when dragging is permitted (omitted if always permitted) + -collect: method that returns an object of the props to inject into drop component + -Takes two params: (1) monitor and (2) props + -A monitor is a wrapper that allows update of props of components in response to drag-and-drop state changes + -Within the collect method used here, monitor.isDragging returns a boolean reflecting whether a drag is in progress + and an item is the source of the drag +*/ + +// ComponentPanelItem is a tile that represents a single component +const ComponentPanelItem: React.FC<{ + name: string; + id: number; + root: boolean; + isFocus: boolean; +}> = ({ name, id, root, isFocus }) => { + const classes = useStyles(); + const [state, dispatch] = useContext(StateContext); + + // useDrag hook allows components in left panel to be drag source + const [{ isDragging }, drag] = useDrag({ + item: { + type: ItemTypes.INSTANCE, + newInstance: true, + instanceType: 'Component', + instanceTypeId: id + }, + canDrag: !root && !isFocus, // dragging not permitted if component is root component or current component + collect: (monitor: any) => ({ + isDragging: !!monitor.isDragging(), // !! converts an object to a boolean (i.e., if falsy, becomes false => !!0 === false) + }), + }); + + // when a component is clicked in the left panel, change canvas focus to that component + const handleClick = () => { + dispatch({ + type: 'CHANGE FOCUS', + payload: { componentId: id, childId: null } + }); + }; + return ( + +
    + {isFocus &&
    } +

    {name}

    +
    +
    + ); +}; + +const useStyles = makeStyles({ + activeFocus: { + backgroundColor: 'rgba(1,212,109,0.3)' + }, + focusMark: { + backgroundColor: '#808080', + position: 'absolute', + width: '12px', + height: '12px', + borderRadius: '12px', + left: '-35px', + top: '30px' + } +}); + +export default ComponentPanelItem; diff --git a/app/src/components/left/ComponentPanelRoutingItem.tsx b/app/src/components/right/ComponentPanelRoutingItem.tsx similarity index 68% rename from app/src/components/left/ComponentPanelRoutingItem.tsx rename to app/src/components/right/ComponentPanelRoutingItem.tsx index 1927d9b88..e3a5196a1 100644 --- a/app/src/components/left/ComponentPanelRoutingItem.tsx +++ b/app/src/components/right/ComponentPanelRoutingItem.tsx @@ -7,18 +7,36 @@ import { ItemTypes } from '../../constants/ItemTypes'; import MenuItem from '@material-ui/core/MenuItem'; import Select from '@material-ui/core/Select'; +/* +N.B.: RENDERED ONLY IN NEXT.JS MODE + +DESCRIPTION: This is the box beneath the "Navigation" heading. It allows insertion of links + ("routing items") between pages (which are listed in the "Pages" menu, located above in the app). + +First, this component gathers all Pages (as listed in the Pages menu) and puts them in an + array of names of those Pages (navigableComponents). + +Next, it sets route (hook state) to the first value in navigableComponents and checks whether + that value (referencedComponent) still exists in the app's central state (Redux). If it does, + the variable routeId is set to the id property of referencedComponent. If it doesn't, + referencedComponent is replaced by index (the only Page guaranteed to exist) in navigableComponents. + +Dragging works in the same manner as in ComponentPanelItem.tsx + +*/ + // a component panel routing item is a Next.js component that allows the user to navigate between pages const ComponentPanelRoutingItem: React.FC<{}> = () => { - const classes = useStyles(); + const classes = useStyles();'s there, ' const [state, dispatch] = useContext(StateContext); // find the root components that can be associated with a route - // These will be the components that are displayed in the dropdown + // These will be the components that are displayed in the dropdown adjacent to "Route Link" let navigableComponents = state.components .filter(comp => state.rootComponents.includes(comp.id)) .map(comp => comp.name); - // set state for the route curently selected in the dropdown + // set state for the route currently selected in the dropdown const [route, setRoute] = useState(navigableComponents[0]); // TODO: Add a useMemo so that this isn't recalculated on every render @@ -27,7 +45,7 @@ const ComponentPanelRoutingItem: React.FC<{}> = () => { const referencedComponent = state.components.find( comp => comp.name === route ); - // if so, set the route id for that component to the id of the referenced compnent + // if so, set the route id for that component to the id of the referenced component if (referencedComponent) routeId = referencedComponent.id; // otherwise, set the component name and and id to the root component else { @@ -35,6 +53,7 @@ const ComponentPanelRoutingItem: React.FC<{}> = () => { routeId = 1; } + // on switching to another Page in the dropdown menu, update hook state const handleRouteChange = event => { setRoute(event.target.value); }; @@ -60,16 +79,18 @@ const ComponentPanelRoutingItem: React.FC<{}> = () => { ref={drag} xs={8} style={{ - color: 'white', + color: '#186BB4', backgroundColor: 'transparent', height: '75px', marginBottom: '15px', - border: '2px solid rgba(211,201,121, 0.75)', + border: '2px dotted #186BB4', borderRadius: '8px' }} > + {/* Route Link component */}

    Route Link

    + {/* Select is the dropdown menu */} - Next.js - Classic React - - -
    - {state.isLoggedIn ? : ''} - {state.isLoggedIn ? : ''} - {state.isLoggedIn ? : ''} - {/*
    */} - - - -
    -
    - - {/*
    */} - {modal} -
    -
    + return ( +
    {modal}
    ); }; const useStyles = makeStyles({ - projectManagerWrapper: { - border: '1px solid rgba(70,131,83)', - padding: '20px', - margin: '40px', - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifySelf: 'flex-end' - }, logoutButton: { position: 'absolute', bottom: '50px', - right: '150px' + right: '150px', }, btnGroup: { display: 'flex', @@ -270,16 +175,6 @@ const useStyles = makeStyles({ marginTop: '10px', marginBotton: '10px' }, - projectTypeWrapper: { - width: '300px', - marginTop: '10px', - marginBotton: '10px' - }, - projectSelector: { - backgroundColor: 'rgba(255,255,255,0.15)', - width: '300px', - color: '#fff' - } }); export default withRouter(ProjectManager); diff --git a/app/src/components/right/SaveProjectButton.tsx b/app/src/components/right/SaveProjectButton.tsx index 72006fb54..237661ac8 100644 --- a/app/src/components/right/SaveProjectButton.tsx +++ b/app/src/components/right/SaveProjectButton.tsx @@ -1,6 +1,5 @@ import React, { useState, useContext } from 'react'; import StateContext from '../../context/context'; - import { makeStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; import TextField from '@material-ui/core/TextField'; @@ -21,8 +20,6 @@ export default function FormDialog() { '' ); - const classes = useStyles(); - const handleClickOpen = () => { setInvalidProjectName(false); setOpen(true); @@ -56,8 +53,6 @@ export default function FormDialog() { return (
    + {/* ==================================ExportButton================================================== */} + + + + + +{/* ================================MANAGE PROJECT DROPDOWN====================================== */} + + {state.isLoggedIn ? // render Manage Project button/dropdown only if user is logged in + : } + + + + + + + + + + + + + + + + + + {modal} +
    + ); +} diff --git a/app/src/containers/AppContainer.tsx b/app/src/containers/AppContainer.tsx index d16e0c627..95081ff81 100644 --- a/app/src/containers/AppContainer.tsx +++ b/app/src/containers/AppContainer.tsx @@ -1,32 +1,50 @@ import React, { useState, useContext, createContext } from 'react'; -import { MuiThemeProvider } from '@material-ui/core/styles'; +import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles'; // import Button from '@material-ui/core/Button'; +import NavBar from '../components/top/NavBar'; import LeftContainer from './LeftContainer'; import MainContainer from './MainContainer'; import RightContainer from './RightContainer'; -import { theme1 } from '../public/styles/theme'; -import { makeStyles } from '@material-ui/core/styles'; +import { theme1, theme2 } from '../public/styles/theme'; + + export const styleContext = createContext({ style: null, setStyle: null }); +// setting light and dark themes (navbar and background); linked to theme.ts +const lightTheme = theme1; +const darkTheme = theme2; // dark mode color in theme.ts not reached + +export const themeContext = createContext({ + +}) const AppContainer = () => { - const [theme, setTheme] = useState(theme1); + + // setting state for changing light vs dark themes; linked to NavBar.tsx + const [isThemeLight, setTheme] = useState(true); + const initialStyle = useContext(styleContext); const [style, setStyle] = useState(initialStyle); + return ( // Mui theme provider provides themed styling to all MUI components in app - + + +
    + +
    - - - - - + + + + +
    +
    ); }; diff --git a/app/src/containers/LeftContainer.tsx b/app/src/containers/LeftContainer.tsx index fd80b6d86..ecc8089ef 100644 --- a/app/src/containers/LeftContainer.tsx +++ b/app/src/containers/LeftContainer.tsx @@ -1,18 +1,19 @@ import React, { useContext } from 'react'; import Grid from '@material-ui/core/Grid'; -import ComponentPanel from '../components/left/ComponentPanel'; +import ComponentPanel from '../components/right/ComponentPanel'; import HTMLPanel from '../components/left/HTMLPanel'; import { styleContext } from './AppContainer'; // Left-hand portion of the app, where component options are displayed -const LeftContainer = (): JSX.Element => { +const LeftContainer = (props): JSX.Element => { const { style } = useContext(styleContext); +// --------------------------COMPONENT PANEL MOVED TO RIGHTCONTAINER---------------------------- + return (
    - - +
    ); diff --git a/app/src/containers/RightContainer.tsx b/app/src/containers/RightContainer.tsx index 89ad60756..773411cee 100644 --- a/app/src/containers/RightContainer.tsx +++ b/app/src/containers/RightContainer.tsx @@ -3,7 +3,6 @@ import React, { useContext, useEffect, useMemo, - Component } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import FormControl from '@material-ui/core/FormControl'; @@ -24,10 +23,11 @@ import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemText from '@material-ui/core/ListItemText'; import createModal from '../components/right/createModal'; +import ComponentPanel from '../components/right/ComponentPanel' // need to pass in props to use the useHistory feature of react router -const RightContainer = (): JSX.Element => { - const classes = useStyles(); +const RightContainer = ({isThemeLight}): JSX.Element => { + const classes = useStyles(isThemeLight); const [state, dispatch] = useContext(StateContext); const [displayMode, setDisplayMode] = useState(''); const [flexDir, setFlexDir] = useState(''); @@ -156,21 +156,6 @@ const RightContainer = (): JSX.Element => { const isIndex = (): boolean => configTarget.id === 1; - const isChildOfPage = (): boolean => { - let isChild: boolean = false; - - // id of target we want to check - const { id } = configTarget; - state.components.forEach(comp => { - comp.children.forEach(child => { - if (child.type === 'Component' && child.typeId === id) { - isChild = true; - } - }); - }); - return isChild; - }; - const isLinkedTo = (): boolean => { const { id } = configTarget; const pageName = state.components[id - 1].name; @@ -204,7 +189,7 @@ const RightContainer = (): JSX.Element => { type: 'UPDATE CSS', payload: { style: styleObj } }); - // resetFields(); + return styleObj; }; @@ -221,16 +206,6 @@ const RightContainer = (): JSX.Element => { : dispatch({ type: 'DELETE PAGE', payload: { id } }); }; - // const handleDeleteReusableComponent = () => { - // dispatch({ type: 'DELETE REUSABLE COMPONENT', payload: {} }); - // }; - - const isReusable = (configTarget): boolean => { - return state.components - .filter(comp => !state.rootComponents.includes(comp.id)) - .some(el => el.id == configTarget.id); - }; - const handleDialogError = err => { if (err === 'index') setDeleteIndexError(true); else setDeleteComponentError(true); @@ -267,8 +242,9 @@ const RightContainer = (): JSX.Element => { marginTop: '5%' }} > - + + { }; return ( -
    +
    + + + {/* -----------------------------MOVED PROJECT MANAGER------------------------------------ */}
    @@ -312,20 +291,22 @@ const RightContainer = (): JSX.Element => { ? ' component' : ' element'}{' '}
    - +
    + {configTarget.child.name} ) : ( -

    - Parent component +

    + Parent Component: +

    {configTarget.name}

    )}
    -
    +

    Display:

    @@ -336,7 +317,7 @@ const RightContainer = (): JSX.Element => { onChange={handleChange} displayEmpty className={classes.select} - inputProps={{ className: classes.selectInput }} + inputProps={{ className: isThemeLight ? `${classes.selectInput} ${classes.lightThemeFontColor}` : `${classes.selectInput} ${classes.darkThemeFontColor}` }} > block @@ -361,7 +342,7 @@ const RightContainer = (): JSX.Element => { onChange={handleChange} displayEmpty className={classes.select} - inputProps={{ className: classes.selectInput }} + inputProps={{ className: isThemeLight ? `${classes.selectInput} ${classes.lightThemeFontColor}` : `${classes.selectInput} ${classes.darkThemeFontColor}` }} > row @@ -382,7 +363,7 @@ const RightContainer = (): JSX.Element => { onChange={handleChange} displayEmpty className={classes.select} - inputProps={{ className: classes.selectInput }} + inputProps={{ className: isThemeLight ? `${classes.selectInput} ${classes.lightThemeFontColor}` : `${classes.selectInput} ${classes.darkThemeFontColor}` }} > flex-start @@ -407,7 +388,7 @@ const RightContainer = (): JSX.Element => { name="flexalign" displayEmpty className={classes.select} - inputProps={{ className: classes.selectInput }} + inputProps={{ className: isThemeLight ? `${classes.selectInput} ${classes.lightThemeFontColor}` : `${classes.selectInput} ${classes.darkThemeFontColor}` }} > stretch @@ -421,7 +402,7 @@ const RightContainer = (): JSX.Element => {
    )}
    -
    +

    Width:

    @@ -432,7 +413,7 @@ const RightContainer = (): JSX.Element => { onChange={handleChange} displayEmpty className={classes.select} - inputProps={{ className: classes.selectInput }} + inputProps={{ className: isThemeLight ? `${classes.selectInput} ${classes.lightThemeFontColor}` : `${classes.selectInput} ${classes.darkThemeFontColor}` }} > auto @@ -443,7 +424,7 @@ const RightContainer = (): JSX.Element => {
    -
    +

    Height:

    @@ -454,7 +435,7 @@ const RightContainer = (): JSX.Element => { onChange={handleChange} displayEmpty className={classes.select} - inputProps={{ className: classes.selectInput }} + inputProps={{ className: isThemeLight ? `${classes.selectInput} ${classes.lightThemeFontColor}` : `${classes.selectInput} ${classes.darkThemeFontColor}` }} > auto @@ -465,7 +446,7 @@ const RightContainer = (): JSX.Element => {
    -
    +

    Background color:

    @@ -474,7 +455,7 @@ const RightContainer = (): JSX.Element => { variant="filled" name="bgcolor" className={classes.select} - inputProps={{ className: classes.selectInput }} + inputProps={{ className: isThemeLight ? `${classes.selectInput} ${classes.lightThemeFontColor}` : `${classes.selectInput} ${classes.darkThemeFontColor}` }} value={BGColor} onChange={handleChange} /> @@ -484,7 +465,7 @@ const RightContainer = (): JSX.Element => {
    )}
    - +
    { - {/* - - {ErrorMessages.deleteComponentTitle} - - - - {ErrorMessages.deleteComponentMessage} - - - - - - */} {modal}
    ); @@ -575,13 +536,12 @@ const RightContainer = (): JSX.Element => { const useStyles = makeStyles({ select: { - fontSize: '1.25em', + fontSize: '1em', '> .MuiSelect-icon': { - color: 'white' + color: '#186BB4' } }, selectInput: { - color: '#fff', paddingTop: '15px', paddingLeft: '15px' }, @@ -596,37 +556,49 @@ const useStyles = makeStyles({ marginTop: '20px' }, configType: { - color: '#fff', minWidth: '185px', - fontSize: '1em' + fontSize: '85%' }, configValue: { marginLeft: '20px' }, - buttonRow: { + buttonRow: isThemeLight => ({ textAlign: 'center', marginTop: '25px', '& > .MuiButton-textSecondary': { - color: 'rgba(255,0,0,0.75)' + color: isThemeLight ? '#808080' : '#ECECEA', // color for delete page + border: isThemeLight ? '1px solid #808080' : '1px solid #ECECEA' } - }, + }), button: { fontSize: '1rem', paddingLeft: '20px', - paddingRight: '20px' + paddingRight: '20px', + }, + saveButtonLight: { + border: '1px solid #186BB4' + }, + saveButtonDark: { + border: '1px solid #3F51B5' }, compName: { - color: '#01d46d', - fontSize: '1.75rem' + fontSize: '1rem' }, + // 'Parent Component' font size configHeader: { height: '70px', '& > h4': { - fontSize: '1.25rem', + fontSize: '1rem', letterSpacing: '0.5px', marginBottom: '0', marginTop: '10px' } + }, + lightThemeFontColor: { + color: '#186BB4' + }, + darkThemeFontColor: { + color: '#fff' } }); diff --git a/app/src/context/HTMLTypes.tsx b/app/src/context/HTMLTypes.tsx index f10c17a84..f8bee2d18 100644 --- a/app/src/context/HTMLTypes.tsx +++ b/app/src/context/HTMLTypes.tsx @@ -19,6 +19,17 @@ const HTMLTypes: HTMLType[] = [ placeHolderLong: '', icon: HeaderIcon }, + // do not move this separator element out of index 1 in this array + // in componentReducer.ts, separator is referenced as 'initialState.HTMLTypes[1]' + { + id: 1000, + tag: 'separator', + name: 'separator', + style: { border: 'none'}, + placeHolderShort: '', + placeHolderLong: '', + icon: '' + }, { id: 1, tag: 'img', diff --git a/app/src/context/initialState.ts b/app/src/context/initialState.ts index 7e4791260..a55b44cfc 100644 --- a/app/src/context/initialState.ts +++ b/app/src/context/initialState.ts @@ -13,13 +13,14 @@ const initialState: State = { code: '
    Drag in a component or HTML element into the canvas!
    ', children: [], isPage: true - } + }, ], - projectType: 'Next.js', + projectType: 'Classic React', rootComponents: [1], canvasFocus: { componentId: 1, childId: null }, nextComponentId: 2, nextChildId: 1, + nextTopSeparatorId: 1000, HTMLTypes }; diff --git a/app/src/context/themeContext 2.ts b/app/src/context/themeContext 2.ts new file mode 100644 index 000000000..151eff18c --- /dev/null +++ b/app/src/context/themeContext 2.ts @@ -0,0 +1,8 @@ +import React from "react"; + +const ThemeContext = React.createContext({ + theme: "light", + setTheme: () => {}, +}) + +export default ThemeContext; \ No newline at end of file diff --git a/app/src/context/themeContext.ts b/app/src/context/themeContext.ts new file mode 100644 index 000000000..151eff18c --- /dev/null +++ b/app/src/context/themeContext.ts @@ -0,0 +1,8 @@ +import React from "react"; + +const ThemeContext = React.createContext({ + theme: "light", + setTheme: () => {}, +}) + +export default ThemeContext; \ No newline at end of file diff --git a/app/src/helperFunctions/auth.ts b/app/src/helperFunctions/auth.ts index 55e304a6a..7dbb2d18f 100644 --- a/app/src/helperFunctions/auth.ts +++ b/app/src/helperFunctions/auth.ts @@ -1,6 +1,6 @@ const fetch = require("node-fetch"); -const isDev = process.env.NODE_ENV === 'development'; +const isDev = process.env.NODE_ENV === 'development' ; let serverURL = 'https://reactype.herokuapp.com'; if (isDev) { serverURL = 'http://localhost:5000'; @@ -16,6 +16,7 @@ export const sessionIsCreated = ( password, isFbOauth }); + const result = fetch(`${serverURL}/login`, { method: 'POST', credentials: 'include', diff --git a/app/src/helperFunctions/generateCode.ts b/app/src/helperFunctions/generateCode.ts index 270a33331..2132e5fbf 100644 --- a/app/src/helperFunctions/generateCode.ts +++ b/app/src/helperFunctions/generateCode.ts @@ -20,6 +20,7 @@ const generateUnformattedCode = ( HTMLTypes: HTMLType[] ) => { const components = [...comps]; + // find the component that we're going to generate code for const currentComponent = components.find(elem => elem.id === componentId); // find the unique components that we need to import into this component file @@ -28,14 +29,20 @@ const generateUnformattedCode = ( const isRoot = rootComponents.includes(componentId); - // get metadata for each child (e.g. the name/tag of the component/element) + // returns an array of objects which may include components, html elements, and/or route links const getEnrichedChildren = (currentComponent: Component | ChildElement) => { + // declare an array of enriched children + const enrichedChildren = currentComponent.children.map((elem: any) => { const child = { ...elem }; + + // check if child is a component if (child.type === 'Component') { + // verify that the child is in the components array in state const referencedComponent = components.find( elem => elem.id === child.typeId ); + // check if imports array include the referenced component, if not, add its name to the imports array (e.g. the name/tag of the component/element) if (!imports.includes(referencedComponent.name)) imports.push(referencedComponent.name); child['name'] = referencedComponent.name; @@ -43,7 +50,10 @@ const generateUnformattedCode = ( } else if (child.type === 'HTML Element') { const referencedHTML = HTMLTypes.find(elem => elem.id === child.typeId); child['tag'] = referencedHTML.tag; - if (referencedHTML.tag === 'div') { + if ( + referencedHTML.tag === 'div' || + referencedHTML.tag === 'separator' + ) { child.children = getEnrichedChildren(child); } return child; @@ -55,6 +65,7 @@ const generateUnformattedCode = ( return child; } }); + return enrichedChildren; }; @@ -101,17 +112,23 @@ const generateUnformattedCode = ( return `<${child.tag}${formatStyles(child.style)}>BUTTON`; - } else { + } else if (child.tag !== 'separator') { return `<${child.tag}${formatStyles(child.style)}>`; } } - // route links are only a next.js feature. if the user creates a route link and then switches projects, generate code for a normal link instead + // route links are for gatsby.js and next.js feature. if the user creates a route link and then switches projects, generate code for a normal link instead else if (child.type === 'Route Link') { - return projectType === 'Next.js' - ? `` - : ``; + if (projectType === 'Next.js') { + // if route link points to index, to go endpoint / rather than /index + if (child.name === 'index') return ``; + else return ``; + } else if (projectType === 'Gatsby.js') { + if (child.name === 'index') return `
    ${child.name}
    `; + else return `
    ${child.name}
    `; + } else return `` } }) + .filter(element => !!element) .join('\n')}`; }; @@ -132,7 +149,7 @@ const generateUnformattedCode = ( // import statements differ between root (pages) and regular components (components) const importsMapped = - projectType === 'Next.js' + projectType === 'Next.js' || projectType === 'Gatsby.js' ? imports .map((comp: string) => { return isRoot @@ -149,9 +166,9 @@ const generateUnformattedCode = ( const stateful = true; const classBased = false; - // create final component code. component code differs between classic react and next.js + // create final component code. component code differs between classic react, next.js, gatsby.js // classic react code - if (projectType !== 'Next.js') { + if (projectType === 'Classic React') { return ` ${stateful && !classBased ? `import React, {useState} from 'react';` : ''} ${classBased ? `import React, {Component} from 'react';` : ''} @@ -190,7 +207,7 @@ const generateUnformattedCode = ( `; } // next.js component code - else { + else if (projectType === 'Next.js') { return ` import React, { useState } from 'react'; ${importsMapped} @@ -217,6 +234,37 @@ const generateUnformattedCode = ( ); } + export default ${currentComponent.name}; + `; + } else { + // gatsby component code + return ` + import React, { useState } from 'react'; + ${importsMapped} + import { StaticQuery, graphql } from 'gatsby'; + ${links ? `import { Link } from 'gatsby'` : ``} + + + const ${currentComponent.name} = (props): JSX.Element => { + + const [value, setValue] = useState("INITIAL VALUE"); + + return ( + <> + ${ + isRoot + ? ` + ${currentComponent.name} + ` + : `` + } +
    + ${writeNestedElements(enrichedChildren)} +
    + + ); + } + export default ${currentComponent.name}; `; } @@ -235,8 +283,10 @@ const formatCode = (code: string) => { jsxBracketSameLine: true, parser: 'babel' }); - } else { + } else if (process.env.NODE_ENV === 'production') { return window.api.formatCode(code); + } else { + return code; } }; diff --git a/app/src/helperFunctions/manageSeparators.ts b/app/src/helperFunctions/manageSeparators.ts new file mode 100644 index 000000000..5ff957849 --- /dev/null +++ b/app/src/helperFunctions/manageSeparators.ts @@ -0,0 +1,62 @@ +import { ChildElement } from '../interfaces/Interfaces'; +import initialState from '../context/initialState'; + +const separator = initialState.HTMLTypes[1]; + +const manageSeparators = {}; + +manageSeparators.nextTopSeparatorId = initialState.nextTopSeparatorId; + +// this function checks for two separators in a row or missing separators and adds/removes as needed +manageSeparators.handleSeparators = (arr: object[], str: string) => { + if ((str === 'delete' || str === 'change position') && arr.length === 1 && arr[0].name === 'separator') { + arr.splice(0, 1); + } + + for (let index = 0; index < arr.length; index++) { + if (arr[index].name === 'separator' && arr[index + 1].name === 'separator') { + arr.splice(index, 1); // removes extra separator from array + } + // check for duplicated separator at the end of array and remove it if separator is at the last index + if (arr[arr.length - 1].name === 'separator') arr.splice(arr.length - 1, 1); + // check for missing separators + if (arr[index].name !== 'separator' && (index === 0 || arr[index - 1].name !== 'separator')) { + // initialize topSeparator inside the if condition so that every time this condition evaluated to true, + // a new topSeparator with incremented id will be created + const topSeparator: ChildElement = { + type: 'HTML Element', + typeId: separator.id, + name: 'separator', + childId: manageSeparators.nextTopSeparatorId, + style: separator.style, + children: [] + }; + // add a topSeparator before the element that does not have one + arr.splice(index, 0, topSeparator) + // update this value in state + manageSeparators.nextTopSeparatorId += 1; + } + // check is length is > 0 or it is a nested element + if (arr[index].children.length) { + // recursive call if children array + (str === 'delete' || str === 'change position') ? manageSeparators.handleSeparators(arr[index].children, str) : manageSeparators.handleSeparators(arr[index].children); + } + } + return manageSeparators.nextTopSeparatorId; +}; + +// this function replaces separators onto which an element is dropped with the element itself +manageSeparators.mergeSeparator = (arr: object[], index: number) => { + return arr.map((child) => { + if (child.name === 'div' && child.children.length) { + const divContents = manageSeparators.mergeSeparator(child.children, index); + return { ...child, children: divContents } + } + else if (child.name === 'separator' && child.children.length) { + return child.children[index]; + } + else return child; + }); +}; + +export default manageSeparators; diff --git a/app/src/helperFunctions/renderChildren.tsx b/app/src/helperFunctions/renderChildren.tsx index 01bb54abd..a35a461a0 100644 --- a/app/src/helperFunctions/renderChildren.tsx +++ b/app/src/helperFunctions/renderChildren.tsx @@ -3,6 +3,7 @@ import { ChildElement } from '../interfaces/Interfaces'; import DirectChildComponent from '../components/main/DirectChildComponent'; import DirectChildHTML from '../components/main/DirectChildHTML'; import DirectChildHTMLNestable from '../components/main/DirectChildHTMLNestable'; +import SeparatorChild from '../components/main/SeparatorChild'; import RouteLink from '../components/main/RouteLink'; import StateContext from '../context/context'; @@ -30,7 +31,7 @@ const renderChildren = (children: ChildElement[]) => { ); } // child is a non-nestable type of HTML element (everything except for divs) - else if (type === 'HTML Element' && typeId !== 11) { + else if (type === 'HTML Element' && typeId !== 11 && typeId !== 1000) { return ( { /> ); } - // A route link is a next.js navigation link + else if (type === 'HTML Element' && typeId === 1000) { + return ( + + ); + } + // A route link is a next.js or gatsby.js navigation link // The route link component includes a clickable link that, when clicked, will change the user focus to the referenced component else if (type === 'Route Link') { return ( diff --git a/app/src/index.js b/app/src/index.js index e82da6194..9c01802f8 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -1,35 +1,31 @@ import 'babel-polyfill'; import React from 'react'; import ReactDOM from 'react-dom'; -import App from './components/App.tsx'; import Cookies from 'js-cookie'; - +import App from './components/App.tsx'; import SignIn from './components/login/SignIn.tsx'; import SignUp from './components/login/SignUp.tsx'; import FBPassWord from './components/login/FBPassWord.tsx'; import Tutorial from './tutorial/Tutorial.tsx'; import TutorialPage from './tutorial/TutorialPage.tsx'; - import { HashRouter as Router, Route, Redirect, - Switch, + Switch } from 'react-router-dom'; - const PrivateRoute = ({ component: Component, ...rest }) => ( { return Cookies.get('ssid') || window.localStorage.getItem('ssid') ? ( - ) : ( - - ) - } - } + ) : ( + + ); + }} /> ); @@ -40,7 +36,7 @@ ReactDOM.render( - + , diff --git a/app/src/interfaces/Interfaces.ts b/app/src/interfaces/Interfaces.ts index f0ae40289..6b8e080dd 100644 --- a/app/src/interfaces/Interfaces.ts +++ b/app/src/interfaces/Interfaces.ts @@ -6,8 +6,11 @@ export interface State { components: Component[]; rootComponents: number[]; projectType: string; + separator: ChildElement; canvasFocus: { componentId: number; childId: number | null }; nextComponentId: number; + nextTopSeparatorId: number; + nextBottomSeparatorId: number; nextChildId: number; HTMLTypes: HTMLType[]; } @@ -17,7 +20,6 @@ export interface ChildElement { typeId: number; name: string; childId: number; - // update this interface later so that we enforce that each value of style object is a string style: object; attributes?: object; children?: ChildElement[]; diff --git a/app/src/public/icons/png/512x512.png b/app/src/public/icons/png/512x512.png index 21624daf7..d18dde76d 100644 Binary files a/app/src/public/icons/png/512x512.png and b/app/src/public/icons/png/512x512.png differ diff --git a/app/src/public/icons/win/logo.png b/app/src/public/icons/win/logo.png new file mode 100644 index 000000000..d18dde76d Binary files /dev/null and b/app/src/public/icons/win/logo.png differ diff --git a/app/src/public/styles/style.css b/app/src/public/styles/style.css index 6a042e798..452e75795 100644 --- a/app/src/public/styles/style.css +++ b/app/src/public/styles/style.css @@ -4,40 +4,24 @@ box-sizing: inherit; } +@import url('https://fonts.googleapis.com/css2?family=Raleway&display=swap'); + html { box-sizing: border-box; - /* overflow: hidden; */ } body { margin: 0; padding: 0; - font-family: 'Open sans', sans-serif; + font-family: 'Raleway', sans-serif; + font-size: 0.9 em; font-weight: 400; overflow: hidden; } -/* span > button { - background-color: #01d46d; - width: 600px; -} */ -/************** -* -* for Material React Table found in Add Child Props -* -***************/ - -/* Sean sucks button */ -#app - > div - > div - > div - > div.main-container - > div.bottom-panel - > div - > div:nth-child(2) - > button { - font-size: 20px; +h4 { + color: #155084; + text-align: center; } /* @@ -76,59 +60,117 @@ header { .column { /* width: 25%; */ - background-color: #252526; -} - -/* .column-right-export-btn { - transition: display 400ms ease-in-out 300ms; -} */ - -/* -/////////////////////////////////////////// -BUTTONS -///////////////////////////////////////////// -*/ - -.btn { - font-size: 16px; + background-color: #ececea; + /* margin-top: 65px; */ } /* -/////////////////////////////////////////// +///////////////////////////////////////////////// LEFT COLUMN -///////////////////////////////////////////// +///////////////////////////////////////////////// */ .left { - padding: 10px; + padding: 10px 10px 10px 0px; display: flex; - width: 380px; - min-width: 380px; + width: 200px; + min-width: 200px; flex-direction: column; position: relative; overflow-x: hidden; /* box-shadow: 0 5px 7px 2px rgba(0, 0, 0, 0.4); */ } -/* .htmlPanel { - position: absolute; - bottom: 0; - margin-right: 50px; - margin-left: 50px; - margin-bottom: 50px; - left: 0px; - right: 0px; +.HTMLItems { + display: flex; + flex-direction: column; + justify-content: space-around; + flex-wrap: wrap; width: 100%; - padding: 1%; -} */ +} + +#HTMLItemsTopHalf { + height: 52vh; + overflow-y: auto; +} + +.HTMLElements { + margin: 0px 0px 20px 10px; + display: flex; + flex-direction: column; + align-items: flex-start; + flex-wrap: wrap; +} + +#HTMLItemsGrid { + margin-left: 27px; +} + +#HTMLItem { + transition: 0.3s; + } + +#HTMLItem:hover { + background-color: #297ac2; + color: white; +} + +#submitButton { + margin-left: 5px; +} + +#submitButton:hover { + background-color: #297ac2; + color: white; + border: none; +} + +.lineDiv { + width: 150%; + margin-left: 0px; +} + +.customForm { + width: 100px; + display: flex; + flex-direction: column; + justify-content: center; + margin-left: 43px; + color: white; +} + +.customForm h5 { + text-align: center; + font-size: 100%; + font-weight: 700; + width: 125%; + margin-left: -5px; +} -/* affecting HTMl elements in HTML Panel */ -.MuiGrid-root.MuiGrid-container.MuiGrid-spacing-xs-8.MuiGrid-align-items-xs-baseline { - /* padding: 3rem; */ - margin: -5rem; - padding-right: 5rem; - padding-top: 3rem; - padding-left: 1rem; +.customForm input { + float: left; + margin: 20px 0px 0px -13px; + border-color: white; +} + +/* deleteAllInstances button in HTMLItem.tsx - 'x' attached to custom HTML element */ +#newElement { + border: none; + background: none; + display: flex; + align-items: center; + justify-content: flex-start; + justify-self: flex-end; + align-self: flex-start; + width: .5em; +} + +/* span container in HTMLItem.tsx */ +#customHTMLElement { + display: flex; + flex-direction: row; + justify-content: center; + width: 125px; } .component-input { @@ -142,9 +184,9 @@ LEFT COLUMN } /* -/////////////////////////////////////////// +///////////////////////////////////////////////// MAIN COLUMN -///////////////////////////////////////////// +//////////////////////////////////////////////// */ h1 { @@ -155,14 +197,10 @@ h1 { display: flex; flex-direction: column; flex: 1; - /* overflow-x: hidden; */ + min-width: 700px; } .main-header { - /* display: flex; */ - /* border-left: 1px solid grey; - border-right: 1px solid grey; - border-bottom: 1px solid grey; */ background-color: #212121; box-shadow: 0 5px 7px -2px rgba(0, 0, 0, 0.1); z-index: 10; @@ -174,16 +212,12 @@ h1 { } .main { - /* border-left: 1px solid grey; */ - /* border-right: 1px solid grey; */ background: #fff; flex: 1; width: 100%; overflow: auto; display: flex; background-color: #e4e4e4; - /* justify-content: center; - align-items: center; */ } .draggable { @@ -234,11 +268,46 @@ h1 { } /* -/////////////////////////////////////////// +////////////////////////////////////////////////// RIGHT COLUMN -///////////////////////////////////////////// +///////////////////////////////////////////////// */ +#rightContainer { + overflow-y: scroll; + overflow-x: hidden; +} + +.right { + padding-top: 35px; + width: 420px; +} + +.rightContainer { + margin-top: 65px; +} + +.rightPanelWrapper { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + width: 100%; +} + +#addComponentButton:hover { + background-color: #297ac2; + color: white; + border: none; + cursor: pointer; +} + +#checkboxContainer { + display: flex; + justify-content: space-around; + margin-left: -40px; +} + .export { border-top: 1px solid #ccc; display: flex; @@ -247,20 +316,40 @@ RIGHT COLUMN flex-direction: row; } -/* BOTTOM PANEL */ +.makeStyles-panelWrapperList-4::-webkit-scrollbar-track { + background-color: rgba(25, 25, 25, 0.8); +} + +.compPanelItem { + height: 100%; + position: relative; + display: flex; +} + +.compPanelItem h3 { + font-size: 1.25em; + letter-spacing: 0.75px; +} + +.compPanelItem:hover { + cursor: pointer; + padding-bottom: 25px; +} + +/* +///////////////////////////////////////////////////// +BOTTOM PANEL +///////////////////////////////////////////////////// +*/ .bottom-panel { transition: width 250ms ease-in-out; width: 100%; height: 100%; - min-height: 500px; - /* display: flex; */ - /* flex-direction: row; */ - background-color: #fcfcfc; + min-height: 25%; } .htmlattr { - /* background-size: auto; */ height: 80%; overflow-y: scroll; margin-left: 50px; @@ -289,7 +378,6 @@ RIGHT COLUMN .flex1 { padding: 10px 20px 10px 5px; - color: #01d46d; } .flex2 { @@ -300,6 +388,21 @@ RIGHT COLUMN background-color: 252526; } +/* +////////////////////////////////////////// +NAVBAR +////////////////////////////////////////// +*/ + +#navbarButton { + color: white; + margin: 0px 5px 0px 5px; +} + +#customized-menu { + width: 16%; +} + /* Material-UI */ /* Sortable tree sorting */ @@ -358,94 +461,11 @@ a.nav_link:hover { opacity: 0.75; } -.componentPanelWrapper { - margin-top: 75px; - width: 100%; -} - .inputName { margin-bottom: 35px; text-align: center; } -.makeStyles-panelWrapperList-4::-webkit-scrollbar-track { - background-color: rgba(25, 25, 25, 0.8); -} - -h4 { - color: white; - text-align: center; -} - -h3 { - margin: 0; - padding-left: 15px; - padding-top: 15px; - font-size: 1em; - letter-spacing: 0.5px; -} - -.compPanelItem { - height: 100%; - position: relative; - display: flex; -} - -.compPanelItem h3 { - padding-top: 25px; - font-size: 1.15em; - letter-spacing: 0.75px; -} - -.compPanelItem:hover { - cursor: pointer; -} - -.right { - padding-top: 35px; - width: 420px; -} - -.rightPanelWrapper { - display: flex; - flex-direction: column; - justify-content: space-between; - height: 100%; - width: 100%; -} - -.oauth-btn { - -webkit-font-smoothing: antialiased; - border: 0; - display: inline-flex; - outline: 0; - position: relative; - align-items: center; - user-select: none; - vertical-align: middle; - justify-content: center; - text-decoration: none; - -webkit-appearance: none; - -webkit-tap-highlight-color: transparent; - padding: 6px 16px; - font-size: 0.875rem; - min-width: 64px; - box-sizing: border-box; - transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; - font-family: "Roboto", "Helvetica", "Arial", sans-serif; - font-weight: 500; - line-height: 1.75; - border-radius: 4px; - letter-spacing: 0.02857em; - text-transform: uppercase; - color: white; - box-shadow: 0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12); - background-color:#4267B2; - width: 396px; - cursor: pointer; - margin: 8px 0px 8px; -} - .oauth-btn:hover { background-color: #4e76c7; } \ No newline at end of file diff --git a/app/src/public/styles/theme.ts b/app/src/public/styles/theme.ts index 7474d7109..99cdf9bc3 100644 --- a/app/src/public/styles/theme.ts +++ b/app/src/public/styles/theme.ts @@ -1,27 +1,22 @@ import { createMuiTheme } from '@material-ui/core/styles'; -// import purple from '@material-ui/core/colors/purple'; -// import indigo from '@material-ui/core/colors/indigo'; -// import orange from '@material-ui/core/colors/orange'; export const theme1 = createMuiTheme({ - // typography: { - // useNextVariants: true - // }, + + palette: { primary: { - // light: '#00e676', - main: '#01d46d' // less blinding green - //main:'#00FFFF' // teal color - // dark: '#14a37f', - // contrastText: '#fff' - } - } + main: '#186BB4', // light mode color + }, + }, }); -// export const theme2 = createMuiTheme({ -// // overrides: { -// // MuiContainer: {} -// // } -// }); + +export const theme2 = createMuiTheme({ + palette: { + secondary: { + main: '#304D6D', // dark mode color + }, + }, +}); // export default theme; diff --git a/app/src/reducers/componentReducer.ts b/app/src/reducers/componentReducer.ts index a24b57d1c..8aa14a0e6 100644 --- a/app/src/reducers/componentReducer.ts +++ b/app/src/reducers/componentReducer.ts @@ -1,4 +1,3 @@ -import React from 'react'; import { State, Action, @@ -8,12 +7,13 @@ import { } from '../interfaces/Interfaces'; import initialState from '../context/initialState'; import generateCode from '../helperFunctions/generateCode'; -import cloneDeep from '../helperFunctions/cloneDeep'; -import { isValueObject } from 'immutable'; -import Canvas from '../components/main/Canvas'; +import manageSeparators from '../helperFunctions/manageSeparators'; +let separator = initialState.HTMLTypes[1]; + +// } const reducer = (state: State, action: Action) => { - // if the project type is set as Next.js, next component code should be generated + // if the project type is set as Next.js or Gatsby.js, next/gatsby component code should be generated // otherwise generate classic react code // find top-level component given a component id @@ -54,14 +54,14 @@ const reducer = (state: State, action: Action) => { directParent.children.splice(childIndexValue, 1); }; - // determine if there's child of a given type in a component + // determine if there's a child of a given type in a component const childTypeExists = ( type: string, typeId: number, component: Component ) => { const nodeArr = [...component.children]; - // breadth first search through component tree to see if a child exists + // breadth-first search through component tree to see if a child exists while (nodeArr.length > 0) { // shift off the first value and assign to an element const currentNode = nodeArr.shift(); @@ -124,7 +124,7 @@ const reducer = (state: State, action: Action) => { updateAllIds(components); - // create KV pairs of component names and corresponding IDs + // create key-value pairs of component names and corresponding IDs const componentIds = {}; components.forEach(component => { if (!component.isPage) componentIds[component.name] = component.id; @@ -154,7 +154,6 @@ const reducer = (state: State, action: Action) => { const deleteById = (id: number, name: string): Component[] => { // name of the component we want to delete - const checkChildren = (child: Component[] | ChildElement[]) => { // for each of the components in the passed in components array, if the child // component has a children array, iterate through the array of children @@ -193,21 +192,6 @@ const reducer = (state: State, action: Action) => { } }; - const deleteComponentFromPages = (components, name) => { - const searchNestedComps = childComponents => { - // if (childComponents.length === 0) return console.log('empty children array'); - // childComponents.forEach((comp, i, arr) => { - // console.log('each individual comp', comp); - // if (comp.name === name){ - // arr.splice(i, 1); - // } else searchNestedComps(childComponents.children) - // }); - }; - components.forEach(comp => { - searchNestedComps(comp.children); - }); - }; - switch (action.type) { case 'ADD COMPONENT': { if ( @@ -217,6 +201,7 @@ const reducer = (state: State, action: Action) => { return state; const components = [...state.components]; + const newComponent = { id: state.components.length + 1, name: action.payload.componentName, @@ -258,7 +243,7 @@ const reducer = (state: State, action: Action) => { const parentComponentId: number = state.canvasFocus.componentId; const components = [...state.components]; - // find component that we're adding a child to + // find component (an object) that we're adding a child to const parentComponent = findComponent(components, parentComponentId); let componentName = ''; @@ -302,18 +287,44 @@ const reducer = (state: State, action: Action) => { style: {}, children: componentChildren }; + const topSeparator: ChildElement = { + type: 'HTML Element', + typeId: separator.id, + name: 'separator', + childId: state.nextTopSeparatorId, + style: separator.style, + children: [] + }; + - // if the childId is null, this signifies that we are adding a child to the top level component rather than another child element - + // if the childId is null, this signifies that we are adding a child to the top-level component rather than another child element + // we also add a separator before any new child + let directParent; if (childId === null) { + parentComponent.children.push(topSeparator); parentComponent.children.push(newChild); + } // if there is a childId (childId here references the direct parent of the new child) find that child and a new child to its children array else { - const directParent = findChild(parentComponent, childId); + directParent = findChild(parentComponent, childId); + directParent.children.push(topSeparator); directParent.children.push(newChild); } + const canvasFocus = { + ...state.canvasFocus, + componentId: state.canvasFocus.componentId, + childId: newChild.childId + }; + const nextChildId = state.nextChildId + 1; + let nextTopSeparatorId = state.nextTopSeparatorId + 1; + let addChildArray = components[canvasFocus.componentId-1].children + addChildArray = manageSeparators.mergeSeparator(addChildArray, 1); + if (directParent && directParent.name === 'separator') + nextTopSeparatorId = manageSeparators.handleSeparators(addChildArray, 'add'); + components[canvasFocus.componentId-1].children = addChildArray; + parentComponent.code = generateCode( components, parentComponentId, @@ -321,24 +332,18 @@ const reducer = (state: State, action: Action) => { state.projectType, state.HTMLTypes ); - - const canvasFocus = { - ...state.canvasFocus, - componentId: state.canvasFocus.componentId, - childId: newChild.childId - }; - const nextChildId = state.nextChildId + 1; - return { ...state, components, nextChildId, canvasFocus }; + + return { ...state, components, nextChildId, canvasFocus, nextTopSeparatorId }; } // move an instance from one position in a component to another position in a component case 'CHANGE POSITION': { const { currentChildId, newParentChildId } = action.payload; - + // if the currentChild Id is the same as the newParentId (i.e. a component is trying to drop itself into itself), don't update sate if (currentChildId === newParentChildId) return state; // find the current component in focus - const components = [...state.components]; + let components = [...state.components]; const component = findComponent( components, state.canvasFocus.componentId @@ -350,7 +355,9 @@ const reducer = (state: State, action: Action) => { component, currentChildId ); + const child = { ...directParent.children[childIndexValue] }; + directParent.children.splice(childIndexValue, 1); // if the childId is null, this signifies that we are adding a child to the top level component rather than another child element @@ -362,6 +369,11 @@ const reducer = (state: State, action: Action) => { const directParent = findChild(component, newParentChildId); directParent.children.push(child); } + + let nextTopSeparatorId = state.nextTopSeparatorId; + + components[state.canvasFocus.componentId-1].children = manageSeparators.mergeSeparator(components[state.canvasFocus.componentId-1].children, 0); + nextTopSeparatorId = manageSeparators.handleSeparators(components[state.canvasFocus.componentId-1].children, 'change position') component.code = generateCode( components, @@ -371,7 +383,7 @@ const reducer = (state: State, action: Action) => { state.HTMLTypes ); - return { ...state, components }; + return { ...state, components, nextTopSeparatorId }; } // Change the focus component and child case 'CHANGE FOCUS': { @@ -380,9 +392,13 @@ const reducer = (state: State, action: Action) => { childId }: { componentId: number; childId: number | null } = action.payload; - const canvasFocus = { ...state.canvasFocus, componentId, childId }; - return { ...state, canvasFocus }; + if (childId < 1000) { // makes separators not selectable + const canvasFocus = { ...state.canvasFocus, componentId, childId }; + return { ...state, canvasFocus }; + } + return { ...state }; } + case 'UPDATE CSS': { const { style } = action.payload; const components = [...state.components]; @@ -416,24 +432,26 @@ const reducer = (state: State, action: Action) => { state.canvasFocus.componentId ); // find the moved element's former parent - // delete the element from its former parent's children array const { directParent, childIndexValue } = findParent( component, state.canvasFocus.childId ); - const child = { ...directParent.children[childIndexValue] }; + + // delete the element from its former parent's children array directParent.children.splice(childIndexValue, 1); - - component.code = generateCode( - components, - state.canvasFocus.componentId, - [...state.rootComponents], - state.projectType, - state.HTMLTypes - ); - + const canvasFocus = { ...state.canvasFocus, childId: null }; - return { ...state, components, canvasFocus }; + let nextTopSeparatorId = manageSeparators.handleSeparators(components[canvasFocus.componentId-1].children, 'delete') + + component.code = generateCode( + components, + state.canvasFocus.componentId, + [...state.rootComponents], + state.projectType, + state.HTMLTypes + ); + + return { ...state, components, canvasFocus, nextTopSeparatorId }; } case 'DELETE PAGE': { @@ -456,7 +474,7 @@ const reducer = (state: State, action: Action) => { // iterate over the length of the components array for (let i = 0; i < components.length; i++) { - // for each components' code, run the generateCode function to + // for each component's code, run the generateCode function to // update the code preview on the app components[i].code = generateCode( components, @@ -511,8 +529,8 @@ const reducer = (state: State, action: Action) => { ); }); - // also update the name of the root component of the application to fit classic React and next.js conventions - if (projectType === 'Next.js') components[0]['name'] = 'index'; + // also update the name of the root component of the application to fit classic React and next.js/gatsby conventions + if (projectType === 'Next.js' || projectType === 'Gatsby.js') components[0]['name'] = 'index'; else components[0]['name'] = 'App'; return { ...state, components, projectType }; @@ -520,6 +538,7 @@ const reducer = (state: State, action: Action) => { // Reset all component data back to their initial state but maintain the user's project name and log-in status case 'RESET STATE': { const nextChildId = 1; + const nextTopSeparatorId = 1000; const rootComponents = [1]; const nextComponentId = 2; const canvasFocus = { @@ -537,6 +556,7 @@ const reducer = (state: State, action: Action) => { return { ...state, nextChildId, + nextTopSeparatorId, rootComponents, nextComponentId, components, diff --git a/app/src/tree/TreeChart.tsx b/app/src/tree/TreeChart.tsx index ad50844fd..b73762bbf 100644 --- a/app/src/tree/TreeChart.tsx +++ b/app/src/tree/TreeChart.tsx @@ -1,6 +1,7 @@ -import React, { useRef, useEffect, useContext } from "react"; -import { select, hierarchy, tree, linkHorizontal } from "d3"; -import useResizeObserver from "./useResizeObserver"; +import React, { useRef, useEffect, useContext } from 'react'; +import { select, hierarchy, tree, linkHorizontal } from 'd3'; +import cloneDeep from 'lodash/cloneDeep'; +import useResizeObserver from './useResizeObserver'; import StateContext from '../context/context'; function usePrevious(value) { @@ -11,7 +12,7 @@ function usePrevious(value) { return ref.current; } -function TreeChart({ data }) { +function TreeChart({ data }) { // data is components from state - passed in from BottomTabs const [state, dispatch] = useContext(StateContext); const canvasId = state.canvasFocus.componentId; @@ -19,13 +20,40 @@ function TreeChart({ data }) { const wrapperRef = useRef(); const xPosition = 50; - const textAndBorderColor = 'rgb(51, 235, 145)'; + const textAndBorderColor = '#bdbdbd'; const dimensions = useResizeObserver(wrapperRef); // we save data to see if it changed const previouslyRenderedData = usePrevious(data); + // function to filter out separators to prevent render on tree chart + const removeSeparators = (arr: object[]) => { + // loop over array + for (let i = 0; i < arr.length; i++) { + // if element is separator, remove it + if (arr[i].name === 'separator') { + arr.splice(i, 1); + i -= 1; + } + // if element has a children array and that array has length, recursive call + else if ((arr[i].name === 'div' || arr[i].type === 'Component') && arr[i].children.length) { + // if element is a component, replace it with deep clone of latest version (to update with new HTML elements) + if (arr[i].type === 'Component') arr[i] = cloneDeep(data.find(component => component.name === arr[i].name)); + removeSeparators(arr[i].children); + } + } + // return mutated array + return arr; + }; + + // create a deep clone of data to avoid mutating the actual children array in removing separators + const dataDeepClone = cloneDeep(data); + // remove separators and update components to current versions + dataDeepClone.forEach(component => { + removeSeparators(component.children); + }); + // will be called initially and on every data change useEffect(() => { const svg = select(svgRef.current); @@ -33,9 +61,9 @@ function TreeChart({ data }) { // but use getBoundingClientRect on initial render // (dimensions are null for the first render) const { width, height } = - dimensions || wrapperRef.current.getBoundingClientRect(); + dimensions || wrapperRef.current.getBoundingClientRect(); // transform hierarchical data - const root = hierarchy(data[canvasId - 1]); + const root = hierarchy(dataDeepClone[canvasId - 1]); // pass in clone here instead of data const treeLayout = tree().size([height, width - 125]); // Returns a new link generator with horizontal display. @@ -115,15 +143,14 @@ function TreeChart({ data }) { width: '100%', height: '90%', display: 'flex', - justifyContent: 'center' + justifyContent: 'center', + backgroundColor: '#42464C', }; return ( - //
    - //
    ); } diff --git a/app/src/tutorial/Canvas.tsx b/app/src/tutorial/Canvas.tsx index 65c443554..a4e1866d8 100644 --- a/app/src/tutorial/Canvas.tsx +++ b/app/src/tutorial/Canvas.tsx @@ -19,7 +19,7 @@ const Canvas: React.FC<{

    Drag-n-Drop

    The drag-n-drop functionality is implemented for the canvas to be populated.
    - This functionality can be located on the entire left container of the application.
    + This functionality can be located in the left container of the application for elements, and the right container for components.
    Select a given setPage('HTML_Elements')} >HTML Element, custom setPage('HTML_Elements')} >HTML Element, or setPage('Reusable_Components')} >reusable component, click and hold to drag on to a reusable components or page.

    diff --git a/app/src/tutorial/CodePreview.tsx b/app/src/tutorial/CodePreview.tsx index 184ca6693..d1745513a 100644 --- a/app/src/tutorial/CodePreview.tsx +++ b/app/src/tutorial/CodePreview.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import codePreview from '../../../resources/code_preview_images/CodePreview.png'; const CodePreview: React.FC<{ classes: any; @@ -9,10 +10,13 @@ const CodePreview: React.FC<{

    Code Preview

    The code preview is located at the bottom center of the page on the first tab.
    - In the preview, the code will load functional components.
    + In the preview, the code will generate for functional components.
    This preview will populate and generate in real-time as you use the drag-n-drop functionality with the setPage('Canvas')} >Canvas.
    To learn more about the canvas, click setPage('Canvas')} >"here"

    +
    + +
    ); }; diff --git a/app/src/tutorial/Customization.tsx b/app/src/tutorial/Customization.tsx index 47a25354d..12a151c14 100644 --- a/app/src/tutorial/Customization.tsx +++ b/app/src/tutorial/Customization.tsx @@ -44,14 +44,14 @@ const Customization: React.FC<{ element {' '} into the canvas, select the one that needs customizing simply by - clicking on it. Then, to give it a display feature of either a block, - inline-block, or flex styling, select from the drop down box. + clicking on it. Then, to give it block, + inline-block, or flex styling, select from the drop down box in the right container.

    - If the display option 'flex' is chosen, few more sub-options are + If the display option 'flex' is chosen, a few more sub-options are displayed under the display option.


    @@ -119,7 +119,7 @@ const Customization: React.FC<{

    Select an element, type in the color you wish to change the background - color to, and click save! + color to, and click save. The code preview will be updated to include your custom styling!


    diff --git a/app/src/tutorial/HtmlElements.tsx b/app/src/tutorial/HtmlElements.tsx index 759206cee..418d0b894 100644 --- a/app/src/tutorial/HtmlElements.tsx +++ b/app/src/tutorial/HtmlElements.tsx @@ -1,5 +1,4 @@ import React from 'react'; - import defaultElements from '../../../resources/html_elements_tutorial_images/defaultElements.png'; import createNew from '../../../resources/html_elements_tutorial_images/createNew.png'; import newTag from '../../../resources/html_elements_tutorial_images/newTag.png'; @@ -23,18 +22,12 @@ const HtmlElements: React.FC<{

    You can create new custom elements to better suit your needs.
    Click here for a link to more HTML tags that you can add.
    "Tag" should be the HTML tag you are creating and "Tag Name" should be something that makes it easy to remember what this tag is/does.
    - You can also create your own custom elements besides the standard HTML Elements in the document above. For example you can create an element <hello><hello> and it will work! You can add functionality to these elements once you export your project. Just be sure to import them into the files that you are using them! For more information on how to create custom tags check out these resources from HTML5Rocks and smashing magazine. + You can also create your own custom elements besides the standard HTML Elements. For example you can create an element <hello><hello> and it will work! You can add functionality to these elements once you export your project. Just be sure to import them into the files where you are using them! For more information on how to create custom tags check out these resources from HTML5Rocks and smashing magazine.


    -

    Delete Buttons

    -

    Delete buttons that you don't need.

    -
    - -
    -

    Persisting Elements

    Saving the project (available only to users) will allow you to save custom elements that you created. However, when opening a new project, only the tags saved for each specific project will show up again.
    In order to save custom tags across multiple projects, we recommend creating custom tags first, then saving multiple projects with the custom tags. This will allow access to custom tags across multiple projects.

    diff --git a/app/src/tutorial/Pages.tsx b/app/src/tutorial/Pages.tsx index cd35239f0..8b1f3fc1c 100644 --- a/app/src/tutorial/Pages.tsx +++ b/app/src/tutorial/Pages.tsx @@ -12,37 +12,39 @@ const Pages: React.FC<{

    Pages


    -
    - -

    Start off by giving your page a name. Make sure to check the page box - next to the textbox. Then, simply click the add button and it'll show in + next to the Name input. Then, simply click the add button and it will show in the pages section below.

    - +
    -
    +
    - +
    +

    Switch between pages by selecting the page and customize it by dragging the elements you want into the{' '} setPage('Canvas')}> canvas {' '} - of the page you're on. (Note the green dot next to the page name shows - you which page you are currently on). + of the page you're on. (Note the gray dot next to the page name signals + which page you are currently on).

    -
    - +
    + +

    - Delete the page by simply clicking the button. + Delete the page by simply clicking the delete button below the style attribute dropdowns.

    +
    + +

    ); diff --git a/app/src/tutorial/ReusableComponents.tsx b/app/src/tutorial/ReusableComponents.tsx index df331575e..fd610400c 100644 --- a/app/src/tutorial/ReusableComponents.tsx +++ b/app/src/tutorial/ReusableComponents.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import reusableComponents1 from '../../../resources/reusable_components_tutorial_images/reusableComponents1.png'; -import reusableComponents2 from '../../../resources/reusable_components_tutorial_images/reusableComponents2.png'; -import reusableComponents3 from '../../../resources/reusable_components_tutorial_images/reusableComponents3.png'; +import reusableComponents1 from '../../../resources/reusable_components_tutorial_images/reusablecomponents1.png'; +import reusableComponents2 from '../../../resources/reusable_components_tutorial_images/reusablecomponents2.png'; +import reusableComponents3 from '../../../resources/reusable_components_tutorial_images/reusablecomponents3.png'; const ReusableComponents: React.FC<{ classes: any; @@ -12,23 +12,23 @@ const ReusableComponents: React.FC<{

    Reusable Components


    -

    To add a Reusable Component, use the top left input form to name a Component. Then select add to create a new Component.

    +

    To add a Reusable Component, use the top right input form to name a Component. Leave the Root/Page checkbox unchecked. Then select add to create a new Reusable Component.


    -

    The Components you create will populate the left container under the section called 'Reusable Components'.

    +

    The Components you create will populate the right container under the section 'Reusable Components'.


    -

    After creating the desired Component, you can now use the components with the drag-n-drop functionality. +

    After creating the desired Component, you can now drag-n-drop to the Canvas. If you'd like to know about about the drag-n-drop functionality, please locate the setPage('Canvas')} >Canvas Tutorial for more information on how it works.

    -

    You can place a reusable component inside setPage('Pages')} >Pages and populate the component itself with the setPage('HTML_Elements')} >HTML Element.

    +

    You can place a reusable component inside setPage('Pages')} >Pages and populate the component itself with the setPage('HTML_Elements')} >HTML Elements.


    ); diff --git a/app/src/tutorial/RouteLinks.tsx b/app/src/tutorial/RouteLinks.tsx index 9c96ef508..570d82168 100644 --- a/app/src/tutorial/RouteLinks.tsx +++ b/app/src/tutorial/RouteLinks.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import links2 from '../../../resources/route_links_tutorial_images/links2.png'; -import links3 from '../../../resources/route_links_tutorial_images/links3.png'; -import links4 from '../../../resources/route_links_tutorial_images/links4.png'; -import links6 from '../../../resources/route_links_tutorial_images/links6.png'; -import linksCanvas from '../../../resources/route_links_tutorial_images/links-canvas.png'; +import links2 from '../../../resources/route_links_tutorial_images/links2.PNG'; +import links3 from '../../../resources/route_links_tutorial_images/links3.PNG'; +import links4 from '../../../resources/route_links_tutorial_images/links4.PNG'; +import links6 from '../../../resources/route_links_tutorial_images/links6.PNG'; +import linksCanvas from '../../../resources/route_links_tutorial_images/links-canvas.PNG'; const RouteLinks: React.FC<{ classes: any; @@ -14,7 +14,7 @@ const RouteLinks: React.FC<{

    Next.js Route Links


    -

    Route Links are only available for Next.js projects.

    +

    Route Links are only available for Next.js and Gatsby.js projects.

    Users are able to drag-and-drop 'Link' components onto the canvas which allow navigation to different setPage('Pages')} >pages.

    @@ -36,7 +36,7 @@ const RouteLinks: React.FC<{

    -

    For more information on 'Link' for Next.js, please visit the official documentation on nextjs.org.

    +

    For more information on 'Link' for Next.js, please visit the official documentation section at nextjs.org. For more information on 'Link' for Gatsby.js, please visit the official documentation section at www.gatsbyjs.com.


    ); diff --git a/app/src/tutorial/Styling.tsx b/app/src/tutorial/Styling.tsx index f5179bbf5..a2e0a8d5d 100644 --- a/app/src/tutorial/Styling.tsx +++ b/app/src/tutorial/Styling.tsx @@ -13,23 +13,13 @@ const Styling: React.FC<{

    Styling Features


    -

    Code Preview Theme Changer

    -
    - -
    -

    - Select your favorite theme from the drop down menu to personalize your - view of the setPage('Code_Preview')} >code preview! -

    -
    -

    Lighting Mode

    +

    Dark Mode

    - Spice up the app by toggling between different lighting modes! The - lighting mode will change the background color of the app as well as the - background color of the setPage('Component_Tree')} >component tree. + Spice up the app by switching to DARK MODE! DARK + MODE will change the background and text colors of the app.


    Resize Code Preview & Component Tree

    @@ -46,7 +36,7 @@ const Styling: React.FC<{

    - Change your code before exporting and see the changes in your exported + Manually change your code before exporting and see the changes in your exported file!


    diff --git a/app/src/tutorial/TutorialPage.tsx b/app/src/tutorial/TutorialPage.tsx index c77c16577..9268b2521 100644 --- a/app/src/tutorial/TutorialPage.tsx +++ b/app/src/tutorial/TutorialPage.tsx @@ -27,7 +27,6 @@ const useStyles = makeStyles({ }, img: { borderRadius: '3px', - // alignSelf: 'center' width: '100%' }, smallImg: { @@ -39,7 +38,6 @@ const useStyles = makeStyles({ display: 'flex', flexDirection: 'row', alignItems: 'center', - // border: '1px solid black', width: 'auto' }, notLink: { diff --git a/app/src/utils/createApplication.util.ts b/app/src/utils/createApplication.util.ts index 7cba6c281..443e9d9cc 100644 --- a/app/src/utils/createApplication.util.ts +++ b/app/src/utils/createApplication.util.ts @@ -1,7 +1,7 @@ // Create all files necessary to run a classic react application import createFiles from './createFiles.util'; -import { Component, State, ChildElement } from '../interfaces/Interfaces'; +import { Component} from '../interfaces/Interfaces'; const camelToKebab= (camel:string) => { return camel.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); diff --git a/app/src/utils/createGatsbyApp.util.ts b/app/src/utils/createGatsbyApp.util.ts new file mode 100644 index 000000000..32d54df28 --- /dev/null +++ b/app/src/utils/createGatsbyApp.util.ts @@ -0,0 +1,160 @@ +// Create all files necessary to run a gatsby.js application + +import createGatsbyFiles from './createGatsbyFiles.util'; +import { Component } from '../interfaces/Interfaces'; + +const camelToKebab= (camel:string) => { + return camel.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); +}; + +const compToCSS = (component: Component) => { + const name = component.name; + const styleObj = component.style; + let cssClass = ` + .${name} { + `; + Object.keys(styleObj).forEach(property => { + let cssStyle = `${camelToKebab(property)}: ${styleObj[property]}; + `; + cssClass += cssStyle; + }) + cssClass += `} + `; + return cssClass; +} + +//createPackage +export const createPackage = (path, appName) => { + const filePath = `${path}/${appName}/package.json`; + const data = ` +{ + "name": "reactype-gatsby", + "version": "1.0.0", + "description": "", + "scripts": { + "dev": "gatsby develop", + "build": "gatsby build", + "start": "npm run dev" + }, + "dependencies": { + "gatsby": "^2.26.1", + "react": "16.13.1", + "react-dom": "16.13.1" + }, + "devDependencies": { + "@types/node": "^14.0.20", + "@types/react": "^16.9.41", + "typescript": "^3.9.6" + } +} + `; + window.api.writeFile(filePath, data, err => { + if (err) { + console.log('package.json error:', err.message); + } else { + console.log('package.json written successfully'); + } + }); +}; +//createTSConfig (empty) +export const createTsConfig = (path, appName) => { + const filePath = `${path}/${appName}/tsconfig.json`; + //running 'gatsby dev' will autopopulate this with default values + window.api.writeFile(filePath, '', err => { + if (err) { + console.log('TSConfig error:', err.message); + } else { + console.log('TSConfig written successfully'); + } + }); +}; + +//createDefaultCSS +export const createDefaultCSS = (path, appName, components) => { + const filePath = `${path}/${appName}/global.css`; + let data = ` + #__gatsby div { + box-sizing: border-box; + width: 100%; + border: 1px solid rgba(0,0,0,0.25); + padding: 12px; + text-align: center; + font-family: Helvetica, Arial; + } + `; + components.forEach(comp => { + data += compToCSS(comp); + }) + window.api.writeFile(filePath, data, err => { + if (err) { + console.log('global.css error:', err.message); + } else { + console.log('global.css written successfully'); + } + }); +} + +export const initFolders = (path:string, appName: string) => { + let dir = path; + let dirPages; + let dirComponents; + dir = `${dir}/${appName}`; + if (!window.api.existsSync(dir)) { + window.api.mkdirSync(dir); + window.api.mkdirSync(`${dir}/src`) + dirPages = `${dir}/src/pages`; + window.api.mkdirSync(dirPages); + dirComponents = `${dir}/src/components`; + window.api.mkdirSync(dirComponents); + } +}; + +//createBaseTsx +export const createBaseTsx = (path, appName) => { + + const filePath:string = `${path}/${appName}/src/pages/_app.tsx`; + const data:string = ` + import React from 'react'; + import '../global.css'; + + const Base = ({ Component }):JSX.Element => { + return ( + <> + + + ) + } + + export default Base; + `; + window.api.writeFile(filePath, data, err => { + if (err) { + console.log('_app.tsx error:', err.message); + } else { + console.log('_app.tsx written successfully'); + } + }); +}; + +async function createGatsbyAppUtil({ + path, + appName, + components, + rootComponents +}: { + path: string; + appName: string; + components: Component[]; + rootComponents: number[]; +}) { + console.log('in the createGatsbyApplication util'); + + await initFolders(path, appName); + await createBaseTsx(path, appName); + await createDefaultCSS(path, appName, components); + await createPackage(path, appName); + await createTsConfig(path, appName); + await createGatsbyFiles(components, path, appName, rootComponents); + +} +export default createGatsbyAppUtil; diff --git a/app/src/utils/createGatsbyFiles.util.ts b/app/src/utils/createGatsbyFiles.util.ts new file mode 100644 index 000000000..ff0cc448d --- /dev/null +++ b/app/src/utils/createGatsbyFiles.util.ts @@ -0,0 +1,47 @@ +// Create all component files for a Gatsby.js application +// all components are stored in a src folder +// "Root" level components are stored in a pages directory +// all other components will be in a components directory + +import { Component } from '../interfaces/Interfaces'; + +const isRoot = (component: Component, rootArray: number[]) => { + return rootArray.includes(component.id) ? true : false; +}; + +const createGatsbyFiles = ( + components: Component[], + path: string, + appName: string, + rootComponents: number[] +) => { + let dir = path; + dir = `${dir}/${appName}`; + + const promises: Array = []; + components.forEach((component: Component) => { + let code: string; + let fileName: string; + if (isRoot(component, rootComponents)) { + if (component.id === 1) { + // first root component must be index.tsx + fileName = `${dir}/src/pages/index.tsx`; + } else { + fileName = `${dir}/src/pages/${component.name}.tsx`; + } + } else { + fileName = `${dir}/src/components/${component.name}.tsx`; + } + const newPromise = new Promise((resolve, reject) => { + window.api.writeFileSync(fileName, component.code, (err: any) => { + if (err) return reject(err.message); + return resolve(path); + }); + }); + + promises.push(newPromise); + }); + return Promise.all(promises); +}; + +export default createGatsbyFiles; diff --git a/app/src/utils/exportProject.util.ts b/app/src/utils/exportProject.util.ts index 6f4e68a98..5f65afe2d 100644 --- a/app/src/utils/exportProject.util.ts +++ b/app/src/utils/exportProject.util.ts @@ -1,6 +1,7 @@ import createApplicationUtil from './createApplication.util'; import createNextApp from './createNextApp.util'; import createFiles from './createFiles.util'; +import createGatsbyApp from './createGatsbyApp.util'; // When a user clicks the "Export project" function from the app, this function is invoked const exportProject = ( @@ -19,11 +20,14 @@ const exportProject = ( } // export all component files, but don't create all application files else if (genOption === 0) { createFiles(components, path, appName, false); - } // Create fully functional Next.js application + } // Create fully functional Next.js and Gatsby.js application files else if (genOption === 1 && projectType === 'Next.js') { createNextApp({ path, appName, components, rootComponents }).catch(err => console.log(err) ); + } else if (genOption === 1 && projectType === 'Gatsby.js') { + createGatsbyApp({ path, appName, components, rootComponents }).catch(err => + console.log(err)); } }; diff --git a/electron-builder.yml b/electron-builder.yml index dcef0d9d8..b1651da2c 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -1,5 +1,5 @@ appId: com.electron.reactype -copyright: Copyright © 2020 +copyright: Copyright © 2021 productName: ReacType directories: buildResources: resources diff --git a/package.json b/package.json index fa9cf1e41..1841f0c7a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "reactype", - "version": "4.0.0", + "version": "5.0.0", "description": "Prototyping tool for React/Typescript Applications.", "private": true, "main": "app/electron/main.js", @@ -8,6 +8,7 @@ "contributors": [ "Aaron Bumanglag", "Adam Singer", + "Alex Wolinsky", "Andrew Cho", "Brian Han", "Charles Finocchiaro", @@ -18,6 +19,9 @@ "Fredo Chen", "Jesse Zuniga", "Jin Soo Lim", + "Julie Wu", + "Linh Tran", + "Luke Madden", "Mitchel Severe", "Natalie Vick", "Sean Sadykoff", @@ -34,7 +38,7 @@ "dev": "concurrently --success first \"npm run dev-server\" \"cross-env NODE_ENV=development electron .\" \"cross-env NODE_ENV=development npm run server\" -k", "p": "concurrently --success first \"npm run dev-server\" \"cross-env NODE_ENV=production electron .\" \"cross-env NODE_ENV=production npm run server\" -k", "prod-build": "cross-env NODE_ENV=production npx webpack --mode=production --config ./webpack.production.js", - "prod": "npm run prod-build && electron .", + "prod": "npm run prod-build && electron . --no-sandbox", "pack": "electron-builder --dir", "dist": "npm run prod-build && electron-builder", "dist-mac": "npm run prod-build && electron-builder --mac", @@ -57,8 +61,7 @@ "license": "MIT", "jest": { "moduleNameMapper": { - "^.+\\.(css|scss|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "identity-obj-proxy", - "electron": "/__mocks__/electron.ts" + "^.+\\.(css|scss|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "identity-obj-proxy" }, "moduleFileExtensions": [ "ts", @@ -95,6 +98,7 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.0.1", "@material-ui/styles": "^4.9.6", + "@material-ui/system": "^4.11.2", "@types/js-cookie": "^2.2.6", "@types/node": "^14.0.20", "@types/prettier": "^1.19.0", @@ -113,6 +117,7 @@ "cors": "^2.8.5", "d3": "^6.2.0", "dotenv": "^8.2.0", + "electron": "^9.4.2", "electron-debug": "^3.1.0", "electron-devtools-installer": "^2.2.4", "electron-splashscreen": "^1.0.0", @@ -121,6 +126,7 @@ "immutable": "^4.0.0-rc.12", "js-cookie": "^2.2.1", "localforage": "^1.7.2", + "lodash": "^4.17.20", "node-fetch": "^2.6.0", "passport": "^0.4.1", "passport-github2": "^0.1.12", @@ -135,6 +141,7 @@ "react-facebook-login": "^4.1.1", "react-google-login": "^5.1.22", "react-router-dom": "^5.2.0", + "regenerator-runtime": "^0.13.7", "resize-observer-polyfill": "^1.5.1", "seamless-immutable": "^7.1.4", "source-map-support": "^0.5.19", @@ -160,7 +167,6 @@ "csp-html-webpack-plugin": "^4.0.0", "css-loader": "^2.1.1", "dotenv-webpack": "^5.0.1", - "electron": "^9.1.0", "electron-builder": "^22.7.0", "enzyme-to-json": "^3.5.0", "eslint": "^4.19.1", diff --git a/resources/canvas_tutorial_images/canvas1.png b/resources/canvas_tutorial_images/canvas1.png index 35981d6d9..773430122 100644 Binary files a/resources/canvas_tutorial_images/canvas1.png and b/resources/canvas_tutorial_images/canvas1.png differ diff --git a/resources/canvas_tutorial_images/drag1.png b/resources/canvas_tutorial_images/drag1.png index 80b3c41ff..8044d0a51 100644 Binary files a/resources/canvas_tutorial_images/drag1.png and b/resources/canvas_tutorial_images/drag1.png differ diff --git a/resources/code_preview_images/CodePreview.png b/resources/code_preview_images/CodePreview.png new file mode 100644 index 000000000..38d4f2bc2 Binary files /dev/null and b/resources/code_preview_images/CodePreview.png differ diff --git a/resources/customizing_elements_images/BackgroundColor.png b/resources/customizing_elements_images/BackgroundColor.png index ea68687ad..31ebee571 100644 Binary files a/resources/customizing_elements_images/BackgroundColor.png and b/resources/customizing_elements_images/BackgroundColor.png differ diff --git a/resources/customizing_elements_images/CodeChange.png b/resources/customizing_elements_images/CodeChange.png index 621307881..21e74c30f 100644 Binary files a/resources/customizing_elements_images/CodeChange.png and b/resources/customizing_elements_images/CodeChange.png differ diff --git a/resources/customizing_elements_images/Display.png b/resources/customizing_elements_images/Display.png index 39516528f..a7ea4cbda 100644 Binary files a/resources/customizing_elements_images/Display.png and b/resources/customizing_elements_images/Display.png differ diff --git a/resources/customizing_elements_images/Flex.png b/resources/customizing_elements_images/Flex.png index 0e52fe2c8..5a25d40d1 100644 Binary files a/resources/customizing_elements_images/Flex.png and b/resources/customizing_elements_images/Flex.png differ diff --git a/resources/customizing_elements_images/Height.png b/resources/customizing_elements_images/Height.png index 49bd2f8f3..e3d318a48 100644 Binary files a/resources/customizing_elements_images/Height.png and b/resources/customizing_elements_images/Height.png differ diff --git a/resources/customizing_elements_images/Lighting.png b/resources/customizing_elements_images/Lighting.png index aa4b4f700..3d89f8dab 100644 Binary files a/resources/customizing_elements_images/Lighting.png and b/resources/customizing_elements_images/Lighting.png differ diff --git a/resources/customizing_elements_images/Resize.png b/resources/customizing_elements_images/Resize.png index 94f5a8819..b65912512 100644 Binary files a/resources/customizing_elements_images/Resize.png and b/resources/customizing_elements_images/Resize.png differ diff --git a/resources/customizing_elements_images/Width.png b/resources/customizing_elements_images/Width.png index fc469f548..e28e25e3b 100644 Binary files a/resources/customizing_elements_images/Width.png and b/resources/customizing_elements_images/Width.png differ diff --git a/resources/html_elements_tutorial_images/codeSnippet.png b/resources/html_elements_tutorial_images/codeSnippet.png index d940e5f4e..1022cabb4 100644 Binary files a/resources/html_elements_tutorial_images/codeSnippet.png and b/resources/html_elements_tutorial_images/codeSnippet.png differ diff --git a/resources/html_elements_tutorial_images/createNew.png b/resources/html_elements_tutorial_images/createNew.png index 80d835df0..6f81748de 100644 Binary files a/resources/html_elements_tutorial_images/createNew.png and b/resources/html_elements_tutorial_images/createNew.png differ diff --git a/resources/html_elements_tutorial_images/defaultElements.png b/resources/html_elements_tutorial_images/defaultElements.png index 41d34a060..11f9730f6 100644 Binary files a/resources/html_elements_tutorial_images/defaultElements.png and b/resources/html_elements_tutorial_images/defaultElements.png differ diff --git a/resources/html_elements_tutorial_images/newTag.png b/resources/html_elements_tutorial_images/newTag.png index 05fc38dc8..aea7d9789 100644 Binary files a/resources/html_elements_tutorial_images/newTag.png and b/resources/html_elements_tutorial_images/newTag.png differ diff --git a/resources/pages_images/AddElements.png b/resources/pages_images/AddElements.png index 844bf82bd..e76f499dc 100644 Binary files a/resources/pages_images/AddElements.png and b/resources/pages_images/AddElements.png differ diff --git a/resources/pages_images/DeletePage.png b/resources/pages_images/DeletePage.png index 9508e30f7..5b638da60 100644 Binary files a/resources/pages_images/DeletePage.png and b/resources/pages_images/DeletePage.png differ diff --git a/resources/pages_images/Pages.png b/resources/pages_images/Pages.png index 1cc0ea05b..ee8a40c04 100644 Binary files a/resources/pages_images/Pages.png and b/resources/pages_images/Pages.png differ diff --git a/resources/pages_images/Toggle.png b/resources/pages_images/Toggle.png index 55b2920e2..29947f2b5 100644 Binary files a/resources/pages_images/Toggle.png and b/resources/pages_images/Toggle.png differ diff --git a/resources/reusable_components_tutorial_images/reusablecomponents1.png b/resources/reusable_components_tutorial_images/reusablecomponents1.png index 189a5b098..02874418b 100644 Binary files a/resources/reusable_components_tutorial_images/reusablecomponents1.png and b/resources/reusable_components_tutorial_images/reusablecomponents1.png differ diff --git a/resources/reusable_components_tutorial_images/reusablecomponents2.png b/resources/reusable_components_tutorial_images/reusablecomponents2.png index f4c88a41c..0829c6caa 100644 Binary files a/resources/reusable_components_tutorial_images/reusablecomponents2.png and b/resources/reusable_components_tutorial_images/reusablecomponents2.png differ diff --git a/resources/reusable_components_tutorial_images/reusablecomponents3.png b/resources/reusable_components_tutorial_images/reusablecomponents3.png index 573c36281..678a67a3c 100644 Binary files a/resources/reusable_components_tutorial_images/reusablecomponents3.png and b/resources/reusable_components_tutorial_images/reusablecomponents3.png differ diff --git a/resources/route_links_tutorial_images/Screen Shot 2021-02-02 at 12.18.46 PM.png b/resources/route_links_tutorial_images/Screen Shot 2021-02-02 at 12.18.46 PM.png new file mode 100644 index 000000000..3a40fd5e3 Binary files /dev/null and b/resources/route_links_tutorial_images/Screen Shot 2021-02-02 at 12.18.46 PM.png differ diff --git a/resources/route_links_tutorial_images/links-canvas.PNG b/resources/route_links_tutorial_images/links-canvas.PNG index 8191a4b8d..b86a69829 100644 Binary files a/resources/route_links_tutorial_images/links-canvas.PNG and b/resources/route_links_tutorial_images/links-canvas.PNG differ diff --git a/resources/route_links_tutorial_images/links1.PNG b/resources/route_links_tutorial_images/links1.PNG index aa23a5d62..065cadad4 100644 Binary files a/resources/route_links_tutorial_images/links1.PNG and b/resources/route_links_tutorial_images/links1.PNG differ diff --git a/resources/route_links_tutorial_images/links2.PNG b/resources/route_links_tutorial_images/links2.PNG index 7c9da43c1..9c79e6f9a 100644 Binary files a/resources/route_links_tutorial_images/links2.PNG and b/resources/route_links_tutorial_images/links2.PNG differ diff --git a/resources/route_links_tutorial_images/links3.PNG b/resources/route_links_tutorial_images/links3.PNG index 8d6146cd9..e14082ac1 100644 Binary files a/resources/route_links_tutorial_images/links3.PNG and b/resources/route_links_tutorial_images/links3.PNG differ diff --git a/resources/route_links_tutorial_images/links4.PNG b/resources/route_links_tutorial_images/links4.PNG index 9a113c664..bc6309c38 100644 Binary files a/resources/route_links_tutorial_images/links4.PNG and b/resources/route_links_tutorial_images/links4.PNG differ diff --git a/resources/route_links_tutorial_images/links5.PNG b/resources/route_links_tutorial_images/links5.PNG index 91e14ab14..2db642e58 100644 Binary files a/resources/route_links_tutorial_images/links5.PNG and b/resources/route_links_tutorial_images/links5.PNG differ diff --git a/resources/route_links_tutorial_images/links6.PNG b/resources/route_links_tutorial_images/links6.PNG index d95567056..da3e35abc 100644 Binary files a/resources/route_links_tutorial_images/links6.PNG and b/resources/route_links_tutorial_images/links6.PNG differ diff --git a/resources/tree_tutorial_images/tree1.png b/resources/tree_tutorial_images/tree1.png index 9365fd148..9e9935b73 100644 Binary files a/resources/tree_tutorial_images/tree1.png and b/resources/tree_tutorial_images/tree1.png differ diff --git a/resources/tree_tutorial_images/tree2.png b/resources/tree_tutorial_images/tree2.png index 7224de603..7e49ce9ee 100644 Binary files a/resources/tree_tutorial_images/tree2.png and b/resources/tree_tutorial_images/tree2.png differ diff --git a/resources/tree_tutorial_images/tree3.png b/resources/tree_tutorial_images/tree3.png index 17f5b371e..1f0611d0d 100644 Binary files a/resources/tree_tutorial_images/tree3.png and b/resources/tree_tutorial_images/tree3.png differ diff --git a/resources/tree_tutorial_images/tree4.png b/resources/tree_tutorial_images/tree4.png index ee32e6f6e..65172aa56 100644 Binary files a/resources/tree_tutorial_images/tree4.png and b/resources/tree_tutorial_images/tree4.png differ diff --git a/resources/tree_tutorial_images/tree5.png b/resources/tree_tutorial_images/tree5.png index 9a1162413..d649242b1 100644 Binary files a/resources/tree_tutorial_images/tree5.png and b/resources/tree_tutorial_images/tree5.png differ diff --git a/server/controllers/projectController.js b/server/controllers/projectController.js index 3c0ca2150..094afed20 100644 --- a/server/controllers/projectController.js +++ b/server/controllers/projectController.js @@ -33,7 +33,6 @@ projectController.saveProject = (req, res, next) => { }; // gets all of current user's projects - projectController.getProjects = (req, res, next) => { const { userId } = req.body; Projects.find({ userId }, (err, projects) => { diff --git a/server/controllers/sessionController.js b/server/controllers/sessionController.js index b15d26d5c..49de717f1 100644 --- a/server/controllers/sessionController.js +++ b/server/controllers/sessionController.js @@ -1,8 +1,8 @@ require('dotenv').config(); -const fetch = require('node-fetch'); const { Sessions } = require('../models/reactypeModels'); + const sessionController = {}; -// isLoggedIn finds appropriate session for this request in database, then verifies whether or not the session is still valid +// isLoggedIn finds the right session, if the session is invalid in the database, the app redirect user straight to the root endpoint witht he login page, if not, continue session sessionController.isLoggedIn = (req, res, next) => { let cookieId; if (req.cookies.ssid) { @@ -17,141 +17,146 @@ sessionController.isLoggedIn = (req, res, next) => { return next({ log: `Error in sessionController.isLoggedIn: ${err}`, message: { - err: `Error in sessionController.isLoggedIn, check server logs for details`, - }, + err: + 'Error in sessionController.isLoggedIn, check server logs for details' + } }); - // no session found, redirect to signup page - } else if (!session) { + } + if (!session) { return res.redirect('/'); - } else { - // session found, move onto next middleware - return next(); } + return next(); }); }; // startSession - create and save a new session into the database sessionController.startSession = (req, res, next) => { // first check if user is logged in already - Sessions.findOne({ cookieId: res.locals.id }, (err, session) => { + Sessions.findOne({ cookieId: res.locals.id }, (err, ses) => { if (err) { return next({ log: `Error in sessionController.startSession find session: ${err}`, message: { - err: `Error in sessionController.startSession find session, check server logs for details`, - }, + err: + 'Error in sessionController.startSession find session, check server logs for details' + } }); // if session doesn't exist, create a session // if valid user logged in/signed up, res.locals.id should be user's id generated from mongodb, which we will set as this session's cookieId - } else if (!session) { - Sessions.create({ cookieId: res.locals.id }, (err, session) => { - if (err) { + } + if (!ses) { + Sessions.create({ cookieId: res.locals.id }, (error, session) => { + if (error) { return next({ log: `Error in sessionController.startSession create session: ${err}`, message: { - err: `Error in sessionController.startSession create session, check server logs for details`, - }, + err: + 'Error in sessionController.startSession create session, check server logs for details' + } }); - } else { - res.locals.ssid = session.cookieId; - return next(); } + res.locals.ssid = session.cookieId; + return next(); }); // if session exists, move onto next middleware } else { - res.locals.ssid = session.cookieId; + res.locals.ssid = ses.cookieId; return next(); } }); }; -// -sessionController.gitHubResponse = (req, res, next) => { - // console.log(req) - const { code } = req.query; - // console.log('code =>', code); - if (!code) return next({ - log: 'Undefined or no code received from github.com', - message: 'Undefined or no code received from github.com', - status: 400 - }); - fetch( - `https://github.com/login/oauth/access_token`,{ - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - client_id: process.env.GITHUB_ID, - client_secret: process.env.GITHUB_SECRET, - code - }) - }) - .then(res => res.json()) - .then(token => { - res.locals.token = token['access_token']; - return next(); - }) - .catch(err => res.status(500).json({message: `${err.message} in gitHubResponse`})) -} +// sessionController.gitHubResponse = (req, res, next) => { +// // console.log(req) +// const { code } = req.query; +// // console.log('code =>', code); +// if (!code) +// return next({ +// log: 'Undefined or no code received from github.com', +// message: 'Undefined or no code received from github.com', +// status: 400 +// }); +// fetch(`https://github.com/login/oauth/access_token`, { +// method: 'POST', +// headers: { +// Accept: 'application/json', +// 'Content-Type': 'application/json' +// }, +// body: JSON.stringify({ +// client_id: process.env.GITHUB_ID, +// client_secret: process.env.GITHUB_SECRET, +// code +// }) +// }) +// .then(res => res.json()) +// .then(token => { +// res.locals.token = token['access_token']; +// return next(); +// }) +// .catch(err => +// res.status(500).json({ message: `${err.message} in gitHubResponse` }) +// ); +// }; -sessionController.gitHubSendToken = (req, res, next) => { - const { token } = res.locals; - fetch( - `https://api.github.com/user/emails`,{ - method: 'GET', - headers: { - 'Accept': 'application/vnd.github.v3+json', - 'Authorization': `token ${token}` - }, - }) - .then(res => res.json()) - .then(data => { - res.locals.githubEmail = data[0]['email']; - res.locals.signUpType = 'oauth'; - return next(); - }) - .catch(err =>{ - if (err.message === `Cannot read property 'email' of undefined`) { - return res.status(400).json({message: `${err.message} in gitHubSendToken`}); - } else { - return res.status(500).json({message: `${err.message} in gitHubSendToken`}); - } - }); -} +// sessionController.gitHubSendToken = (req, res, next) => { +// const { token } = res.locals; +// fetch(`https://api.github.com/user/emails`, { +// method: 'GET', +// headers: { +// Accept: 'application/vnd.github.v3+json', +// Authorization: `token ${token}` +// } +// }) +// .then(res => res.json()) +// .then(data => { +// res.locals.githubEmail = data[0]['email']; +// res.locals.signUpType = 'oauth'; +// return next(); +// }) +// .catch(err => { +// if (err.message === `Cannot read property 'email' of undefined`) { +// return res +// .status(400) +// .json({ message: `${err.message} in gitHubSendToken` }); +// } else { +// return res +// .status(500) +// .json({ message: `${err.message} in gitHubSendToken` }); +// } +// }); +// }; -// creates a session when logging in with github -sessionController.githubSession = (req, res, next) => { - // req.user is passed in from passport js -> serializeuser/deserializeuser - const cookieId = req.user._id; - Sessions.findOne({ cookieId }, (err, session) => { - if (err) { - return next({ - log: `Error in sessionController.githubSession find session: ${err}`, - message: { - err: `Error in sessionController.githubSession find session, check server logs for details`, - }, - }); - } else if (!session) { - Sessions.create({ cookieId }, (err, session) => { - if (err) { - return next({ - log: `Error in sessionController.githubSession create session: ${err}`, - message: { - err: `Error in sessionController.githubSession create session, check server logs for details`, - }, - }); - } else { - res.locals.id = session.cookieId; - return next(); - } - }); - } else { - res.locals.id = session.cookieId; - return next(); - } - }); -}; +// // creates a session when logging in with github +// sessionController.githubSession = (req, res, next) => { +// // req.user is passed in from passport js -> serializeuser/deserializeuser +// const cookieId = req.user._id; +// Sessions.findOne({ cookieId }, (err, session) => { +// if (err) { +// return next({ +// log: `Error in sessionController.githubSession find session: ${err}`, +// message: { +// err: `Error in sessionController.githubSession find session, check server logs for details` +// } +// }); +// } else if (!session) { +// Sessions.create({ cookieId }, (err, session) => { +// if (err) { +// return next({ +// log: `Error in sessionController.githubSession create session: ${err}`, +// message: { +// err: `Error in sessionController.githubSession create session, check server logs for details` +// } +// }); +// } else { +// res.locals.id = session.cookieId; +// return next(); +// } +// }); +// } else { +// res.locals.id = session.cookieId; +// return next(); +// } +// }); +// }; module.exports = sessionController; diff --git a/server/controllers/userController.js b/server/controllers/userController.js index 79b0806e0..fbc8d4dfa 100644 --- a/server/controllers/userController.js +++ b/server/controllers/userController.js @@ -4,36 +4,37 @@ const { Users } = require('../models/reactypeModels'); const userController = {}; const bcrypt = require('bcryptjs'); -const randomPassword = () => { - function getRandomSpecialChar() { - const code = Math.round(Math.random() * (38 - 37) + 37); - return String.fromCharCode(code); - } - function getRandomDigit() { - const code = Math.round(Math.random() * (57 - 48) + 48); - return String.fromCharCode(code); - } - function getRandomLetter() { - const code = Math.round(Math.random() * (90 - 65) + 65); - return String.fromCharCode(code); - } - let password = ''; - for (let i = 0; i < 6; i += 1) { - password += getRandomLetter() + getRandomDigit() + getRandomSpecialChar(); - } - return password; -} +// random password is subtituted when user uses Oauth and no new password is provided +// const randomPassword = () => { +// function getRandomSpecialChar() { +// const code = Math.round(Math.random() * (38 - 37) + 37); +// return String.fromCharCode(code); +// } +// function getRandomDigit() { +// const code = Math.round(Math.random() * (57 - 48) + 48); +// return String.fromCharCode(code); +// } +// function getRandomLetter() { +// const code = Math.round(Math.random() * (90 - 65) + 65); +// return String.fromCharCode(code); +// } +// let password = ''; +// for (let i = 0; i < 6; i += 1) { +// password += getRandomLetter() + getRandomDigit() + getRandomSpecialChar(); +// } +// return password; +// } userController.createUser = (req, res, next) => { - - let { email, username, password } = req.body; - if (res.locals.signUpType === 'oauth') { - email = res.locals.githubEmail; - username = email; - password = randomPassword(); - } - // error handling if username or password is missing - // TODO: make this more vague for security purposes + const { email, username, password } = req.body; + + // use this condition for Oauth login + // if (res.locals.signUpType === 'oauth') { + // email = res.locals.githubEmail; + // username = email; + // password = randomPassword(); + // } + // error handling if username or email or password is missing if (!username) { return res.status(400).json('No username input'); @@ -47,8 +48,9 @@ userController.createUser = (req, res, next) => { // create user using username and password Users.create({ username, password, email }, (err, newUser) => { + // handle error of creating a new user if (err) { - if (err.keyValue.email && res.locals.signUpType === 'oauth') { + if (res.locals.signUpType === 'oauth') { return next(); } if (err.keyValue.email) { @@ -64,14 +66,14 @@ userController.createUser = (req, res, next) => { return next({ log: `Error in userController.createUser: ${err}`, message: { - err: `Error in userController.createUser. Check server logs for details`, - }, + err: + 'Error in userController.createUser. Check server logs for details' + } }); - } else { - // this id property will be used in other middleware for cookie - res.locals.id = newUser.id; - return next(); } + // if no error found when creating a new user, send back user ID in res.locals + res.locals.id = newUser.id; + return next(); }); }; @@ -80,10 +82,11 @@ userController.createUser = (req, res, next) => { userController.verifyUser = (req, res, next) => { let { username, password, isFbOauth } = req.body; - if (res.locals.signUpType === 'oauth') { - username = res.locals.githubEmail; - password = res.locals.githubPassword; - } + // handle Oauth + // if (res.locals.signUpType === 'oauth') { + // username = res.locals.githubEmail; + // password = res.locals.githubPassword; + // } if (!username) { return res.status(400).json('No Username Input'); } @@ -95,24 +98,26 @@ userController.verifyUser = (req, res, next) => { return next({ log: `Error in userController.verifyUser: ${err}`, message: { - err: `Error in userController.verifyUser, check server logs for details`, - }, + err: `Error in userController.verifyUser, check server logs for details` + } }); - } - else if (user && (res.locals.signUpType === 'oauth' || isFbOauth)) { + } + if (user && (res.locals.signUpType === 'oauth' || isFbOauth)) { res.locals.id = user.id; return next(); - } - else if (user) { + } + if (user) { // bcrypt compare function checks input password against hashed password - bcrypt.compare(password, user.password).then((isMatch) => { + // eslint-disable-next-line arrow-parens + bcrypt.compare(password, user.password).then(isMatch => { if (isMatch) { // if password matches, save user id for following middleware res.locals.id = user.id; return next(); - } else { - return res.status(400).json('Incorrect Password'); } + // if hashed password is not matched saved password in db, send 400 response + + return res.status(400).json('Incorrect Password'); }); } else { return res.status(400).json('Invalid Username'); @@ -120,35 +125,4 @@ userController.verifyUser = (req, res, next) => { }); }; -// userController.doesUserExist = (req, res, next) => { -// const { email } = req.body; -// Users.findOne({ email }, (err, user) => { -// if (err) return next({ -// log: `Error in userController.doesUserExist: ${err}`, -// message: { -// err: `Error in userController.doesUserExist, check server logs for details`, -// }, -// }); -// else if(!user) { -// console.log('email NOT found', user); -// res.locals.userExists = false; -// return next(); -// } -// else { -// console.log('email found', user); -// res.locals.userExists = true; -// return next(); -// } -// }) -// } - -// userController.isOauth = (req, res, next) => { -// const { signUpType } = res.localsbody; -// if (signUpType === 'oauth'){ -// return next(); -// } else { -// return next(); -// } -// } - module.exports = userController; diff --git a/server/models/reactypeModels.js b/server/models/reactypeModels.js index ff7d58c42..67e049a39 100644 --- a/server/models/reactypeModels.js +++ b/server/models/reactypeModels.js @@ -1,44 +1,49 @@ +/* + @desc: defines Schemas for the app: sessionSchema (cookieId, created_at), userSchema (username, password, email), projectSchema (name, userId, project, created_at) + @export: Users, Sessions, Projects (3 schemas) + @important: URI to database is hidden in config.js file which is not available to future team. we recommend that your team will create a mongoDB database to test in dev mode. the real database is deployed in heroku + */ const mongoose = require('mongoose'); -require('dotenv').config(); +const bcrypt = require('bcryptjs'); +const config = require('../../config'); +const SALT_WORK_FACTOR = 14; // connect to mongo db mongoose - .connect(process.env.MONGO_URI, { - // options for the connect method to parse the URI - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true, - // stop deprecation warning for findOneAndUpdate and findOneAndDelete queries - useFindAndModify: false, - // sets the name of the DB that our collections are part of - dbName: 'ReacType', - }) + .connect(config.URI, + { + // options for the connect method to parse the URI + useNewUrlParser: true, + useUnifiedTopology: true, + useCreateIndex: true, + // stop deprecation warning for findOneAndUpdate and findOneAndDelete queries + useFindAndModify: false, + // sets the name of the DB that our collections are part of + dbName: 'ReacType' + } + ) .then(() => console.log('Connected to Mongo DB.')) - .catch((err) => console.log(err)); + .catch(err => console.log(err)); -const Schema = mongoose.Schema; +const { Schema } = mongoose; const userSchema = new Schema({ username: { type: String, required: true, unique: true }, email: { type: String, required: false, unique: true }, - password: { type: String, required: true }, + password: { type: String, required: true } }); -// salt will go through 10 rounds of hashing -const SALT_WORK_FACTOR = 10; -const bcrypt = require('bcryptjs'); - // mongoose middleware that will run before the save to collection happens (user gets put into database) // cannot use arrow function here as context of 'this' is important -userSchema.pre('save', function (next) { +userSchema.pre('save', function cb(next) { // within this context, 'this' refers to the document (new user) about to be saved, in our case, it should have properties username, password, and projects array bcrypt.hash(this.password, SALT_WORK_FACTOR, (err, hash) => { if (err) { return next({ log: `bcrypt password hashing error: ${err}`, message: { - err: `bcrypt hash error: check server logs for details`, - }, + err: 'bcrypt hash error: check server logs for details.' + } }); } this.password = hash; @@ -48,7 +53,7 @@ userSchema.pre('save', function (next) { const sessionSchema = new Schema({ cookieId: { type: String, required: true, unique: true }, - createdAt: { type: Date, default: Date.now }, + createdAt: { type: Date, default: Date.now } }); const projectSchema = new Schema({ @@ -56,9 +61,9 @@ const projectSchema = new Schema({ project: { type: Object, required: true }, userId: { type: Schema.Types.ObjectId, - ref: 'Users', + ref: 'Users' }, - createdAt: { type: Date, default: Date.now }, + createdAt: { type: Date, default: Date.now } }); const Users = mongoose.model('Users', userSchema); diff --git a/server/server.js b/server/server.js index 8cf83c6f3..008e4a6e1 100644 --- a/server/server.js +++ b/server/server.js @@ -1,22 +1,16 @@ const express = require('express'); -const fs = require('fs'); -const path = require('path'); -// const passport = require('passport'); -// require('./passport-setup'); -const bodyParser = require('body-parser'); const cookieParser = require('cookie-parser'); const cors = require('cors'); const userController = require('./controllers/userController'); const cookieController = require('./controllers/cookieController'); const sessionController = require('./controllers/sessionController'); const projectController = require('./controllers/projectController'); + const app = express(); const PORT = process.env.PORT || 5000; const isDev = process.env.NODE_ENV === 'development'; -console.log('PORT is ', PORT); - app.use(express.json()); app.use(cookieParser()); @@ -25,7 +19,7 @@ app.use(cookieParser()); app.use( cors({ origin: ['http://localhost:8080', 'app://rse'], - credentials: true, + credentials: true }) ); @@ -37,22 +31,23 @@ app.use( // app.use(passport.initialize()); // app.use(passport.session()); -app.get( - '/github/callback', - sessionController.gitHubResponse, - sessionController.gitHubSendToken, - userController.createUser, - userController.verifyUser, - cookieController.setSSIDCookie, - sessionController.startSession, - (req, res) => { - if (isDev) { - return res.status(200).redirect(`http://localhost:8080?=${res.locals.ssid}`); - } else { - return res.status(200).redirect('app://rse'); - } - } -); +// // for Oauth which is currently not working +// app.get( +// '/github/callback', +// sessionController.gitHubResponse, +// sessionController.gitHubSendToken, +// userController.createUser, +// userController.verifyUser, +// cookieController.setSSIDCookie, +// sessionController.startSession, +// (req, res) => { +// if (isDev) { +// return res.status(200).redirect(`http://localhost:8080?=${res.locals.ssid}`); +// } else { +// return res.status(200).redirect('app://rse'); +// } +// } +// ); app.post( '/signup', @@ -113,14 +108,14 @@ app.use((err, req, res, next) => { const defaultErr = { log: 'Express error handler caught unknown middleware', status: 500, - message: { err: 'An error occurred' }, + message: { err: 'An error occurred' } }; const errorObj = Object.assign({}, defaultErr, err); return res.status(errorObj.status).json(errorObj.message); }); -//starts server on PORT +// starts server on PORT app.listen(PORT, () => { console.log(`Server listening on port: ${PORT}`); });