🔀 merge from develop
This commit is contained in:
commit
384a185028
44 changed files with 842 additions and 292 deletions
|
@ -36,9 +36,7 @@ const DropdownWrapper = styled.ul`
|
||||||
background-color: ${prop('Button.hover.background')};
|
background-color: ${prop('Button.hover.background')};
|
||||||
color: ${prop('Button.hover.foreground')};
|
color: ${prop('Button.hover.foreground')};
|
||||||
|
|
||||||
& button, & a {
|
* { color: ${prop('Button.hover.foreground')}; }
|
||||||
color: ${prop('Button.hover.foreground')};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
|
@ -48,12 +46,21 @@ const DropdownWrapper = styled.ul`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
& button,
|
& button,
|
||||||
|
& button span,
|
||||||
& a {
|
& a {
|
||||||
color: ${prop('primaryTextColor')};
|
|
||||||
width: 100%;
|
|
||||||
text-align: left;
|
|
||||||
padding: ${remSize(8)} ${remSize(16)};
|
padding: ${remSize(8)} ${remSize(16)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
text-align: left;
|
||||||
|
justify-content: left;
|
||||||
|
|
||||||
|
color: ${prop('primaryTextColor')};
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
& button span { padding: 0px }
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -63,24 +70,29 @@ const DropdownWrapper = styled.ul`
|
||||||
const Dropdown = ({ items, align }) => (
|
const Dropdown = ({ items, align }) => (
|
||||||
<DropdownWrapper align={align} >
|
<DropdownWrapper align={align} >
|
||||||
{/* className="nav__items-left" */}
|
{/* className="nav__items-left" */}
|
||||||
{items && items.map(({ title, icon, href }) => (
|
{items && items.map(({
|
||||||
|
title, icon, href, action
|
||||||
|
}) => (
|
||||||
<li key={`nav-${title && title.toLowerCase()}`}>
|
<li key={`nav-${title && title.toLowerCase()}`}>
|
||||||
<Link to={href}>
|
|
||||||
{/* {MaybeIcon(icon, `Navigate to ${title}`)} */}
|
{/* {MaybeIcon(icon, `Navigate to ${title}`)} */}
|
||||||
{title}
|
{href
|
||||||
</Link>
|
? <IconButton to={href}>{title}</IconButton>
|
||||||
|
: <IconButton onClick={() => action()}>{title}</IconButton>}
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</DropdownWrapper>
|
</DropdownWrapper>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
Dropdown.propTypes = {
|
Dropdown.propTypes = {
|
||||||
align: PropTypes.oneOf(['left', 'right']),
|
align: PropTypes.oneOf(['left', 'right']),
|
||||||
items: PropTypes.arrayOf(PropTypes.shape({
|
items: PropTypes.arrayOf(PropTypes.shape({
|
||||||
action: PropTypes.func,
|
action: PropTypes.func,
|
||||||
icon: PropTypes.func,
|
icon: PropTypes.func,
|
||||||
href: PropTypes.string
|
href: PropTypes.string,
|
||||||
|
title: PropTypes.string
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ const IconButton = (props) => {
|
||||||
const Icon = icon;
|
const Icon = icon;
|
||||||
|
|
||||||
return (<ButtonWrapper
|
return (<ButtonWrapper
|
||||||
iconBefore={<Icon />}
|
iconBefore={icon && <Icon />}
|
||||||
kind={Button.kinds.inline}
|
kind={Button.kinds.inline}
|
||||||
focusable="false"
|
focusable="false"
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
|
@ -25,7 +25,11 @@ const IconButton = (props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
IconButton.propTypes = {
|
IconButton.propTypes = {
|
||||||
icon: PropTypes.func.isRequired
|
icon: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
IconButton.defaultProps = {
|
||||||
|
icon: null
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IconButton;
|
export default IconButton;
|
||||||
|
|
|
@ -62,6 +62,7 @@ export const COLLAPSE_CONSOLE = 'COLLAPSE_CONSOLE';
|
||||||
|
|
||||||
export const UPDATE_LINT_MESSAGE = 'UPDATE_LINT_MESSAGE';
|
export const UPDATE_LINT_MESSAGE = 'UPDATE_LINT_MESSAGE';
|
||||||
export const CLEAR_LINT_MESSAGE = 'CLEAR_LINT_MESSAGE';
|
export const CLEAR_LINT_MESSAGE = 'CLEAR_LINT_MESSAGE';
|
||||||
|
export const TOGGLE_FORCE_DESKTOP = 'TOGGLE_FORCE_DESKTOP';
|
||||||
|
|
||||||
export const UPDATE_FILE_NAME = 'UPDATE_FILE_NAME';
|
export const UPDATE_FILE_NAME = 'UPDATE_FILE_NAME';
|
||||||
export const DELETE_FILE = 'DELETE_FILE';
|
export const DELETE_FILE = 'DELETE_FILE';
|
||||||
|
|
|
@ -3,3 +3,21 @@ import '@babel/polyfill';
|
||||||
// See: https://github.com/testing-library/jest-dom
|
// See: https://github.com/testing-library/jest-dom
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
|
|
||||||
|
import lodash from 'lodash';
|
||||||
|
|
||||||
|
// For testing, we use en-US and provide a mock implementation
|
||||||
|
// of t() that finds the correct translation
|
||||||
|
import translations from '../translations/locales/en-US/translations.json';
|
||||||
|
|
||||||
|
// This function name needs to be prefixed with "mock" so that Jest doesn't
|
||||||
|
// complain that it's out-of-scope in the mock below
|
||||||
|
const mockTranslate = key => lodash.get(translations, key);
|
||||||
|
|
||||||
|
jest.mock('react-i18next', () => ({
|
||||||
|
// this mock makes sure any components using the translate HoC receive the t function as a prop
|
||||||
|
withTranslation: () => (Component) => {
|
||||||
|
Component.defaultProps = { ...Component.defaultProps, t: mockTranslate };
|
||||||
|
return Component;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import ExitIcon from '../../../images/exit.svg';
|
import ExitIcon from '../../../images/exit.svg';
|
||||||
|
|
||||||
|
@ -80,7 +81,7 @@ class Overlay extends React.Component {
|
||||||
<h2 className="overlay__title">{title}</h2>
|
<h2 className="overlay__title">{title}</h2>
|
||||||
<div className="overlay__actions">
|
<div className="overlay__actions">
|
||||||
{actions}
|
{actions}
|
||||||
<button className="overlay__close-button" onClick={this.close} aria-label={`Close ${title} overlay`} >
|
<button className="overlay__close-button" onClick={this.close} aria-label={this.props.t('Overlay.AriaLabel', { title })}>
|
||||||
<ExitIcon focusable="false" aria-hidden="true" />
|
<ExitIcon focusable="false" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -101,6 +102,7 @@ Overlay.propTypes = {
|
||||||
ariaLabel: PropTypes.string,
|
ariaLabel: PropTypes.string,
|
||||||
previousPath: PropTypes.string,
|
previousPath: PropTypes.string,
|
||||||
isFixedHeight: PropTypes.bool,
|
isFixedHeight: PropTypes.bool,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
Overlay.defaultProps = {
|
Overlay.defaultProps = {
|
||||||
|
@ -113,4 +115,4 @@ Overlay.defaultProps = {
|
||||||
isFixedHeight: false,
|
isFixedHeight: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Overlay;
|
export default withTranslation()(Overlay);
|
||||||
|
|
|
@ -14,3 +14,9 @@ export function clearLintMessage() {
|
||||||
type: ActionTypes.CLEAR_LINT_MESSAGE
|
type: ActionTypes.CLEAR_LINT_MESSAGE
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toggleForceDesktop() {
|
||||||
|
return {
|
||||||
|
type: ActionTypes.TOGGLE_FORCE_DESKTOP
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import * as ProjectActions from '../actions/project';
|
import * as ProjectActions from '../actions/project';
|
||||||
import * as ProjectsActions from '../actions/projects';
|
import * as ProjectsActions from '../actions/projects';
|
||||||
|
@ -14,7 +15,7 @@ import Loader from '../../App/components/loader';
|
||||||
import QuickAddList from './QuickAddList';
|
import QuickAddList from './QuickAddList';
|
||||||
|
|
||||||
const projectInCollection = (project, collection) =>
|
const projectInCollection = (project, collection) =>
|
||||||
collection.items.find(item => item.project.id === project.id) != null;
|
collection.items.find(item => item.projectId === project.id) != null;
|
||||||
|
|
||||||
class CollectionList extends React.Component {
|
class CollectionList extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -42,9 +43,9 @@ class CollectionList extends React.Component {
|
||||||
|
|
||||||
getTitle() {
|
getTitle() {
|
||||||
if (this.props.username === this.props.user.username) {
|
if (this.props.username === this.props.user.username) {
|
||||||
return 'p5.js Web Editor | My collections';
|
return this.props.t('AddToCollectionList.Title');
|
||||||
}
|
}
|
||||||
return `p5.js Web Editor | ${this.props.username}'s collections`;
|
return this.props.t('AddToCollectionList.AnothersTitle', { anotheruser: this.props.username });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCollectionAdd = (collection) => {
|
handleCollectionAdd = (collection) => {
|
||||||
|
@ -74,13 +75,15 @@ class CollectionList extends React.Component {
|
||||||
items={collectionWithSketchStatus}
|
items={collectionWithSketchStatus}
|
||||||
onAdd={this.handleCollectionAdd}
|
onAdd={this.handleCollectionAdd}
|
||||||
onRemove={this.handleCollectionRemove}
|
onRemove={this.handleCollectionRemove}
|
||||||
|
t={this.props.t}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
content = 'No collections';
|
content = this.props.t('AddToCollectionList.Empty');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="collection-add-sketch">
|
||||||
<div className="quick-add-wrapper">
|
<div className="quick-add-wrapper">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{this.getTitle()}</title>
|
<title>{this.getTitle()}</title>
|
||||||
|
@ -88,6 +91,7 @@ class CollectionList extends React.Component {
|
||||||
|
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +137,8 @@ CollectionList.propTypes = {
|
||||||
owner: PropTypes.shape({
|
owner: PropTypes.shape({
|
||||||
id: PropTypes.string
|
id: PropTypes.string
|
||||||
})
|
})
|
||||||
})
|
}),
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
CollectionList.defaultProps = {
|
CollectionList.defaultProps = {
|
||||||
|
@ -162,4 +167,4 @@ function mapDispatchToProps(dispatch) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(CollectionList);
|
export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(CollectionList));
|
||||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
// import find from 'lodash/find';
|
// import find from 'lodash/find';
|
||||||
import * as ProjectsActions from '../actions/projects';
|
import * as ProjectsActions from '../actions/projects';
|
||||||
import * as CollectionsActions from '../actions/collections';
|
import * as CollectionsActions from '../actions/collections';
|
||||||
|
@ -32,9 +33,9 @@ class SketchList extends React.Component {
|
||||||
|
|
||||||
getSketchesTitle() {
|
getSketchesTitle() {
|
||||||
if (this.props.username === this.props.user.username) {
|
if (this.props.username === this.props.user.username) {
|
||||||
return 'p5.js Web Editor | My sketches';
|
return this.props.t('AddToCollectionSketchList.Title');
|
||||||
}
|
}
|
||||||
return `p5.js Web Editor | ${this.props.username}'s sketches`;
|
return this.props.t('AddToCollectionSketchList.AnothersTitle', { anotheruser: this.props.username });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCollectionAdd = (sketch) => {
|
handleCollectionAdd = (sketch) => {
|
||||||
|
@ -68,16 +69,18 @@ class SketchList extends React.Component {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
content = 'No collections';
|
content = this.props.t('AddToCollectionSketchList.NoCollections');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="collection-add-sketch">
|
||||||
<div className="quick-add-wrapper">
|
<div className="quick-add-wrapper">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{this.getSketchesTitle()}</title>
|
<title>{this.getSketchesTitle()}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,6 +114,7 @@ SketchList.propTypes = {
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
addToCollection: PropTypes.func.isRequired,
|
addToCollection: PropTypes.func.isRequired,
|
||||||
removeFromCollection: PropTypes.func.isRequired,
|
removeFromCollection: PropTypes.func.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
SketchList.defaultProps = {
|
SketchList.defaultProps = {
|
||||||
|
@ -134,4 +138,4 @@ function mapDispatchToProps(dispatch) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(SketchList);
|
export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(SketchList));
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
@ -50,9 +51,9 @@ class CollectionList extends React.Component {
|
||||||
|
|
||||||
getTitle() {
|
getTitle() {
|
||||||
if (this.props.username === this.props.user.username) {
|
if (this.props.username === this.props.user.username) {
|
||||||
return 'p5.js Web Editor | My collections';
|
return this.props.t('CollectionList.Title');
|
||||||
}
|
}
|
||||||
return `p5.js Web Editor | ${this.props.username}'s collections`;
|
return this.props.t('CollectionList.AnothersTitle', { anotheruser: this.props.username });
|
||||||
}
|
}
|
||||||
|
|
||||||
showAddSketches = (collectionId) => {
|
showAddSketches = (collectionId) => {
|
||||||
|
@ -78,7 +79,7 @@ class CollectionList extends React.Component {
|
||||||
|
|
||||||
_renderEmptyTable() {
|
_renderEmptyTable() {
|
||||||
if (!this.props.loading && this.props.collections.length === 0) {
|
if (!this.props.loading && this.props.collections.length === 0) {
|
||||||
return (<p className="sketches-table__empty">No collections.</p>);
|
return (<p className="sketches-table__empty">{this.props.t('CollectionList.NoCollections')}</p>);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -88,14 +89,14 @@ class CollectionList extends React.Component {
|
||||||
let buttonLabel;
|
let buttonLabel;
|
||||||
if (field !== fieldName) {
|
if (field !== fieldName) {
|
||||||
if (field === 'name') {
|
if (field === 'name') {
|
||||||
buttonLabel = `Sort by ${displayName} ascending.`;
|
buttonLabel = this.props.t('CollectionList.ButtonLabelAscendingARIA', { displayName });
|
||||||
} else {
|
} else {
|
||||||
buttonLabel = `Sort by ${displayName} descending.`;
|
buttonLabel = this.props.t('CollectionList.ButtonLabelDescendingARIA', { displayName });
|
||||||
}
|
}
|
||||||
} else if (direction === SortingActions.DIRECTION.ASC) {
|
} else if (direction === SortingActions.DIRECTION.ASC) {
|
||||||
buttonLabel = `Sort by ${displayName} descending.`;
|
buttonLabel = this.props.t('CollectionList.ButtonLabelDescendingARIA', { displayName });
|
||||||
} else {
|
} else {
|
||||||
buttonLabel = `Sort by ${displayName} ascending.`;
|
buttonLabel = this.props.t('CollectionList.ButtonLabelAscendingARIA', { displayName });
|
||||||
}
|
}
|
||||||
return buttonLabel;
|
return buttonLabel;
|
||||||
}
|
}
|
||||||
|
@ -116,10 +117,10 @@ class CollectionList extends React.Component {
|
||||||
>
|
>
|
||||||
<span className={headerClass}>{displayName}</span>
|
<span className={headerClass}>{displayName}</span>
|
||||||
{field === fieldName && direction === SortingActions.DIRECTION.ASC &&
|
{field === fieldName && direction === SortingActions.DIRECTION.ASC &&
|
||||||
<ArrowUpIcon role="img" aria-label="Ascending" focusable="false" />
|
<ArrowUpIcon role="img" aria-label={this.props.t('CollectionList.DirectionAscendingARIA')} focusable="false" />
|
||||||
}
|
}
|
||||||
{field === fieldName && direction === SortingActions.DIRECTION.DESC &&
|
{field === fieldName && direction === SortingActions.DIRECTION.DESC &&
|
||||||
<ArrowDownIcon role="img" aria-label="Descending" focusable="false" />
|
<ArrowDownIcon role="img" aria-label={this.props.t('CollectionList.DirectionDescendingARIA')} focusable="false" />
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</th>
|
</th>
|
||||||
|
@ -142,10 +143,10 @@ class CollectionList extends React.Component {
|
||||||
<table className="sketches-table" summary="table containing all collections">
|
<table className="sketches-table" summary="table containing all collections">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{this._renderFieldHeader('name', 'Name')}
|
{this._renderFieldHeader('name', this.props.t('CollectionList.HeaderName'))}
|
||||||
{this._renderFieldHeader('createdAt', `${mobile ? '' : 'Date '}Created`)}
|
{this._renderFieldHeader('createdAt', this.props.t('CollectionList.HeaderCreatedAt', { context: mobile ? 'mobile' : '' }))}
|
||||||
{this._renderFieldHeader('updatedAt', `${mobile ? '' : 'Date '}Updated`)}
|
{this._renderFieldHeader('updatedAt', this.props.t('CollectionList.HeaderUpdatedAt', { context: mobile ? 'mobile' : '' }))}
|
||||||
{this._renderFieldHeader('numItems', mobile ? 'Sketches' : '# sketches')}
|
{this._renderFieldHeader('numItems', this.props.t('CollectionList.HeaderNumItems', { context: mobile ? 'mobile' : '' }))}
|
||||||
<th scope="col"></th>
|
<th scope="col"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -165,17 +166,15 @@ class CollectionList extends React.Component {
|
||||||
{
|
{
|
||||||
this.state.addingSketchesToCollectionId && (
|
this.state.addingSketchesToCollectionId && (
|
||||||
<Overlay
|
<Overlay
|
||||||
title="Add sketch"
|
title={this.props.t('CollectionList.AddSketch')}
|
||||||
actions={<SketchSearchbar />}
|
actions={<SketchSearchbar />}
|
||||||
closeOverlay={this.hideAddSketches}
|
closeOverlay={this.hideAddSketches}
|
||||||
isFixedHeight
|
isFixedHeight
|
||||||
>
|
>
|
||||||
<div className="collection-add-sketch">
|
|
||||||
<AddToCollectionSketchList
|
<AddToCollectionSketchList
|
||||||
username={this.props.username}
|
username={this.props.username}
|
||||||
collection={find(this.props.collections, { id: this.state.addingSketchesToCollectionId })}
|
collection={find(this.props.collections, { id: this.state.addingSketchesToCollectionId })}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</Overlay>
|
</Overlay>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -213,6 +212,7 @@ CollectionList.propTypes = {
|
||||||
id: PropTypes.string
|
id: PropTypes.string
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
t: PropTypes.func.isRequired,
|
||||||
mobile: PropTypes.bool,
|
mobile: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -244,4 +244,4 @@ function mapDispatchToProps(dispatch) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(CollectionList);
|
export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(CollectionList));
|
||||||
|
|
|
@ -4,6 +4,7 @@ import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import * as ProjectActions from '../../actions/project';
|
import * as ProjectActions from '../../actions/project';
|
||||||
import * as CollectionsActions from '../../actions/collections';
|
import * as CollectionsActions from '../../actions/collections';
|
||||||
import * as IdeActions from '../../actions/ide';
|
import * as IdeActions from '../../actions/ide';
|
||||||
|
@ -81,7 +82,7 @@ class CollectionListRowBase extends React.Component {
|
||||||
|
|
||||||
handleCollectionDelete = () => {
|
handleCollectionDelete = () => {
|
||||||
this.closeAll();
|
this.closeAll();
|
||||||
if (window.confirm(`Are you sure you want to delete "${this.props.collection.name}"?`)) {
|
if (window.confirm(this.props.t('Common.DeleteConfirmation', { name: this.props.collection.name }))) {
|
||||||
this.props.deleteCollection(this.props.collection.id);
|
this.props.deleteCollection(this.props.collection.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,7 +131,7 @@ class CollectionListRowBase extends React.Component {
|
||||||
onClick={this.toggleOptions}
|
onClick={this.toggleOptions}
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
aria-label="Toggle Open/Close collection options"
|
aria-label={this.props.t('CollectionListRow.ToggleCollectionOptionsARIA')}
|
||||||
>
|
>
|
||||||
<DownFilledTriangleIcon title="Menu" />
|
<DownFilledTriangleIcon title="Menu" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -145,7 +146,7 @@ class CollectionListRowBase extends React.Component {
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
>
|
>
|
||||||
Add sketch
|
{this.props.t('CollectionListRow.AddSketch')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{userIsOwner &&
|
{userIsOwner &&
|
||||||
|
@ -156,7 +157,7 @@ class CollectionListRowBase extends React.Component {
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
>
|
>
|
||||||
Delete
|
{this.props.t('CollectionListRow.Delete')}
|
||||||
</button>
|
</button>
|
||||||
</li>}
|
</li>}
|
||||||
{userIsOwner &&
|
{userIsOwner &&
|
||||||
|
@ -167,7 +168,7 @@ class CollectionListRowBase extends React.Component {
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
>
|
>
|
||||||
Rename
|
{this.props.t('CollectionListRow.Rename')}
|
||||||
</button>
|
</button>
|
||||||
</li>}
|
</li>}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -248,6 +249,7 @@ CollectionListRowBase.propTypes = {
|
||||||
editCollection: PropTypes.func.isRequired,
|
editCollection: PropTypes.func.isRequired,
|
||||||
onAddSketches: PropTypes.func.isRequired,
|
onAddSketches: PropTypes.func.isRequired,
|
||||||
mobile: PropTypes.bool,
|
mobile: PropTypes.bool,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
CollectionListRowBase.defaultProps = {
|
CollectionListRowBase.defaultProps = {
|
||||||
|
@ -258,4 +260,4 @@ function mapDispatchToPropsSketchListRow(dispatch) {
|
||||||
return bindActionCreators(Object.assign({}, CollectionsActions, ProjectActions, IdeActions, ToastActions), dispatch);
|
return bindActionCreators(Object.assign({}, CollectionsActions, ProjectActions, IdeActions, ToastActions), dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(null, mapDispatchToPropsSketchListRow)(CollectionListRowBase);
|
export default withTranslation()(connect(null, mapDispatchToPropsSketchListRow)(CollectionListRowBase));
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
|
@ -72,7 +74,7 @@ const getConsoleFeedStyle = (theme, times, fontSize) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Console = () => {
|
const Console = ({ t }) => {
|
||||||
const consoleEvents = useSelector(state => state.console);
|
const consoleEvents = useSelector(state => state.console);
|
||||||
const isExpanded = useSelector(state => state.ide.consoleIsExpanded);
|
const isExpanded = useSelector(state => state.ide.consoleIsExpanded);
|
||||||
const { theme, fontSize } = useSelector(state => state.preferences);
|
const { theme, fontSize } = useSelector(state => state.preferences);
|
||||||
|
@ -98,19 +100,19 @@ const Console = () => {
|
||||||
return (
|
return (
|
||||||
<section className={consoleClass} >
|
<section className={consoleClass} >
|
||||||
<header className="preview-console__header">
|
<header className="preview-console__header">
|
||||||
<h2 className="preview-console__header-title">Console</h2>
|
<h2 className="preview-console__header-title">{t('Console.Title')}</h2>
|
||||||
<div className="preview-console__header-buttons">
|
<div className="preview-console__header-buttons">
|
||||||
<button className="preview-console__clear" onClick={clearConsole} aria-label="Clear console">
|
<button className="preview-console__clear" onClick={clearConsole} aria-label={t('Console.ClearARIA')}>
|
||||||
Clear
|
{t('Console.Clear')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="preview-console__collapse"
|
className="preview-console__collapse"
|
||||||
onClick={collapseConsole}
|
onClick={collapseConsole}
|
||||||
aria-label="Close console"
|
aria-label={t('Console.CloseARIA')}
|
||||||
>
|
>
|
||||||
<DownArrowIcon focusable="false" aria-hidden="true" />
|
<DownArrowIcon focusable="false" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
<button className="preview-console__expand" onClick={expandConsole} aria-label="Open console" >
|
<button className="preview-console__expand" onClick={expandConsole} aria-label={t('Console.OpenARIA')} >
|
||||||
<UpArrowIcon focusable="false" aria-hidden="true" />
|
<UpArrowIcon focusable="false" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -140,5 +142,9 @@ const Console = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Console.propTypes = {
|
||||||
|
t: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
export default Console;
|
|
||||||
|
export default withTranslation()(Console);
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
|
|
||||||
class ErrorModal extends React.Component {
|
class ErrorModal extends React.Component {
|
||||||
forceAuthentication() {
|
forceAuthentication() {
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
In order to save sketches, you must be logged in. Please
|
{this.props.t('ErrorModal.MessageLogin')}
|
||||||
<Link to="/login" onClick={this.props.closeModal}>Login</Link>
|
<Link to="/login" onClick={this.props.closeModal}> {this.props.t('ErrorModal.Login')}</Link>
|
||||||
or
|
{this.props.t('ErrorModal.LoginOr')}
|
||||||
<Link to="/signup" onClick={this.props.closeModal}>Sign Up</Link>.
|
<Link to="/signup" onClick={this.props.closeModal}>{this.props.t('ErrorModal.SignUp')}</Link>.
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -17,8 +18,8 @@ class ErrorModal extends React.Component {
|
||||||
staleSession() {
|
staleSession() {
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
It looks like you've been logged out. Please
|
{this.props.t('ErrorModal.MessageLoggedOut')}
|
||||||
<Link to="/login" onClick={this.props.closeModal}>log in</Link>.
|
<Link to="/login" onClick={this.props.closeModal}>{this.props.t('ErrorModal.LogIn')}</Link>.
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -26,8 +27,7 @@ class ErrorModal extends React.Component {
|
||||||
staleProject() {
|
staleProject() {
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
The project you have attempted to save has been saved from another window.
|
{this.props.t('ErrorModal.SavedDifferentWindow')}
|
||||||
Please refresh the page to see the latest version.
|
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,8 @@ class ErrorModal extends React.Component {
|
||||||
|
|
||||||
ErrorModal.propTypes = {
|
ErrorModal.propTypes = {
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
closeModal: PropTypes.func.isRequired
|
closeModal: PropTypes.func.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ErrorModal;
|
export default withTranslation()(ErrorModal);
|
||||||
|
|
|
@ -3,6 +3,8 @@ import React from 'react';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import * as IDEActions from '../actions/ide';
|
import * as IDEActions from '../actions/ide';
|
||||||
import * as FileActions from '../actions/files';
|
import * as FileActions from '../actions/files';
|
||||||
import DownArrowIcon from '../../../images/down-filled-triangle.svg';
|
import DownArrowIcon from '../../../images/down-filled-triangle.svg';
|
||||||
|
@ -152,7 +154,9 @@ export class FileNode extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClickDelete = () => {
|
handleClickDelete = () => {
|
||||||
if (window.confirm(`Are you sure you want to delete ${this.props.name}?`)) {
|
const prompt = this.props.t('Common.DeleteConfirmation', { name: this.props.name });
|
||||||
|
|
||||||
|
if (window.confirm(prompt)) {
|
||||||
this.setState({ isDeleting: true });
|
this.setState({ isDeleting: true });
|
||||||
this.props.resetSelectedFile(this.props.id);
|
this.props.resetSelectedFile(this.props.id);
|
||||||
setTimeout(() => this.props.deleteFile(this.props.id, this.props.parentId), 100);
|
setTimeout(() => this.props.deleteFile(this.props.id, this.props.parentId), 100);
|
||||||
|
@ -237,6 +241,8 @@ export class FileNode extends React.Component {
|
||||||
const isFolder = this.props.fileType === 'folder';
|
const isFolder = this.props.fileType === 'folder';
|
||||||
const isRoot = this.props.name === 'root';
|
const isRoot = this.props.name === 'root';
|
||||||
|
|
||||||
|
const { t } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={itemClass} >
|
<div className={itemClass} >
|
||||||
{ !isRoot &&
|
{ !isRoot &&
|
||||||
|
@ -252,14 +258,14 @@ export class FileNode extends React.Component {
|
||||||
<button
|
<button
|
||||||
className="sidebar__file-item-closed"
|
className="sidebar__file-item-closed"
|
||||||
onClick={this.showFolderChildren}
|
onClick={this.showFolderChildren}
|
||||||
aria-label="Open folder contents"
|
aria-label={t('FileNode.OpenFolderARIA')}
|
||||||
>
|
>
|
||||||
<FolderRightIcon className="folder-right" focusable="false" aria-hidden="true" />
|
<FolderRightIcon className="folder-right" focusable="false" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="sidebar__file-item-open"
|
className="sidebar__file-item-open"
|
||||||
onClick={this.hideFolderChildren}
|
onClick={this.hideFolderChildren}
|
||||||
aria-label="Close file contents"
|
aria-label={t('FileNode.CloseFolderARIA')}
|
||||||
>
|
>
|
||||||
<FolderDownIcon className="folder-down" focusable="false" aria-hidden="true" />
|
<FolderDownIcon className="folder-down" focusable="false" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -286,7 +292,7 @@ export class FileNode extends React.Component {
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className="sidebar__file-item-show-options"
|
className="sidebar__file-item-show-options"
|
||||||
aria-label="Toggle open/close file options"
|
aria-label={t('FileNode.ToggleFileOptionsARIA')}
|
||||||
ref={(element) => { this[`fileOptions-${this.props.id}`] = element; }}
|
ref={(element) => { this[`fileOptions-${this.props.id}`] = element; }}
|
||||||
tabIndex="0"
|
tabIndex="0"
|
||||||
onClick={this.toggleFileOptions}
|
onClick={this.toggleFileOptions}
|
||||||
|
@ -301,35 +307,35 @@ export class FileNode extends React.Component {
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
aria-label="add folder"
|
aria-label={t('FileNode.AddFolderARIA')}
|
||||||
onClick={this.handleClickAddFolder}
|
onClick={this.handleClickAddFolder}
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
className="sidebar__file-item-option"
|
className="sidebar__file-item-option"
|
||||||
>
|
>
|
||||||
Create folder
|
{t('FileNode.AddFolder')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
aria-label="add file"
|
aria-label={t('FileNode.AddFileARIA')}
|
||||||
onClick={this.handleClickAddFile}
|
onClick={this.handleClickAddFile}
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
className="sidebar__file-item-option"
|
className="sidebar__file-item-option"
|
||||||
>
|
>
|
||||||
Create file
|
{t('FileNode.AddFile')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{ this.props.authenticated &&
|
{ this.props.authenticated &&
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
aria-label="upload file"
|
aria-label={t('FileNode.UploadFileARIA')}
|
||||||
onClick={this.handleClickUploadFile}
|
onClick={this.handleClickUploadFile}
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
>
|
>
|
||||||
Upload file
|
{t('FileNode.UploadFile')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
@ -342,7 +348,7 @@ export class FileNode extends React.Component {
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
className="sidebar__file-item-option"
|
className="sidebar__file-item-option"
|
||||||
>
|
>
|
||||||
Rename
|
{t('FileNode.Rename')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
@ -352,7 +358,7 @@ export class FileNode extends React.Component {
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
className="sidebar__file-item-option"
|
className="sidebar__file-item-option"
|
||||||
>
|
>
|
||||||
Delete
|
{t('FileNode.Delete')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -388,6 +394,7 @@ FileNode.propTypes = {
|
||||||
canEdit: PropTypes.bool.isRequired,
|
canEdit: PropTypes.bool.isRequired,
|
||||||
openUploadFileModal: PropTypes.func.isRequired,
|
openUploadFileModal: PropTypes.func.isRequired,
|
||||||
authenticated: PropTypes.bool.isRequired,
|
authenticated: PropTypes.bool.isRequired,
|
||||||
|
t: PropTypes.func.isRequired,
|
||||||
onClickFile: PropTypes.func
|
onClickFile: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -408,5 +415,8 @@ function mapDispatchToProps(dispatch) {
|
||||||
return bindActionCreators(Object.assign(FileActions, IDEActions), dispatch);
|
return bindActionCreators(Object.assign(FileActions, IDEActions), dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConnectedFileNode = connect(mapStateToProps, mapDispatchToProps)(FileNode);
|
const TranslatedFileNode = withTranslation()(FileNode);
|
||||||
|
|
||||||
|
const ConnectedFileNode = connect(mapStateToProps, mapDispatchToProps)(TranslatedFileNode);
|
||||||
|
|
||||||
export default ConnectedFileNode;
|
export default ConnectedFileNode;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
||||||
import Dropzone from 'dropzone';
|
import Dropzone from 'dropzone';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import * as UploaderActions from '../actions/uploader';
|
import * as UploaderActions from '../actions/uploader';
|
||||||
import getConfig from '../../../utils/getConfig';
|
import getConfig from '../../../utils/getConfig';
|
||||||
import { fileExtensionsAndMimeTypes } from '../../../../server/utils/fileUtils';
|
import { fileExtensionsAndMimeTypes } from '../../../../server/utils/fileUtils';
|
||||||
|
@ -30,7 +31,7 @@ class FileUploader extends React.Component {
|
||||||
thumbnailWidth: 200,
|
thumbnailWidth: 200,
|
||||||
thumbnailHeight: 200,
|
thumbnailHeight: 200,
|
||||||
acceptedFiles: fileExtensionsAndMimeTypes,
|
acceptedFiles: fileExtensionsAndMimeTypes,
|
||||||
dictDefaultMessage: 'Drop files here or click to use the file browser',
|
dictDefaultMessage: this.props.t('FileUploader.DictDefaultMessage'),
|
||||||
accept: this.props.dropzoneAcceptCallback.bind(this, userId),
|
accept: this.props.dropzoneAcceptCallback.bind(this, userId),
|
||||||
sending: this.props.dropzoneSendingCallback,
|
sending: this.props.dropzoneSendingCallback,
|
||||||
complete: this.props.dropzoneCompleteCallback
|
complete: this.props.dropzoneCompleteCallback
|
||||||
|
@ -59,7 +60,8 @@ FileUploader.propTypes = {
|
||||||
}),
|
}),
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
id: PropTypes.string
|
id: PropTypes.string
|
||||||
})
|
}),
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
FileUploader.defaultProps = {
|
FileUploader.defaultProps = {
|
||||||
|
@ -84,4 +86,4 @@ function mapDispatchToProps(dispatch) {
|
||||||
return bindActionCreators(UploaderActions, dispatch);
|
return bindActionCreators(UploaderActions, dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(FileUploader);
|
export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(FileUploader));
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import Icons from './Icons';
|
import Icons from './Icons';
|
||||||
|
|
||||||
const Item = ({
|
const Item = ({
|
||||||
isAdded, onSelect, name, url
|
isAdded, onSelect, name, url, t
|
||||||
}) => {
|
}) => {
|
||||||
const buttonLabel = isAdded ? 'Remove from collection' : 'Add to collection';
|
const buttonLabel = isAdded ? 'Remove from collection' : 'Add to collection';
|
||||||
return (
|
return (
|
||||||
|
@ -20,7 +21,7 @@ const Item = ({
|
||||||
target="_blank"
|
target="_blank"
|
||||||
onClick={e => e.stopPropogation()}
|
onClick={e => e.stopPropogation()}
|
||||||
>
|
>
|
||||||
View
|
{t('QuickAddList.View')}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
@ -37,10 +38,11 @@ Item.propTypes = {
|
||||||
url: PropTypes.string.isRequired,
|
url: PropTypes.string.isRequired,
|
||||||
isAdded: PropTypes.bool.isRequired,
|
isAdded: PropTypes.bool.isRequired,
|
||||||
onSelect: PropTypes.func.isRequired,
|
onSelect: PropTypes.func.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const QuickAddList = ({
|
const QuickAddList = ({
|
||||||
items, onAdd, onRemove
|
items, onAdd, onRemove, t
|
||||||
}) => {
|
}) => {
|
||||||
const handleAction = (item) => {
|
const handleAction = (item) => {
|
||||||
if (item.isAdded) {
|
if (item.isAdded) {
|
||||||
|
@ -53,6 +55,7 @@ const QuickAddList = ({
|
||||||
return (
|
return (
|
||||||
<ul className="quick-add">{items.map(item => (<Item
|
<ul className="quick-add">{items.map(item => (<Item
|
||||||
key={item.id}
|
key={item.id}
|
||||||
|
t={t}
|
||||||
{...item}
|
{...item}
|
||||||
onSelect={
|
onSelect={
|
||||||
(event) => {
|
(event) => {
|
||||||
|
@ -70,6 +73,7 @@ QuickAddList.propTypes = {
|
||||||
items: PropTypes.arrayOf(ItemType).isRequired,
|
items: PropTypes.arrayOf(ItemType).isRequired,
|
||||||
onAdd: PropTypes.func.isRequired,
|
onAdd: PropTypes.func.isRequired,
|
||||||
onRemove: PropTypes.func.isRequired,
|
onRemove: PropTypes.func.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default QuickAddList;
|
export default withTranslation()(QuickAddList);
|
||||||
|
|
|
@ -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 CopyableInput from './CopyableInput';
|
import CopyableInput from './CopyableInput';
|
||||||
|
|
||||||
class ShareModal extends React.PureComponent {
|
class ShareModal extends React.PureComponent {
|
||||||
|
@ -16,21 +17,21 @@ class ShareModal extends React.PureComponent {
|
||||||
{projectName}
|
{projectName}
|
||||||
</h3>
|
</h3>
|
||||||
<CopyableInput
|
<CopyableInput
|
||||||
label="Embed"
|
label={this.props.t('ShareModal.Embed')}
|
||||||
value={`<iframe src="${hostname}/${ownerUsername}/embed/${projectId}"></iframe>`}
|
value={`<iframe src="${hostname}/${ownerUsername}/embed/${projectId}"></iframe>`}
|
||||||
/>
|
/>
|
||||||
<CopyableInput
|
<CopyableInput
|
||||||
label="Present"
|
label={this.props.t('ShareModal.Present')}
|
||||||
hasPreviewLink
|
hasPreviewLink
|
||||||
value={`${hostname}/${ownerUsername}/present/${projectId}`}
|
value={`${hostname}/${ownerUsername}/present/${projectId}`}
|
||||||
/>
|
/>
|
||||||
<CopyableInput
|
<CopyableInput
|
||||||
label="Fullscreen"
|
label={this.props.t('ShareModal.Fullscreen')}
|
||||||
hasPreviewLink
|
hasPreviewLink
|
||||||
value={`${hostname}/${ownerUsername}/full/${projectId}`}
|
value={`${hostname}/${ownerUsername}/full/${projectId}`}
|
||||||
/>
|
/>
|
||||||
<CopyableInput
|
<CopyableInput
|
||||||
label="Edit"
|
label={this.props.t('ShareModal.Edit')}
|
||||||
hasPreviewLink
|
hasPreviewLink
|
||||||
value={`${hostname}/${ownerUsername}/sketches/${projectId}`}
|
value={`${hostname}/${ownerUsername}/sketches/${projectId}`}
|
||||||
/>
|
/>
|
||||||
|
@ -42,7 +43,8 @@ class ShareModal extends React.PureComponent {
|
||||||
ShareModal.propTypes = {
|
ShareModal.propTypes = {
|
||||||
projectId: PropTypes.string.isRequired,
|
projectId: PropTypes.string.isRequired,
|
||||||
ownerUsername: PropTypes.string.isRequired,
|
ownerUsername: PropTypes.string.isRequired,
|
||||||
projectName: PropTypes.string.isRequired
|
projectName: PropTypes.string.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ShareModal;
|
export default withTranslation()(ShareModal);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import ConnectedFileNode from './FileNode';
|
import ConnectedFileNode from './FileNode';
|
||||||
|
|
||||||
import DownArrowIcon from '../../../images/down-filled-triangle.svg';
|
import DownArrowIcon from '../../../images/down-filled-triangle.svg';
|
||||||
|
@ -71,11 +73,11 @@ class Sidebar extends React.Component {
|
||||||
<section className={sidebarClass}>
|
<section className={sidebarClass}>
|
||||||
<header className="sidebar__header" onContextMenu={this.toggleProjectOptions}>
|
<header className="sidebar__header" onContextMenu={this.toggleProjectOptions}>
|
||||||
<h3 className="sidebar__title">
|
<h3 className="sidebar__title">
|
||||||
<span>Sketch Files</span>
|
<span>{this.props.t('Sidebar.Title')}</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="sidebar__icons">
|
<div className="sidebar__icons">
|
||||||
<button
|
<button
|
||||||
aria-label="Toggle open/close sketch file options"
|
aria-label={this.props.t('Sidebar.ToggleARIA')}
|
||||||
className="sidebar__add"
|
className="sidebar__add"
|
||||||
tabIndex="0"
|
tabIndex="0"
|
||||||
ref={(element) => { this.sidebarOptions = element; }}
|
ref={(element) => { this.sidebarOptions = element; }}
|
||||||
|
@ -88,7 +90,7 @@ class Sidebar extends React.Component {
|
||||||
<ul className="sidebar__project-options">
|
<ul className="sidebar__project-options">
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
aria-label="add folder"
|
aria-label={this.props.t('Sidebar.AddFolderARIA')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.props.newFolder(rootFile.id);
|
this.props.newFolder(rootFile.id);
|
||||||
setTimeout(this.props.closeProjectOptions, 0);
|
setTimeout(this.props.closeProjectOptions, 0);
|
||||||
|
@ -96,12 +98,12 @@ class Sidebar extends React.Component {
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
>
|
>
|
||||||
Create folder
|
{this.props.t('Sidebar.AddFolder')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
aria-label="add file"
|
aria-label={this.props.t('Sidebar.AddFileARIA')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.props.newFile(rootFile.id);
|
this.props.newFile(rootFile.id);
|
||||||
setTimeout(this.props.closeProjectOptions, 0);
|
setTimeout(this.props.closeProjectOptions, 0);
|
||||||
|
@ -109,14 +111,14 @@ class Sidebar extends React.Component {
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
>
|
>
|
||||||
Create file
|
{this.props.t('Sidebar.AddFile')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{
|
{
|
||||||
this.props.user.authenticated &&
|
this.props.user.authenticated &&
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
aria-label="upload file"
|
aria-label={this.props.t('Sidebar.UploadFileARIA')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.props.openUploadFileModal(rootFile.id);
|
this.props.openUploadFileModal(rootFile.id);
|
||||||
setTimeout(this.props.closeProjectOptions, 0);
|
setTimeout(this.props.closeProjectOptions, 0);
|
||||||
|
@ -124,7 +126,7 @@ class Sidebar extends React.Component {
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
>
|
>
|
||||||
Upload file
|
{this.props.t('Sidebar.UploadFile')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
@ -159,11 +161,12 @@ Sidebar.propTypes = {
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
authenticated: PropTypes.bool.isRequired
|
authenticated: PropTypes.bool.isRequired
|
||||||
}).isRequired
|
}).isRequired,
|
||||||
|
t: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
Sidebar.defaultProps = {
|
Sidebar.defaultProps = {
|
||||||
owner: undefined
|
owner: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
export default withTranslation()(Sidebar);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import format from 'date-fns/format';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
@ -148,14 +149,14 @@ class SketchListRowBase extends React.Component {
|
||||||
|
|
||||||
handleSketchDelete = () => {
|
handleSketchDelete = () => {
|
||||||
this.closeAll();
|
this.closeAll();
|
||||||
if (window.confirm(`Are you sure you want to delete "${this.props.sketch.name}"?`)) {
|
if (window.confirm(this.props.t('Common.DeleteConfirmation', { name: this.props.sketch.name }))) {
|
||||||
this.props.deleteProject(this.props.sketch.id);
|
this.props.deleteProject(this.props.sketch.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderViewButton = sketchURL => (
|
renderViewButton = sketchURL => (
|
||||||
<td className="sketch-list__dropdown-column">
|
<td className="sketch-list__dropdown-column">
|
||||||
<Link to={sketchURL}>View</Link>
|
<Link to={sketchURL}>{this.props.t('SketchList.View')}</Link>
|
||||||
</td>
|
</td>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -170,7 +171,7 @@ class SketchListRowBase extends React.Component {
|
||||||
onClick={this.toggleOptions}
|
onClick={this.toggleOptions}
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
aria-label="Toggle Open/Close Sketch Options"
|
aria-label={this.props.t('SketchList.ToggleLabelARIA')}
|
||||||
>
|
>
|
||||||
<DownFilledTriangleIcon focusable="false" aria-hidden="true" />
|
<DownFilledTriangleIcon focusable="false" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -186,7 +187,7 @@ class SketchListRowBase extends React.Component {
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
>
|
>
|
||||||
Rename
|
{this.props.t('SketchList.DropdownRename')}
|
||||||
</button>
|
</button>
|
||||||
</li>}
|
</li>}
|
||||||
<li>
|
<li>
|
||||||
|
@ -196,7 +197,7 @@ class SketchListRowBase extends React.Component {
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
>
|
>
|
||||||
Download
|
{this.props.t('SketchList.DropdownDownload')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{this.props.user.authenticated &&
|
{this.props.user.authenticated &&
|
||||||
|
@ -207,7 +208,7 @@ class SketchListRowBase extends React.Component {
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
>
|
>
|
||||||
Duplicate
|
{this.props.t('SketchList.DropdownDuplicate')}
|
||||||
</button>
|
</button>
|
||||||
</li>}
|
</li>}
|
||||||
{this.props.user.authenticated &&
|
{this.props.user.authenticated &&
|
||||||
|
@ -221,7 +222,7 @@ class SketchListRowBase extends React.Component {
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
>
|
>
|
||||||
Add to collection
|
{this.props.t('SketchList.DropdownAddToCollection')}
|
||||||
</button>
|
</button>
|
||||||
</li>}
|
</li>}
|
||||||
{ /* <li>
|
{ /* <li>
|
||||||
|
@ -242,7 +243,7 @@ class SketchListRowBase extends React.Component {
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
>
|
>
|
||||||
Delete
|
{this.props.t('SketchList.DropdownDelete')}
|
||||||
</button>
|
</button>
|
||||||
</li>}
|
</li>}
|
||||||
</ul>}
|
</ul>}
|
||||||
|
@ -262,8 +263,6 @@ class SketchListRowBase extends React.Component {
|
||||||
url = `/${username}/sketches/${slugify(sketch.name, '_')}`;
|
url = `/${username}/sketches/${slugify(sketch.name, '_')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.mobile) url = `/mobile${url}`;
|
|
||||||
|
|
||||||
const name = (
|
const name = (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Link to={url}>
|
<Link to={url}>
|
||||||
|
@ -319,7 +318,8 @@ SketchListRowBase.propTypes = {
|
||||||
exportProjectAsZip: PropTypes.func.isRequired,
|
exportProjectAsZip: PropTypes.func.isRequired,
|
||||||
changeProjectName: PropTypes.func.isRequired,
|
changeProjectName: PropTypes.func.isRequired,
|
||||||
onAddToCollection: PropTypes.func.isRequired,
|
onAddToCollection: PropTypes.func.isRequired,
|
||||||
mobile: PropTypes.bool
|
mobile: PropTypes.bool,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
SketchListRowBase.defaultProps = {
|
SketchListRowBase.defaultProps = {
|
||||||
|
@ -354,9 +354,9 @@ class SketchList extends React.Component {
|
||||||
|
|
||||||
getSketchesTitle() {
|
getSketchesTitle() {
|
||||||
if (this.props.username === this.props.user.username) {
|
if (this.props.username === this.props.user.username) {
|
||||||
return 'p5.js Web Editor | My sketches';
|
return this.props.t('SketchList.Title');
|
||||||
}
|
}
|
||||||
return `p5.js Web Editor | ${this.props.username}'s sketches`;
|
return this.props.t('SketchList.AnothersTitle', { anotheruser: this.props.username });
|
||||||
}
|
}
|
||||||
|
|
||||||
hasSketches() {
|
hasSketches() {
|
||||||
|
@ -374,7 +374,7 @@ class SketchList extends React.Component {
|
||||||
|
|
||||||
_renderEmptyTable() {
|
_renderEmptyTable() {
|
||||||
if (!this.isLoading() && this.props.sketches.length === 0) {
|
if (!this.isLoading() && this.props.sketches.length === 0) {
|
||||||
return (<p className="sketches-table__empty">No sketches.</p>);
|
return (<p className="sketches-table__empty">{this.props.t('SketchList.NoSketches')}</p>);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -384,14 +384,14 @@ class SketchList extends React.Component {
|
||||||
let buttonLabel;
|
let buttonLabel;
|
||||||
if (field !== fieldName) {
|
if (field !== fieldName) {
|
||||||
if (field === 'name') {
|
if (field === 'name') {
|
||||||
buttonLabel = `Sort by ${displayName} ascending.`;
|
buttonLabel = this.props.t('SketchList.ButtonLabelAscendingARIA', { displayName });
|
||||||
} else {
|
} else {
|
||||||
buttonLabel = `Sort by ${displayName} descending.`;
|
buttonLabel = this.props.t('SketchList.ButtonLabelDescendingARIA', { displayName });
|
||||||
}
|
}
|
||||||
} else if (direction === SortingActions.DIRECTION.ASC) {
|
} else if (direction === SortingActions.DIRECTION.ASC) {
|
||||||
buttonLabel = `Sort by ${displayName} descending.`;
|
buttonLabel = this.props.t('SketchList.ButtonLabelDescendingARIA', { displayName });
|
||||||
} else {
|
} else {
|
||||||
buttonLabel = `Sort by ${displayName} ascending.`;
|
buttonLabel = this.props.t('SketchList.ButtonLabelAscendingARIA', { displayName });
|
||||||
}
|
}
|
||||||
return buttonLabel;
|
return buttonLabel;
|
||||||
}
|
}
|
||||||
|
@ -412,10 +412,10 @@ class SketchList extends React.Component {
|
||||||
>
|
>
|
||||||
<span className={headerClass}>{displayName}</span>
|
<span className={headerClass}>{displayName}</span>
|
||||||
{field === fieldName && direction === SortingActions.DIRECTION.ASC &&
|
{field === fieldName && direction === SortingActions.DIRECTION.ASC &&
|
||||||
<ArrowUpIcon role="img" aria-label="Ascending" focusable="false" />
|
<ArrowUpIcon role="img" aria-label={this.props.t('SketchList.DirectionAscendingARIA')} focusable="false" />
|
||||||
}
|
}
|
||||||
{field === fieldName && direction === SortingActions.DIRECTION.DESC &&
|
{field === fieldName && direction === SortingActions.DIRECTION.DESC &&
|
||||||
<ArrowDownIcon role="img" aria-label="Descending" focusable="false" />
|
<ArrowDownIcon role="img" aria-label={this.props.t('SketchList.DirectionDescendingARIA')} focusable="false" />
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</th>
|
</th>
|
||||||
|
@ -436,9 +436,9 @@ class SketchList extends React.Component {
|
||||||
<table className="sketches-table" summary="table containing all saved projects">
|
<table className="sketches-table" summary="table containing all saved projects">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{this._renderFieldHeader('name', 'Sketch')}
|
{this._renderFieldHeader('name', this.props.t('SketchList.HeaderName'))}
|
||||||
{this._renderFieldHeader('createdAt', `${mobile ? '' : 'Date '}Created`)}
|
{this._renderFieldHeader('createdAt', this.props.t('SketchList.HeaderCreatedAt', { context: mobile ? 'mobile' : '' }))}
|
||||||
{this._renderFieldHeader('updatedAt', `${mobile ? '' : 'Date '}Updated`)}
|
{this._renderFieldHeader('updatedAt', this.props.t('SketchList.HeaderUpdatedAt', { context: mobile ? 'mobile' : '' }))}
|
||||||
<th scope="col"></th>
|
<th scope="col"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -453,6 +453,7 @@ class SketchList extends React.Component {
|
||||||
onAddToCollection={() => {
|
onAddToCollection={() => {
|
||||||
this.setState({ sketchToAddToCollection: sketch });
|
this.setState({ sketchToAddToCollection: sketch });
|
||||||
}}
|
}}
|
||||||
|
t={this.props.t}
|
||||||
/>))}
|
/>))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>}
|
</table>}
|
||||||
|
@ -460,7 +461,7 @@ class SketchList extends React.Component {
|
||||||
this.state.sketchToAddToCollection &&
|
this.state.sketchToAddToCollection &&
|
||||||
<Overlay
|
<Overlay
|
||||||
isFixedHeight
|
isFixedHeight
|
||||||
title="Add to collection"
|
title={this.props.t('SketchList.AddToCollectionOverlayTitle')}
|
||||||
closeOverlay={() => this.setState({ sketchToAddToCollection: null })}
|
closeOverlay={() => this.setState({ sketchToAddToCollection: null })}
|
||||||
>
|
>
|
||||||
<AddToCollectionList
|
<AddToCollectionList
|
||||||
|
@ -496,6 +497,7 @@ SketchList.propTypes = {
|
||||||
direction: PropTypes.string.isRequired
|
direction: PropTypes.string.isRequired
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
mobile: PropTypes.bool,
|
mobile: PropTypes.bool,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
SketchList.defaultProps = {
|
SketchList.defaultProps = {
|
||||||
|
@ -520,4 +522,4 @@ function mapDispatchToProps(dispatch) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(SketchList);
|
export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(SketchList));
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import prettyBytes from 'pretty-bytes';
|
import prettyBytes from 'pretty-bytes';
|
||||||
import getConfig from '../../../utils/getConfig';
|
import getConfig from '../../../utils/getConfig';
|
||||||
import FileUploader from './FileUploader';
|
import FileUploader from './FileUploader';
|
||||||
|
@ -14,7 +15,8 @@ const limitText = prettyBytes(limit);
|
||||||
class UploadFileModal extends React.Component {
|
class UploadFileModal extends React.Component {
|
||||||
propTypes = {
|
propTypes = {
|
||||||
reachedTotalSizeLimit: PropTypes.bool.isRequired,
|
reachedTotalSizeLimit: PropTypes.bool.isRequired,
|
||||||
closeModal: PropTypes.func.isRequired
|
closeModal: PropTypes.func.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -31,22 +33,18 @@ class UploadFileModal 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">Upload File</h2>
|
<h2 className="modal__title">{this.props.t('UploadFileModal.Title')}</h2>
|
||||||
<button
|
<button
|
||||||
className="modal__exit-button"
|
className="modal__exit-button"
|
||||||
onClick={this.props.closeModal}
|
onClick={this.props.closeModal}
|
||||||
aria-label="Close upload file modal"
|
aria-label={this.props.t('UploadFileModal.CloseButtonARIA')}
|
||||||
>
|
>
|
||||||
<ExitIcon focusable="false" aria-hidden="true" />
|
<ExitIcon focusable="false" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{ this.props.reachedTotalSizeLimit &&
|
{ this.props.reachedTotalSizeLimit &&
|
||||||
<p>
|
<p>
|
||||||
{
|
{this.props.t('UploadFileModal.SizeLimitError', { sizeLimit: limitText })}
|
||||||
`Error: You cannot upload any more files. You have reached the total size limit of ${limitText}.
|
|
||||||
If you would like to upload more, please remove the ones you aren't using anymore by
|
|
||||||
in your `
|
|
||||||
}
|
|
||||||
<Link to="/assets" onClick={this.props.closeModal}>assets</Link>
|
<Link to="/assets" onClick={this.props.closeModal}>assets</Link>
|
||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
|
@ -68,4 +66,4 @@ function mapStateToProps(state) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps)(UploadFileModal);
|
export default withTranslation()(connect(mapStateToProps)(UploadFileModal));
|
||||||
|
|
|
@ -418,15 +418,15 @@ class IDEView extends React.Component {
|
||||||
<Overlay
|
<Overlay
|
||||||
title={this.props.t('IDEView.SubmitFeedback')}
|
title={this.props.t('IDEView.SubmitFeedback')}
|
||||||
previousPath={this.props.ide.previousPath}
|
previousPath={this.props.ide.previousPath}
|
||||||
ariaLabel="submit-feedback"
|
ariaLabel={this.props.t('IDEView.SubmitFeedbackARIA')}
|
||||||
>
|
>
|
||||||
<Feedback previousPath={this.props.ide.previousPath} />
|
<Feedback previousPath={this.props.ide.previousPath} />
|
||||||
</Overlay>
|
</Overlay>
|
||||||
)}
|
)}
|
||||||
{this.props.location.pathname.match(/add-to-collection$/) && (
|
{this.props.location.pathname.match(/add-to-collection$/) && (
|
||||||
<Overlay
|
<Overlay
|
||||||
ariaLabel="add to collection"
|
ariaLabel={this.props.t('IDEView.AddCollectionARIA')}
|
||||||
title="Add to collection"
|
title={this.props.t('IDEView.AddCollectionTitle')}
|
||||||
previousPath={this.props.ide.previousPath}
|
previousPath={this.props.ide.previousPath}
|
||||||
actions={<CollectionSearchbar />}
|
actions={<CollectionSearchbar />}
|
||||||
isFixedHeight
|
isFixedHeight
|
||||||
|
@ -440,8 +440,8 @@ class IDEView extends React.Component {
|
||||||
)}
|
)}
|
||||||
{this.props.ide.shareModalVisible && (
|
{this.props.ide.shareModalVisible && (
|
||||||
<Overlay
|
<Overlay
|
||||||
title="Share"
|
title={this.props.t('IDEView.ShareTitle')}
|
||||||
ariaLabel="share"
|
ariaLabel={this.props.t('IDEView.ShareARIA')}
|
||||||
closeOverlay={this.props.closeShareModal}
|
closeOverlay={this.props.closeShareModal}
|
||||||
>
|
>
|
||||||
<ShareModal
|
<ShareModal
|
||||||
|
@ -462,8 +462,8 @@ class IDEView extends React.Component {
|
||||||
)}
|
)}
|
||||||
{this.props.ide.errorType && (
|
{this.props.ide.errorType && (
|
||||||
<Overlay
|
<Overlay
|
||||||
title="Error"
|
title={this.props.t('Common.Error')}
|
||||||
ariaLabel={this.props.t('Common.Error')}
|
ariaLabel={this.props.t('Common.ErrorARIA')}
|
||||||
closeOverlay={this.props.hideErrorModal}
|
closeOverlay={this.props.hideErrorModal}
|
||||||
>
|
>
|
||||||
<ErrorModal
|
<ErrorModal
|
||||||
|
|
|
@ -12,7 +12,7 @@ import * as IDEActions from '../actions/ide';
|
||||||
import * as ProjectActions from '../actions/project';
|
import * as ProjectActions from '../actions/project';
|
||||||
import * as ConsoleActions from '../actions/console';
|
import * as ConsoleActions from '../actions/console';
|
||||||
import * as PreferencesActions from '../actions/preferences';
|
import * as PreferencesActions from '../actions/preferences';
|
||||||
|
import * as EditorAccessibilityActions from '../actions/editorAccessibility';
|
||||||
|
|
||||||
// Local Imports
|
// Local Imports
|
||||||
import Editor from '../components/Editor';
|
import Editor from '../components/Editor';
|
||||||
|
@ -58,18 +58,20 @@ const NavItem = styled.li`
|
||||||
position: relative;
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const getNavOptions = (username = undefined) =>
|
const getNavOptions = (username = undefined, logoutUser = () => {}, toggleForceDesktop = () => {}) =>
|
||||||
(username
|
(username
|
||||||
? [
|
? [
|
||||||
{ icon: PreferencesIcon, title: 'Preferences', href: '/mobile/preferences', },
|
{ icon: PreferencesIcon, title: 'Preferences', href: '/preferences', },
|
||||||
{ icon: PreferencesIcon, title: 'My Stuff', href: `/mobile/${username}/sketches` },
|
{ icon: PreferencesIcon, title: 'My Stuff', href: `/${username}/sketches` },
|
||||||
{ icon: PreferencesIcon, title: 'Examples', href: '/mobile/p5/sketches' },
|
{ icon: PreferencesIcon, title: 'Examples', href: '/p5/sketches' },
|
||||||
{ icon: PreferencesIcon, title: 'Original Editor', href: '/', },
|
{ icon: PreferencesIcon, title: 'Original Editor', action: toggleForceDesktop, },
|
||||||
|
{ icon: PreferencesIcon, title: 'Logout', action: logoutUser, },
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{ icon: PreferencesIcon, title: 'Preferences', href: '/mobile/preferences', },
|
{ icon: PreferencesIcon, title: 'Preferences', href: '/preferences', },
|
||||||
{ icon: PreferencesIcon, title: 'Examples', href: '/mobile/p5/sketches' },
|
{ icon: PreferencesIcon, title: 'Examples', href: '/p5/sketches' },
|
||||||
{ icon: PreferencesIcon, title: 'Original Editor', href: '/', },
|
{ icon: PreferencesIcon, title: 'Original Editor', action: toggleForceDesktop, },
|
||||||
|
{ icon: PreferencesIcon, title: 'Login', href: '/login', },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -144,7 +146,7 @@ const handleGlobalKeydown = (props, cmController) => (e) => {
|
||||||
|
|
||||||
const autosave = (autosaveInterval, setAutosaveInterval) => (props, prevProps) => {
|
const autosave = (autosaveInterval, setAutosaveInterval) => (props, prevProps) => {
|
||||||
const {
|
const {
|
||||||
autosaveProject, preferences, ide, selectedFile: file, project, user
|
autosaveProject, preferences, ide, selectedFile: file, project
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { selectedFile: oldFile } = prevProps;
|
const { selectedFile: oldFile } = prevProps;
|
||||||
|
@ -170,10 +172,23 @@ const autosave = (autosaveInterval, setAutosaveInterval) => (props, prevProps) =
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ide, preferences, project, selectedFile, user, params, unsavedChanges, expandConsole, collapseConsole,
|
||||||
|
// stopSketch, startSketch, getProject, clearPersistedState, autosaveProject, saveProject, files
|
||||||
|
|
||||||
const MobileIDEView = (props) => {
|
const MobileIDEView = (props) => {
|
||||||
|
// const {
|
||||||
|
// preferences, ide, editorAccessibility, project, updateLintMessage, clearLintMessage,
|
||||||
|
// selectedFile, updateFileContent, files, user, params,
|
||||||
|
// closeEditorOptions, showEditorOptions, logoutUser,
|
||||||
|
// startRefreshSketch, stopSketch, expandSidebar, collapseSidebar, clearConsole, console,
|
||||||
|
// showRuntimeErrorWarning, hideRuntimeErrorWarning, startSketch, getProject, clearPersistedState, setUnsavedChanges,
|
||||||
|
// toggleForceDesktop
|
||||||
|
// } = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ide, preferences, project, selectedFile, user, params, unsavedChanges, expandConsole, collapseConsole,
|
ide, preferences, project, selectedFile, user, params, unsavedChanges, expandConsole, collapseConsole,
|
||||||
stopSketch, startSketch, getProject, clearPersistedState, autosaveProject, saveProject, files
|
stopSketch, startSketch, getProject, clearPersistedState, autosaveProject, saveProject, files,
|
||||||
|
toggleForceDesktop, logoutUser
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
|
||||||
|
@ -204,7 +219,7 @@ const MobileIDEView = (props) => {
|
||||||
|
|
||||||
// Screen Modals
|
// Screen Modals
|
||||||
const [toggleNavDropdown, NavDropDown] = useAsModal(<Dropdown
|
const [toggleNavDropdown, NavDropDown] = useAsModal(<Dropdown
|
||||||
items={getNavOptions(username)}
|
items={getNavOptions(username, logoutUser, toggleForceDesktop)}
|
||||||
align="right"
|
align="right"
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
|
@ -239,6 +254,7 @@ const MobileIDEView = (props) => {
|
||||||
subtitle={filename}
|
subtitle={filename}
|
||||||
>
|
>
|
||||||
<NavItem>
|
<NavItem>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={toggleNavDropdown}
|
onClick={toggleNavDropdown}
|
||||||
icon={MoreIcon}
|
icon={MoreIcon}
|
||||||
|
@ -247,7 +263,7 @@ const MobileIDEView = (props) => {
|
||||||
<NavDropDown />
|
<NavDropDown />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
<li>
|
<li>
|
||||||
<IconButton to="/mobile/preview" onClick={() => { startSketch(); }} icon={PlayIcon} aria-label="Run sketch" />
|
<IconButton to="/preview" onClick={() => { startSketch(); }} icon={PlayIcon} aria-label="Run sketch" />
|
||||||
</li>
|
</li>
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
|
@ -307,12 +323,22 @@ MobileIDEView.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
|
||||||
|
files: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
content: PropTypes.string.isRequired,
|
||||||
|
})).isRequired,
|
||||||
|
|
||||||
|
toggleForceDesktop: PropTypes.func.isRequired,
|
||||||
|
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
authenticated: PropTypes.bool.isRequired,
|
authenticated: PropTypes.bool.isRequired,
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
|
||||||
|
logoutUser: PropTypes.func.isRequired,
|
||||||
|
|
||||||
getProject: PropTypes.func.isRequired,
|
getProject: PropTypes.func.isRequired,
|
||||||
clearPersistedState: PropTypes.func.isRequired,
|
clearPersistedState: PropTypes.func.isRequired,
|
||||||
params: PropTypes.shape({
|
params: PropTypes.shape({
|
||||||
|
@ -320,10 +346,9 @@ MobileIDEView.propTypes = {
|
||||||
username: PropTypes.string
|
username: PropTypes.string
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
|
||||||
unsavedChanges: PropTypes.bool.isRequired,
|
|
||||||
|
|
||||||
startSketch: PropTypes.func.isRequired,
|
startSketch: PropTypes.func.isRequired,
|
||||||
stopSketch: PropTypes.func.isRequired,
|
|
||||||
|
unsavedChanges: PropTypes.bool.isRequired,
|
||||||
autosaveProject: PropTypes.func.isRequired,
|
autosaveProject: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
|
||||||
|
@ -351,7 +376,8 @@ const mapDispatchToProps = dispatch => bindActionCreators({
|
||||||
...ProjectActions,
|
...ProjectActions,
|
||||||
...IDEActions,
|
...IDEActions,
|
||||||
...ConsoleActions,
|
...ConsoleActions,
|
||||||
...PreferencesActions
|
...PreferencesActions,
|
||||||
|
...EditorAccessibilityActions
|
||||||
}, dispatch);
|
}, dispatch);
|
||||||
|
|
||||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MobileIDEView));
|
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MobileIDEView));
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import * as ActionTypes from '../../../constants';
|
import * as ActionTypes from '../../../constants';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
lintMessages: []
|
lintMessages: [],
|
||||||
|
forceDesktop: false
|
||||||
};
|
};
|
||||||
let messageId = 0;
|
let messageId = 0;
|
||||||
|
|
||||||
|
@ -16,6 +17,8 @@ const editorAccessibility = (state = initialState, action) => {
|
||||||
});
|
});
|
||||||
case ActionTypes.CLEAR_LINT_MESSAGE:
|
case ActionTypes.CLEAR_LINT_MESSAGE:
|
||||||
return Object.assign({}, state, { lintMessages: [] });
|
return Object.assign({}, state, { lintMessages: [] });
|
||||||
|
case ActionTypes.TOGGLE_FORCE_DESKTOP:
|
||||||
|
return Object.assign({}, state, { forceDesktop: !(state.forceDesktop) });
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,8 +138,8 @@ const Panels = {
|
||||||
|
|
||||||
|
|
||||||
const navOptions = username => [
|
const navOptions = username => [
|
||||||
{ title: 'Create Sketch', href: '/mobile' },
|
{ title: 'Create Sketch', href: '/' },
|
||||||
{ title: 'Create Collection', href: `/mobile/${username}/collections/create` }
|
{ title: 'Create Collection', href: `/${username}/collections/create` }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ const MobileDashboard = ({ params, location }) => {
|
||||||
<NavDropdown />
|
<NavDropdown />
|
||||||
|
|
||||||
</NavItem>
|
</NavItem>
|
||||||
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to ide view" />
|
<IconButton to="/" icon={ExitIcon} aria-label="Return to ide view" />
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ const MobilePreferences = () => {
|
||||||
<Screen fullscreen>
|
<Screen fullscreen>
|
||||||
<section>
|
<section>
|
||||||
<Header transparent title="Preferences">
|
<Header transparent title="Preferences">
|
||||||
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to ide view" />
|
<IconButton to="/" icon={ExitIcon} aria-label="Return to ide view" />
|
||||||
</Header>
|
</Header>
|
||||||
<section className="preferences">
|
<section className="preferences">
|
||||||
<Content>
|
<Content>
|
||||||
|
|
|
@ -39,7 +39,7 @@ const MobileSketchView = () => {
|
||||||
return (
|
return (
|
||||||
<Screen fullscreen>
|
<Screen fullscreen>
|
||||||
<Header
|
<Header
|
||||||
leftButton={<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to original editor" />}
|
leftButton={<IconButton to="/" icon={ExitIcon} aria-label="Return to original editor" />}
|
||||||
title={projectName}
|
title={projectName}
|
||||||
/>
|
/>
|
||||||
<Content>
|
<Content>
|
||||||
|
|
|
@ -229,7 +229,7 @@ export function updateSettings(formValues) {
|
||||||
dispatch(updateSettingsSuccess(response.data));
|
dispatch(updateSettingsSuccess(response.data));
|
||||||
browserHistory.push('/');
|
browserHistory.push('/');
|
||||||
dispatch(showToast(5500));
|
dispatch(showToast(5500));
|
||||||
dispatch(setToastText('Settings saved.'));
|
dispatch(setToastText('Toast.SettingsSaved'));
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
const { response } = error;
|
const { response } = error;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { Helmet } from 'react-helmet';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import Button from '../../../common/Button';
|
import Button from '../../../common/Button';
|
||||||
|
@ -27,7 +28,7 @@ import ArrowUpIcon from '../../../images/sort-arrow-up.svg';
|
||||||
import ArrowDownIcon from '../../../images/sort-arrow-down.svg';
|
import ArrowDownIcon from '../../../images/sort-arrow-down.svg';
|
||||||
import RemoveIcon from '../../../images/close.svg';
|
import RemoveIcon from '../../../images/close.svg';
|
||||||
|
|
||||||
const ShareURL = ({ value }) => {
|
const ShareURL = ({ value, t }) => {
|
||||||
const [showURL, setShowURL] = useState(false);
|
const [showURL, setShowURL] = useState(false);
|
||||||
const node = useRef();
|
const node = useRef();
|
||||||
|
|
||||||
|
@ -56,11 +57,11 @@ const ShareURL = ({ value }) => {
|
||||||
onClick={() => setShowURL(!showURL)}
|
onClick={() => setShowURL(!showURL)}
|
||||||
iconAfter={<DropdownArrowIcon />}
|
iconAfter={<DropdownArrowIcon />}
|
||||||
>
|
>
|
||||||
Share
|
{t('Collection.Share')}
|
||||||
</Button>
|
</Button>
|
||||||
{ showURL &&
|
{ showURL &&
|
||||||
<div className="collection__share-dropdown">
|
<div className="collection__share-dropdown">
|
||||||
<CopyableInput value={value} label="Link to Collection" />
|
<CopyableInput value={value} label={t('Collection.URLLink')} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -69,22 +70,23 @@ const ShareURL = ({ value }) => {
|
||||||
|
|
||||||
ShareURL.propTypes = {
|
ShareURL.propTypes = {
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const CollectionItemRowBase = ({
|
const CollectionItemRowBase = ({
|
||||||
collection, item, isOwner, removeFromCollection
|
collection, item, isOwner, removeFromCollection, t
|
||||||
}) => {
|
}) => {
|
||||||
const projectIsDeleted = item.isDeleted;
|
const projectIsDeleted = item.isDeleted;
|
||||||
|
|
||||||
const handleSketchRemove = () => {
|
const handleSketchRemove = () => {
|
||||||
const name = projectIsDeleted ? 'deleted sketch' : item.project.name;
|
const name = projectIsDeleted ? 'deleted sketch' : item.project.name;
|
||||||
|
|
||||||
if (window.confirm(`Are you sure you want to remove "${name}" from this collection?`)) {
|
if (window.confirm(t('Collection.DeleteFromCollection', { name_sketch: name }))) {
|
||||||
removeFromCollection(collection.id, item.projectId);
|
removeFromCollection(collection.id, item.projectId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const name = projectIsDeleted ? <span>Sketch was deleted</span> : (
|
const name = projectIsDeleted ? <span>{t('Collection.SketchDeleted')}</span> : (
|
||||||
<Link to={`/${item.project.user.username}/sketches/${item.projectId}`}>
|
<Link to={`/${item.project.user.username}/sketches/${item.projectId}`}>
|
||||||
{item.project.name}
|
{item.project.name}
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -106,7 +108,7 @@ const CollectionItemRowBase = ({
|
||||||
<button
|
<button
|
||||||
className="collection-row__remove-button"
|
className="collection-row__remove-button"
|
||||||
onClick={handleSketchRemove}
|
onClick={handleSketchRemove}
|
||||||
aria-label="Remove sketch from collection"
|
aria-label={t('Collection.SketchRemoveARIA')}
|
||||||
>
|
>
|
||||||
<RemoveIcon focusable="false" aria-hidden="true" />
|
<RemoveIcon focusable="false" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -138,7 +140,8 @@ CollectionItemRowBase.propTypes = {
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
authenticated: PropTypes.bool.isRequired
|
authenticated: PropTypes.bool.isRequired
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
removeFromCollection: PropTypes.func.isRequired
|
removeFromCollection: PropTypes.func.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapDispatchToPropsSketchListRow(dispatch) {
|
function mapDispatchToPropsSketchListRow(dispatch) {
|
||||||
|
@ -163,9 +166,9 @@ class Collection extends React.Component {
|
||||||
|
|
||||||
getTitle() {
|
getTitle() {
|
||||||
if (this.props.username === this.props.user.username) {
|
if (this.props.username === this.props.user.username) {
|
||||||
return 'p5.js Web Editor | My collections';
|
return this.props.t('Collection.Title');
|
||||||
}
|
}
|
||||||
return `p5.js Web Editor | ${this.props.username}'s collections`;
|
return this.props.t('Collection.AnothersTitle', { anotheruser: this.props.username });
|
||||||
}
|
}
|
||||||
|
|
||||||
getUsername() {
|
getUsername() {
|
||||||
|
@ -257,27 +260,27 @@ class Collection extends React.Component {
|
||||||
InputComponent="textarea"
|
InputComponent="textarea"
|
||||||
value={description}
|
value={description}
|
||||||
onChange={handleEditCollectionDescription}
|
onChange={handleEditCollectionDescription}
|
||||||
emptyPlaceholder="Add description"
|
emptyPlaceholder={this.props.t('Collection.DescriptionPlaceholder')}
|
||||||
/> :
|
/> :
|
||||||
description
|
description
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="collection-metadata__user">Collection by{' '}
|
<p className="collection-metadata__user">{this.props.t('Collection.By')}
|
||||||
<Link to={`${hostname}/${username}/sketches`}>{owner.username}</Link>
|
<Link to={`${hostname}/${username}/sketches`}>{owner.username}</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="collection-metadata__user">{items.length} sketch{items.length === 1 ? '' : 'es'}</p>
|
<p className="collection-metadata__user">{this.props.t('Collection.NumSketches', { count: items.length }) }</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="collection-metadata__column--right">
|
<div className="collection-metadata__column--right">
|
||||||
<p className="collection-metadata__share">
|
<p className="collection-metadata__share">
|
||||||
<ShareURL value={`${baseURL}${id}`} />
|
<ShareURL value={`${baseURL}${id}`} t={this.props.t} />
|
||||||
</p>
|
</p>
|
||||||
{
|
{
|
||||||
this.isOwner() &&
|
this.isOwner() &&
|
||||||
<Button onClick={this.showAddSketches}>
|
<Button onClick={this.showAddSketches}>
|
||||||
Add Sketch
|
{this.props.t('Collection.AddSketch')}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -304,7 +307,7 @@ class Collection extends React.Component {
|
||||||
this.props.collection.items.length > 0;
|
this.props.collection.items.length > 0;
|
||||||
|
|
||||||
if (!isLoading && !hasCollectionItems) {
|
if (!isLoading && !hasCollectionItems) {
|
||||||
return (<p className="collection-empty-message">No sketches in collection</p>);
|
return (<p className="collection-empty-message">{this.props.t('Collection.NoSketches')}</p>);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -314,14 +317,14 @@ class Collection extends React.Component {
|
||||||
let buttonLabel;
|
let buttonLabel;
|
||||||
if (field !== fieldName) {
|
if (field !== fieldName) {
|
||||||
if (field === 'name') {
|
if (field === 'name') {
|
||||||
buttonLabel = `Sort by ${displayName} ascending.`;
|
buttonLabel = this.props.t('Collection.ButtonLabelAscendingARIA', { displayName });
|
||||||
} else {
|
} else {
|
||||||
buttonLabel = `Sort by ${displayName} descending.`;
|
buttonLabel = this.props.t('Collection.ButtonLabelDescendingARIA', { displayName });
|
||||||
}
|
}
|
||||||
} else if (direction === SortingActions.DIRECTION.ASC) {
|
} else if (direction === SortingActions.DIRECTION.ASC) {
|
||||||
buttonLabel = `Sort by ${displayName} descending.`;
|
buttonLabel = this.props.t('Collection.ButtonLabelDescendingARIA', { displayName });
|
||||||
} else {
|
} else {
|
||||||
buttonLabel = `Sort by ${displayName} ascending.`;
|
buttonLabel = this.props.t('Collection.ButtonLabelAscendingARIA', { displayName });
|
||||||
}
|
}
|
||||||
return buttonLabel;
|
return buttonLabel;
|
||||||
}
|
}
|
||||||
|
@ -342,10 +345,10 @@ class Collection extends React.Component {
|
||||||
>
|
>
|
||||||
<span className={headerClass}>{displayName}</span>
|
<span className={headerClass}>{displayName}</span>
|
||||||
{field === fieldName && direction === SortingActions.DIRECTION.ASC &&
|
{field === fieldName && direction === SortingActions.DIRECTION.ASC &&
|
||||||
<ArrowUpIcon role="img" aria-label="Ascending" focusable="false" />
|
<ArrowUpIcon role="img" aria-label={this.props.t('Collection.DirectionAscendingARIA')} focusable="false" />
|
||||||
}
|
}
|
||||||
{field === fieldName && direction === SortingActions.DIRECTION.DESC &&
|
{field === fieldName && direction === SortingActions.DIRECTION.DESC &&
|
||||||
<ArrowDownIcon role="img" aria-label="Descending" focusable="false" />
|
<ArrowDownIcon role="img" aria-label={this.props.t('Collection.DirectionDescendingARIA')} focusable="false" />
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</th>
|
</th>
|
||||||
|
@ -371,9 +374,9 @@ class Collection extends React.Component {
|
||||||
<table className="sketches-table" summary="table containing all collections">
|
<table className="sketches-table" summary="table containing all collections">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{this._renderFieldHeader('name', 'Name')}
|
{this._renderFieldHeader('name', this.props.t('Collection.HeaderName'))}
|
||||||
{this._renderFieldHeader('createdAt', 'Date Added')}
|
{this._renderFieldHeader('createdAt', this.props.t('Collection.HeaderCreatedAt'))}
|
||||||
{this._renderFieldHeader('user', 'Owner')}
|
{this._renderFieldHeader('user', this.props.t('Collection.HeaderUser'))}
|
||||||
<th scope="col"></th>
|
<th scope="col"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -386,6 +389,7 @@ class Collection extends React.Component {
|
||||||
username={this.getUsername()}
|
username={this.getUsername()}
|
||||||
collection={this.props.collection}
|
collection={this.props.collection}
|
||||||
isOwner={isOwner}
|
isOwner={isOwner}
|
||||||
|
t={this.props.t}
|
||||||
/>))}
|
/>))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -393,14 +397,15 @@ class Collection extends React.Component {
|
||||||
{
|
{
|
||||||
this.state.isAddingSketches && (
|
this.state.isAddingSketches && (
|
||||||
<Overlay
|
<Overlay
|
||||||
title="Add sketch"
|
title={this.props.t('Collection.AddSketch')}
|
||||||
actions={<SketchSearchbar />}
|
actions={<SketchSearchbar />}
|
||||||
closeOverlay={this.hideAddSketches}
|
closeOverlay={this.hideAddSketches}
|
||||||
isFixedHeight
|
isFixedHeight
|
||||||
>
|
>
|
||||||
<div className="collection-add-sketch">
|
<AddToCollectionSketchList
|
||||||
<AddToCollectionSketchList username={this.props.username} collection={this.props.collection} />
|
username={this.props.username}
|
||||||
</div>
|
collection={this.props.collection}
|
||||||
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -436,7 +441,8 @@ Collection.propTypes = {
|
||||||
sorting: PropTypes.shape({
|
sorting: PropTypes.shape({
|
||||||
field: PropTypes.string.isRequired,
|
field: PropTypes.string.isRequired,
|
||||||
direction: PropTypes.string.isRequired
|
direction: PropTypes.string.isRequired
|
||||||
}).isRequired
|
}).isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
Collection.defaultProps = {
|
Collection.defaultProps = {
|
||||||
|
@ -467,4 +473,4 @@ function mapDispatchToProps(dispatch) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Collection);
|
export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(Collection));
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import * as CollectionsActions from '../../IDE/actions/collections';
|
import * as CollectionsActions from '../../IDE/actions/collections';
|
||||||
|
@ -24,7 +25,7 @@ class CollectionCreate extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
getTitle() {
|
getTitle() {
|
||||||
return 'p5.js Web Editor | Create collection';
|
return this.props.t('CollectionCreate.Title');
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTextChange = field => (evt) => {
|
handleTextChange = field => (evt) => {
|
||||||
|
@ -55,34 +56,34 @@ class CollectionCreate extends React.Component {
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="sketches-table-container">
|
<div className="sketches-table-container">
|
||||||
<form className="form" onSubmit={this.handleCreateCollection}>
|
<form className="form" onSubmit={this.handleCreateCollection}>
|
||||||
{creationError && <span className="form-error">Couldn't create collection</span>}
|
{creationError && <span className="form-error">{this.props.t('CollectionCreate.FormError')}</span>}
|
||||||
<p className="form__field">
|
<p className="form__field">
|
||||||
<label htmlFor="name" className="form__label">Collection name</label>
|
<label htmlFor="name" className="form__label">{this.props.t('CollectionCreate.FormLabel')}</label>
|
||||||
<input
|
<input
|
||||||
className="form__input"
|
className="form__input"
|
||||||
aria-label="name"
|
aria-label={this.props.t('CollectionCreate.FormLabelARIA')}
|
||||||
type="text"
|
type="text"
|
||||||
id="name"
|
id="name"
|
||||||
value={name}
|
value={name}
|
||||||
placeholder={generatedCollectionName}
|
placeholder={generatedCollectionName}
|
||||||
onChange={this.handleTextChange('name')}
|
onChange={this.handleTextChange('name')}
|
||||||
/>
|
/>
|
||||||
{invalid && <span className="form-error">Collection name is required</span>}
|
{invalid && <span className="form-error">{this.props.t('CollectionCreate.NameRequired')}</span>}
|
||||||
</p>
|
</p>
|
||||||
<p className="form__field">
|
<p className="form__field">
|
||||||
<label htmlFor="description" className="form__label">Description (optional)</label>
|
<label htmlFor="description" className="form__label">{this.props.t('CollectionCreate.Description')}</label>
|
||||||
<textarea
|
<textarea
|
||||||
className="form__input form__input-flexible-height"
|
className="form__input form__input-flexible-height"
|
||||||
aria-label="description"
|
aria-label={this.props.t('CollectionCreate.DescriptionARIA')}
|
||||||
type="text"
|
type="text"
|
||||||
id="description"
|
id="description"
|
||||||
value={description}
|
value={description}
|
||||||
onChange={this.handleTextChange('description')}
|
onChange={this.handleTextChange('description')}
|
||||||
placeholder="My fave sketches"
|
placeholder={this.props.t('CollectionCreate.DescriptionPlaceholder')}
|
||||||
rows="4"
|
rows="4"
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<Button type="submit" disabled={invalid}>Create collection</Button>
|
<Button type="submit" disabled={invalid}>{this.props.t('CollectionCreate.SubmitCollectionCreate')}</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,7 +96,8 @@ CollectionCreate.propTypes = {
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
authenticated: PropTypes.bool.isRequired
|
authenticated: PropTypes.bool.isRequired
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
createCollection: PropTypes.func.isRequired
|
createCollection: PropTypes.func.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state, ownProps) {
|
function mapStateToProps(state, ownProps) {
|
||||||
|
@ -108,4 +110,4 @@ function mapDispatchToProps(dispatch) {
|
||||||
return bindActionCreators(Object.assign({}, CollectionsActions), dispatch);
|
return bindActionCreators(Object.assign({}, CollectionsActions), dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(CollectionCreate);
|
export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(CollectionCreate));
|
||||||
|
|
|
@ -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 { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
const TabKey = {
|
const TabKey = {
|
||||||
|
@ -28,12 +29,14 @@ Tab.propTypes = {
|
||||||
to: PropTypes.string.isRequired,
|
to: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DashboardTabSwitcher = ({ currentTab, isOwner, username }) => (
|
const DashboardTabSwitcher = ({
|
||||||
|
currentTab, isOwner, username, t
|
||||||
|
}) => (
|
||||||
<ul className="dashboard-header__switcher">
|
<ul className="dashboard-header__switcher">
|
||||||
<div className="dashboard-header__tabs">
|
<div className="dashboard-header__tabs">
|
||||||
<Tab to={`/${username}/sketches`} isSelected={currentTab === TabKey.sketches}>Sketches</Tab>
|
<Tab to={`/${username}/sketches`} isSelected={currentTab === TabKey.sketches}>{t('DashboardTabSwitcher.Sketches')}</Tab>
|
||||||
<Tab to={`/${username}/collections`} isSelected={currentTab === TabKey.collections}>Collections</Tab>
|
<Tab to={`/${username}/collections`} isSelected={currentTab === TabKey.collections}>{t('DashboardTabSwitcher.Collections')}</Tab>
|
||||||
{isOwner && <Tab to={`/${username}/assets`} isSelected={currentTab === TabKey.assets}>Assets</Tab>}
|
{isOwner && <Tab to={`/${username}/assets`} isSelected={currentTab === TabKey.assets}>{t('DashboardTabSwitcher.Assets')}</Tab>}
|
||||||
</div>
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
|
@ -42,6 +45,9 @@ DashboardTabSwitcher.propTypes = {
|
||||||
currentTab: PropTypes.string.isRequired,
|
currentTab: PropTypes.string.isRequired,
|
||||||
isOwner: PropTypes.bool.isRequired,
|
isOwner: PropTypes.bool.isRequired,
|
||||||
username: PropTypes.string.isRequired,
|
username: PropTypes.string.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export { DashboardTabSwitcher as default, TabKey };
|
|
||||||
|
const DashboardTabSwitcherPublic = withTranslation()(DashboardTabSwitcher);
|
||||||
|
export { DashboardTabSwitcherPublic as default, TabKey };
|
||||||
|
|
45
client/modules/User/components/ResponsiveForm.jsx
Normal file
45
client/modules/User/components/ResponsiveForm.jsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { remSize } from '../../../theme';
|
||||||
|
|
||||||
|
|
||||||
|
const ResponsiveForm = styled.div`
|
||||||
|
.form-container__content {
|
||||||
|
width: unset !important;
|
||||||
|
padding-top: ${remSize(16)};
|
||||||
|
padding-bottom: ${remSize(64)};
|
||||||
|
}
|
||||||
|
|
||||||
|
.form__input {
|
||||||
|
min-width: unset;
|
||||||
|
padding: 0px ${remSize(12)};
|
||||||
|
height: ${remSize(28)};
|
||||||
|
}
|
||||||
|
.form-container__title { margin-bottom: ${remSize(14)}}
|
||||||
|
p.form__field { margin-top: 0px !important; }
|
||||||
|
label.form__label { margin-top: ${remSize(8)} !important; }
|
||||||
|
|
||||||
|
.form-error { width: 100% }
|
||||||
|
|
||||||
|
.nav__items-right:last-child { display: none }
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
height: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav__dropdown {
|
||||||
|
right: 0 !important;
|
||||||
|
left: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container__stack {
|
||||||
|
svg {
|
||||||
|
width: ${remSize(12)};
|
||||||
|
height: ${remSize(12)}
|
||||||
|
}
|
||||||
|
a { padding: 0px }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default ResponsiveForm;
|
|
@ -1,6 +1,8 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import Nav from '../../../components/Nav';
|
import Nav from '../../../components/Nav';
|
||||||
|
|
||||||
import CollectionCreate from '../components/CollectionCreate';
|
import CollectionCreate from '../components/CollectionCreate';
|
||||||
|
@ -25,10 +27,10 @@ class CollectionView extends React.Component {
|
||||||
|
|
||||||
pageTitle() {
|
pageTitle() {
|
||||||
if (this.isCreatePage()) {
|
if (this.isCreatePage()) {
|
||||||
return 'Create collection';
|
return this.props.t('CollectionView.TitleCreate');
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'collection';
|
return this.props.t('CollectionView.TitleDefault');
|
||||||
}
|
}
|
||||||
|
|
||||||
isOwner() {
|
isOwner() {
|
||||||
|
@ -87,6 +89,7 @@ CollectionView.propTypes = {
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(CollectionView);
|
export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(CollectionView));
|
||||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions';
|
import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions';
|
||||||
|
|
||||||
import Button from '../../../common/Button';
|
import Button from '../../../common/Button';
|
||||||
|
@ -17,7 +18,7 @@ import SketchList from '../../IDE/components/SketchList';
|
||||||
import { CollectionSearchbar, SketchSearchbar } from '../../IDE/components/Searchbar';
|
import { CollectionSearchbar, SketchSearchbar } from '../../IDE/components/Searchbar';
|
||||||
|
|
||||||
import CollectionCreate from '../components/CollectionCreate';
|
import CollectionCreate from '../components/CollectionCreate';
|
||||||
import DashboardTabSwitcher, { TabKey } from '../components/DashboardTabSwitcher';
|
import DashboardTabSwitcherPublic, { TabKey } from '../components/DashboardTabSwitcher';
|
||||||
|
|
||||||
class DashboardView extends React.Component {
|
class DashboardView extends React.Component {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -75,7 +76,7 @@ class DashboardView extends React.Component {
|
||||||
browserHistory.push(`/${this.ownerName()}/collections`);
|
browserHistory.push(`/${this.ownerName()}/collections`);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderActionButton(tabKey, username) {
|
renderActionButton(tabKey, username, t) {
|
||||||
switch (tabKey) {
|
switch (tabKey) {
|
||||||
case TabKey.assets:
|
case TabKey.assets:
|
||||||
return this.isOwner() && <AssetSize />;
|
return this.isOwner() && <AssetSize />;
|
||||||
|
@ -83,7 +84,7 @@ class DashboardView extends React.Component {
|
||||||
return this.isOwner() && (
|
return this.isOwner() && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Button to={`/${username}/collections/create`}>
|
<Button to={`/${username}/collections/create`}>
|
||||||
Create collection
|
{t('DashboardView.CreateCollection')}
|
||||||
</Button>
|
</Button>
|
||||||
<CollectionSearchbar />
|
<CollectionSearchbar />
|
||||||
</React.Fragment>);
|
</React.Fragment>);
|
||||||
|
@ -91,7 +92,7 @@ class DashboardView extends React.Component {
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{this.isOwner() && <Button to="/">New sketch</Button>}
|
{this.isOwner() && <Button to="/">{t('DashboardView.NewSketch')}</Button>}
|
||||||
<SketchSearchbar />
|
<SketchSearchbar />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
@ -114,7 +115,7 @@ class DashboardView extends React.Component {
|
||||||
const currentTab = this.selectedTabKey();
|
const currentTab = this.selectedTabKey();
|
||||||
const isOwner = this.isOwner();
|
const isOwner = this.isOwner();
|
||||||
const { username } = this.props.params;
|
const { username } = this.props.params;
|
||||||
const actions = this.renderActionButton(currentTab, username);
|
const actions = this.renderActionButton(currentTab, username, this.props.t);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard">
|
<div className="dashboard">
|
||||||
|
@ -124,7 +125,7 @@ class DashboardView extends React.Component {
|
||||||
<div className="dashboard-header__header">
|
<div className="dashboard-header__header">
|
||||||
<h2 className="dashboard-header__header__title">{this.ownerName()}</h2>
|
<h2 className="dashboard-header__header__title">{this.ownerName()}</h2>
|
||||||
<div className="dashboard-header__nav">
|
<div className="dashboard-header__nav">
|
||||||
<DashboardTabSwitcher currentTab={currentTab} isOwner={isOwner} username={username} />
|
<DashboardTabSwitcherPublic currentTab={currentTab} isOwner={isOwner} username={username} />
|
||||||
{actions &&
|
{actions &&
|
||||||
<div className="dashboard-header__actions">
|
<div className="dashboard-header__actions">
|
||||||
{actions}
|
{actions}
|
||||||
|
@ -139,7 +140,7 @@ class DashboardView extends React.Component {
|
||||||
</main>
|
</main>
|
||||||
{this.isCollectionCreate() &&
|
{this.isCollectionCreate() &&
|
||||||
<Overlay
|
<Overlay
|
||||||
title="Create collection"
|
title={this.props.t('DashboardView.CreateCollectionOverlay')}
|
||||||
closeOverlay={this.returnToDashboard}
|
closeOverlay={this.returnToDashboard}
|
||||||
>
|
>
|
||||||
<CollectionCreate />
|
<CollectionCreate />
|
||||||
|
@ -176,6 +177,7 @@ DashboardView.propTypes = {
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(DashboardView);
|
export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(DashboardView));
|
||||||
|
|
|
@ -10,6 +10,7 @@ import LoginForm from '../components/LoginForm';
|
||||||
import { validateLogin } from '../../../utils/reduxFormUtils';
|
import { validateLogin } from '../../../utils/reduxFormUtils';
|
||||||
import SocialAuthButton from '../components/SocialAuthButton';
|
import SocialAuthButton from '../components/SocialAuthButton';
|
||||||
import Nav from '../../../components/Nav';
|
import Nav from '../../../components/Nav';
|
||||||
|
import ResponsiveForm from '../components/ResponsiveForm';
|
||||||
|
|
||||||
class LoginView extends React.Component {
|
class LoginView extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -79,13 +80,13 @@ LoginView.propTypes = {
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
authenticated: PropTypes.bool
|
authenticated: PropTypes.bool
|
||||||
}),
|
}),
|
||||||
t: PropTypes.func.isRequired
|
t: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
LoginView.defaultProps = {
|
LoginView.defaultProps = {
|
||||||
user: {
|
user: {
|
||||||
authenticated: false
|
authenticated: false
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(reduxForm({
|
export default withTranslation()(reduxForm({
|
||||||
|
|
|
@ -11,6 +11,8 @@ import apiClient from '../../../utils/apiClient';
|
||||||
import { validateSignup } from '../../../utils/reduxFormUtils';
|
import { validateSignup } from '../../../utils/reduxFormUtils';
|
||||||
import SocialAuthButton from '../components/SocialAuthButton';
|
import SocialAuthButton from '../components/SocialAuthButton';
|
||||||
import Nav from '../../../components/Nav';
|
import Nav from '../../../components/Nav';
|
||||||
|
import ResponsiveForm from '../components/ResponsiveForm';
|
||||||
|
|
||||||
|
|
||||||
class SignupView extends React.Component {
|
class SignupView extends React.Component {
|
||||||
gotoHomePage = () => {
|
gotoHomePage = () => {
|
||||||
|
@ -110,13 +112,13 @@ SignupView.propTypes = {
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
authenticated: PropTypes.bool
|
authenticated: PropTypes.bool
|
||||||
}),
|
}),
|
||||||
t: PropTypes.func.isRequired
|
t: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
SignupView.defaultProps = {
|
SignupView.defaultProps = {
|
||||||
user: {
|
user: {
|
||||||
authenticated: false
|
authenticated: false
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(reduxForm({
|
export default withTranslation()(reduxForm({
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Route, IndexRoute } from 'react-router';
|
import { Route, IndexRoute } from 'react-router';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import App from './modules/App/App';
|
import App from './modules/App/App';
|
||||||
import IDEView from './modules/IDE/pages/IDEView';
|
import IDEView from './modules/IDE/pages/IDEView';
|
||||||
import MobileIDEView from './modules/IDE/pages/MobileIDEView';
|
import MobileIDEView from './modules/IDE/pages/MobileIDEView';
|
||||||
|
@ -19,6 +20,7 @@ import MobileDashboardView from './modules/Mobile/MobileDashboardView';
|
||||||
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';
|
||||||
import { userIsAuthenticated, userIsNotAuthenticated, userIsAuthorized } from './utils/auth';
|
import { userIsAuthenticated, userIsNotAuthenticated, userIsAuthorized } from './utils/auth';
|
||||||
|
import { mobileFirst, responsiveForm } from './utils/responsive';
|
||||||
|
|
||||||
const checkAuth = (store) => {
|
const checkAuth = (store) => {
|
||||||
store.dispatch(getUser());
|
store.dispatch(getUser());
|
||||||
|
@ -27,16 +29,17 @@ const checkAuth = (store) => {
|
||||||
// TODO: This short-circuit seems unnecessary - using the mobile <Switch /> navigator (future) should prevent this from being called
|
// TODO: This short-circuit seems unnecessary - using the mobile <Switch /> navigator (future) should prevent this from being called
|
||||||
const onRouteChange = (store) => {
|
const onRouteChange = (store) => {
|
||||||
const path = window.location.pathname;
|
const path = window.location.pathname;
|
||||||
if (path.includes('/mobile/preview')) return;
|
if (path.includes('preview')) return;
|
||||||
|
|
||||||
store.dispatch(stopSketch());
|
store.dispatch(stopSketch());
|
||||||
};
|
};
|
||||||
|
|
||||||
const routes = store => (
|
const routes = store => (
|
||||||
<Route path="/" component={App} onChange={() => { onRouteChange(store); }}>
|
<Route path="/" component={App} onChange={() => { onRouteChange(store); }}>
|
||||||
<IndexRoute component={IDEView} onEnter={checkAuth(store)} />
|
<IndexRoute onEnter={checkAuth(store)} component={mobileFirst(MobileIDEView, IDEView)} />
|
||||||
<Route path="/login" component={userIsNotAuthenticated(LoginView)} />
|
|
||||||
<Route path="/signup" component={userIsNotAuthenticated(SignupView)} />
|
<Route path="/login" component={userIsNotAuthenticated(mobileFirst(responsiveForm(LoginView), LoginView))} />
|
||||||
|
<Route path="/signup" component={userIsNotAuthenticated(mobileFirst(responsiveForm(SignupView), SignupView))} />
|
||||||
<Route path="/reset-password" component={userIsNotAuthenticated(ResetPasswordView)} />
|
<Route path="/reset-password" component={userIsNotAuthenticated(ResetPasswordView)} />
|
||||||
<Route path="/verify" component={EmailVerificationView} />
|
<Route path="/verify" component={EmailVerificationView} />
|
||||||
<Route
|
<Route
|
||||||
|
@ -46,28 +49,24 @@ 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={createRedirectWithUsername('/:username/sketches')} />
|
|
||||||
<Route path="/:username/assets" component={userIsAuthenticated(userIsAuthorized(DashboardView))} />
|
<Route path="/:username/assets" component={userIsAuthenticated(userIsAuthorized(mobileFirst(MobileDashboardView, DashboardView)))} />
|
||||||
<Route path="/assets" component={createRedirectWithUsername('/:username/assets')} />
|
<Route path="/:username/sketches" component={mobileFirst(MobileDashboardView, DashboardView)} />
|
||||||
<Route path="/account" component={userIsAuthenticated(AccountView)} />
|
<Route path="/:username/sketches/:project_id" component={mobileFirst(MobileIDEView, IDEView)} />
|
||||||
<Route path="/:username/sketches/:project_id" component={IDEView} />
|
<Route path="/:username/sketches/:project_id/add-to-collection" component={mobileFirst(MobileIDEView, IDEView)} />
|
||||||
<Route path="/:username/sketches/:project_id/add-to-collection" component={IDEView} />
|
<Route path="/:username/collections" component={mobileFirst(MobileDashboardView, DashboardView)} />
|
||||||
<Route path="/:username/sketches" component={DashboardView} />
|
|
||||||
<Route path="/:username/collections" component={DashboardView} />
|
|
||||||
<Route path="/:username/collections/create" component={DashboardView} />
|
<Route path="/:username/collections/create" component={DashboardView} />
|
||||||
<Route path="/:username/collections/:collection_id" component={CollectionView} />
|
<Route path="/:username/collections/:collection_id" component={CollectionView} />
|
||||||
|
|
||||||
|
<Route path="/sketches" component={createRedirectWithUsername('/:username/sketches')} />
|
||||||
|
<Route path="/assets" component={createRedirectWithUsername('/:username/assets')} />
|
||||||
|
<Route path="/account" component={userIsAuthenticated(AccountView)} />
|
||||||
<Route path="/about" component={IDEView} />
|
<Route path="/about" component={IDEView} />
|
||||||
|
|
||||||
|
{/* Mobile-only Routes */}
|
||||||
<Route path="/mobile/preview" component={MobileSketchView} />
|
<Route path="/preview" component={MobileSketchView} />
|
||||||
<Route path="/mobile/preferences" component={MobilePreferences} />
|
<Route path="/preferences" component={MobilePreferences} />
|
||||||
<Route path="/mobile" component={MobileIDEView} />
|
|
||||||
|
|
||||||
<Route path="/mobile/:username/sketches/:project_id" component={MobileIDEView} />
|
|
||||||
<Route path="/mobile/:username/assets" component={userIsAuthenticated(userIsAuthorized(MobileDashboardView))} />
|
|
||||||
<Route path="/mobile/:username/sketches" component={MobileDashboardView} />
|
|
||||||
<Route path="/mobile/:username/collections" component={MobileDashboardView} />
|
|
||||||
<Route path="/mobile/:username/collections/create" component={MobileDashboardView} />
|
|
||||||
|
|
||||||
</Route>
|
</Route>
|
||||||
);
|
);
|
||||||
|
|
|
@ -88,6 +88,7 @@ $themes: (
|
||||||
nav-border-color: $middle-light,
|
nav-border-color: $middle-light,
|
||||||
error-color: $p5js-pink,
|
error-color: $p5js-pink,
|
||||||
table-row-stripe-color: $medium-light,
|
table-row-stripe-color: $medium-light,
|
||||||
|
table-row-stripe-color-alternate: $medium-light,
|
||||||
codefold-icon-open: url(../images/triangle-arrow-down.svg?byUrl),
|
codefold-icon-open: url(../images/triangle-arrow-down.svg?byUrl),
|
||||||
codefold-icon-closed: url(../images/triangle-arrow-right.svg?byUrl),
|
codefold-icon-closed: url(../images/triangle-arrow-right.svg?byUrl),
|
||||||
|
|
||||||
|
@ -163,6 +164,7 @@ $themes: (
|
||||||
nav-border-color: $middle-dark,
|
nav-border-color: $middle-dark,
|
||||||
error-color: $p5js-pink,
|
error-color: $p5js-pink,
|
||||||
table-row-stripe-color: $dark,
|
table-row-stripe-color: $dark,
|
||||||
|
table-row-stripe-color-alternate: $darker,
|
||||||
codefold-icon-open: url(../images/triangle-arrow-down-white.svg?byUrl),
|
codefold-icon-open: url(../images/triangle-arrow-down-white.svg?byUrl),
|
||||||
codefold-icon-closed: url(../images/triangle-arrow-right-white.svg?byUrl),
|
codefold-icon-closed: url(../images/triangle-arrow-right-white.svg?byUrl),
|
||||||
|
|
||||||
|
@ -236,6 +238,7 @@ $themes: (
|
||||||
nav-border-color: $middle-dark,
|
nav-border-color: $middle-dark,
|
||||||
error-color: $p5-contrast-pink,
|
error-color: $p5-contrast-pink,
|
||||||
table-row-stripe-color: $dark,
|
table-row-stripe-color: $dark,
|
||||||
|
table-row-stripe-color-alternate: $darker,
|
||||||
codefold-icon-open: url(../images/triangle-arrow-down-white.svg?byUrl),
|
codefold-icon-open: url(../images/triangle-arrow-down-white.svg?byUrl),
|
||||||
codefold-icon-closed: url(../images/triangle-arrow-right-white.svg?byUrl),
|
codefold-icon-closed: url(../images/triangle-arrow-right-white.svg?byUrl),
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
.quick-add-wrapper {
|
.quick-add-wrapper {
|
||||||
min-width: #{600 / $base-font-size}rem;
|
min-width: #{600 / $base-font-size}rem;
|
||||||
overflow-y: scroll;
|
padding: #{24 / $base-font-size}rem;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-add {
|
.quick-add {
|
||||||
width: auto;
|
width: auto;
|
||||||
padding: #{24 / $base-font-size}rem;
|
overflow-y: scroll;
|
||||||
|
height: 100%;
|
||||||
|
@include themify() {
|
||||||
|
border: 1px solid getThemifyVariable('modal-border-color');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-add__item {
|
.quick-add__item {
|
||||||
|
@ -23,7 +28,7 @@
|
||||||
|
|
||||||
.quick-add__item:nth-child(odd) {
|
.quick-add__item:nth-child(odd) {
|
||||||
@include themify() {
|
@include themify() {
|
||||||
background: getThemifyVariable('table-row-stripe-color');
|
background: getThemifyVariable('table-row-stripe-color-alternate');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
client/utils/responsive.jsx
Normal file
23
client/utils/responsive.jsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import MediaQuery from 'react-responsive';
|
||||||
|
import ResponsiveForm from '../modules/User/components/ResponsiveForm';
|
||||||
|
|
||||||
|
export const mobileEnabled = () => (window.process.env.MOBILE_ENABLED === true);
|
||||||
|
|
||||||
|
export const mobileFirst = (MobileComponent, Fallback) => (props) => {
|
||||||
|
const { forceDesktop } = useSelector(state => state.editorAccessibility);
|
||||||
|
return (
|
||||||
|
<MediaQuery minWidth={770}>
|
||||||
|
{matches => ((matches || forceDesktop || (!mobileEnabled()))
|
||||||
|
? <Fallback {...props} />
|
||||||
|
: <MobileComponent {...props} />)}
|
||||||
|
</MediaQuery>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const responsiveForm = DesktopComponent => props => (
|
||||||
|
<ResponsiveForm>
|
||||||
|
<DesktopComponent {...props} />
|
||||||
|
</ResponsiveForm>
|
||||||
|
);
|
32
package-lock.json
generated
32
package-lock.json
generated
|
@ -13124,6 +13124,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"css-mediaquery": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz",
|
||||||
|
"integrity": "sha1-aiw3NEkoYYYxxUvTPO3TAdoYvqA="
|
||||||
|
},
|
||||||
"css-select": {
|
"css-select": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
||||||
|
@ -19020,6 +19025,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hyphenate-style-name": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
|
||||||
|
},
|
||||||
"i18next": {
|
"i18next": {
|
||||||
"version": "19.5.4",
|
"version": "19.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-19.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-19.5.4.tgz",
|
||||||
|
@ -25866,6 +25876,14 @@
|
||||||
"unquote": "^1.1.0"
|
"unquote": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"matchmediaquery": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.3.1.tgz",
|
||||||
|
"integrity": "sha512-Hlk20WQHRIm9EE9luN1kjRjYXAQToHOIAHPJn9buxBwuhfTHoKUcX+lXBbxc85DVQfXYbEQ4HcwQdd128E3qHQ==",
|
||||||
|
"requires": {
|
||||||
|
"css-mediaquery": "^0.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"material-colors": {
|
"material-colors": {
|
||||||
"version": "1.2.6",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
|
||||||
|
@ -32233,6 +32251,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-responsive": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-U8Nv2/ZWACIw/fAE9XNPbc2Xo33X5q1bcCASc2SufvJ9ifB+o/rokfogfznSVcvS22hN1rafGi0uZD6GiVFEHw==",
|
||||||
|
"requires": {
|
||||||
|
"hyphenate-style-name": "^1.0.0",
|
||||||
|
"matchmediaquery": "^0.3.0",
|
||||||
|
"prop-types": "^15.6.1",
|
||||||
|
"shallow-equal": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-router": {
|
"react-router": {
|
||||||
"version": "3.2.5",
|
"version": "3.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-3.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-3.2.5.tgz",
|
||||||
|
@ -34519,8 +34548,7 @@
|
||||||
"shallow-equal": {
|
"shallow-equal": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
|
||||||
"integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==",
|
"integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"shallowequal": {
|
"shallowequal": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
|
|
|
@ -201,6 +201,7 @@
|
||||||
"react-hot-loader": "^4.12.19",
|
"react-hot-loader": "^4.12.19",
|
||||||
"react-i18next": "^11.5.0",
|
"react-i18next": "^11.5.0",
|
||||||
"react-redux": "^7.2.0",
|
"react-redux": "^7.2.0",
|
||||||
|
"react-responsive": "^8.1.0",
|
||||||
"react-router": "^3.2.5",
|
"react-router": "^3.2.5",
|
||||||
"react-split-pane": "^0.1.89",
|
"react-split-pane": "^0.1.89",
|
||||||
"react-tabs": "^2.3.1",
|
"react-tabs": "^2.3.1",
|
||||||
|
|
|
@ -132,10 +132,6 @@ app.get(
|
||||||
// isomorphic rendering
|
// isomorphic rendering
|
||||||
app.use('/', serverRoutes);
|
app.use('/', serverRoutes);
|
||||||
|
|
||||||
if (process.env.MOBILE_ENABLED) {
|
|
||||||
app.use('/mobile', serverRoutes);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(assetRoutes);
|
app.use(assetRoutes);
|
||||||
|
|
||||||
app.use('/', embedRoutes);
|
app.use('/', embedRoutes);
|
||||||
|
|
|
@ -33,7 +33,7 @@ export function renderIndex() {
|
||||||
window.process.env.UI_COLLECTIONS_ENABLED = ${process.env.UI_COLLECTIONS_ENABLED === 'false' ? false : true};
|
window.process.env.UI_COLLECTIONS_ENABLED = ${process.env.UI_COLLECTIONS_ENABLED === 'false' ? false : true};
|
||||||
window.process.env.UPLOAD_LIMIT = ${process.env.UPLOAD_LIMIT ? `${process.env.UPLOAD_LIMIT}` : undefined};
|
window.process.env.UPLOAD_LIMIT = ${process.env.UPLOAD_LIMIT ? `${process.env.UPLOAD_LIMIT}` : undefined};
|
||||||
window.process.env.MOBILE_ENABLED = ${process.env.MOBILE_ENABLED ? `${process.env.MOBILE_ENABLED}` : undefined};
|
window.process.env.MOBILE_ENABLED = ${process.env.MOBILE_ENABLED ? `${process.env.MOBILE_ENABLED}` : undefined};
|
||||||
window.process.env.TRANSLATIONS_ENABLED = ${process.env.TRANSLATIONS_ENABLED === 'true' ? true :false};
|
window.process.env.TRANSLATIONS_ENABLED = ${process.env.TRANSLATIONS_ENABLED === 'true' ? true : false};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -88,7 +88,8 @@
|
||||||
"SketchSaved": "Sketch saved.",
|
"SketchSaved": "Sketch saved.",
|
||||||
"SketchFailedSave": "Failed to save sketch.",
|
"SketchFailedSave": "Failed to save sketch.",
|
||||||
"AutosaveEnabled": "Autosave enabled.",
|
"AutosaveEnabled": "Autosave enabled.",
|
||||||
"LangChange": "Language changed"
|
"LangChange": "Language changed",
|
||||||
|
"SettingsSaved": "Settings saved."
|
||||||
},
|
},
|
||||||
"Toolbar": {
|
"Toolbar": {
|
||||||
"Preview": "Preview",
|
"Preview": "Preview",
|
||||||
|
@ -174,18 +175,42 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Sidebar": {
|
"Sidebar": {
|
||||||
"Create": "Create",
|
"Title": "Sketch Files",
|
||||||
"EnterName": "enter a name",
|
"ToggleARIA": "Toggle open/close sketch file options",
|
||||||
"Add": "Add",
|
"AddFolder": "Create folder",
|
||||||
"Folder": "Folder"
|
"AddFolderARIA": "add folder",
|
||||||
|
"AddFile": "Create file",
|
||||||
|
"AddFileARIA": "add file",
|
||||||
|
"UploadFile": "Upload file",
|
||||||
|
"UploadFileARIA": "upload file"
|
||||||
|
},
|
||||||
|
"FileNode": {
|
||||||
|
"OpenFolderARIA": "Open folder contents",
|
||||||
|
"CloseFolderARIA": "Close folder contents",
|
||||||
|
"ToggleFileOptionsARIA": "Toggle open/close file options",
|
||||||
|
"AddFolder": "Create folder",
|
||||||
|
"AddFolderARIA": "add folder",
|
||||||
|
"AddFile": "Create file",
|
||||||
|
"AddFileARIA": "add file",
|
||||||
|
"UploadFile": "Upload file",
|
||||||
|
"UploadFileARIA": "upload file",
|
||||||
|
"Rename": "Rename",
|
||||||
|
"Delete": "Delete"
|
||||||
},
|
},
|
||||||
"Common": {
|
"Common": {
|
||||||
"Error": "Error",
|
"Error": "Error",
|
||||||
|
"ErrorARIA": "Error",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"p5logoARIA": "p5.js Logo"
|
"p5logoARIA": "p5.js Logo",
|
||||||
|
"DeleteConfirmation": "Are you sure you want to delete {{name}}?"
|
||||||
},
|
},
|
||||||
"IDEView": {
|
"IDEView": {
|
||||||
"SubmitFeedback": "Submit Feedback"
|
"SubmitFeedback": "Submit Feedback",
|
||||||
|
"SubmitFeedbackARIA": "submit-feedback",
|
||||||
|
"AddCollectionTitle": "Add to collection",
|
||||||
|
"AddCollectionARIA":"add to collection",
|
||||||
|
"ShareTitle": "Share",
|
||||||
|
"ShareARIA":"share"
|
||||||
},
|
},
|
||||||
"NewFileModal": {
|
"NewFileModal": {
|
||||||
"Title": "Create File",
|
"Title": "Create File",
|
||||||
|
@ -308,7 +333,6 @@
|
||||||
"AlreadyHave": "Already have an account?",
|
"AlreadyHave": "Already have an account?",
|
||||||
"Login": "Log In"
|
"Login": "Log In"
|
||||||
},
|
},
|
||||||
|
|
||||||
"EmailVerificationView": {
|
"EmailVerificationView": {
|
||||||
"Title": "p5.js Web Editor | Email Verification",
|
"Title": "p5.js Web Editor | Email Verification",
|
||||||
"Verify": "Verify your email",
|
"Verify": "Verify your email",
|
||||||
|
@ -316,5 +340,139 @@
|
||||||
"Checking": "Validating token, please wait...",
|
"Checking": "Validating token, please wait...",
|
||||||
"Verified": "All done, your email address has been verified.",
|
"Verified": "All done, your email address has been verified.",
|
||||||
"InvalidState": "Something went wrong."
|
"InvalidState": "Something went wrong."
|
||||||
|
},
|
||||||
|
"UploadFileModal": {
|
||||||
|
"Title": "Upload File",
|
||||||
|
"CloseButtonARIA": "Close upload file modal",
|
||||||
|
"SizeLimitError": "Error: You cannot upload any more files. You have reached the total size limit of {{sizeLimit}}.\n If you would like to upload more, please remove the ones you aren't using anymore by\n in your "
|
||||||
|
},
|
||||||
|
"FileUploader": {
|
||||||
|
"DictDefaultMessage": "Drop files here or click to use the file browser"
|
||||||
|
},
|
||||||
|
"ErrorModal": {
|
||||||
|
"MessageLogin": "In order to save sketches, you must be logged in. Please ",
|
||||||
|
"Login": "Login",
|
||||||
|
"LoginOr": " or ",
|
||||||
|
"SignUp": "Sign Up",
|
||||||
|
"MessageLoggedOut": "It looks like you've been logged out. Please ",
|
||||||
|
"LogIn": "log in",
|
||||||
|
"SavedDifferentWindow": "The project you have attempted to save has been saved from another window.\n Please refresh the page to see the latest version."
|
||||||
|
},
|
||||||
|
"ShareModal": {
|
||||||
|
"Embed": "Embed",
|
||||||
|
"Present": "Present",
|
||||||
|
"Fullscreen": "Fullscreen",
|
||||||
|
"Edit": "Edit"
|
||||||
|
},
|
||||||
|
"CollectionView": {
|
||||||
|
"TitleCreate": "Create collection",
|
||||||
|
"TitleDefault": "collection"
|
||||||
|
},
|
||||||
|
"Collection": {
|
||||||
|
"Title": "p5.js Web Editor | My collections",
|
||||||
|
"AnothersTitle": "p5.js Web Editor | {{anotheruser}}'s collections",
|
||||||
|
"Share": "Share",
|
||||||
|
"URLLink": "Link to Collection",
|
||||||
|
"AddSketch": "Add Sketch",
|
||||||
|
"DeleteFromCollection": "Are you sure you want to remove {{name_sketch}} from this collection?",
|
||||||
|
"SketchDeleted": "Sketch deleted",
|
||||||
|
"SketchRemoveARIA": "Remove sketch from collection",
|
||||||
|
"DescriptionPlaceholder": "Add description",
|
||||||
|
"Description": "description",
|
||||||
|
"NumSketches": "{{count}} sketch",
|
||||||
|
"NumSketches_plural": "{{count}} sketches",
|
||||||
|
"By":"Collection by ",
|
||||||
|
"NoSketches": "No sketches in collection",
|
||||||
|
"HeaderName": "Name",
|
||||||
|
"HeaderCreatedAt": "Date Added",
|
||||||
|
"HeaderUser": "Owner",
|
||||||
|
"DirectionAscendingARIA": "Ascending",
|
||||||
|
"DirectionDescendingARIA": "Descending",
|
||||||
|
"ButtonLabelAscendingARIA": "Sort by {{displayName}} ascending.",
|
||||||
|
"ButtonLabelDescendingARIA": "Sort by {{displayName}} descending."
|
||||||
|
},
|
||||||
|
"AddToCollectionList": {
|
||||||
|
"Title": "p5.js Web Editor | My collections",
|
||||||
|
"AnothersTitle": "p5.js Web Editor | {{anotheruser}}'s collections",
|
||||||
|
"Empty": "No collections"
|
||||||
|
},
|
||||||
|
"CollectionCreate": {
|
||||||
|
"Title": "p5.js Web Editor | Create collection",
|
||||||
|
"FormError": "Couldn't create collection",
|
||||||
|
"FormLabel": "Collection name",
|
||||||
|
"FormLabelARIA": "name",
|
||||||
|
"NameRequired": "Collection name is required",
|
||||||
|
"Description": "Description (optional)",
|
||||||
|
"DescriptionARIA": "description",
|
||||||
|
"DescriptionPlaceholder": "My fave sketches",
|
||||||
|
"SubmitCollectionCreate": "Create collection"
|
||||||
|
},
|
||||||
|
"DashboardView": {
|
||||||
|
"CreateCollection": "Create collection",
|
||||||
|
"NewSketch": "New sketch",
|
||||||
|
"CreateCollectionOverlay": "Create collection"
|
||||||
|
},
|
||||||
|
"DashboardTabSwitcher": {
|
||||||
|
"Sketches": "Sketches",
|
||||||
|
"Collections": "Collections",
|
||||||
|
"Assets": "Assets"
|
||||||
|
},
|
||||||
|
"CollectionList": {
|
||||||
|
"Title": "p5.js Web Editor | My collections",
|
||||||
|
"AnothersTitle": "p5.js Web Editor | {{anotheruser}}'s collections",
|
||||||
|
"NoCollections": "No collections.",
|
||||||
|
"HeaderName": "Name",
|
||||||
|
"HeaderCreatedAt": "Date Created",
|
||||||
|
"HeaderCreatedAt_mobile": "Created",
|
||||||
|
"HeaderUpdatedAt": "Date Updated",
|
||||||
|
"HeaderUpdatedAt_mobile": "Updated",
|
||||||
|
"HeaderNumItems": "# sketches",
|
||||||
|
"HeaderNumItems_mobile": "# sketches",
|
||||||
|
"DirectionAscendingARIA": "Ascending",
|
||||||
|
"DirectionDescendingARIA": "Descending",
|
||||||
|
"ButtonLabelAscendingARIA": "Sort by {{displayName}} ascending.",
|
||||||
|
"ButtonLabelDescendingARIA": "Sort by {{displayName}} descending.",
|
||||||
|
"AddSketch": "Add Sketch"
|
||||||
|
},
|
||||||
|
"CollectionListRow": {
|
||||||
|
"ToggleCollectionOptionsARIA": "Toggle Open/Close collection options",
|
||||||
|
"AddSketch": "Add sketch",
|
||||||
|
"Delete": "Delete",
|
||||||
|
"Rename": "Rename"
|
||||||
|
},
|
||||||
|
"Overlay": {
|
||||||
|
"AriaLabel": "Close {{title}} overlay"
|
||||||
|
},
|
||||||
|
"QuickAddList":{
|
||||||
|
"ButtonLabelRemove": "Remove from collection",
|
||||||
|
"ButtonLabelAddToCollection": "Add to collection",
|
||||||
|
"View": "View"
|
||||||
|
},
|
||||||
|
"SketchList": {
|
||||||
|
"View": "View",
|
||||||
|
"Title": "p5.js Web Editor | My sketches",
|
||||||
|
"AnothersTitle": "p5.js Web Editor | {{anotheruser}}'s sketches",
|
||||||
|
"ToggleLabelARIA": "Toggle Open/Close Sketch Options",
|
||||||
|
"DropdownRename": "Rename",
|
||||||
|
"DropdownDownload": "Download",
|
||||||
|
"DropdownDuplicate": "Duplicate",
|
||||||
|
"DropdownAddToCollection": "Add to collection",
|
||||||
|
"DropdownDelete": "Delete",
|
||||||
|
"DirectionAscendingARIA": "Ascending",
|
||||||
|
"DirectionDescendingARIA": "Descending",
|
||||||
|
"ButtonLabelAscendingARIA": "Sort by {{displayName}} ascending.",
|
||||||
|
"ButtonLabelDescendingARIA": "Sort by {{displayName}} descending.",
|
||||||
|
"AddToCollectionOverlayTitle": "Add to collection",
|
||||||
|
"HeaderName": "Sketch",
|
||||||
|
"HeaderCreatedAt": "Date Created",
|
||||||
|
"HeaderCreatedAt_mobile": "Created",
|
||||||
|
"HeaderUpdatedAt": "Date Updated",
|
||||||
|
"HeaderUpdatedAt_mobile": "Updated",
|
||||||
|
"NoSketches": "No sketches."
|
||||||
|
},
|
||||||
|
"AddToCollectionSketchList": {
|
||||||
|
"Title": "p5.js Web Editor | My sketches",
|
||||||
|
"AnothersTitle": "p5.js Web Editor | {{anotheruser}}'s sketches",
|
||||||
|
"NoCollections": "No collections."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,8 @@
|
||||||
"SketchSaved": "Bosquejo guardado.",
|
"SketchSaved": "Bosquejo guardado.",
|
||||||
"SketchFailedSave": "Fallo al guardar el bosquejo.",
|
"SketchFailedSave": "Fallo al guardar el bosquejo.",
|
||||||
"AutosaveEnabled": "Grabado automático activado.",
|
"AutosaveEnabled": "Grabado automático activado.",
|
||||||
"LangChange": "Lenguaje cambiado"
|
"LangChange": "Lenguaje cambiado",
|
||||||
|
"SettingsSaved": "Configuración guardada."
|
||||||
},
|
},
|
||||||
"Toolbar": {
|
"Toolbar": {
|
||||||
"Preview": "Vista previa",
|
"Preview": "Vista previa",
|
||||||
|
@ -174,18 +175,42 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Sidebar": {
|
"Sidebar": {
|
||||||
"Create": "Crear",
|
"Title": "Archivos de Bosquejo",
|
||||||
"EnterName": "Introduce un nombre",
|
"ToggleARIA": "Alternar abrir/cerrar opciones de archivo de bosquejo",
|
||||||
"Add": "Agregar",
|
"AddFolder": "Crear directorio",
|
||||||
"Folder": "Directorio"
|
"AddFolderARIA": "Agregar directorio",
|
||||||
|
"AddFile": "Crear archivo",
|
||||||
|
"AddFileARIA": "agregar archivo",
|
||||||
|
"UploadFile": "Subir archivo",
|
||||||
|
"UploadFileARIA": "Subir archivo"
|
||||||
|
},
|
||||||
|
"FileNode": {
|
||||||
|
"OpenFolderARIA": "Abrir contenidos del directorio",
|
||||||
|
"CloseFolderARIA": "Cerrar contenidos del directorio",
|
||||||
|
"ToggleFileOptionsARIA": "Alternar abrir/cerrar opciones de archivo",
|
||||||
|
"AddFolder": "Crear directorio",
|
||||||
|
"AddFolderARIA": "Agregar directorio",
|
||||||
|
"AddFile": "Crear archivo",
|
||||||
|
"AddFileARIA": "agregar archivo",
|
||||||
|
"UploadFile": "Subir archivo",
|
||||||
|
"UploadFileARIA": "Subir archivo",
|
||||||
|
"Rename": "Renombrar",
|
||||||
|
"Delete": "Borrar"
|
||||||
},
|
},
|
||||||
"Common": {
|
"Common": {
|
||||||
"Error": "Error",
|
"Error": "Error",
|
||||||
|
"ErrorARIA": "Error",
|
||||||
"Save": "Guardar",
|
"Save": "Guardar",
|
||||||
"p5logoARIA": "Logo p5.js "
|
"p5logoARIA": "Logo p5.js ",
|
||||||
|
"DeleteConfirmation": "¿Estás seguro que quieres borrar {{name}}?"
|
||||||
},
|
},
|
||||||
"IDEView": {
|
"IDEView": {
|
||||||
"SubmitFeedback": "Enviar retroalimentación"
|
"SubmitFeedback": "Enviar retroalimentación",
|
||||||
|
"SubmitFeedbackARIA": "Enviar retroalimentación",
|
||||||
|
"AddCollectionTitle": "Agregar a colección",
|
||||||
|
"AddCollectionARIA":"Agregar a colección",
|
||||||
|
"ShareTitle": "Compartir",
|
||||||
|
"ShareARIA":"compartir"
|
||||||
},
|
},
|
||||||
"NewFileModal": {
|
"NewFileModal": {
|
||||||
"Title": "Crear Archivo",
|
"Title": "Crear Archivo",
|
||||||
|
@ -216,7 +241,7 @@
|
||||||
"ResetPasswordView": {
|
"ResetPasswordView": {
|
||||||
"Title": "Editor Web p5.js | Regenerar Contraseña",
|
"Title": "Editor Web p5.js | Regenerar Contraseña",
|
||||||
"Reset": "Regenerar Contraseña",
|
"Reset": "Regenerar Contraseña",
|
||||||
"Submitted": "Your password reset email should arrive shortly. If you don't see it, check\n in your spam folder as sometimes it can end up there.",
|
"Submitted": "Tu correo para regenerar la contraseña debe llegar pronto. Si no lo ves, revisa\n tu carpeta de spam puesto que algunas veces puede terminar ahí.",
|
||||||
"Login": "Ingresa",
|
"Login": "Ingresa",
|
||||||
"LoginOr": "o",
|
"LoginOr": "o",
|
||||||
"SignUp": "Registráte"
|
"SignUp": "Registráte"
|
||||||
|
@ -264,7 +289,7 @@
|
||||||
"AccessTokensTab": "Tokens de acceso"
|
"AccessTokensTab": "Tokens de acceso"
|
||||||
},
|
},
|
||||||
"APIKeyForm": {
|
"APIKeyForm": {
|
||||||
"ConfirmDelete": "¿Estas seguro que quieres borrar {{key_label}}?",
|
"ConfirmDelete": "¿Estás seguro que quieres borrar {{key_label}}?",
|
||||||
"Summary": " Los Tokens de acceso personal actuan como tu contraseña para permitir\n a scripts automáticos acceder al API del Editor. Crea un token por cada script \n que necesite acceso.",
|
"Summary": " Los Tokens de acceso personal actuan como tu contraseña para permitir\n a scripts automáticos acceder al API del Editor. Crea un token por cada script \n que necesite acceso.",
|
||||||
"CreateToken": "Crear nuevo token",
|
"CreateToken": "Crear nuevo token",
|
||||||
"TokenLabel": "¿Para que es este token?",
|
"TokenLabel": "¿Para que es este token?",
|
||||||
|
@ -315,5 +340,139 @@
|
||||||
"Checking": "Validando token, por favor espera...",
|
"Checking": "Validando token, por favor espera...",
|
||||||
"Verified": "Concluido, tu correo electrónico ha sido verificado.",
|
"Verified": "Concluido, tu correo electrónico ha sido verificado.",
|
||||||
"InvalidState": "Algo salió mal."
|
"InvalidState": "Algo salió mal."
|
||||||
|
},
|
||||||
|
"UploadFileModal": {
|
||||||
|
"Title": "Subir Archivo",
|
||||||
|
"CloseButtonARIA": "Cerrar diálogo para subir archivo",
|
||||||
|
"SizeLimitError": "Error: No puedes subir archivos. Has alcanzado el limite de tamaño total de {{sizeLimit}}.\n Si quieres agregar más,por favor remueve alugnos que no estes usando en tus "
|
||||||
|
},
|
||||||
|
"FileUploader": {
|
||||||
|
"DictDefaultMessage": "Deposita los archivos aquí o haz click para usar el navegador de archivos"
|
||||||
|
},
|
||||||
|
"ErrorModal": {
|
||||||
|
"MessageLogin": "Para poder guardar bosquejos, debes ingresar a tu cuenta. Por favor ",
|
||||||
|
"Login": "Ingresa",
|
||||||
|
"LoginOr": " o ",
|
||||||
|
"SignUp": "Registráte",
|
||||||
|
"MessageLoggedOut": "Parece que has salido de tu cuenta. Por favor ",
|
||||||
|
"LogIn": "ingresa",
|
||||||
|
"SavedDifferentWindow": " El proyecto que has intentado guardar ha sido guardado desde otra ventana.\n Por favor refresca la página para ver la versión más actual."
|
||||||
|
},
|
||||||
|
"ShareModal": {
|
||||||
|
"Embed": "Incrustar",
|
||||||
|
"Present": "Presentar",
|
||||||
|
"Fullscreen": "Pantalla Completa",
|
||||||
|
"Edit": "Editar"
|
||||||
|
},
|
||||||
|
"CollectionView": {
|
||||||
|
"TitleCreate": "Crear colección",
|
||||||
|
"TitleDefault": "colección"
|
||||||
|
},
|
||||||
|
"Collection": {
|
||||||
|
"Title": "p5.js Web Editor | Mis colecciones",
|
||||||
|
"AnothersTitle": "Editor Web p5.js | Colecciones de {{anotheruser}}",
|
||||||
|
"Share": "Compartir",
|
||||||
|
"URLLink": "Liga a la Colección",
|
||||||
|
"AddSketch": "Agregar Bosquejo",
|
||||||
|
"DeleteFromCollection": "¿Estás seguro que quieres remover {{name_sketch}} de esta colección?",
|
||||||
|
"SketchDeleted": "El bosquejo fue eliminado",
|
||||||
|
"SketchRemoveARIA": "Remover bosquejo de la colección",
|
||||||
|
"DescriptionPlaceholder": "Agregar descripción",
|
||||||
|
"Description": "descripción",
|
||||||
|
"NumSketches": "{{count}} bosquejo",
|
||||||
|
"NumSketches_plural": "{{count}} bosquejos",
|
||||||
|
"By":"Colección por ",
|
||||||
|
"NoSketches": "No hay bosquejos en la colección",
|
||||||
|
"HeaderName": "Nombre",
|
||||||
|
"HeaderCreatedAt": "Fecha de agregación",
|
||||||
|
"HeaderUser": "Propietario",
|
||||||
|
"DirectionAscendingARIA": "Ascendente",
|
||||||
|
"DirectionDescendingARIA": "Descendente",
|
||||||
|
"ButtonLabelAscendingARIA": "Ordenar por {{displayName}} ascendente.",
|
||||||
|
"ButtonLabelDescendingARIA": "Ordenar por {{displayName}} descendente."
|
||||||
|
},
|
||||||
|
"AddToCollectionList": {
|
||||||
|
"Title": "p5.js Web Editor | Mis colecciones",
|
||||||
|
"AnothersTitle": "Editor Web p5.js | Colecciones de {{anotheruser}}",
|
||||||
|
"Empty": "No hay colecciones"
|
||||||
|
},
|
||||||
|
"CollectionCreate": {
|
||||||
|
"Title": "Editor Web p5.js | Crear colección",
|
||||||
|
"FormError": "No se pudo crear colección",
|
||||||
|
"FormLabel": "Nombre colección ",
|
||||||
|
"FormLabelARIA": "nombre de la colección",
|
||||||
|
"NameRequired": "Se requiere nombre de colección",
|
||||||
|
"Description": "Descripción (opcional)",
|
||||||
|
"DescriptionARIA": "descripción",
|
||||||
|
"DescriptionPlaceholder": "Mis bosquejos favoritos",
|
||||||
|
"SubmitCollectionCreate": "Crear colección"
|
||||||
|
},
|
||||||
|
"DashboardView": {
|
||||||
|
"CreateCollection": "Crear colección",
|
||||||
|
"NewSketch": "Nuevo bosquejo",
|
||||||
|
"CreateCollectionOverlay": "Crear colección"
|
||||||
|
},
|
||||||
|
"DashboardTabSwitcher": {
|
||||||
|
"Sketches": "Bosquejos",
|
||||||
|
"Collections": "Colecciones ",
|
||||||
|
"Assets": "Assets"
|
||||||
|
},
|
||||||
|
"CollectionList": {
|
||||||
|
"Title": "p5.js Web Editor | Mis colecciones",
|
||||||
|
"AnothersTitle": "Editor Web p5.js | Colecciones de {{anotheruser}}",
|
||||||
|
"NoCollections": "No hay colecciones.",
|
||||||
|
"HeaderName": "Nombre",
|
||||||
|
"HeaderCreatedAt": "Fecha Creación",
|
||||||
|
"HeaderCreatedAt_mobile": "Creación",
|
||||||
|
"HeaderUpdatedAt": "Fecha Actualización",
|
||||||
|
"HeaderUpdatedAt_mobile": "Actualización",
|
||||||
|
"HeaderNumItems": "# bosquejos",
|
||||||
|
"HeaderNumItems_mobile": "Bosquejos",
|
||||||
|
"DirectionAscendingARIA": "Ascendente",
|
||||||
|
"DirectionDescendingARIA": "Descendente",
|
||||||
|
"ButtonLabelAscendingARIA": "Ordenar por {{displayName}} ascendente.",
|
||||||
|
"ButtonLabelDescendingARIA": "Ordenar por {{displayName}} descendente.",
|
||||||
|
"AddSketch": "Agregar Bosquejo"
|
||||||
|
},
|
||||||
|
"CollectionListRow": {
|
||||||
|
"ToggleCollectionOptionsARIA": "Alternar Abrir/Cerrar opciones de colección",
|
||||||
|
"AddSketch": "Agregar bosquejo",
|
||||||
|
"Delete": "Borrar",
|
||||||
|
"Rename": "Renombrar"
|
||||||
|
},
|
||||||
|
"Overlay": {
|
||||||
|
"AriaLabel": "Cerrar la capa {{title}}"
|
||||||
|
},
|
||||||
|
"QuickAddList":{
|
||||||
|
"ButtonLabelRemove": "Remove from collection",
|
||||||
|
"ButtonLabelAddToCollection": "Add to collection",
|
||||||
|
"View": "Ver"
|
||||||
|
},
|
||||||
|
"SketchList": {
|
||||||
|
"View": "Ver",
|
||||||
|
"Title": "p5.js Web Editor | Mis bosquejos",
|
||||||
|
"AnothersTitle": "Editor Web p5.js | Bosquejos de {{anotheruser}}",
|
||||||
|
"ToggleLabelARIA": "Alternar Abrir/Cerrar Opciones de Bosquejo",
|
||||||
|
"DropdownRename": "Renombrar",
|
||||||
|
"DropdownDownload": "Descargar",
|
||||||
|
"DropdownDuplicate": "Duplicar",
|
||||||
|
"DropdownAddToCollection": "Agregar a Colección",
|
||||||
|
"DropdownDelete": "Borrar",
|
||||||
|
"DirectionAscendingARIA": "Ascendente",
|
||||||
|
"DirectionDescendingARIA": "Descendente",
|
||||||
|
"ButtonLabelAscendingARIA": "Ordenar por {{displayName}} ascendente.",
|
||||||
|
"ButtonLabelDescendingARIA": "Ordenar por {{displayName}} descendente.",
|
||||||
|
"AddToCollectionOverlayTitle": "Agregar a colección",
|
||||||
|
"HeaderName": "Bosquejo",
|
||||||
|
"HeaderCreatedAt": "Fecha Creación",
|
||||||
|
"HeaderCreatedAt_mobile": "Creación",
|
||||||
|
"HeaderUpdatedAt": "Fecha Actualización",
|
||||||
|
"HeaderUpdatedAt_mobile": "Actualización",
|
||||||
|
"NoSketches": "No hay bosquejos."
|
||||||
|
},
|
||||||
|
"AddToCollectionSketchList": {
|
||||||
|
"Title": "p5.js Web Editor | Mis bosquejos",
|
||||||
|
"AnothersTitle": "Editor Web p5.js | Bosquejos de {{anotheruser}}",
|
||||||
|
"NoCollections": "No hay colecciones."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue