Merge pull request #1143 from andrewn/feature/standalone-sketches

Standalone sketches and asset page (fixes #1142)
This commit is contained in:
Cassie Tarakajian 2019-09-11 16:52:39 -04:00 committed by GitHub
commit f09e743e92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 728 additions and 409 deletions

View file

@ -11,6 +11,7 @@ import { setAllAccessibleOutput } from '../modules/IDE/actions/preferences';
import { logoutUser } from '../modules/User/actions'; import { logoutUser } from '../modules/User/actions';
import { metaKeyName, } from '../utils/metaKey'; import { metaKeyName, } from '../utils/metaKey';
import caretLeft from '../images/left-arrow.svg';
const triangleUrl = require('../images/down-filled-triangle.svg'); const triangleUrl = require('../images/down-filled-triangle.svg');
const logoUrl = require('../images/p5js-logo-small.svg'); const logoUrl = require('../images/p5js-logo-small.svg');
@ -217,6 +218,424 @@ class Nav extends React.PureComponent {
this.timer = setTimeout(this.setDropdown.bind(this, 'none'), 10); this.timer = setTimeout(this.setDropdown.bind(this, 'none'), 10);
} }
renderDashboardMenu(navDropdownState) {
return (
<ul className="nav__items-left" title="project-menu">
<li className="nav__item-logo">
<InlineSVG src={logoUrl} alt="p5.js logo" className="svg__logo" />
</li>
<li className="nav__item nav__item--no-icon">
<Link to="/" className="nav__back-link">
<InlineSVG src={caretLeft} className="nav__back-icon" />
<span className="nav__item-header">
Back to Editor
</span>
</Link>
</li>
</ul>
);
}
renderProjectMenu(navDropdownState) {
return (
<ul className="nav__items-left" title="project-menu">
<li className="nav__item-logo">
<InlineSVG src={logoUrl} alt="p5.js logo" className="svg__logo" />
</li>
<li className={navDropdownState.file}>
<button
onClick={this.toggleDropdownForFile}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
onMouseOver={() => {
if (this.state.dropdownOpen !== 'none') {
this.setDropdown('file');
}
}}
>
<span className="nav__item-header">File</span>
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
</button>
<ul className="nav__dropdown">
<li className="nav__dropdown-item">
<button
onClick={this.handleNew}
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
>
New
</button>
</li>
{ __process.env.LOGIN_ENABLED && (!this.props.project.owner || this.isUserOwner()) &&
<li className="nav__dropdown-item">
<button
onClick={this.handleSave}
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
>
Save
<span className="nav__keyboard-shortcut">{metaKeyName}+s</span>
</button>
</li> }
{ this.props.project.id && this.props.user.authenticated &&
<li className="nav__dropdown-item">
<button
onClick={this.handleDuplicate}
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
>
Duplicate
</button>
</li> }
{ this.props.project.id &&
<li className="nav__dropdown-item">
<button
onClick={this.handleShare}
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
>
Share
</button>
</li> }
{ this.props.project.id &&
<li className="nav__dropdown-item">
<button
onClick={this.handleDownload}
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
>
Download
</button>
</li> }
{ this.props.user.authenticated &&
<li className="nav__dropdown-item">
<Link
to={`/${this.props.user.username}/sketches`}
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>
Open
</Link>
</li> }
{ __process.env.EXAMPLES_ENABLED &&
<li className="nav__dropdown-item">
<Link
to="/p5/sketches"
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>
Examples
</Link>
</li> }
</ul>
</li>
<li className={navDropdownState.edit}>
<button
onClick={this.toggleDropdownForEdit}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
onMouseOver={() => {
if (this.state.dropdownOpen !== 'none') {
this.setDropdown('edit');
}
}}
>
<span className="nav__item-header">Edit</span>
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
</button>
<ul className="nav__dropdown" >
<li className="nav__dropdown-item">
<button
onClick={() => {
this.props.cmController.tidyCode();
this.setDropdown('none');
}}
onFocus={this.handleFocusForEdit}
onBlur={this.handleBlur}
>
Tidy Code
<span className="nav__keyboard-shortcut">{'\u21E7'}+Tab</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleFind}
onFocus={this.handleFocusForEdit}
onBlur={this.handleBlur}
>
Find
<span className="nav__keyboard-shortcut">{metaKeyName}+F</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleFindNext}
onFocus={this.handleFocusForEdit}
onBlur={this.handleBlur}
>
Find Next
<span className="nav__keyboard-shortcut">{metaKeyName}+G</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleFindPrevious}
onFocus={this.handleFocusForEdit}
onBlur={this.handleBlur}
>
Find Previous
<span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+G</span>
</button>
</li>
</ul>
</li>
<li className={navDropdownState.sketch}>
<button
onClick={this.toggleDropdownForSketch}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
onMouseOver={() => {
if (this.state.dropdownOpen !== 'none') {
this.setDropdown('sketch');
}
}}
>
<span className="nav__item-header">Sketch</span>
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
</button>
<ul className="nav__dropdown">
<li className="nav__dropdown-item">
<button
onClick={this.handleAddFile}
onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur}
>
Add File
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleAddFolder}
onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur}
>
Add Folder
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleRun}
onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur}
>
Run
<span className="nav__keyboard-shortcut">{metaKeyName}+Enter</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleStop}
onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur}
>
Stop
<span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+Enter</span>
</button>
</li>
{/* <li className="nav__dropdown-item">
<button
onClick={this.handleStartAccessible}
onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur}
>
Start Accessible
<span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+1</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleStopAccessible}
onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur}
>
Stop Accessible
<span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+2</span>
</button>
</li> */}
</ul>
</li>
<li className={navDropdownState.help}>
<button
onClick={this.toggleDropdownForHelp}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
onMouseOver={() => {
if (this.state.dropdownOpen !== 'none') {
this.setDropdown('help');
}
}}
>
<span className="nav__item-header">Help & Feedback</span>
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
</button>
<ul className="nav__dropdown">
<li className="nav__dropdown-item">
<button
onFocus={this.handleFocusForHelp}
onBlur={this.handleBlur}
onClick={this.handleKeyboardShortcuts}
>
Keyboard Shortcuts
</button>
</li>
<li className="nav__dropdown-item">
<a
href="https://p5js.org/reference/"
target="_blank"
rel="noopener noreferrer"
onFocus={this.handleFocusForHelp}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>Reference
</a>
</li>
<li className="nav__dropdown-item">
<Link
to="/about"
onFocus={this.handleFocusForHelp}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>
About
</Link>
</li>
<li className="nav__dropdown-item">
<Link
to="/feedback"
onFocus={this.handleFocusForHelp}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>
Feedback
</Link>
</li>
</ul>
</li>
</ul>
);
}
renderUnauthenticatedUserMenu(navDropdownState) {
return (
<ul className="nav__items-right" title="user-menu">
<li>
<Link to="/login">
<span className="nav__item-header">Log in</span>
</Link>
</li>
<span className="nav__item-spacer">or</span>
<li>
<Link to="/signup">
<span className="nav__item-header">Sign up</span>
</Link>
</li>
</ul>
);
}
renderAuthenticatedUserMenu(navDropdownState) {
return (
<ul className="nav__items-right" title="user-menu">
<li className="nav__item">
<span>Hello, {this.props.user.username}!</span>
</li>
<span className="nav__item-spacer">|</span>
<li className={navDropdownState.account}>
<button
className="nav__item-header"
onClick={this.toggleDropdownForAccount}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
onMouseOver={() => {
if (this.state.dropdownOpen !== 'none') {
this.setDropdown('account');
}
}}
>
My Account
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
</button>
<ul className="nav__dropdown">
<li className="nav__dropdown-item">
<Link
to={`/${this.props.user.username}/sketches`}
onFocus={this.handleFocusForAccount}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>
My sketches
</Link>
</li>
<li className="nav__dropdown-item">
<Link
to={`/${this.props.user.username}/assets`}
onFocus={this.handleFocusForAccount}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>
My assets
</Link>
</li>
<li className="nav__dropdown-item">
<Link
to="/account"
onFocus={this.handleFocusForAccount}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>
Settings
</Link>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleLogout}
onFocus={this.handleFocusForAccount}
onBlur={this.handleBlur}
>
Log out
</button>
</li>
</ul>
</li>
</ul>
);
}
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;
}
renderLeftLayout(navDropdownState) {
switch (this.props.layout) {
case 'dashboard':
return this.renderDashboardMenu(navDropdownState);
case 'project':
default:
return this.renderProjectMenu(navDropdownState);
}
}
render() { render() {
const navDropdownState = { const navDropdownState = {
file: classNames({ file: classNames({
@ -240,372 +659,11 @@ class Nav extends React.PureComponent {
'nav__item--open': this.state.dropdownOpen === 'account' 'nav__item--open': this.state.dropdownOpen === 'account'
}) })
}; };
return ( return (
<nav className="nav" title="main-navigation" ref={(node) => { this.node = node; }}> <nav className="nav" title="main-navigation" ref={(node) => { this.node = node; }}>
<ul className="nav__items-left" title="project-menu"> {this.renderLeftLayout(navDropdownState)}
<li className="nav__item-logo"> {this.renderUserMenu(navDropdownState)}
<InlineSVG src={logoUrl} alt="p5.js logo" className="svg__logo" />
</li>
<li className={navDropdownState.file}>
<button
onClick={this.toggleDropdownForFile}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
onMouseOver={() => {
if (this.state.dropdownOpen !== 'none') {
this.setDropdown('file');
}
}}
>
<span className="nav__item-header">File</span>
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
</button>
<ul className="nav__dropdown">
<li className="nav__dropdown-item">
<button
onClick={this.handleNew}
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
>
New
</button>
</li>
{ __process.env.LOGIN_ENABLED && (!this.props.project.owner || this.isUserOwner()) &&
<li className="nav__dropdown-item">
<button
onClick={this.handleSave}
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
>
Save
<span className="nav__keyboard-shortcut">{metaKeyName}+s</span>
</button>
</li> }
{ this.props.project.id && this.props.user.authenticated &&
<li className="nav__dropdown-item">
<button
onClick={this.handleDuplicate}
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
>
Duplicate
</button>
</li> }
{ this.props.project.id &&
<li className="nav__dropdown-item">
<button
onClick={this.handleShare}
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
>
Share
</button>
</li> }
{ this.props.project.id &&
<li className="nav__dropdown-item">
<button
onClick={this.handleDownload}
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
>
Download
</button>
</li> }
{ this.props.user.authenticated &&
<li className="nav__dropdown-item">
<Link
to={`/${this.props.user.username}/sketches`}
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>
Open
</Link>
</li> }
{ __process.env.EXAMPLES_ENABLED &&
<li className="nav__dropdown-item">
<Link
to="/p5/sketches"
onFocus={this.handleFocusForFile}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>
Examples
</Link>
</li> }
</ul>
</li>
<li className={navDropdownState.edit}>
<button
onClick={this.toggleDropdownForEdit}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
onMouseOver={() => {
if (this.state.dropdownOpen !== 'none') {
this.setDropdown('edit');
}
}}
>
<span className="nav__item-header">Edit</span>
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
</button>
<ul className="nav__dropdown" >
<li className="nav__dropdown-item">
<button
onClick={() => {
this.props.cmController.tidyCode();
this.setDropdown('none');
}}
onFocus={this.handleFocusForEdit}
onBlur={this.handleBlur}
>
Tidy Code
<span className="nav__keyboard-shortcut">{'\u21E7'}+Tab</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleFind}
onFocus={this.handleFocusForEdit}
onBlur={this.handleBlur}
>
Find
<span className="nav__keyboard-shortcut">{metaKeyName}+F</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleFindNext}
onFocus={this.handleFocusForEdit}
onBlur={this.handleBlur}
>
Find Next
<span className="nav__keyboard-shortcut">{metaKeyName}+G</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleFindPrevious}
onFocus={this.handleFocusForEdit}
onBlur={this.handleBlur}
>
Find Previous
<span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+G</span>
</button>
</li>
</ul>
</li>
<li className={navDropdownState.sketch}>
<button
onClick={this.toggleDropdownForSketch}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
onMouseOver={() => {
if (this.state.dropdownOpen !== 'none') {
this.setDropdown('sketch');
}
}}
>
<span className="nav__item-header">Sketch</span>
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
</button>
<ul className="nav__dropdown">
<li className="nav__dropdown-item">
<button
onClick={this.handleAddFile}
onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur}
>
Add File
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleAddFolder}
onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur}
>
Add Folder
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleRun}
onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur}
>
Run
<span className="nav__keyboard-shortcut">{metaKeyName}+Enter</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleStop}
onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur}
>
Stop
<span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+Enter</span>
</button>
</li>
{/* <li className="nav__dropdown-item">
<button
onClick={this.handleStartAccessible}
onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur}
>
Start Accessible
<span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+1</span>
</button>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleStopAccessible}
onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur}
>
Stop Accessible
<span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+2</span>
</button>
</li> */}
</ul>
</li>
<li className={navDropdownState.help}>
<button
onClick={this.toggleDropdownForHelp}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
onMouseOver={() => {
if (this.state.dropdownOpen !== 'none') {
this.setDropdown('help');
}
}}
>
<span className="nav__item-header">Help & Feedback</span>
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
</button>
<ul className="nav__dropdown">
<li className="nav__dropdown-item">
<button
onFocus={this.handleFocusForHelp}
onBlur={this.handleBlur}
onClick={this.handleKeyboardShortcuts}
>
Keyboard Shortcuts
</button>
</li>
<li className="nav__dropdown-item">
<a
href="https://p5js.org/reference/"
target="_blank"
rel="noopener noreferrer"
onFocus={this.handleFocusForHelp}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>Reference
</a>
</li>
<li className="nav__dropdown-item">
<Link
to="/about"
onFocus={this.handleFocusForHelp}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>
About
</Link>
</li>
<li className="nav__dropdown-item">
<Link
to="/feedback"
onFocus={this.handleFocusForHelp}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>
Feedback
</Link>
</li>
</ul>
</li>
</ul>
{ __process.env.LOGIN_ENABLED && !this.props.user.authenticated &&
<ul className="nav__items-right" title="user-menu">
<li>
<Link to="/login">
<span className="nav__item-header">Log in</span>
</Link>
</li>
<span className="nav__item-spacer">or</span>
<li>
<Link to="/signup">
<span className="nav__item-header">Sign up</span>
</Link>
</li>
</ul>}
{ __process.env.LOGIN_ENABLED && this.props.user.authenticated &&
<ul className="nav__items-right" title="user-menu">
<li className="nav__item">
<span>Hello, {this.props.user.username}!</span>
</li>
<span className="nav__item-spacer">|</span>
<li className={navDropdownState.account}>
<button
className="nav__item-header"
onClick={this.toggleDropdownForAccount}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
onMouseOver={() => {
if (this.state.dropdownOpen !== 'none') {
this.setDropdown('account');
}
}}
>
My Account
<InlineSVG className="nav__item-header-triangle" src={triangleUrl} />
</button>
<ul className="nav__dropdown">
<li className="nav__dropdown-item">
<Link
to={`/${this.props.user.username}/sketches`}
onFocus={this.handleFocusForAccount}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>
My sketches
</Link>
</li>
<li className="nav__dropdown-item">
<Link
to="/assets"
onFocus={this.handleFocusForAccount}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>
My assets
</Link>
</li>
<li className="nav__dropdown-item">
<Link
to="/account"
onFocus={this.handleFocusForAccount}
onBlur={this.handleBlur}
onClick={this.setDropdownForNone}
>
Settings
</Link>
</li>
<li className="nav__dropdown-item">
<button
onClick={this.handleLogout}
onFocus={this.handleFocusForAccount}
onBlur={this.handleBlur}
>
Log out
</button>
</li>
</ul>
</li>
</ul> }
{/* {/*
<div className="nav__announce"> <div className="nav__announce">
This is a preview version of the editor, that has not yet been officially released. This is a preview version of the editor, that has not yet been officially released.
@ -656,7 +714,8 @@ Nav.propTypes = {
stopSketch: PropTypes.func.isRequired, stopSketch: PropTypes.func.isRequired,
setAllAccessibleOutput: PropTypes.func.isRequired, setAllAccessibleOutput: PropTypes.func.isRequired,
newFile: PropTypes.func.isRequired, newFile: PropTypes.func.isRequired,
newFolder: PropTypes.func.isRequired newFolder: PropTypes.func.isRequired,
layout: PropTypes.oneOf(['dashboard', 'project'])
}; };
Nav.defaultProps = { Nav.defaultProps = {
@ -664,7 +723,8 @@ Nav.defaultProps = {
id: undefined, id: undefined,
owner: undefined owner: undefined
}, },
cmController: {} cmController: {},
layout: 'project'
}; };
function mapStateToProps(state) { function mapStateToProps(state) {

View file

@ -0,0 +1,27 @@
import React from 'react';
import { connect } from 'react-redux';
import { browserHistory } from 'react-router';
const RedirectToUser = ({ username, url = '/:username/sketches' }) => {
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 => <ConnectedRedirectToUser {...props} url={url} />;
export default createRedirectWithUsername;

View file

@ -18,7 +18,10 @@ class App extends React.Component {
} }
componentWillReceiveProps(nextProps) { 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); this.props.setPreviousPath(this.props.location.pathname);
} }
} }
@ -36,7 +39,10 @@ class App extends React.Component {
App.propTypes = { App.propTypes = {
children: PropTypes.element, children: PropTypes.element,
location: PropTypes.shape({ location: PropTypes.shape({
pathname: PropTypes.string pathname: PropTypes.string,
state: PropTypes.shape({
skipSavingPath: PropTypes.bool,
}),
}).isRequired, }).isRequired,
setPreviousPath: PropTypes.func.isRequired, setPreviousPath: PropTypes.func.isRequired,
}; };

View file

@ -29,8 +29,6 @@ import * as ToastActions from '../actions/toast';
import * as ConsoleActions from '../actions/console'; import * as ConsoleActions from '../actions/console';
import { getHTMLFile } from '../reducers/files'; import { getHTMLFile } from '../reducers/files';
import Overlay from '../../App/components/Overlay'; import Overlay from '../../App/components/Overlay';
import SketchList from '../components/SketchList';
import AssetList from '../components/AssetList';
import About from '../components/About'; import About from '../components/About';
import Feedback from '../components/Feedback'; import Feedback from '../components/Feedback';
@ -365,30 +363,6 @@ class IDEView extends React.Component {
createFolder={this.props.createFolder} createFolder={this.props.createFolder}
/> />
} }
{ this.props.location.pathname.match(/sketches$/) &&
<Overlay
ariaLabel="project list"
title="Open a Sketch"
previousPath={this.props.ide.previousPath}
>
<SketchList
username={this.props.params.username}
user={this.props.user}
/>
</Overlay>
}
{ this.props.location.pathname.match(/assets$/) &&
<Overlay
title="Assets"
ariaLabel="asset list"
previousPath={this.props.ide.previousPath}
>
<AssetList
username={this.props.params.username}
user={this.props.user}
/>
</Overlay>
}
{ this.props.location.pathname === '/about' && { this.props.location.pathname === '/about' &&
<Overlay <Overlay
previousPath={this.props.ide.previousPath} previousPath={this.props.ide.previousPath}

View file

@ -1,6 +1,5 @@
import { browserHistory } from 'react-router'; import { browserHistory } from 'react-router';
import axios from 'axios'; import axios from 'axios';
import crypto from 'crypto';
import * as ActionTypes from '../../constants'; import * as ActionTypes from '../../constants';
import { showErrorModal, justOpenedProject } from '../IDE/actions/ide'; import { showErrorModal, justOpenedProject } from '../IDE/actions/ide';
import { showToast, setToastText } from '../IDE/actions/toast'; import { showToast, setToastText } from '../IDE/actions/toast';

View file

@ -0,0 +1,45 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Link } from 'react-router';
const TabKey = {
assets: 'assets',
sketches: 'sketches',
};
const Tab = ({ children, isSelected, to }) => {
const selectedClassName = 'dashboard-header__tab--selected';
const location = { pathname: to, state: { skipSavingPath: true } };
const content = isSelected ? children : <Link to={location}>{children}</Link>;
return (
<li className={`dashboard-header__tab ${isSelected && selectedClassName}`}>
<h4 className="dashboard-header__tab__title">
{content}
</h4>
</li>
);
};
Tab.propTypes = {
children: PropTypes.element.isRequired,
isSelected: PropTypes.bool.isRequired,
to: PropTypes.string.isRequired,
};
const DashboardTabSwitcher = ({ currentTab, isOwner, username }) => (
<ul className="dashboard-header__switcher">
<div className="dashboard-header__tabs">
<Tab to={`/${username}/sketches`} isSelected={currentTab === 'sketches'}>Sketches</Tab>
{isOwner && <Tab to={`/${username}/assets`} isSelected={currentTab === 'assets'}>Assets</Tab>}
</div>
</ul>
);
DashboardTabSwitcher.propTypes = {
currentTab: PropTypes.string.isRequired,
isOwner: PropTypes.bool.isRequired,
username: PropTypes.string.isRequired,
};
export { DashboardTabSwitcher as default, TabKey };

View file

@ -4,7 +4,6 @@ import { reduxForm } from 'redux-form';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { browserHistory } from 'react-router'; import { browserHistory } from 'react-router';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import InlineSVG from 'react-inlinesvg';
import axios from 'axios'; import axios from 'axios';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions'; import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions';
@ -14,8 +13,6 @@ import GithubButton from '../components/GithubButton';
import APIKeyForm from '../components/APIKeyForm'; import APIKeyForm from '../components/APIKeyForm';
import NavBasic from '../../../components/NavBasic'; import NavBasic from '../../../components/NavBasic';
const exitUrl = require('../../../images/exit.svg');
class AccountView extends React.Component { class AccountView extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);

View file

@ -0,0 +1,119 @@
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { browserHistory } from 'react-router';
import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions';
import Nav from '../../../components/Nav';
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 (
<div className="dashboard">
<Nav layout="dashboard" />
<section className="dashboard-header">
<div className="dashboard-header__header">
<h2 className="dashboard-header__header__title">{this.ownerName()}</h2>
<DashboardTabSwitcher currentTab={currentTab} isOwner={isOwner} username={username} />
</div>
<div className="dashboard-content">
{
currentTab === TabKey.sketches ? <SketchList username={username} /> : <AssetList username={username} />
}
</div>
</section>
</div>
);
}
}
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);

View file

@ -9,7 +9,8 @@ import ResetPasswordView from './modules/User/pages/ResetPasswordView';
import EmailVerificationView from './modules/User/pages/EmailVerificationView'; import EmailVerificationView from './modules/User/pages/EmailVerificationView';
import NewPasswordView from './modules/User/pages/NewPasswordView'; import NewPasswordView from './modules/User/pages/NewPasswordView';
import AccountView from './modules/User/pages/AccountView'; import AccountView from './modules/User/pages/AccountView';
// import SketchListView from './modules/Sketch/pages/SketchListView'; import DashboardView from './modules/User/pages/DashboardView';
import createRedirectWithUsername from './components/createRedirectWithUsername';
import { getUser } from './modules/User/actions'; import { getUser } from './modules/User/actions';
import { stopSketch } from './modules/IDE/actions/ide'; import { stopSketch } from './modules/IDE/actions/ide';
@ -35,11 +36,13 @@ const routes = store => (
<Route path="/projects/:project_id" component={IDEView} /> <Route path="/projects/:project_id" component={IDEView} />
<Route path="/:username/full/:project_id" component={FullView} /> <Route path="/:username/full/:project_id" component={FullView} />
<Route path="/full/:project_id" component={FullView} /> <Route path="/full/:project_id" component={FullView} />
<Route path="/sketches" component={IDEView} /> <Route path="/sketches" component={createRedirectWithUsername('/:username/sketches')} />
<Route path="/assets" component={IDEView} /> <Route path="/:username/assets" component={DashboardView} />
<Route path="/assets" component={createRedirectWithUsername('/:username/assets')} />
<Route path="/account" component={AccountView} /> <Route path="/account" component={AccountView} />
<Route path="/:username/sketches/:project_id" component={IDEView} /> <Route path="/:username/sketches/:project_id" component={IDEView} />
<Route path="/:username/sketches" component={IDEView} /> <Route path="/:username/sketches" component={DashboardView} />
<Route path="/:username/assets" component={DashboardView} />
<Route path="/about" component={IDEView} /> <Route path="/about" component={IDEView} />
<Route path="/feedback" component={IDEView} /> <Route path="/feedback" component={IDEView} />
</Route> </Route>

View file

@ -2,13 +2,12 @@
// flex: 1 1 0%; // flex: 1 1 0%;
overflow-y: scroll; overflow-y: scroll;
max-width: 100%; max-width: 100%;
width: #{1000 / $base-font-size}rem;
min-height: #{400 / $base-font-size}rem; min-height: #{400 / $base-font-size}rem;
} }
.asset-table { .asset-table {
width: 100%; width: 100%;
padding: #{10 / $base-font-size}rem #{20 / $base-font-size}rem; padding: #{10 / $base-font-size}rem 0;
max-height: 100%; max-height: 100%;
border-spacing: 0; border-spacing: 0;
& .asset-list__delete-column { & .asset-list__delete-column {
@ -53,4 +52,5 @@
.asset-table__empty { .asset-table__empty {
text-align: center; text-align: center;
font-size: #{16 / $base-font-size}rem; font-size: #{16 / $base-font-size}rem;
padding: #{42 / $base-font-size}rem 0;
} }

View file

@ -0,0 +1,43 @@
.dashboard-header {
padding: #{24 / $base-font-size}rem #{66 / $base-font-size}rem;
}
.dashboard-header__tabs {
display: flex;
padding-top: #{24 / $base-font-size}rem;
margin-bottom: #{24 / $base-font-size}rem;
@include themify() {
border-bottom: 1px solid getThemifyVariable('inactive-text-color');
}
}
.dashboard-header__tab {
@include themify() {
color: getThemifyVariable('inactive-text-color');
border-bottom: #{4 / $base-font-size}rem solid transparent;
padding: 0 0 #{8 / $base-font-size}rem 0;
margin-right: #{26 / $base-font-size}rem;
&:hover, &:focus, &.dashboard-header__tab--selected {
color: getThemifyVariable('primary-text-color');
border-bottom-color: getThemifyVariable('nav-hover-color');
cursor: pointer;
}
}
font-size: #{21 / $base-font-size}rem;
}
.dashboard-header__tab--selected {
cursor: auto;
}
.dashboard-header__tab a {
color: inherit;
}
.dashboard-header__tab__title {
font-weight: bold;
margin: 0;
}

View file

@ -43,7 +43,8 @@
} }
} }
.nav__item:first-child { .nav__item:first-child,
.nav__item--no-icon {
padding-left: #{15 / $base-font-size}rem; padding-left: #{15 / $base-font-size}rem;
} }
@ -57,6 +58,12 @@
color: getThemifyVariable('nav-hover-color'); color: getThemifyVariable('nav-hover-color');
} }
} }
& g, & path {
@include themify() {
fill: getThemifyVariable('nav-hover-color');
}
}
.nav__item-header-triangle polygon { .nav__item-header-triangle polygon {
@include themify() { @include themify() {
@ -67,8 +74,13 @@
.nav__item-header:hover { .nav__item-header:hover {
@include themify() { @include themify() {
color: getThemifyVariable('nav-hover-color'); color: getThemifyVariable('nav-hover-color');
}
& g, & path {
@include themify() {
fill: getThemifyVariable('nav-hover-color');
} }
}
} }
.nav__item-header-triangle { .nav__item-header-triangle {
@ -179,4 +191,18 @@
color: getThemifyVariable('button-hover-color'); color: getThemifyVariable('button-hover-color');
} }
} }
} }
.nav__back-icon {
& g, & path {
opacity: 1;
@include themify() {
fill: getThemifyVariable('inactive-text-color');
}
}
margin-right: #{5 / $base-font-size}rem;
}
.nav__back-link {
display: flex;
}

View file

@ -1,13 +1,11 @@
.sketches-table-container { .sketches-table-container {
overflow-y: scroll; overflow-y: scroll;
max-width: 100%; max-width: 100%;
width: #{1000 / $base-font-size}rem;
min-height: #{400 / $base-font-size}rem; min-height: #{400 / $base-font-size}rem;
} }
.sketches-table { .sketches-table {
width: 100%; width: 100%;
padding: #{10 / $base-font-size}rem #{20 / $base-font-size}rem;
max-height: 100%; max-height: 100%;
border-spacing: 0; border-spacing: 0;
& .sketch-list__dropdown-column { & .sketch-list__dropdown-column {
@ -106,4 +104,5 @@
.sketches-table__empty { .sketches-table__empty {
text-align: center; text-align: center;
font-size: #{16 / $base-font-size}rem; font-size: #{16 / $base-font-size}rem;
padding: #{42 / $base-font-size}rem 0;
} }

View file

@ -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');
}
}

View file

@ -46,7 +46,9 @@
@import 'components/loader'; @import 'components/loader';
@import 'components/uploader'; @import 'components/uploader';
@import 'components/tabs'; @import 'components/tabs';
@import 'components/dashboard-header';
@import 'layout/dashboard';
@import 'layout/ide'; @import 'layout/ide';
@import 'layout/fullscreen'; @import 'layout/fullscreen';
@import 'layout/user'; @import 'layout/user';

View file

@ -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) => { router.get('/account', (req, res) => {
if (req.user) { if (req.user) {
res.send(renderIndex()); res.send(renderIndex());