From 4397a959e806bf476be4c81bc804616adf58b969 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 12 May 2021 22:42:12 +0200 Subject: ui: Migrate from CRA to Next.js This change updates the web frontend to use Next.js instead of Create React App (CRA). Next.js enables the possibility of rendering pages on the server side (which reduces the time to first frame) and overall provides a better development experience. Future commits will try to futher optimize the implementation for Next.js. --- opendc-web/opendc-web-ui/.gitignore | 4 +- opendc-web/opendc-web-ui/README.md | 69 +- opendc-web/opendc-web-ui/package.json | 13 +- opendc-web/opendc-web-ui/src/actions/map.js | 1 + .../opendc-web-ui/src/actions/modals/profile.js | 14 - opendc-web/opendc-web-ui/src/api/index.js | 31 +- .../opendc-web-ui/src/api/routes/token-signin.js | 4 +- opendc-web/opendc-web-ui/src/auth/hook.js | 44 + opendc-web/opendc-web-ui/src/auth/index.js | 2 +- .../src/components/app/map/MapStageComponent.js | 118 +- .../app/sidebars/project/PortfolioListComponent.js | 13 +- .../app/sidebars/project/ScenarioListComponent.js | 13 +- .../src/components/modals/ConfirmationModal.js | 48 +- .../components/navigation/AppNavbarComponent.js | 20 +- .../src/components/navigation/LogoutButton.js | 3 +- .../src/components/navigation/Navbar.js | 47 +- .../src/components/not-found/TerminalWindow.js | 8 +- .../components/projects/ProjectActionButtons.js | 8 +- opendc-web/opendc-web-ui/src/config.js | 40 - opendc-web/opendc-web-ui/src/containers/app/App.js | 132 + .../src/containers/app/map/MapStage.js | 4 +- .../app/sidebars/project/PortfolioListContainer.js | 6 +- .../sidebars/project/ProjectSidebarContainer.js | 6 +- .../app/sidebars/project/TopologyListContainer.js | 8 +- .../opendc-web-ui/src/containers/auth/Login.js | 5 +- .../src/containers/modals/DeleteProfileModal.js | 27 - .../src/containers/modals/NewScenarioModal.js | 2 - opendc-web/opendc-web-ui/src/index.js | 29 - opendc-web/opendc-web-ui/src/index.scss | 2 +- opendc-web/opendc-web-ui/src/pages/404.js | 19 + opendc-web/opendc-web-ui/src/pages/404.module.scss | 8 + opendc-web/opendc-web-ui/src/pages/App.js | 103 - opendc-web/opendc-web-ui/src/pages/Home.js | 40 - .../opendc-web-ui/src/pages/Home.module.scss | 16 - opendc-web/opendc-web-ui/src/pages/NotFound.js | 15 - .../opendc-web-ui/src/pages/NotFound.module.scss | 8 - opendc-web/opendc-web-ui/src/pages/Profile.js | 31 - opendc-web/opendc-web-ui/src/pages/Projects.js | 30 - opendc-web/opendc-web-ui/src/pages/_app.js | 42 + opendc-web/opendc-web-ui/src/pages/_document.js | 96 + opendc-web/opendc-web-ui/src/pages/index.js | 42 + .../opendc-web-ui/src/pages/index.module.scss | 16 + opendc-web/opendc-web-ui/src/pages/profile.js | 54 + .../src/pages/projects/[project]/index.js | 37 + .../projects/[project]/portfolios/[portfolio].js | 37 + .../opendc-web-ui/src/pages/projects/index.js | 36 + opendc-web/opendc-web-ui/src/reducers/modals.js | 2 - opendc-web/opendc-web-ui/src/routes/index.js | 40 - .../opendc-web-ui/src/store/configure-store.js | 51 +- opendc-web/opendc-web-ui/src/util/hooks.js | 29 - opendc-web/opendc-web-ui/src/util/sidebar-space.js | 4 +- opendc-web/opendc-web-ui/yarn.lock | 12924 +++---------------- 52 files changed, 2721 insertions(+), 11680 deletions(-) delete mode 100644 opendc-web/opendc-web-ui/src/actions/modals/profile.js create mode 100644 opendc-web/opendc-web-ui/src/auth/hook.js delete mode 100644 opendc-web/opendc-web-ui/src/config.js create mode 100644 opendc-web/opendc-web-ui/src/containers/app/App.js delete mode 100644 opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js delete mode 100644 opendc-web/opendc-web-ui/src/index.js create mode 100644 opendc-web/opendc-web-ui/src/pages/404.js create mode 100644 opendc-web/opendc-web-ui/src/pages/404.module.scss delete mode 100644 opendc-web/opendc-web-ui/src/pages/App.js delete mode 100644 opendc-web/opendc-web-ui/src/pages/Home.js delete mode 100644 opendc-web/opendc-web-ui/src/pages/Home.module.scss delete mode 100644 opendc-web/opendc-web-ui/src/pages/NotFound.js delete mode 100644 opendc-web/opendc-web-ui/src/pages/NotFound.module.scss delete mode 100644 opendc-web/opendc-web-ui/src/pages/Profile.js delete mode 100644 opendc-web/opendc-web-ui/src/pages/Projects.js create mode 100644 opendc-web/opendc-web-ui/src/pages/_app.js create mode 100644 opendc-web/opendc-web-ui/src/pages/_document.js create mode 100644 opendc-web/opendc-web-ui/src/pages/index.js create mode 100644 opendc-web/opendc-web-ui/src/pages/index.module.scss create mode 100644 opendc-web/opendc-web-ui/src/pages/profile.js create mode 100644 opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js create mode 100644 opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js create mode 100644 opendc-web/opendc-web-ui/src/pages/projects/index.js delete mode 100644 opendc-web/opendc-web-ui/src/routes/index.js delete mode 100644 opendc-web/opendc-web-ui/src/util/hooks.js (limited to 'opendc-web') diff --git a/opendc-web/opendc-web-ui/.gitignore b/opendc-web/opendc-web-ui/.gitignore index 4fa931fe..3340b9ee 100644 --- a/opendc-web/opendc-web-ui/.gitignore +++ b/opendc-web/opendc-web-ui/.gitignore @@ -22,7 +22,9 @@ yarn-error.log* /.idea # Environment variables -.env +.env.local # Sass output *.css + +/.next diff --git a/opendc-web/opendc-web-ui/README.md b/opendc-web/opendc-web-ui/README.md index f3a58e7a..370c693d 100644 --- a/opendc-web/opendc-web-ui/README.md +++ b/opendc-web/opendc-web-ui/README.md @@ -7,16 +7,19 @@ Collaborative Datacenter Simulation and Exploration for Everybody

-The user-facing component of the OpenDC stack, allowing users to build and interact with their own (virtual) datacenters. Built in *React.js* and *Redux*, with the help of `create-react-app`. - +The user-facing component of the OpenDC stack, allowing users to build and interact with their own (virtual) +datacenters. Built in *React.js* and *Redux*, with the help of [Next.js](https://nextjs.org/). ## Get Up and Running -Looking for the full OpenDC stack? Check out [the main OpenDC repo](https://github.com/atlarge-research/opendc) for instructions on how to set up a Docker container with all of OpenDC, without the hassle of running each of the components manually. +Looking for the full OpenDC stack? Check out the [root](https://github.com/atlarge-research/opendc) for instructions on +how to set up a Docker container with all of OpenDC, without the hassle of running each of the components manually. ### Installation -To get started, you'll need the [Node.js environment](https://nodejs.org) and the [Yarn package manager](https://yarnpkg.com). Once you have those installed, run the following command from the root directory of this repo: +To get started, you'll need the [Node.js environment](https://nodejs.org) and +the [Yarn package manager](https://yarnpkg.com). Once you have those installed, run the following command from the root +directory of this repo: ```bash yarn @@ -24,17 +27,22 @@ yarn ### Running the development server -First, you need to have a Google OAuth client ID set up. Check the [documentation of the main OpenDC repo](https://github.com/atlarge-research/opendc) if you're not sure how to do this. Once you have such an ID, you need to set it as environment variable `REACT_APP_OAUTH_CLIENT_ID`. One way of doing this is to create an `.env` file with content `REACT_APP_OAUTH_CLIENT_ID=YOUR_ID` (`YOUR_ID` without quotes), in the root directory of this repo. +First, you need to have a Google OAuth client ID set up. Check +the [documentation of the main OpenDC repo](https://github.com/atlarge-research/opendc) if you're not sure how to do +this. Once you have such an ID, you need to set it as environment variable `NEXT_PUBLIC_OAUTH_CLIENT_ID`. One way of +doing this is to create an `.env.local` file with content `NEXT_PUBLIC_OAUTH_CLIENT_ID=YOUR_ID` (`YOUR_ID` without +quotes), in the root directory of this repo. Once you've set this variable, start the OpenDC `docker-compose` setup. See the root README for instructions on this. - + Now, you're ready to start the development server: ```bash -yarn start +yarn dev ``` -This will start a development server running on [`localhost:3000`](http://localhost:3000), watching for changes you make to the code and rebuilding automatically when you save changes. +This will start a development server running on [`localhost:3000`](http://localhost:3000), watching for changes you make +to the code and rebuilding automatically when you save changes. To compile everything for camera-ready deployment, use the following command: @@ -42,47 +50,62 @@ To compile everything for camera-ready deployment, use the following command: yarn build ``` - ## Architecture -The codebase follows a standard React.js structure, with static assets being contained in the `public` folder, while dynamic components and their styles are contained in `src`. The app uses client-side routing (with `react-router`), meaning that the only HTML file needing to be served is a `index.html` file. +The codebase follows a standard React.js structure, with static assets being contained in the `public` folder, while +dynamic components and their styles are contained in `src`. ### Pages -All pages are represented by a component in the `src/pages` directory. There are components for the following pages: +All pages are represented by a component in the `src/pages` directory, following +the [Next.js conventions](https://nextjs.org/docs/routing/introduction) for routing. There are components for the +following pages: -**Home.js** - Entry page (`/`) +**index.js** - Entry page (`/`) -**Projects.js** - Overview of projects of the user (`/projects`) +**projects/index.js** - Overview of projects of the user (`/projects`) -**App.js** - Main application, with datacenter construction and simulation UI (`/projects/:projectId` and `/projects/:projectId/portfolios/:portfolioId`) +**projects/[project]/index.js** - Main application, with datacenter construction and simulation UI (`/projects/:projectId` +and `/projects/:projectId/portfolios/:portfolioId`) -**Profile.js** - Profile of the current user (`/profile`) +**profile.js** - Profile of the current user (`/profile`) -**NotFound.js** - 404 page to appear when the route is invalid (`/*`) +**404.js** - 404 page to appear when the route is invalid (`/*`) ### Components & Containers -The building blocks of the UI are divided into so-called *components* and *containers* ([as encouraged](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) by the author of Redux). *Components* are considered 'pure', rendered as a function of input properties. *Containers*, on the other hand, are wrappers around *components*, injecting state through the properties of the components they wrap. +The building blocks of the UI are divided into so-called *components* and * +containers* ([as encouraged](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) by the author of +Redux). *Components* are considered 'pure', rendered as a function of input properties. *Containers*, on the other hand, +are wrappers around *components*, injecting state through the properties of the components they wrap. -Even the canvas (the main component of the app) is built using React components, with the help of the `react-konva` module. To illustrate: A rectangular object on the canvas is defined in a way that is not very different from how we define a standard `div` element on the splashpage. +Even the canvas (the main component of the app) is built using React components, with the help of the `react-konva` +module. To illustrate: A rectangular object on the canvas is defined in a way that is not very different from how we +define a standard `div` element on the splashpage. ### State Management -Almost all state is kept in a central Redux store. State is kept there in an immutable form, only to be modified through actions being dispatched. These actions are contained in the `src/actions` folder, and the reducers (managing how state is updated according to dispatched actions) are located in `src/reducers`. If you're not familiar with the Redux approach to state management, have a look at their [official documentation](https://redux.js.org/). +Almost all state is kept in a central Redux store. State is kept there in an immutable form, only to be modified through +actions being dispatched. These actions are contained in the `src/actions` folder, and the reducers (managing how state +is updated according to dispatched actions) are located in `src/reducers`. If you're not familiar with the Redux +approach to state management, have a look at their [official documentation](https://redux.js.org/). ### API Interaction -The web-app needs to pull data in from the API of a backend running on a server. The functions that call routes are located in `src/api`. The actual logic responsible for calling these functions is contained in `src/sagas`. These API fetch procedures are written with the help of `redux-saga`. The [official documentation](https://redux-saga.js.org/) of `redux-saga` can be a helpful aid in understanding that part of the codebase. - +The web-app needs to pull data in from the API of a backend running on a server. The functions that call routes are +located in `src/api`. The actual logic responsible for calling these functions is contained in `src/sagas`. These API +fetch procedures are written with the help of `redux-saga`. The [official documentation](https://redux-saga.js.org/) +of `redux-saga` can be a helpful aid in understanding that part of the codebase. ## Tests -Files containing tests can be recognized by the `.test.js` suffix. They are usually located right next to the source code they are testing, to make discovery easier. +Files containing tests can be recognized by the `.test.js` suffix. They are usually located right next to the source +code they are testing, to make discovery easier. ### Running all tests -The following command runs all tests in the codebase. On top of this, it also watches the code for changes and reruns the tests whenever any file is saved. +The following command runs all tests in the codebase. On top of this, it also watches the code for changes and reruns +the tests whenever any file is saved. ```bash yarn test diff --git a/opendc-web/opendc-web-ui/package.json b/opendc-web/opendc-web-ui/package.json index 61099709..f6917398 100644 --- a/opendc-web/opendc-web-ui/package.json +++ b/opendc-web/opendc-web-ui/package.json @@ -16,7 +16,6 @@ "author": "Georgios Andreadis (https://gandreadis.com/)", "license": "MIT", "private": true, - "proxy": "http://localhost:8081", "dependencies": { "@sentry/react": "^5.30.0", "@sentry/tracing": "^5.30.0", @@ -27,6 +26,7 @@ "konva": "~7.2.5", "lint-staged": "~10.2.2", "mathjs": "~7.6.0", + "next": "^10.2.0", "prettier": "~2.0.5", "prop-types": "~15.7.2", "react": "~17.0.2", @@ -36,12 +36,10 @@ "react-hotkeys": "^2.0.0", "react-konva": "~17.0.2-0", "react-redux": "~7.2.0", - "react-router-dom": "~5.1.2", - "react-scripts": "~4.0.3", "reactstrap": "^8.9.0", "recharts": "~2.0.9", "redux": "~4.0.5", - "redux-localstorage": "~0.4.1", + "redux-localstorage": "^0.4.1", "redux-logger": "~3.0.6", "redux-saga": "~1.1.3", "redux-thunk": "~2.3.0", @@ -58,10 +56,9 @@ "scripts": { "format": "prettier --write src", "precommit": "lint-staged", - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" + "dev": "next dev", + "build": "next build", + "start": "next start" }, "browserslist": { "production": [ diff --git a/opendc-web/opendc-web-ui/src/actions/map.js b/opendc-web/opendc-web-ui/src/actions/map.js index 0d49d849..09196dca 100644 --- a/opendc-web/opendc-web-ui/src/actions/map.js +++ b/opendc-web/opendc-web-ui/src/actions/map.js @@ -64,6 +64,7 @@ export function setMapPositionWithBoundsCheck(x, y) { const state = getState() const scaledMapSize = MAP_SIZE_IN_PIXELS * state.map.scale + const updatedX = x > 0 ? 0 diff --git a/opendc-web/opendc-web-ui/src/actions/modals/profile.js b/opendc-web/opendc-web-ui/src/actions/modals/profile.js deleted file mode 100644 index 39c72c03..00000000 --- a/opendc-web/opendc-web-ui/src/actions/modals/profile.js +++ /dev/null @@ -1,14 +0,0 @@ -export const OPEN_DELETE_PROFILE_MODAL = 'OPEN_DELETE_PROFILE_MODAL' -export const CLOSE_DELETE_PROFILE_MODAL = 'CLOSE_DELETE_PROFILE_MODAL' - -export function openDeleteProfileModal() { - return { - type: OPEN_DELETE_PROFILE_MODAL, - } -} - -export function closeDeleteProfileModal() { - return { - type: CLOSE_DELETE_PROFILE_MODAL, - } -} diff --git a/opendc-web/opendc-web-ui/src/api/index.js b/opendc-web/opendc-web-ui/src/api/index.js index e6528fd9..65358745 100644 --- a/opendc-web/opendc-web-ui/src/api/index.js +++ b/opendc-web/opendc-web-ui/src/api/index.js @@ -1,8 +1,35 @@ -import config from '../config' +/* + * Copyright (c) 2021 AtLarge Research + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + import { getAuthToken } from '../auth' -const apiUrl = config['API_BASE_URL'] +const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL +/** + * Send the specified request to the OpenDC API. + * @param path Relative path for the API. + * @param method The method to use for the request. + * @param body The body of the request. + */ export async function request(path, method = 'GET', body) { const res = await fetch(`${apiUrl}/v2/${path}`, { method: method, diff --git a/opendc-web/opendc-web-ui/src/api/routes/token-signin.js b/opendc-web/opendc-web-ui/src/api/routes/token-signin.js index ced5d2e0..1c285bdb 100644 --- a/opendc-web/opendc-web-ui/src/api/routes/token-signin.js +++ b/opendc-web/opendc-web-ui/src/api/routes/token-signin.js @@ -1,7 +1,5 @@ -import config from '../../config' - export function performTokenSignIn(token) { - const apiUrl = config['API_BASE_URL'] + const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL return fetch(`${apiUrl}/tokensignin`, { method: 'POST', diff --git a/opendc-web/opendc-web-ui/src/auth/hook.js b/opendc-web/opendc-web-ui/src/auth/hook.js new file mode 100644 index 00000000..ddaf53ed --- /dev/null +++ b/opendc-web/opendc-web-ui/src/auth/hook.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useEffect, useState } from 'react' +import { userIsLoggedIn } from './index' +import { useRouter } from 'next/router' + +export function useAuth() { + const [isLoggedIn, setLoggedIn] = useState(false) + + useEffect(() => { + setLoggedIn(userIsLoggedIn()) + }, []) + + return isLoggedIn +} + +export function useRequireAuth() { + const router = useRouter() + useEffect(() => { + if (!userIsLoggedIn()) { + router.replace('/') + } + }) +} diff --git a/opendc-web/opendc-web-ui/src/auth/index.js b/opendc-web/opendc-web-ui/src/auth/index.js index b5953990..148e2e13 100644 --- a/opendc-web/opendc-web-ui/src/auth/index.js +++ b/opendc-web/opendc-web-ui/src/auth/index.js @@ -2,7 +2,7 @@ import { LOG_IN_SUCCEEDED, LOG_OUT } from '../actions/auth' import { DELETE_CURRENT_USER_SUCCEEDED } from '../actions/users' const getAuthObject = () => { - const authItem = localStorage.getItem('auth') + const authItem = global.localStorage && localStorage.getItem('auth') if (!authItem || authItem === '{}') { return undefined } diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js index 7ca10792..dd32927f 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react' import { HotKeys } from 'react-hotkeys' import { Stage } from 'react-konva' import MapLayer from '../../../containers/app/map/layers/MapLayer' @@ -6,85 +6,75 @@ import ObjectHoverLayer from '../../../containers/app/map/layers/ObjectHoverLaye import RoomHoverLayer from '../../../containers/app/map/layers/RoomHoverLayer' import { NAVBAR_HEIGHT } from '../../navigation/Navbar' import { MAP_MOVE_PIXELS_PER_EVENT } from './MapConstants' -import { Provider } from 'react-redux' -import { store } from '../../../store/configure-store' +import { Provider, useStore } from 'react-redux' -class MapStageComponent extends React.Component { - state = { - mouseX: 0, - mouseY: 0, +function MapStageComponent({ + mapDimensions, + mapPosition, + setMapDimensions, + setMapPositionWithBoundsCheck, + zoomInOnPosition, +}) { + const [pos, setPos] = useState([0, 0]) + const stage = useRef(null) + const [x, y] = pos + const handlers = { + MOVE_LEFT: () => moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0), + MOVE_RIGHT: () => moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0), + MOVE_UP: () => moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT), + MOVE_DOWN: () => moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT), } - constructor(props) { - super(props) + const moveWithDelta = (deltaX, deltaY) => + setMapPositionWithBoundsCheck(mapPosition.x + deltaX, mapPosition.y + deltaY) + const updateMousePosition = () => { + if (!stage.current) { + return + } - this.updateDimensions = this.updateDimensions.bind(this) - this.updateScale = this.updateScale.bind(this) + const mousePos = stage.current.getStage().getPointerPosition() + setPos([mousePos.x, mousePos.y]) } + const updateDimensions = () => setMapDimensions(window.innerWidth, window.innerHeight - NAVBAR_HEIGHT) + const updateScale = (e) => zoomInOnPosition(e.deltaY < 0, x, y) - componentDidMount() { - this.updateDimensions() + useEffect(() => { + updateDimensions() - window.addEventListener('resize', this.updateDimensions) - window.addEventListener('wheel', this.updateScale) + window.addEventListener('resize', updateDimensions) + window.addEventListener('wheel', updateScale) window['exportCanvasToImage'] = () => { const download = document.createElement('a') - download.href = this.stage.getStage().toDataURL() + download.href = stage.current.getStage().toDataURL() download.download = 'opendc-canvas-export-' + Date.now() + '.png' download.click() } - } - - componentWillUnmount() { - window.removeEventListener('resize', this.updateDimensions) - window.removeEventListener('wheel', this.updateScale) - } - - updateDimensions() { - this.props.setMapDimensions(window.innerWidth, window.innerHeight - NAVBAR_HEIGHT) - } - - updateScale(e) { - this.props.zoomInOnPosition(e.deltaY < 0, this.state.mouseX, this.state.mouseY) - } - - updateMousePosition() { - const mousePos = this.stage.getStage().getPointerPosition() - this.setState({ mouseX: mousePos.x, mouseY: mousePos.y }) - } - handlers = { - MOVE_LEFT: () => this.moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0), - MOVE_RIGHT: () => this.moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0), - MOVE_UP: () => this.moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT), - MOVE_DOWN: () => this.moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT), - } + return () => { + window.removeEventListener('resize', updateDimensions) + window.removeEventListener('wheel', updateScale) + } + }, []) - moveWithDelta(deltaX, deltaY) { - this.props.setMapPositionWithBoundsCheck(this.props.mapPosition.x + deltaX, this.props.mapPosition.y + deltaY) - } + const store = useStore() - render() { - return ( - - { - this.stage = stage - }} - width={this.props.mapDimensions.width} - height={this.props.mapDimensions.height} - onMouseMove={this.updateMousePosition.bind(this)} - > - - - - - - - - ) - } + return ( + + + + + + + + + + ) } export default MapStageComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js index b000b9e2..b714a7d2 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types' import React from 'react' import Shapes from '../../../../shapes' -import { Link } from 'react-router-dom' +import Link from 'next/link' import FontAwesome from 'react-fontawesome' import ScenarioListContainer from '../../../../containers/app/sidebars/project/ScenarioListContainer' @@ -44,11 +44,12 @@ class PortfolioListComponent extends React.Component { {portfolio.name}
- this.props.onChoosePortfolio(portfolio._id)} - /> + + this.props.onChoosePortfolio(portfolio._id)} + /> + this.onDelete(portfolio._id)} diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js index e775a663..4efa99ef 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types' import React from 'react' import Shapes from '../../../../shapes' -import { Link } from 'react-router-dom' +import Link from 'next/link' import FontAwesome from 'react-fontawesome' class ScenarioListComponent extends React.Component { @@ -34,10 +34,13 @@ class ScenarioListComponent extends React.Component {
this.props.onChooseScenario(scenario.portfolioId, scenario._id)} - /> + href={`/projects/${this.props.currentProjectId}/portfolios/${scenario.portfolioId}/scenarios/${scenario._id}`} + > + this.props.onChooseScenario(scenario.portfolioId, scenario._id)} + /> + (idx !== 0 ? this.onDelete(scenario._id) : undefined)} diff --git a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js b/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js index 589047dc..5a95810a 100644 --- a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js +++ b/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js @@ -2,36 +2,26 @@ import PropTypes from 'prop-types' import React from 'react' import Modal from './Modal' -class ConfirmationModal extends React.Component { - static propTypes = { - title: PropTypes.string.isRequired, - message: PropTypes.string.isRequired, - show: PropTypes.bool.isRequired, - callback: PropTypes.func.isRequired, - } - - onConfirm() { - this.props.callback(true) - } - - onCancel() { - this.props.callback(false) - } +function ConfirmationModal({ title, message, show, callback }) { + return ( + callback(true)} + onCancel={() => callback(false)} + submitButtonType="danger" + submitButtonText="Confirm" + > + {message} + + ) +} - render() { - return ( - - {this.props.message} - - ) - } +ConfirmationModal.propTypes = { + title: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, + show: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, } export default ConfirmationModal diff --git a/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js b/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js index 8c28c542..7b1eaae2 100644 --- a/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js +++ b/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js @@ -1,6 +1,6 @@ import React from 'react' import FontAwesome from 'react-fontawesome' -import { Link } from 'react-router-dom' +import Link from 'next/link' import { NavLink } from 'reactstrap' import Navbar, { NavItem } from './Navbar' import {} from './Navbar.module.scss' @@ -8,16 +8,20 @@ import {} from './Navbar.module.scss' const AppNavbarComponent = ({ project, fullWidth }) => ( - - - My Projects - + + + + My Projects + + {project ? ( - - {project.name} - + + + {project.name} + + ) : undefined} diff --git a/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js b/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js index 78b02b44..0c0feeb1 100644 --- a/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js +++ b/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js @@ -1,11 +1,10 @@ import PropTypes from 'prop-types' import React from 'react' import FontAwesome from 'react-fontawesome' -import { Link } from 'react-router-dom' import { NavLink } from 'reactstrap' const LogoutButton = ({ onLogout }) => ( - + ) diff --git a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js index bf18f1c4..90f55665 100644 --- a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js +++ b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js @@ -1,5 +1,6 @@ import React, { useState } from 'react' -import { Link, useLocation } from 'react-router-dom' +import Link from 'next/link' +import { useRouter } from 'next/router' import { Navbar as RNavbar, NavItem as RNavItem, @@ -15,6 +16,7 @@ import Login from '../../containers/auth/Login' import Logout from '../../containers/auth/Logout' import ProfileName from '../../containers/auth/ProfileName' import { login, navbar, opendcBrand } from './Navbar.module.scss' +import { useAuth } from '../../auth/hook' export const NAVBAR_HEIGHT = 60 @@ -24,32 +26,45 @@ const GitHubLink = () => ( className="ml-2 mr-3 text-dark" style={{ position: 'relative', top: 7 }} > - + ) export const NavItem = ({ route, children }) => { - const location = useLocation() - return {children} + const router = useRouter() + const handleClick = (e) => { + e.preventDefault() + router.push(route) + } + return ( + + {children} + + ) } export const LoggedInSection = () => { - const location = useLocation() + const router = useRouter() + const isLoggedIn = useAuth() return ( ) @@ -74,7 +89,7 @@ const Navbar = ({ fullWidth, children }) => { - + OpenDC diff --git a/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js b/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js index b38fc183..28fd81e9 100644 --- a/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js +++ b/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js @@ -1,5 +1,5 @@ import React from 'react' -import { Link } from 'react-router-dom' +import Link from 'next/link' import BlinkingCursor from './BlinkingCursor' import CodeBlock from './CodeBlock' import { terminalWindow, terminalHeader, terminalBody, segfault, subTitle, homeBtn } from './TerminalWindow.module.scss' @@ -23,8 +23,10 @@ const TerminalWindow = () => ( Got lost?
- - GET ME BACK TO OPENDC + + + GET ME BACK TO OPENDC + diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js index 1c76cc7f..48cce019 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js @@ -1,11 +1,13 @@ import PropTypes from 'prop-types' import React from 'react' -import { Link } from 'react-router-dom' +import Link from 'next/link' const ProjectActionButtons = ({ projectId, onViewUsers, onDelete }) => ( - - + + + +
{ + useRequireAuth() + + const projectName = useSelector( + (state) => + state.currentProjectId !== '-1' && + state.objects.project[state.currentProjectId] && + state.objects.project[state.currentProjectId].name + ) + const topologyIsLoading = useSelector((state) => state.currentTopologyIdd === '-1') + + const dispatch = useDispatch() + useEffect(() => { + if (scenarioId) { + dispatch(openScenarioSucceeded(projectId, portfolioId, scenarioId)) + } else if (portfolioId) { + dispatch(openPortfolioSucceeded(projectId, portfolioId)) + } else { + dispatch(openProjectSucceeded(projectId)) + } + }, [projectId, portfolioId, scenarioId, dispatch]) + + const constructionElements = topologyIsLoading ? ( +
+ +
+ ) : ( +
+ + + + + +
+ ) + + const portfolioElements = ( +
+ +
+ +
+
+ ) + + const scenarioElements = ( +
+ +
+

Scenario loading

+
+
+ ) + + const title = projectName ? projectName + ' - OpenDC' : 'Simulation - OpenDC' + + return ( + + + {title} + + + {scenarioId ? scenarioElements : portfolioId ? portfolioElements : constructionElements} + + + + + + + + + + ) +} + +App.propTypes = { + projectId: PropTypes.string.isRequired, + portfolioId: PropTypes.string, + scenarioId: PropTypes.string, +} + +export default App diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js b/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js index 61d123e8..9394238d 100644 --- a/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js +++ b/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js @@ -4,11 +4,13 @@ import { setMapDimensions, setMapPositionWithBoundsCheck, zoomInOnPosition } fro import MapStageComponent from '../../../components/app/map/MapStageComponent' const MapStage = () => { - const { position, dimensions } = useSelector((state) => state.map) + const position = useSelector((state) => state.map.position) + const dimensions = useSelector((state) => state.map.dimensions) const dispatch = useDispatch() const zoomInOnPositionA = (zoomIn, x, y) => dispatch(zoomInOnPosition(zoomIn, x, y)) const setMapPositionWithBoundsCheckA = (x, y) => dispatch(setMapPositionWithBoundsCheck(x, y)) const setMapDimensionsA = (width, height) => dispatch(setMapDimensions(width, height)) + return ( { }) const dispatch = useDispatch() - const history = useHistory() + const router = useRouter() const actions = { onNewPortfolio: () => { dispatch(openNewPortfolioModal()) @@ -37,7 +37,7 @@ const PortfolioListContainer = (props) => { const state = await getState(dispatch) dispatch(deletePortfolio(id)) dispatch(setCurrentTopology(state.objects.project[state.currentProjectId].topologyIds[0])) - history.push(`/projects/${state.currentProjectId}`) + router.push(`/projects/${state.currentProjectId}`) } }, } diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js index 35e6c52b..06c7f0f7 100644 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js +++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js @@ -1,11 +1,11 @@ import React from 'react' -import { useLocation } from 'react-router-dom' +import { useRouter } from 'next/router' import ProjectSidebarComponent from '../../../../components/app/sidebars/project/ProjectSidebarComponent' import { isCollapsible } from '../../../../util/sidebar-space' const ProjectSidebarContainer = (props) => { - const location = useLocation() - return + const router = useRouter() + return } export default ProjectSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js index 954284a6..e9c05f17 100644 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js +++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js @@ -3,13 +3,13 @@ import { useDispatch, useSelector } from 'react-redux' import TopologyListComponent from '../../../../components/app/sidebars/project/TopologyListComponent' import { setCurrentTopology } from '../../../../actions/topology/building' import { openNewTopologyModal } from '../../../../actions/modals/topology' -import { useHistory } from 'react-router-dom' +import { useRouter } from 'next/router' import { getState } from '../../../../util/state-utils' import { deleteTopology } from '../../../../actions/topologies' const TopologyListContainer = () => { const dispatch = useDispatch() - const history = useHistory() + const router = useRouter() const topologies = useSelector((state) => { let topologies = state.objects.project[state.currentProjectId] @@ -26,7 +26,7 @@ const TopologyListContainer = () => { const onChooseTopology = async (id) => { dispatch(setCurrentTopology(id)) const state = await getState(dispatch) - history.push(`/projects/${state.currentProjectId}`) + router.push(`/projects/${state.currentProjectId}`) } const onNewTopology = () => { dispatch(openNewTopologyModal()) @@ -36,7 +36,7 @@ const TopologyListContainer = () => { const state = await getState(dispatch) dispatch(deleteTopology(id)) dispatch(setCurrentTopology(state.objects.project[state.currentProjectId].topologyIds[0])) - history.push(`/projects/${state.currentProjectId}`) + router.push(`/projects/${state.currentProjectId}`) } } diff --git a/opendc-web/opendc-web-ui/src/containers/auth/Login.js b/opendc-web/opendc-web-ui/src/containers/auth/Login.js index f652429d..178f269e 100644 --- a/opendc-web/opendc-web-ui/src/containers/auth/Login.js +++ b/opendc-web/opendc-web-ui/src/containers/auth/Login.js @@ -3,7 +3,6 @@ import GoogleLogin from 'react-google-login' import { useDispatch } from 'react-redux' import { logIn } from '../../actions/auth' import { Button } from 'reactstrap' -import config from '../../config' function Login({ visible, className }) { const dispatch = useDispatch() @@ -30,12 +29,12 @@ function Login({ visible, className }) { return ( ( )} /> diff --git a/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js deleted file mode 100644 index 93a38642..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { closeDeleteProfileModal } from '../../actions/modals/profile' -import { deleteCurrentUser } from '../../actions/users' -import ConfirmationModal from '../../components/modals/ConfirmationModal' - -const DeleteProfileModal = () => { - const visible = useSelector((state) => state.modals.deleteProfileModalVisible) - - const dispatch = useDispatch() - const callback = (isConfirmed) => { - if (isConfirmed) { - dispatch(deleteCurrentUser()) - } - dispatch(closeDeleteProfileModal()) - } - return ( - - ) -} - -export default DeleteProfileModal diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js index b588b4bc..18ad65f9 100644 --- a/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js +++ b/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js @@ -6,8 +6,6 @@ import { closeNewScenarioModal } from '../../actions/modals/scenarios' const NewScenarioModal = (props) => { const topologies = useSelector(({ currentProjectId, objects }) => { - console.log(currentProjectId, objects) - if (currentProjectId === '-1' || !objects.project[currentProjectId]) { return [] } diff --git a/opendc-web/opendc-web-ui/src/index.js b/opendc-web/opendc-web-ui/src/index.js deleted file mode 100644 index fdfec24b..00000000 --- a/opendc-web/opendc-web-ui/src/index.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom' -import * as Sentry from '@sentry/react' -import { Integrations } from '@sentry/tracing' -import { Provider } from 'react-redux' -import './index.scss' -import Routes from './routes' -import config from './config' -import configureStore from './store/configure-store' - -const store = configureStore() - -// Initialize Sentry if the user has configured a DSN -const dsn = config['SENTRY_DSN'] -if (dsn) { - Sentry.init({ - environment: process.env.NODE_ENV, - dsn: dsn, - integrations: [new Integrations.BrowserTracing()], - tracesSampleRate: 0.1, - }) -} - -ReactDOM.render( - - - , - document.getElementById('root') -) diff --git a/opendc-web/opendc-web-ui/src/index.scss b/opendc-web/opendc-web-ui/src/index.scss index 0c1ddff4..1a4d1303 100644 --- a/opendc-web/opendc-web-ui/src/index.scss +++ b/opendc-web/opendc-web-ui/src/index.scss @@ -5,7 +5,7 @@ html, body, -#root { +#__next { margin: 0; padding: 0; width: 100%; diff --git a/opendc-web/opendc-web-ui/src/pages/404.js b/opendc-web/opendc-web-ui/src/pages/404.js new file mode 100644 index 00000000..cc9281fc --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/404.js @@ -0,0 +1,19 @@ +import React from 'react' +import Head from 'next/head' +import TerminalWindow from '../components/not-found/TerminalWindow' +import style from './404.module.scss' + +const NotFound = () => { + return ( + <> + + Page Not Found - OpenDC + +
+ +
+ + ) +} + +export default NotFound diff --git a/opendc-web/opendc-web-ui/src/pages/404.module.scss b/opendc-web/opendc-web-ui/src/pages/404.module.scss new file mode 100644 index 00000000..e91c2780 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/404.module.scss @@ -0,0 +1,8 @@ +.not-found-backdrop { + display: flex; + + width: 100%; + height: 100%; + + background-image: linear-gradient(135deg, #00678a, #008fbf, #00a6d6); +} diff --git a/opendc-web/opendc-web-ui/src/pages/App.js b/opendc-web/opendc-web-ui/src/pages/App.js deleted file mode 100644 index ea62e8dc..00000000 --- a/opendc-web/opendc-web-ui/src/pages/App.js +++ /dev/null @@ -1,103 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useEffect } from 'react' -import { HotKeys } from 'react-hotkeys' -import { useDispatch, useSelector } from 'react-redux' -import { openPortfolioSucceeded } from '../actions/portfolios' -import { openProjectSucceeded } from '../actions/projects' -import ToolPanelComponent from '../components/app/map/controls/ToolPanelComponent' -import LoadingScreen from '../components/app/map/LoadingScreen' -import ScaleIndicatorContainer from '../containers/app/map/controls/ScaleIndicatorContainer' -import MapStage from '../containers/app/map/MapStage' -import TopologySidebarContainer from '../containers/app/sidebars/topology/TopologySidebarContainer' -import DeleteMachineModal from '../containers/modals/DeleteMachineModal' -import DeleteRackModal from '../containers/modals/DeleteRackModal' -import DeleteRoomModal from '../containers/modals/DeleteRoomModal' -import EditRackNameModal from '../containers/modals/EditRackNameModal' -import EditRoomNameModal from '../containers/modals/EditRoomNameModal' -import NewTopologyModal from '../containers/modals/NewTopologyModal' -import AppNavbarContainer from '../containers/navigation/AppNavbarContainer' -import ProjectSidebarContainer from '../containers/app/sidebars/project/ProjectSidebarContainer' -import { openScenarioSucceeded } from '../actions/scenarios' -import NewPortfolioModal from '../containers/modals/NewPortfolioModal' -import NewScenarioModal from '../containers/modals/NewScenarioModal' -import PortfolioResultsContainer from '../containers/app/results/PortfolioResultsContainer' -import KeymapConfiguration from '../shortcuts/keymap' -import { useDocumentTitle } from '../util/hooks' - -const App = ({ projectId, portfolioId, scenarioId }) => { - const projectName = useSelector( - (state) => - state.currentProjectId !== '-1' && - state.objects.project[state.currentProjectId] && - state.objects.project[state.currentProjectId].name - ) - const topologyIsLoading = useSelector((state) => state.currentTopologyId === '-1') - - const dispatch = useDispatch() - useEffect(() => { - if (scenarioId) { - dispatch(openScenarioSucceeded(projectId, portfolioId, scenarioId)) - } else if (portfolioId) { - dispatch(openPortfolioSucceeded(projectId, portfolioId)) - } else { - dispatch(openProjectSucceeded(projectId)) - } - }, [projectId, portfolioId, scenarioId, dispatch]) - - const constructionElements = topologyIsLoading ? ( -
- -
- ) : ( -
- - - - - -
- ) - - const portfolioElements = ( -
- -
- -
-
- ) - - const scenarioElements = ( -
- -
-

Scenario loading

-
-
- ) - - useDocumentTitle(projectName ? projectName + ' - OpenDC' : 'Simulation - OpenDC') - - return ( - - - {scenarioId ? scenarioElements : portfolioId ? portfolioElements : constructionElements} - - - - - - - - - - ) -} - -App.propTypes = { - projectId: PropTypes.string.isRequired, - portfolioId: PropTypes.string, - scenarioId: PropTypes.string, -} - -export default App diff --git a/opendc-web/opendc-web-ui/src/pages/Home.js b/opendc-web/opendc-web-ui/src/pages/Home.js deleted file mode 100644 index ee930fbe..00000000 --- a/opendc-web/opendc-web-ui/src/pages/Home.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react' -import ContactSection from '../components/home/ContactSection' -import IntroSection from '../components/home/IntroSection' -import JumbotronHeader from '../components/home/JumbotronHeader' -import ModelingSection from '../components/home/ModelingSection' -import SimulationSection from '../components/home/SimulationSection' -import StakeholderSection from '../components/home/StakeholderSection' -import TeamSection from '../components/home/TeamSection' -import TechnologiesSection from '../components/home/TechnologiesSection' -import HomeNavbar from '../components/navigation/HomeNavbar' -import { - introSection, - stakeholderSection, - modelingSection, - simulationSection, - technologiesSection, - teamSection, -} from './Home.module.scss' -import { useDocumentTitle } from '../util/hooks' - -function Home() { - useDocumentTitle('OpenDC') - return ( -
- -
- - - - - - - - -
-
- ) -} - -export default Home diff --git a/opendc-web/opendc-web-ui/src/pages/Home.module.scss b/opendc-web/opendc-web-ui/src/pages/Home.module.scss deleted file mode 100644 index aed1d88f..00000000 --- a/opendc-web/opendc-web-ui/src/pages/Home.module.scss +++ /dev/null @@ -1,16 +0,0 @@ -.bodyWrapper { - position: relative; - overflow-y: hidden; -} - -.introSection, -.modelingSection, -.technologiesSection { - background-color: #fff; -} - -.stakeholderSection, -.simulationSection, -.teamSection { - background-color: #f2f2f2; -} diff --git a/opendc-web/opendc-web-ui/src/pages/NotFound.js b/opendc-web/opendc-web-ui/src/pages/NotFound.js deleted file mode 100644 index 409ffa0e..00000000 --- a/opendc-web/opendc-web-ui/src/pages/NotFound.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import TerminalWindow from '../components/not-found/TerminalWindow' -import style from './NotFound.module.scss' -import { useDocumentTitle } from '../util/hooks' - -const NotFound = () => { - useDocumentTitle('Page Not Found - OpenDC') - return ( -
- -
- ) -} - -export default NotFound diff --git a/opendc-web/opendc-web-ui/src/pages/NotFound.module.scss b/opendc-web/opendc-web-ui/src/pages/NotFound.module.scss deleted file mode 100644 index e91c2780..00000000 --- a/opendc-web/opendc-web-ui/src/pages/NotFound.module.scss +++ /dev/null @@ -1,8 +0,0 @@ -.not-found-backdrop { - display: flex; - - width: 100%; - height: 100%; - - background-image: linear-gradient(135deg, #00678a, #008fbf, #00a6d6); -} diff --git a/opendc-web/opendc-web-ui/src/pages/Profile.js b/opendc-web/opendc-web-ui/src/pages/Profile.js deleted file mode 100644 index ea781686..00000000 --- a/opendc-web/opendc-web-ui/src/pages/Profile.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react' -import { useDispatch } from 'react-redux' -import { openDeleteProfileModal } from '../actions/modals/profile' -import DeleteProfileModal from '../containers/modals/DeleteProfileModal' -import AppNavbarContainer from '../containers/navigation/AppNavbarContainer' -import { useDocumentTitle } from '../util/hooks' - -const Profile = () => { - const dispatch = useDispatch() - const onDelete = () => dispatch(openDeleteProfileModal()) - - useDocumentTitle('My Profile - OpenDC') - return ( -
- -
- -

- This does not delete your Google account, but simply disconnects it from the OpenDC platform and - deletes any project info that is associated with you (projects you own and any authorizations you - may have on other projects). -

-
- -
- ) -} - -export default Profile diff --git a/opendc-web/opendc-web-ui/src/pages/Projects.js b/opendc-web/opendc-web-ui/src/pages/Projects.js deleted file mode 100644 index 5e642a03..00000000 --- a/opendc-web/opendc-web-ui/src/pages/Projects.js +++ /dev/null @@ -1,30 +0,0 @@ -import React, { useEffect } from 'react' -import { useDispatch } from 'react-redux' -import { fetchAuthorizationsOfCurrentUser } from '../actions/users' -import ProjectFilterPanel from '../components/projects/FilterPanel' -import NewProjectModal from '../containers/modals/NewProjectModal' -import NewProjectButtonContainer from '../containers/projects/NewProjectButtonContainer' -import VisibleProjectList from '../containers/projects/VisibleProjectAuthList' -import AppNavbarContainer from '../containers/navigation/AppNavbarContainer' -import { useDocumentTitle } from '../util/hooks' - -function Projects() { - const dispatch = useDispatch() - - useEffect(() => dispatch(fetchAuthorizationsOfCurrentUser())) - useDocumentTitle('My Projects - OpenDC') - - return ( -
- -
- - - -
- -
- ) -} - -export default Projects diff --git a/opendc-web/opendc-web-ui/src/pages/_app.js b/opendc-web/opendc-web-ui/src/pages/_app.js new file mode 100644 index 00000000..77ffa698 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/_app.js @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import Head from 'next/head' +import { Provider } from 'react-redux' +import { useStore } from '../store/configure-store' +import '../index.scss' + +export default function App({ Component, pageProps }) { + const store = useStore(pageProps.initialReduxState) + + return ( + <> + + + + + + + + + ) +} diff --git a/opendc-web/opendc-web-ui/src/pages/_document.js b/opendc-web/opendc-web-ui/src/pages/_document.js new file mode 100644 index 00000000..943ae395 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/_document.js @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import Document, { Html, Head, Main, NextScript } from 'next/document' + +class OpenDCDocument extends Document { + render() { + return ( + + + + + + + + + + + {/* Twitter Card data */} + + + + + + + + {/* OpenGraph meta tags */} + + + + + + + + + {/* CDN Dependencies */} + +