Merge branch 'develop' into feature/mobile-login

This commit is contained in:
Cassie Tarakajian 2020-08-19 16:55:10 -04:00 committed by GitHub
commit f844023aed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 124 additions and 58 deletions

View 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;
},
}));

View File

@ -14,7 +14,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) {
@ -81,12 +81,14 @@ class CollectionList extends React.Component {
} }
return ( return (
<div className="quick-add-wrapper"> <div className="collection-add-sketch">
<Helmet> <div className="quick-add-wrapper">
<title>{this.getTitle()}</title> <Helmet>
</Helmet> <title>{this.getTitle()}</title>
</Helmet>
{content} {content}
</div>
</div> </div>
); );
} }

View File

@ -72,11 +72,13 @@ class SketchList extends React.Component {
} }
return ( return (
<div className="quick-add-wrapper"> <div className="collection-add-sketch">
<Helmet> <div className="quick-add-wrapper">
<title>{this.getSketchesTitle()}</title> <Helmet>
</Helmet> <title>{this.getSketchesTitle()}</title>
{content} </Helmet>
{content}
</div>
</div> </div>
); );
} }

View File

@ -170,12 +170,10 @@ class CollectionList extends React.Component {
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>
) )
} }

View File

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

View File

@ -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;

View File

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

View File

@ -398,9 +398,10 @@ class Collection extends React.Component {
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>
) )
} }

View File

@ -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),

View File

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

View File

@ -174,15 +174,33 @@
} }
}, },
"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",
"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"