Merge branch 'feature/mobile-examples' of https://github.com/ghalestrilo/p5.js-web-editor into feature/mobile-files-tab
This commit is contained in:
commit
b88a40327e
18 changed files with 228 additions and 78 deletions
18
client/components/mobile/Tab.jsx
Normal file
18
client/components/mobile/Tab.jsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
import { prop, remSize } from '../../theme';
|
||||||
|
|
||||||
|
export default styled(Link)`
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
|
||||||
|
background: transparent;
|
||||||
|
/* border-top: ${remSize(4)} solid ${props => prop(props.selected ? 'colors.p5jsPink' : 'MobilePanel.default.background')}; */
|
||||||
|
border-top: ${remSize(4)} solid ${props => (props.selected ? prop('TabHighlight') : 'transparent')};
|
||||||
|
|
||||||
|
color: ${prop('primaryTextColor')};
|
||||||
|
|
||||||
|
padding: ${remSize(8)} ${remSize(16)};
|
||||||
|
width: 30%;
|
||||||
|
`;
|
15
client/components/mobile/TabSwitcher.jsx
Normal file
15
client/components/mobile/TabSwitcher.jsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { prop, remSize } from '../../theme';
|
||||||
|
|
||||||
|
export default styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
h3 { text-align: center; width: 100%; }
|
||||||
|
border-top: 1px solid ${prop('Separator')};
|
||||||
|
|
||||||
|
background: ${props => prop('backgroundColor')};
|
||||||
|
`;
|
||||||
|
|
|
@ -214,8 +214,8 @@ class CollectionListRowBase extends React.Component {
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
{(!mobile) && <td>{format(new Date(collection.createdAt), 'MMM D, YYYY')}</td>}
|
{(!mobile) && <td>{format(new Date(collection.createdAt), 'MMM D, YYYY')}</td>}
|
||||||
<td>{formatDateCell(collection.updatedAt)}</td>
|
<td>{mobile && 'Updated: '}{formatDateCell(collection.updatedAt)}</td>
|
||||||
<td>{(collection.items || []).length}</td>
|
<td>{mobile && '# sketches: '}{(collection.items || []).length}</td>
|
||||||
<td className="sketch-list__dropdown-column">
|
<td className="sketch-list__dropdown-column">
|
||||||
{this.renderActions()}
|
{this.renderActions()}
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
|
||||||
import Button from '../../../common/Button';
|
import Button from '../../../common/Button';
|
||||||
|
@ -35,14 +36,17 @@ class NewFileForm extends React.Component {
|
||||||
className="new-file-form__name-input"
|
className="new-file-form__name-input"
|
||||||
id="name"
|
id="name"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Name"
|
placeholder={this.props.t('NewFileForm.Placeholder')}
|
||||||
maxLength="128"
|
maxLength="128"
|
||||||
{...domOnlyProps(name)}
|
{...domOnlyProps(name)}
|
||||||
ref={(element) => {
|
ref={(element) => {
|
||||||
this.fileName = element;
|
this.fileName = element;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button type="submit">Add File</Button>
|
<Button
|
||||||
|
type="submit"
|
||||||
|
>{this.props.t('NewFileForm.AddFileSubmit')}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{name.touched && name.error && (
|
{name.touched && name.error && (
|
||||||
<span className="form-error">{name.error}</span>
|
<span className="form-error">{name.error}</span>
|
||||||
|
@ -59,6 +63,7 @@ NewFileForm.propTypes = {
|
||||||
handleSubmit: PropTypes.func.isRequired,
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
createFile: PropTypes.func.isRequired,
|
createFile: PropTypes.func.isRequired,
|
||||||
focusOnModal: PropTypes.func.isRequired,
|
focusOnModal: PropTypes.func.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NewFileForm;
|
export default withTranslation()(NewFileForm);
|
||||||
|
|
|
@ -3,6 +3,8 @@ import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators, compose } from 'redux';
|
import { bindActionCreators, compose } from 'redux';
|
||||||
import { reduxForm } from 'redux-form';
|
import { reduxForm } from 'redux-form';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
|
import i18n from 'i18next';
|
||||||
import NewFileForm from './NewFileForm';
|
import NewFileForm from './NewFileForm';
|
||||||
import { closeNewFileModal } from '../actions/ide';
|
import { closeNewFileModal } from '../actions/ide';
|
||||||
import { createFile } from '../actions/files';
|
import { createFile } from '../actions/files';
|
||||||
|
@ -33,11 +35,11 @@ class NewFileModal extends React.Component {
|
||||||
<section className="modal" ref={(element) => { this.modal = element; }}>
|
<section className="modal" ref={(element) => { this.modal = element; }}>
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="modal__header">
|
<div className="modal__header">
|
||||||
<h2 className="modal__title">Create File</h2>
|
<h2 className="modal__title">{this.props.t('NewFileModal.Title')}</h2>
|
||||||
<button
|
<button
|
||||||
className="modal__exit-button"
|
className="modal__exit-button"
|
||||||
onClick={this.props.closeNewFileModal}
|
onClick={this.props.closeNewFileModal}
|
||||||
aria-label="Close New File Modal"
|
aria-label={this.props.t('NewFileModal.CloseButtonARIA')}
|
||||||
>
|
>
|
||||||
<ExitIcon focusable="false" aria-hidden="true" />
|
<ExitIcon focusable="false" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -54,16 +56,17 @@ class NewFileModal extends React.Component {
|
||||||
|
|
||||||
NewFileModal.propTypes = {
|
NewFileModal.propTypes = {
|
||||||
createFile: PropTypes.func.isRequired,
|
createFile: PropTypes.func.isRequired,
|
||||||
closeNewFileModal: PropTypes.func.isRequired
|
closeNewFileModal: PropTypes.func.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function validate(formProps) {
|
function validate(formProps) {
|
||||||
const errors = {};
|
const errors = {};
|
||||||
|
|
||||||
if (!formProps.name) {
|
if (!formProps.name) {
|
||||||
errors.name = 'Please enter a name';
|
errors.name = i18n.t('NewFileModal.EnterName');
|
||||||
} else if (!formProps.name.match(CREATE_FILE_REGEX)) {
|
} else if (!formProps.name.match(CREATE_FILE_REGEX)) {
|
||||||
errors.name = 'Invalid file type. Valid extensions are .js, .css, .json, .txt, .csv, .tsv, .frag, and .vert.';
|
errors.name = i18n.t('NewFileModal.InvalidType');
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
|
@ -77,11 +80,11 @@ function mapDispatchToProps(dispatch) {
|
||||||
return bindActionCreators({ createFile, closeNewFileModal }, dispatch);
|
return bindActionCreators({ createFile, closeNewFileModal }, dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default withTranslation()(compose(
|
||||||
connect(mapStateToProps, mapDispatchToProps),
|
connect(mapStateToProps, mapDispatchToProps),
|
||||||
reduxForm({
|
reduxForm({
|
||||||
form: 'new-file',
|
form: 'new-file',
|
||||||
fields: ['name'],
|
fields: ['name'],
|
||||||
validate
|
validate
|
||||||
})
|
})
|
||||||
)(NewFileModal);
|
)(NewFileModal));
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
|
|
||||||
import Button from '../../../common/Button';
|
import Button from '../../../common/Button';
|
||||||
|
|
||||||
|
|
||||||
class NewFolderForm extends React.Component {
|
class NewFolderForm extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -35,13 +37,14 @@ class NewFolderForm extends React.Component {
|
||||||
id="name"
|
id="name"
|
||||||
type="text"
|
type="text"
|
||||||
maxLength="128"
|
maxLength="128"
|
||||||
placeholder="Name"
|
placeholder={this.props.t('NewFolderForm.Placeholder')}
|
||||||
ref={(element) => {
|
ref={(element) => { this.fileName = element; }}
|
||||||
this.fileName = element;
|
|
||||||
}}
|
|
||||||
{...domOnlyProps(name)}
|
{...domOnlyProps(name)}
|
||||||
/>
|
/>
|
||||||
<Button type="submit">Add Folder</Button>
|
<Button
|
||||||
|
type="submit"
|
||||||
|
>{this.props.t('NewFolderForm.AddFolderSubmit')}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{name.touched && name.error && (
|
{name.touched && name.error && (
|
||||||
<span className="form-error">{name.error}</span>
|
<span className="form-error">{name.error}</span>
|
||||||
|
@ -60,9 +63,10 @@ NewFolderForm.propTypes = {
|
||||||
closeModal: PropTypes.func.isRequired,
|
closeModal: PropTypes.func.isRequired,
|
||||||
submitting: PropTypes.bool,
|
submitting: PropTypes.bool,
|
||||||
pristine: PropTypes.bool,
|
pristine: PropTypes.bool,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
NewFolderForm.defaultProps = {
|
NewFolderForm.defaultProps = {
|
||||||
submitting: false,
|
submitting: false,
|
||||||
pristine: true,
|
pristine: true,
|
||||||
};
|
};
|
||||||
export default NewFolderForm;
|
export default withTranslation()(NewFolderForm);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { reduxForm } from 'redux-form';
|
import { reduxForm } from 'redux-form';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
|
import i18n from 'i18next';
|
||||||
import NewFolderForm from './NewFolderForm';
|
import NewFolderForm from './NewFolderForm';
|
||||||
|
|
||||||
import ExitIcon from '../../../images/exit.svg';
|
import ExitIcon from '../../../images/exit.svg';
|
||||||
|
@ -15,11 +17,11 @@ class NewFolderModal extends React.Component {
|
||||||
<section className="modal" ref={(element) => { this.newFolderModal = element; }} >
|
<section className="modal" ref={(element) => { this.newFolderModal = element; }} >
|
||||||
<div className="modal-content-folder">
|
<div className="modal-content-folder">
|
||||||
<div className="modal__header">
|
<div className="modal__header">
|
||||||
<h2 className="modal__title">Create Folder</h2>
|
<h2 className="modal__title">{this.props.t('NewFolderModal.Title')}</h2>
|
||||||
<button
|
<button
|
||||||
className="modal__exit-button"
|
className="modal__exit-button"
|
||||||
onClick={this.props.closeModal}
|
onClick={this.props.closeModal}
|
||||||
aria-label="Close New Folder Modal"
|
aria-label={this.props.t('NewFolderModal.CloseButtonARIA')}
|
||||||
>
|
>
|
||||||
<ExitIcon focusable="false" aria-hidden="true" />
|
<ExitIcon focusable="false" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -32,23 +34,24 @@ class NewFolderModal extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
NewFolderModal.propTypes = {
|
NewFolderModal.propTypes = {
|
||||||
closeModal: PropTypes.func.isRequired
|
closeModal: PropTypes.func.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function validate(formProps) {
|
function validate(formProps) {
|
||||||
const errors = {};
|
const errors = {};
|
||||||
if (!formProps.name) {
|
if (!formProps.name) {
|
||||||
errors.name = 'Please enter a name';
|
errors.name = i18n.t('NewFolderModal.EnterName');
|
||||||
} else if (formProps.name.trim().length === 0) {
|
} else if (formProps.name.trim().length === 0) {
|
||||||
errors.name = 'Folder name cannot contain only spaces';
|
errors.name = i18n.t('NewFolderModal.EmptyName');
|
||||||
} else if (formProps.name.match(/\.+/i)) {
|
} else if (formProps.name.match(/\.+/i)) {
|
||||||
errors.name = 'Folder name cannot contain an extension';
|
errors.name = i18n.t('NewFolderModal.InvalidExtension');
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
export default reduxForm({
|
export default withTranslation()(reduxForm({
|
||||||
form: 'new-folder',
|
form: 'new-folder',
|
||||||
fields: ['name'],
|
fields: ['name'],
|
||||||
validate
|
validate
|
||||||
})(NewFolderModal);
|
})(NewFolderModal));
|
||||||
|
|
|
@ -293,8 +293,8 @@ class SketchListRowBase extends React.Component {
|
||||||
<th scope="row">
|
<th scope="row">
|
||||||
{name}
|
{name}
|
||||||
</th>
|
</th>
|
||||||
<td>{formatDateCell(sketch.createdAt, mobile)}</td>
|
<td>{mobile && 'Created: '}{formatDateCell(sketch.createdAt, mobile)}</td>
|
||||||
<td>{formatDateCell(sketch.updatedAt, mobile)}</td>
|
<td>{mobile && 'Updated: '}{formatDateCell(sketch.updatedAt, mobile)}</td>
|
||||||
{this.renderDropdown()}
|
{this.renderDropdown()}
|
||||||
</tr>
|
</tr>
|
||||||
</React.Fragment>);
|
</React.Fragment>);
|
||||||
|
|
|
@ -2,27 +2,31 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { withRouter, Link } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
|
|
||||||
import Screen from '../../components/mobile/MobileScreen';
|
import Screen from '../../components/mobile/MobileScreen';
|
||||||
import Header from '../../components/mobile/Header';
|
import Header from '../../components/mobile/Header';
|
||||||
import IconButton from '../../components/mobile/IconButton';
|
import IconButton from '../../components/mobile/IconButton';
|
||||||
import { ExitIcon } from '../../common/icons';
|
import { ExitIcon, MoreIcon } from '../../common/icons';
|
||||||
import Footer from '../../components/mobile/Footer';
|
import Footer from '../../components/mobile/Footer';
|
||||||
import { prop, remSize } from '../../theme';
|
import { remSize, prop } from '../../theme';
|
||||||
import SketchList from '../IDE/components/SketchList';
|
import SketchList from '../IDE/components/SketchList';
|
||||||
import CollectionList from '../IDE/components/CollectionList';
|
import CollectionList from '../IDE/components/CollectionList';
|
||||||
import AssetList from '../IDE/components/AssetList';
|
import AssetList from '../IDE/components/AssetList';
|
||||||
import Content from './MobileViewContent';
|
import Content from './MobileViewContent';
|
||||||
import { SketchSearchbar, CollectionSearchbar } from '../IDE/components/Searchbar';
|
import { SketchSearchbar, CollectionSearchbar } from '../IDE/components/Searchbar';
|
||||||
import Button from '../../common/Button';
|
import Button from '../../common/Button';
|
||||||
|
import useAsModal from '../../components/useAsModal';
|
||||||
|
import Dropdown from '../../components/Dropdown';
|
||||||
|
import FooterTabSwitcher from '../../components/mobile/TabSwitcher';
|
||||||
|
import FooterTab from '../../components/mobile/Tab';
|
||||||
|
import Loader from '../App/components/loader';
|
||||||
|
|
||||||
const EXAMPLE_USERNAME = 'p5';
|
const EXAMPLE_USERNAME = 'p5';
|
||||||
|
|
||||||
const ContentWrapper = styled(Content)`
|
const ContentWrapper = styled(Content)`
|
||||||
table {
|
table {
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
/* white-space: nowrap; */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
td ,thead button {
|
td ,thead button {
|
||||||
|
@ -31,38 +35,57 @@ const ContentWrapper = styled(Content)`
|
||||||
text-align: left;
|
text-align: left;
|
||||||
};
|
};
|
||||||
|
|
||||||
thead th { padding-left: 0; }
|
|
||||||
|
|
||||||
thead th:not(:first-child), tbody td {
|
|
||||||
width: ${remSize(54)};
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody th { font-weight: bold; };
|
|
||||||
|
|
||||||
tbody th {
|
tbody th {
|
||||||
font-size: ${remSize(12)};
|
font-size: ${remSize(16)};
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-right: ${remSize(12)}
|
padding-right: ${remSize(12)};
|
||||||
|
font-weight: bold;
|
||||||
|
display: flex;
|
||||||
|
grid-area: name;
|
||||||
};
|
};
|
||||||
|
|
||||||
tbody td {
|
tbody td, thead th {
|
||||||
text-align: center;
|
justify-self: stretch;
|
||||||
|
align-self: flex-end;
|
||||||
|
color: ${prop('primaryTextColor')}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sketch-list__sort-button { padding: 0 }
|
tbody td { justify-self: center; padding-left: ${remSize(12)}}
|
||||||
tbody {
|
|
||||||
height: ${remSize(48)};
|
thead th svg { margin-left: ${remSize(8)} }
|
||||||
|
|
||||||
|
tbody td:last-child { justify-self: end; text-align: end; };
|
||||||
|
.sketches-table .sketch-list__dropdown-column { min-width: unset };
|
||||||
|
|
||||||
|
tbody { height: ${remSize(48)}; }
|
||||||
|
|
||||||
|
.sketches-table-container {
|
||||||
|
padding-bottom: ${remSize(160)};
|
||||||
|
background: ${prop('SketchList.background')};
|
||||||
|
}
|
||||||
|
.sketches-table__row {
|
||||||
|
background: ${prop('SketchList.card.background')} !important; height: auto
|
||||||
}
|
}
|
||||||
|
|
||||||
.sketches-table-container { padding-bottom: ${remSize(160)} }
|
tr {
|
||||||
`;
|
align-self: start;
|
||||||
|
display: grid;
|
||||||
|
box-shadow: 0 0 18px 0 ${prop('shadowColor')};
|
||||||
|
};
|
||||||
|
|
||||||
const FooterTab = styled(Link)`
|
thead tr {
|
||||||
background: ${props => prop(props.selected ? 'backgroundColor' : 'MobilePanel.default.foreground')};
|
grid-template-columns: auto ${remSize(100)} ${remSize(100)} 0fr;
|
||||||
color: ${props => prop(`MobilePanel.default.${props.selected ? 'foreground' : 'background'}`)};
|
}
|
||||||
padding: ${remSize(8)} ${remSize(16)};
|
|
||||||
width: 100%;
|
tbody tr {
|
||||||
display: flex;
|
padding: ${remSize(8)};
|
||||||
|
border-radius: ${remSize(4)};
|
||||||
|
grid-template-columns: 5fr 5fr 2fr;
|
||||||
|
grid-template-areas: "name name name" "content content content";
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-container { position: fixed ; padding-bottom: 32% }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Subheader = styled.div`
|
const Subheader = styled.div`
|
||||||
|
@ -81,23 +104,17 @@ const SubheaderButton = styled(Button)`
|
||||||
border-radius: 0px !important;
|
border-radius: 0px !important;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
const FooterTabSwitcher = styled.div`
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
h3 { text-align: center; width: 100%; }
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Panels = {
|
const Panels = {
|
||||||
sketches: SketchList,
|
sketches: SketchList,
|
||||||
collections: CollectionList,
|
collections: CollectionList,
|
||||||
assets: AssetList
|
assets: AssetList
|
||||||
};
|
};
|
||||||
|
|
||||||
const CreatePathname = {
|
|
||||||
sketches: '/mobile',
|
const navOptions = username => [
|
||||||
collections: '/mobile/:username/collections/create',
|
{ title: 'Create Sketch', href: '/mobile' },
|
||||||
};
|
{ title: 'Create Collection', href: `/mobile/${username}/collections/create` }
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
const getPanel = (pathname) => {
|
const getPanel = (pathname) => {
|
||||||
|
@ -106,7 +123,10 @@ const getPanel = (pathname) => {
|
||||||
return matches && matches.length > 0 && matches[0];
|
return matches && matches.length > 0 && matches[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCreatePathname = (panel, username) => (CreatePathname[panel] || '#').replace(':username', username);
|
const NavItem = styled.li`
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
const isOwner = (user, params) => user && params && user.username === params.username;
|
const isOwner = (user, params) => user && params && user.username === params.username;
|
||||||
|
|
||||||
|
@ -114,27 +134,41 @@ const renderPanel = (name, props) => (Component => (Component && <Component {...
|
||||||
|
|
||||||
const MobileDashboard = ({ params, location }) => {
|
const MobileDashboard = ({ params, location }) => {
|
||||||
const user = useSelector(state => state.user);
|
const user = useSelector(state => state.user);
|
||||||
const { username } = params;
|
const { username: paramsUsername } = params;
|
||||||
const { pathname } = location;
|
const { pathname } = location;
|
||||||
|
|
||||||
const Tabs = Object.keys(Panels);
|
const Tabs = Object.keys(Panels);
|
||||||
const isExamples = username === EXAMPLE_USERNAME;
|
const isExamples = paramsUsername === EXAMPLE_USERNAME;
|
||||||
const panel = getPanel(pathname);
|
const panel = getPanel(pathname);
|
||||||
|
|
||||||
|
|
||||||
|
const [toggleNavDropdown, NavDropdown] = useAsModal(<Dropdown
|
||||||
|
items={navOptions(user.username)}
|
||||||
|
align="right"
|
||||||
|
/>);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen fullscreen key={pathname}>
|
<Screen fullscreen key={pathname}>
|
||||||
<Header slim inverted title={isExamples ? 'Examples' : 'My Stuff'}>
|
<Header slim inverted title={isExamples ? 'Examples' : 'My Stuff'}>
|
||||||
|
<NavItem>
|
||||||
|
<IconButton
|
||||||
|
onClick={toggleNavDropdown}
|
||||||
|
icon={MoreIcon}
|
||||||
|
aria-label="Options"
|
||||||
|
/>
|
||||||
|
<NavDropdown />
|
||||||
|
|
||||||
|
</NavItem>
|
||||||
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to ide view" />
|
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to ide view" />
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
|
|
||||||
<ContentWrapper slimheader>
|
<ContentWrapper slimheader>
|
||||||
<Subheader>
|
<Subheader>
|
||||||
{isOwner(user, params) && (panel !== Tabs[2]) && <SubheaderButton to={getCreatePathname(panel, username)}>new</SubheaderButton>}
|
|
||||||
{panel === Tabs[0] && <SketchSearchbar />}
|
{panel === Tabs[0] && <SketchSearchbar />}
|
||||||
{panel === Tabs[1] && <CollectionSearchbar />}
|
{panel === Tabs[1] && <CollectionSearchbar />}
|
||||||
</Subheader>
|
</Subheader>
|
||||||
{renderPanel(panel, { username, key: pathname })}
|
{renderPanel(panel, { username: paramsUsername, key: pathname })}
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
<Footer>
|
<Footer>
|
||||||
{!isExamples &&
|
{!isExamples &&
|
||||||
|
|
|
@ -6,5 +6,5 @@ import { remSize } from '../../theme';
|
||||||
export default styled.div`
|
export default styled.div`
|
||||||
/* Dashboard Styles */
|
/* Dashboard Styles */
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
margin-top: ${props => remSize(props.slimheader ? 50 : 68)};
|
margin-top: ${props => remSize(props.slimheader ? 49 : 68)};
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -58,6 +58,7 @@ export const prop = key => (props) => {
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
[Theme.light]: {
|
[Theme.light]: {
|
||||||
colors,
|
colors,
|
||||||
|
@ -103,6 +104,14 @@ export default {
|
||||||
border: grays.middleLight
|
border: grays.middleLight
|
||||||
},
|
},
|
||||||
Separator: grays.middleLight,
|
Separator: grays.middleLight,
|
||||||
|
|
||||||
|
TabHighlight: colors.p5jsPink,
|
||||||
|
SketchList: {
|
||||||
|
background: grays.lighter,
|
||||||
|
card: {
|
||||||
|
background: grays.lighter
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[Theme.dark]: {
|
[Theme.dark]: {
|
||||||
colors,
|
colors,
|
||||||
|
@ -148,6 +157,14 @@ export default {
|
||||||
border: grays.middleDark
|
border: grays.middleDark
|
||||||
},
|
},
|
||||||
Separator: grays.middleDark,
|
Separator: grays.middleDark,
|
||||||
|
|
||||||
|
TabHighlight: colors.p5jsPink,
|
||||||
|
SketchList: {
|
||||||
|
background: grays.darker,
|
||||||
|
card: {
|
||||||
|
background: grays.dark
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[Theme.contrast]: {
|
[Theme.contrast]: {
|
||||||
colors,
|
colors,
|
||||||
|
@ -193,5 +210,13 @@ export default {
|
||||||
border: grays.middleDark
|
border: grays.middleDark
|
||||||
},
|
},
|
||||||
Separator: grays.middleDark,
|
Separator: grays.middleDark,
|
||||||
|
|
||||||
|
TabHighlight: grays.darker,
|
||||||
|
SketchList: {
|
||||||
|
background: colors.yellow,
|
||||||
|
card: {
|
||||||
|
background: grays.dark
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "p5.js-web-editor",
|
"name": "p5.js-web-editor",
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "p5.js-web-editor",
|
"name": "p5.js-web-editor",
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"description": "The web editor for p5.js.",
|
"description": "The web editor for p5.js.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf dist",
|
"clean": "rimraf dist",
|
||||||
|
|
|
@ -30,14 +30,15 @@ export function updateProject(req, res) {
|
||||||
$set: req.body
|
$set: req.body
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true
|
new: true,
|
||||||
|
runValidators: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.populate('user', 'username')
|
.populate('user', 'username')
|
||||||
.exec((updateProjectErr, updatedProject) => {
|
.exec((updateProjectErr, updatedProject) => {
|
||||||
if (updateProjectErr) {
|
if (updateProjectErr) {
|
||||||
console.log(updateProjectErr);
|
console.log(updateProjectErr);
|
||||||
res.json({ success: false });
|
res.status(400).json({ success: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (req.body.files && updatedProject.files.length !== req.body.files.length) {
|
if (req.body.files && updatedProject.files.length !== req.body.files.length) {
|
||||||
|
@ -50,7 +51,7 @@ export function updateProject(req, res) {
|
||||||
updatedProject.save((innerErr, savedProject) => {
|
updatedProject.save((innerErr, savedProject) => {
|
||||||
if (innerErr) {
|
if (innerErr) {
|
||||||
console.log(innerErr);
|
console.log(innerErr);
|
||||||
res.json({ success: false });
|
res.status(400).json({ success: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
res.json(savedProject);
|
res.json(savedProject);
|
||||||
|
|
|
@ -9,7 +9,7 @@ export default function createProject(req, res) {
|
||||||
projectValues = Object.assign(projectValues, req.body);
|
projectValues = Object.assign(projectValues, req.body);
|
||||||
|
|
||||||
function sendFailure() {
|
function sendFailure() {
|
||||||
res.json({ success: false });
|
res.status(400).json({ success: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateUserData(newProject) {
|
function populateUserData(newProject) {
|
||||||
|
|
|
@ -29,7 +29,7 @@ fileSchema.set('toJSON', {
|
||||||
|
|
||||||
const projectSchema = new Schema(
|
const projectSchema = new Schema(
|
||||||
{
|
{
|
||||||
name: { type: String, default: "Hello p5.js, it's the server" },
|
name: { type: String, default: "Hello p5.js, it's the server", maxlength: 128 },
|
||||||
user: { type: Schema.Types.ObjectId, ref: 'User' },
|
user: { type: Schema.Types.ObjectId, ref: 'User' },
|
||||||
serveSecure: { type: Boolean, default: false },
|
serveSecure: { type: Boolean, default: false },
|
||||||
files: { type: [fileSchema] },
|
files: { type: [fileSchema] },
|
||||||
|
|
|
@ -173,5 +173,26 @@
|
||||||
},
|
},
|
||||||
"IDEView": {
|
"IDEView": {
|
||||||
"SubmitFeedback": "Submit Feedback"
|
"SubmitFeedback": "Submit Feedback"
|
||||||
|
},
|
||||||
|
"NewFileModal": {
|
||||||
|
"Title": "Create File",
|
||||||
|
"CloseButtonARIA": "Close New File Modal",
|
||||||
|
"EnterName": "Please enter a name",
|
||||||
|
"InvalidType": "Invalid file type. Valid extensions are .js, .css, .json, .txt, .csv, .tsv, .frag, and .vert."
|
||||||
|
},
|
||||||
|
"NewFileForm": {
|
||||||
|
"AddFileSubmit": "Add File",
|
||||||
|
"Placeholder": "Name"
|
||||||
|
},
|
||||||
|
"NewFolderModal": {
|
||||||
|
"Title": "Create Folder",
|
||||||
|
"CloseButtonARIA": "Close New Folder Modal",
|
||||||
|
"EnterName": "Please enter a name",
|
||||||
|
"EmptyName": "Folder name cannot contain only spaces",
|
||||||
|
"InvalidExtension": "Folder name cannot contain an extension"
|
||||||
|
},
|
||||||
|
"NewFolderForm": {
|
||||||
|
"AddFolderSubmit": "Add Folder",
|
||||||
|
"Placeholder": "Name"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,6 +172,27 @@
|
||||||
},
|
},
|
||||||
"IDEView": {
|
"IDEView": {
|
||||||
"SubmitFeedback": "Enviar retroalimentación"
|
"SubmitFeedback": "Enviar retroalimentación"
|
||||||
|
},
|
||||||
|
"NewFileModal": {
|
||||||
|
"Title": "Crear Archivo",
|
||||||
|
"CloseButtonARIA": "Cerrar diálogo de crear archivo",
|
||||||
|
"EnterName": "Por favor introduce un nombre",
|
||||||
|
"InvalidType": "Tipo de archivo inválido. Las extensiones válidas son .js, .css, .json, .txt, .csv, .tsv, .frag y .vert."
|
||||||
|
},
|
||||||
|
"NewFileForm": {
|
||||||
|
"AddFileSubmit": "Agregar Archivo",
|
||||||
|
"Placeholder": "Nombre"
|
||||||
|
},
|
||||||
|
"NewFolderModal": {
|
||||||
|
"Title": "Crear Directorio",
|
||||||
|
"CloseButtonARIA": "Cerrar Diálogo de Nuevo Directorio",
|
||||||
|
"EnterName": "Por favor introduce un nombre",
|
||||||
|
"EmptyName": " El nombre del directorio no debe contener solo espacios vacíos",
|
||||||
|
"InvalidExtension": "El nombre del directorio no debe contener una extensión"
|
||||||
|
},
|
||||||
|
"NewFolderForm": {
|
||||||
|
"AddFolderSubmit": "Agregar Directorio",
|
||||||
|
"Placeholder": "Nombre"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue