Merge pull request #1143 from andrewn/feature/standalone-sketches
Standalone sketches and asset page (fixes #1142)
This commit is contained in:
commit
f09e743e92
16 changed files with 728 additions and 409 deletions
|
@ -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) {
|
||||||
|
|
27
client/components/createRedirectWithUsername.jsx
Normal file
27
client/components/createRedirectWithUsername.jsx
Normal 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;
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
45
client/modules/User/components/DashboardTabSwitcher.jsx
Normal file
45
client/modules/User/components/DashboardTabSwitcher.jsx
Normal 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 };
|
|
@ -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);
|
||||||
|
|
119
client/modules/User/pages/DashboardView.jsx
Normal file
119
client/modules/User/pages/DashboardView.jsx
Normal 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);
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
43
client/styles/components/_dashboard-header.scss
Normal file
43
client/styles/components/_dashboard-header.scss
Normal 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;
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +59,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& g, & path {
|
||||||
|
@include themify() {
|
||||||
|
fill: getThemifyVariable('nav-hover-color');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.nav__item-header-triangle polygon {
|
.nav__item-header-triangle polygon {
|
||||||
@include themify() {
|
@include themify() {
|
||||||
fill: getThemifyVariable('nav-hover-color');
|
fill: getThemifyVariable('nav-hover-color');
|
||||||
|
@ -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 {
|
||||||
|
@ -180,3 +192,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
9
client/styles/layout/_dashboard.scss
Normal file
9
client/styles/layout/_dashboard.scss
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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';
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Reference in a new issue