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,
setAllAccessibleOutput: PropTypes.func.isRequired,
newFile: PropTypes.func.isRequired,
- newFolder: PropTypes.func.isRequired
+ newFolder: PropTypes.func.isRequired,
+ layout: PropTypes.oneOf(['dashboard', 'project'])
};
Nav.defaultProps = {
@@ -664,7 +723,8 @@ Nav.defaultProps = {
id: undefined,
owner: undefined
},
- cmController: {}
+ cmController: {},
+ layout: 'project'
};
function mapStateToProps(state) {
diff --git a/client/components/createRedirectWithUsername.jsx b/client/components/createRedirectWithUsername.jsx
new file mode 100644
index 00000000..fe76b5cd
--- /dev/null
+++ b/client/components/createRedirectWithUsername.jsx
@@ -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 =>
;
+
+export default createRedirectWithUsername;
diff --git a/client/modules/App/App.jsx b/client/modules/App/App.jsx
index 2678de7e..045074eb 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);
}
}
@@ -36,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/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 }) => (
+
+);
+
+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/AccountView.jsx b/client/modules/User/pages/AccountView.jsx
index d529b5dd..0cda4898 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
new file mode 100644
index 00000000..af93db70
--- /dev/null
+++ b/client/modules/User/pages/DashboardView.jsx
@@ -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 (
+
+
+
+
+
+
{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..89db6977 100644
--- a/client/routes.jsx
+++ b/client/routes.jsx
@@ -9,7 +9,8 @@ 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 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 { stopSketch } from './modules/IDE/actions/ide';
@@ -35,11 +36,13 @@ const routes = store => (
-
-
+
+
+
-
+
+
diff --git a/client/styles/components/_asset-list.scss b/client/styles/components/_asset-list.scss
index a8e76c74..560ad944 100644
--- a/client/styles/components/_asset-list.scss
+++ b/client/styles/components/_asset-list.scss
@@ -2,13 +2,12 @@
// flex: 1 1 0%;
overflow-y: scroll;
max-width: 100%;
- width: #{1000 / $base-font-size}rem;
min-height: #{400 / $base-font-size}rem;
}
.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 +52,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..6737e672
--- /dev/null
+++ b/client/styles/components/_dashboard-header.scss
@@ -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;
+}
diff --git a/client/styles/components/_nav.scss b/client/styles/components/_nav.scss
index 74945c78..b60752c4 100644
--- a/client/styles/components/_nav.scss
+++ b/client/styles/components/_nav.scss
@@ -43,7 +43,8 @@
}
}
-.nav__item:first-child {
+.nav__item:first-child,
+.nav__item--no-icon {
padding-left: #{15 / $base-font-size}rem;
}
@@ -57,6 +58,12 @@
color: getThemifyVariable('nav-hover-color');
}
}
+
+ & g, & path {
+ @include themify() {
+ fill: getThemifyVariable('nav-hover-color');
+ }
+ }
.nav__item-header-triangle polygon {
@include themify() {
@@ -67,8 +74,13 @@
.nav__item-header:hover {
@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 {
@@ -179,4 +191,18 @@
color: getThemifyVariable('button-hover-color');
}
}
-}
\ No newline at end of file
+}
+
+.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;
+}
diff --git a/client/styles/components/_sketch-list.scss b/client/styles/components/_sketch-list.scss
index dca1d759..3dbde984 100644
--- a/client/styles/components/_sketch-list.scss
+++ b/client/styles/components/_sketch-list.scss
@@ -1,13 +1,11 @@
.sketches-table-container {
overflow-y: scroll;
max-width: 100%;
- width: #{1000 / $base-font-size}rem;
min-height: #{400 / $base-font-size}rem;
}
.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 +104,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';
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());