From 1b461d33cf588f20fd1a9760bd4f31a774cda9cd Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Sat, 24 Aug 2019 13:28:21 +0200 Subject: [PATCH 01/10] Do not set prevPath if navigation passes through skipSavingPath flag --- client/modules/App/App.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/modules/App/App.jsx b/client/modules/App/App.jsx index 2678de7e..9a8e6bdb 100644 --- a/client/modules/App/App.jsx +++ b/client/modules/App/App.jsx @@ -18,7 +18,10 @@ class App extends React.Component { } componentWillReceiveProps(nextProps) { - if (nextProps.location !== this.props.location) { + const locationWillChange = nextProps.location !== this.props.location; + const shouldSkipRemembering = nextProps.location.state && nextProps.location.state.skipSavingPath === true; + + if (locationWillChange && !shouldSkipRemembering) { this.props.setPreviousPath(this.props.location.pathname); } } From 0193ee82979d689f63abf2d7e6ef42d0a5799f1f Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Sun, 11 Aug 2019 11:08:17 +0200 Subject: [PATCH 02/10] Standalone sketch list and asset page --- client/modules/IDE/pages/IDEView.jsx | 26 ---- .../User/components/DashboardTabSwitcher.jsx | 47 +++++++ client/modules/User/pages/DashboardView.jsx | 123 ++++++++++++++++++ client/routes.jsx | 7 +- client/styles/components/_asset-list.scss | 3 +- .../styles/components/_dashboard-header.scss | 53 ++++++++ client/styles/components/_sketch-list.scss | 2 +- client/styles/layout/_dashboard.scss | 9 ++ client/styles/main.scss | 2 + 9 files changed, 241 insertions(+), 31 deletions(-) create mode 100644 client/modules/User/components/DashboardTabSwitcher.jsx create mode 100644 client/modules/User/pages/DashboardView.jsx create mode 100644 client/styles/components/_dashboard-header.scss create mode 100644 client/styles/layout/_dashboard.scss diff --git a/client/modules/IDE/pages/IDEView.jsx b/client/modules/IDE/pages/IDEView.jsx index b2ae1ebb..b0be087e 100644 --- a/client/modules/IDE/pages/IDEView.jsx +++ b/client/modules/IDE/pages/IDEView.jsx @@ -29,8 +29,6 @@ import * as ToastActions from '../actions/toast'; import * as ConsoleActions from '../actions/console'; import { getHTMLFile } from '../reducers/files'; import Overlay from '../../App/components/Overlay'; -import SketchList from '../components/SketchList'; -import AssetList from '../components/AssetList'; import About from '../components/About'; import Feedback from '../components/Feedback'; @@ -365,30 +363,6 @@ class IDEView extends React.Component { createFolder={this.props.createFolder} /> } - { this.props.location.pathname.match(/sketches$/) && - - - - } - { this.props.location.pathname.match(/assets$/) && - - - - } { this.props.location.pathname === '/about' && { + const selectedClassName = 'dashboard-header__tab--selected'; + + const location = { pathname: to, state: { skipSavingPath: true } }; + const content = isSelected ? children : {children}; + return ( +
  • +

    + {content} +

    +
  • + ); +}; + +Tab.propTypes = { + children: PropTypes.element.isRequired, + isSelected: PropTypes.bool.isRequired, + to: PropTypes.string.isRequired, +}; + +const DashboardTabSwitcher = ({ currentTab, isOwner, username }) => { + return ( +
      +
      + Sketches + {isOwner && Assets} +
      +
    + ); +}; + +DashboardTabSwitcher.propTypes = { + currentTab: PropTypes.string.isRequired, + isOwner: PropTypes.bool.isRequired, + username: PropTypes.string.isRequired, +}; + +export { DashboardTabSwitcher as default, TabKey }; diff --git a/client/modules/User/pages/DashboardView.jsx b/client/modules/User/pages/DashboardView.jsx new file mode 100644 index 00000000..a3172eee --- /dev/null +++ b/client/modules/User/pages/DashboardView.jsx @@ -0,0 +1,123 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { browserHistory, Link } from 'react-router'; +import { Helmet } from 'react-helmet'; +import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions'; +import NavBasic from '../../../components/NavBasic'; + +import AssetList from '../../IDE/components/AssetList'; +import SketchList from '../../IDE/components/SketchList'; + +import DashboardTabSwitcher, { TabKey } from '../components/DashboardTabSwitcher'; + +class DashboardView extends React.Component { + static defaultProps = { + user: null, + }; + + constructor(props) { + super(props); + this.closeAccountPage = this.closeAccountPage.bind(this); + this.gotoHomePage = this.gotoHomePage.bind(this); + } + + componentDidMount() { + document.body.className = this.props.theme; + } + + closeAccountPage() { + browserHistory.push(this.props.previousPath); + } + + gotoHomePage() { + browserHistory.push('/'); + } + + selectedTabName() { + const path = this.props.location.pathname; + + if (/assets/.test(path)) { + return TabKey.assets; + } + + return TabKey.sketches; + } + + ownerName() { + if (this.props.params.username) { + return this.props.params.username; + } + + return this.props.user.username; + } + + isOwner() { + return this.props.user.username === this.props.params.username; + } + + navigationItem() { + + } + + render() { + const currentTab = this.selectedTabName(); + const isOwner = this.isOwner(); + const { username } = this.props.params; + + return ( +
    + + p5.js Web Editor | Account + + + + +
    +
    +

    {this.ownerName()}

    + + +
    + +
    + { + currentTab === TabKey.sketches ? : + } +
    +
    +
    + ); + } +} + +function mapStateToProps(state) { + return { + previousPath: state.ide.previousPath, + user: state.user, + theme: state.preferences.theme + }; +} + +function mapDispatchToProps(dispatch) { + return bindActionCreators({ + updateSettings, initiateVerification, createApiKey, removeApiKey + }, dispatch); +} + +DashboardView.propTypes = { + location: PropTypes.shape({ + pathname: PropTypes.string.isRequired, + }).isRequired, + params: PropTypes.shape({ + username: PropTypes.string.isRequired, + }).isRequired, + previousPath: PropTypes.string.isRequired, + theme: PropTypes.string.isRequired, + user: PropTypes.shape({ + username: PropTypes.string.isRequired, + }), +}; + +export default connect(mapStateToProps, mapDispatchToProps)(DashboardView); diff --git a/client/routes.jsx b/client/routes.jsx index baa89884..0bcad82b 100644 --- a/client/routes.jsx +++ b/client/routes.jsx @@ -9,6 +9,7 @@ import ResetPasswordView from './modules/User/pages/ResetPasswordView'; import EmailVerificationView from './modules/User/pages/EmailVerificationView'; import NewPasswordView from './modules/User/pages/NewPasswordView'; import AccountView from './modules/User/pages/AccountView'; +import DashboardView from './modules/User/pages/DashboardView'; // import SketchListView from './modules/Sketch/pages/SketchListView'; import { getUser } from './modules/User/actions'; import { stopSketch } from './modules/IDE/actions/ide'; @@ -35,11 +36,11 @@ const routes = store => ( - - + + - + diff --git a/client/styles/components/_asset-list.scss b/client/styles/components/_asset-list.scss index a8e76c74..af1a173b 100644 --- a/client/styles/components/_asset-list.scss +++ b/client/styles/components/_asset-list.scss @@ -8,7 +8,7 @@ .asset-table { width: 100%; - padding: #{10 / $base-font-size}rem #{20 / $base-font-size}rem; + padding: #{10 / $base-font-size}rem 0; max-height: 100%; border-spacing: 0; & .asset-list__delete-column { @@ -53,4 +53,5 @@ .asset-table__empty { text-align: center; font-size: #{16 / $base-font-size}rem; + padding: #{42 / $base-font-size}rem 0; } diff --git a/client/styles/components/_dashboard-header.scss b/client/styles/components/_dashboard-header.scss new file mode 100644 index 00000000..96e4de22 --- /dev/null +++ b/client/styles/components/_dashboard-header.scss @@ -0,0 +1,53 @@ +.dashboard-header { + padding: 24px 66px; +} + +.dashboard-header__tabs { + display: flex; + padding-top: #{24 / $base-font-size}rem; + padding-bottom: #{24 / $base-font-size}rem; +} + +.dashboard-header__tab { + @include themify() { + color: getThemifyVariable('inactive-text-color'); + border-right: 2px solid getThemifyVariable('inactive-text-color'); + + padding: 0 13px; + + &:hover, &:focus { + color: getThemifyVariable('primary-text-color'); + cursor: pointer; + } + &:focus { + color: getThemifyVariable('primary-text-color'); + cursor: pointer; + } + } + + font-size: #{21 / $base-font-size}rem; +} + + +.dashboard-header__tab:first-child { + padding-left: 0; +} + +.dashboard-header__tab:last-child { + border-right: none; +} + +.dashboard-header__tab--selected { + @include themify() { + color: getThemifyVariable('primary-text-color'); + } + cursor: auto; +} + +.dashboard-header__tab a { + color: inherit; +} + +.dashboard-header__tab__title { + margin: 0; +} diff --git a/client/styles/components/_sketch-list.scss b/client/styles/components/_sketch-list.scss index dca1d759..34c962a4 100644 --- a/client/styles/components/_sketch-list.scss +++ b/client/styles/components/_sketch-list.scss @@ -7,7 +7,6 @@ .sketches-table { width: 100%; - padding: #{10 / $base-font-size}rem #{20 / $base-font-size}rem; max-height: 100%; border-spacing: 0; & .sketch-list__dropdown-column { @@ -106,4 +105,5 @@ .sketches-table__empty { text-align: center; font-size: #{16 / $base-font-size}rem; + padding: #{42 / $base-font-size}rem 0; } diff --git a/client/styles/layout/_dashboard.scss b/client/styles/layout/_dashboard.scss new file mode 100644 index 00000000..f928ffd3 --- /dev/null +++ b/client/styles/layout/_dashboard.scss @@ -0,0 +1,9 @@ +.dashboard { + display: flex; + flex-direction: column; + flex-wrap: wrap; + @include themify() { + color: getThemifyVariable('primary-text-color'); + background-color: getThemifyVariable('background-color'); + } +} diff --git a/client/styles/main.scss b/client/styles/main.scss index 62b8d4cc..decb6aba 100644 --- a/client/styles/main.scss +++ b/client/styles/main.scss @@ -46,7 +46,9 @@ @import 'components/loader'; @import 'components/uploader'; @import 'components/tabs'; +@import 'components/dashboard-header'; +@import 'layout/dashboard'; @import 'layout/ide'; @import 'layout/fullscreen'; @import 'layout/user'; From a0a13ab7fc5ccb1bea542993aa03f9ba763564b7 Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Mon, 19 Aug 2019 21:12:09 +0200 Subject: [PATCH 03/10] Flexible table width --- client/styles/components/_asset-list.scss | 1 - client/styles/components/_sketch-list.scss | 1 - 2 files changed, 2 deletions(-) diff --git a/client/styles/components/_asset-list.scss b/client/styles/components/_asset-list.scss index af1a173b..560ad944 100644 --- a/client/styles/components/_asset-list.scss +++ b/client/styles/components/_asset-list.scss @@ -2,7 +2,6 @@ // flex: 1 1 0%; overflow-y: scroll; max-width: 100%; - width: #{1000 / $base-font-size}rem; min-height: #{400 / $base-font-size}rem; } diff --git a/client/styles/components/_sketch-list.scss b/client/styles/components/_sketch-list.scss index 34c962a4..3dbde984 100644 --- a/client/styles/components/_sketch-list.scss +++ b/client/styles/components/_sketch-list.scss @@ -1,7 +1,6 @@ .sketches-table-container { overflow-y: scroll; max-width: 100%; - width: #{1000 / $base-font-size}rem; min-height: #{400 / $base-font-size}rem; } From b1bfb91f805fe1c53fdc0f4f9a016d88764112a4 Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Sat, 24 Aug 2019 12:38:42 +0200 Subject: [PATCH 04/10] Serve assets from /:username/assets, redirecting old path --- client/components/Nav.jsx | 2 +- .../components/createRedirectWithUsername.jsx | 28 +++++++++++++++++++ client/routes.jsx | 8 ++++-- server/routes/server.routes.js | 10 +++++++ 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 client/components/createRedirectWithUsername.jsx diff --git a/client/components/Nav.jsx b/client/components/Nav.jsx index 253146b0..31b7e5b8 100644 --- a/client/components/Nav.jsx +++ b/client/components/Nav.jsx @@ -576,7 +576,7 @@ class Nav extends React.PureComponent {
  • { + React.useEffect(() => { + if (username == null) { + return; + } + + browserHistory.replace(url.replace(':username', username)); + }, [username]); + + return null; +}; + +function mapStateToProps(state) { + return { + username: state.user ? state.user.username : null, + }; +} + +const ConnectedRedirectToUser = connect(mapStateToProps)(RedirectToUser); + +const createRedirectWithUsername = (url) => (props) => ; + +export default createRedirectWithUsername; diff --git a/client/routes.jsx b/client/routes.jsx index 0bcad82b..89db6977 100644 --- a/client/routes.jsx +++ b/client/routes.jsx @@ -10,7 +10,7 @@ import EmailVerificationView from './modules/User/pages/EmailVerificationView'; import NewPasswordView from './modules/User/pages/NewPasswordView'; import AccountView from './modules/User/pages/AccountView'; import DashboardView from './modules/User/pages/DashboardView'; -// import SketchListView from './modules/Sketch/pages/SketchListView'; +import createRedirectWithUsername from './components/createRedirectWithUsername'; import { getUser } from './modules/User/actions'; import { stopSketch } from './modules/IDE/actions/ide'; @@ -36,11 +36,13 @@ const routes = store => ( - - + + + + diff --git a/server/routes/server.routes.js b/server/routes/server.routes.js index adade10d..41d7dc60 100644 --- a/server/routes/server.routes.js +++ b/server/routes/server.routes.js @@ -79,6 +79,16 @@ router.get('/assets', (req, res) => { } }); +router.get('/:username/assets', (req, res) => { + userExists(req.params.username, (exists) => { + const isLoggedInUser = req.user && req.user.username === req.params.username; + const canAccess = exists && isLoggedInUser; + return canAccess ? + res.send(renderIndex()) : + get404Sketch(html => res.send(html)); + }); +}); + router.get('/account', (req, res) => { if (req.user) { res.send(renderIndex()); From 7ea4ae563714ac7f120349bc67ebedbea683c01f Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Sun, 8 Sep 2019 16:43:16 +0200 Subject: [PATCH 05/10] Fix linting errors and warnings --- .../components/createRedirectWithUsername.jsx | 5 ++--- client/modules/App/App.jsx | 5 ++++- client/modules/User/actions.js | 1 - .../User/components/DashboardTabSwitcher.jsx | 18 ++++++++---------- client/modules/User/pages/AccountView.jsx | 3 --- client/modules/User/pages/DashboardView.jsx | 4 ++-- 6 files changed, 16 insertions(+), 20 deletions(-) diff --git a/client/components/createRedirectWithUsername.jsx b/client/components/createRedirectWithUsername.jsx index a2bdd790..fe76b5cd 100644 --- a/client/components/createRedirectWithUsername.jsx +++ b/client/components/createRedirectWithUsername.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { browserHistory } from 'react-router'; @@ -8,7 +7,7 @@ const RedirectToUser = ({ username, url = '/:username/sketches' }) => { if (username == null) { return; } - + browserHistory.replace(url.replace(':username', username)); }, [username]); @@ -23,6 +22,6 @@ function mapStateToProps(state) { const ConnectedRedirectToUser = connect(mapStateToProps)(RedirectToUser); -const createRedirectWithUsername = (url) => (props) => ; +const createRedirectWithUsername = url => props => ; export default createRedirectWithUsername; diff --git a/client/modules/App/App.jsx b/client/modules/App/App.jsx index 9a8e6bdb..045074eb 100644 --- a/client/modules/App/App.jsx +++ b/client/modules/App/App.jsx @@ -39,7 +39,10 @@ class App extends React.Component { App.propTypes = { children: PropTypes.element, location: PropTypes.shape({ - pathname: PropTypes.string + pathname: PropTypes.string, + state: PropTypes.shape({ + skipSavingPath: PropTypes.bool, + }), }).isRequired, setPreviousPath: PropTypes.func.isRequired, }; diff --git a/client/modules/User/actions.js b/client/modules/User/actions.js index 3c2f6d5b..93a874fb 100644 --- a/client/modules/User/actions.js +++ b/client/modules/User/actions.js @@ -1,6 +1,5 @@ import { browserHistory } from 'react-router'; import axios from 'axios'; -import crypto from 'crypto'; import * as ActionTypes from '../../constants'; import { showErrorModal, justOpenedProject } from '../IDE/actions/ide'; import { showToast, setToastText } from '../IDE/actions/toast'; diff --git a/client/modules/User/components/DashboardTabSwitcher.jsx b/client/modules/User/components/DashboardTabSwitcher.jsx index 3d333064..8143759f 100644 --- a/client/modules/User/components/DashboardTabSwitcher.jsx +++ b/client/modules/User/components/DashboardTabSwitcher.jsx @@ -27,16 +27,14 @@ Tab.propTypes = { to: PropTypes.string.isRequired, }; -const DashboardTabSwitcher = ({ currentTab, isOwner, username }) => { - return ( -
      -
      - Sketches - {isOwner && Assets} -
      -
    - ); -}; +const DashboardTabSwitcher = ({ currentTab, isOwner, username }) => ( +
      +
      + Sketches + {isOwner && Assets} +
      +
    +); DashboardTabSwitcher.propTypes = { currentTab: PropTypes.string.isRequired, diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx index 7382656e..5648cee2 100644 --- a/client/modules/User/pages/AccountView.jsx +++ b/client/modules/User/pages/AccountView.jsx @@ -4,7 +4,6 @@ import { reduxForm } from 'redux-form'; import { bindActionCreators } from 'redux'; import { browserHistory } from 'react-router'; import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; -import InlineSVG from 'react-inlinesvg'; import axios from 'axios'; import { Helmet } from 'react-helmet'; import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions'; @@ -14,8 +13,6 @@ import GithubButton from '../components/GithubButton'; import APIKeyForm from '../components/APIKeyForm'; import NavBasic from '../../../components/NavBasic'; -const exitUrl = require('../../../images/exit.svg'); - class AccountView extends React.Component { constructor(props) { super(props); diff --git a/client/modules/User/pages/DashboardView.jsx b/client/modules/User/pages/DashboardView.jsx index a3172eee..ba1b63f0 100644 --- a/client/modules/User/pages/DashboardView.jsx +++ b/client/modules/User/pages/DashboardView.jsx @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import { browserHistory, Link } from 'react-router'; +import { browserHistory } from 'react-router'; import { Helmet } from 'react-helmet'; import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions'; import NavBasic from '../../../components/NavBasic'; @@ -80,7 +80,7 @@ class DashboardView extends React.Component { - +
    { currentTab === TabKey.sketches ? : From 8ac95c0084039b423cfa150c129c82b3dba1f54d Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Sun, 8 Sep 2019 17:10:48 +0200 Subject: [PATCH 06/10] SketchList and AssetList set page title --- client/modules/User/pages/DashboardView.jsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client/modules/User/pages/DashboardView.jsx b/client/modules/User/pages/DashboardView.jsx index ba1b63f0..49b162b6 100644 --- a/client/modules/User/pages/DashboardView.jsx +++ b/client/modules/User/pages/DashboardView.jsx @@ -3,7 +3,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { browserHistory } from 'react-router'; -import { Helmet } from 'react-helmet'; + import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions'; import NavBasic from '../../../components/NavBasic'; @@ -68,10 +68,6 @@ class DashboardView extends React.Component { return (
    - - p5.js Web Editor | Account - -
    From 611730c289d9c7d99515ed787ade98184a6f3c7a Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Wed, 11 Sep 2019 19:06:30 +0200 Subject: [PATCH 07/10] Refactor Nav --- client/components/Nav.jsx | 757 ++++++++++++++++++++------------------ 1 file changed, 393 insertions(+), 364 deletions(-) diff --git a/client/components/Nav.jsx b/client/components/Nav.jsx index 31b7e5b8..4fc0ea61 100644 --- a/client/components/Nav.jsx +++ b/client/components/Nav.jsx @@ -217,6 +217,396 @@ class Nav extends React.PureComponent { this.timer = setTimeout(this.setDropdown.bind(this, 'none'), 10); } + renderProjectMenu(navDropdownState) { + return ( +
      +
    • + +
    • +
    • + +
        +
      • + +
      • + { __process.env.LOGIN_ENABLED && (!this.props.project.owner || this.isUserOwner()) && +
      • + +
      • } + { this.props.project.id && this.props.user.authenticated && +
      • + +
      • } + { this.props.project.id && +
      • + +
      • } + { this.props.project.id && +
      • + +
      • } + { this.props.user.authenticated && +
      • + + Open + +
      • } + { __process.env.EXAMPLES_ENABLED && +
      • + + Examples + +
      • } +
      +
    • +
    • + +
        +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      +
    • +
    • + +
        +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      • + +
      • + {/*
      • + +
      • +
      • + +
      • */} +
      +
    • +
    • + +
        +
      • + +
      • +
      • + Reference + +
      • +
      • + + About + +
      • +
      • + + Feedback + +
      • +
      +
    • +
    + ); + } + + renderUnauthenticatedUserMenu(navDropdownState) { + return ( +
      +
    • + + Log in + +
    • + or +
    • + + Sign up + +
    • +
    + ); + } + + renderAuthenticatedUserMenu(navDropdownState) { + return ( +
      +
    • + Hello, {this.props.user.username}! +
    • + | +
    • + +
        +
      • + + My sketches + +
      • +
      • + + My assets + +
      • +
      • + + Settings + +
      • +
      • + +
      • +
      +
    • +
    + ); + } + + renderUserMenu(navDropdownState) { + const isLoginEnabled = __process.env.LOGIN_ENABLED; + const isAuthenticated = this.props.user.authenticated; + + if (isLoginEnabled && isAuthenticated) { + return this.renderAuthenticatedUserMenu(navDropdownState); + } else if (isLoginEnabled && !isAuthenticated) { + return this.renderUnauthenticatedUserMenu(navDropdownState); + } + + return null; + } + render() { const navDropdownState = { file: classNames({ @@ -240,372 +630,11 @@ class Nav extends React.PureComponent { 'nav__item--open': this.state.dropdownOpen === 'account' }) }; + return (