🔀 merge from develop

This commit is contained in:
ghalestrilo 2020-07-24 17:01:48 -03:00
commit 9bebf9f7c9
24 changed files with 7977 additions and 13115 deletions

View File

@ -13,6 +13,9 @@ import DropdownArrow from '../images/down-filled-triangle.svg';
import Preferences from '../images/preferences.svg';
import Play from '../images/triangle-arrow-right.svg';
import More from '../images/more.svg';
import Code from '../images/code.svg';
import Terminal from '../images/terminal.svg';
// HOC that adds the right web accessibility props
// https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html
@ -76,3 +79,4 @@ export const DropdownArrowIcon = withLabel(DropdownArrow);
export const PreferencesIcon = withLabel(Preferences);
export const PlayIcon = withLabel(Play);
export const MoreIcon = withLabel(More);
export const TerminalIcon = withLabel(Terminal);

View File

@ -4,7 +4,6 @@ import { createPortal } from 'react-dom';
import Dropdown from './Dropdown';
import { PreferencesIcon } from '../common/icons';
const OverlayManager = ({ overlay, hideOverlay }) => {
@ -47,13 +46,8 @@ const OverlayManager = ({ overlay, hideOverlay }) => {
return jsx && createPortal(jsx, document.body);
};
// const refPropType = PropTypes.oneOfType([
// PropTypes.func,
// PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
// ]);
OverlayManager.propTypes = {
// ref: refPropType.isRequired,
overlay: PropTypes.string,
hideOverlay: PropTypes.func.isRequired,
};

View File

@ -0,0 +1,35 @@
import React from 'react';
import styled from 'styled-components';
import { bindActionCreators } from 'redux';
import { useDispatch, useSelector } from 'react-redux';
import { remSize } from '../../theme';
import IconButton from './IconButton';
import { TerminalIcon } from '../../common/icons';
import * as IDEActions from '../../modules/IDE/actions/ide';
const BottomBarContent = styled.h2`
padding: ${remSize(8)};
svg {
max-height: ${remSize(32)};
}
`;
export default () => {
const { expandConsole, collapseConsole } = bindActionCreators(IDEActions, useDispatch());
const { consoleIsExpanded } = useSelector(state => state.ide);
const actions = [{ icon: TerminalIcon, aria: 'Say Something', action: consoleIsExpanded ? collapseConsole : expandConsole }];
return (
<BottomBarContent>
{actions.map(({ icon, aria, action }) =>
(<IconButton
icon={icon}
aria-label={aria}
key={`bottom-bar-${aria}`}
onClick={() => action()}
/>))}
</BottomBarContent>
);
};

View File

@ -1,6 +1,6 @@
import React from 'react';
import styled from 'styled-components';
import { prop } from '../../theme';
import { prop, grays } from '../../theme';
const background = prop('MobilePanel.default.background');
@ -12,4 +12,6 @@ export default styled.div`
bottom: 0;
background: ${background};
color: ${textColor};
& > * + * { border-top: dashed 1px ${prop('Separator')} }
`;

View File

@ -3,14 +3,14 @@ import styled from 'styled-components';
import PropTypes from 'prop-types';
import { prop, remSize } from '../../theme';
const background = prop('MobilePanel.default.background');
const background = transparent => prop(transparent ? 'backgroundColor' : 'MobilePanel.default.background');
const textColor = prop('primaryTextColor');
const HeaderDiv = styled.div`
position: fixed;
width: 100%;
background: ${props => (props.transparent ? 'transparent' : background)};
background: ${props => background(props.transparent === true)};
color: ${textColor};
padding: ${remSize(12)};
padding-left: ${remSize(16)};
@ -23,7 +23,6 @@ const HeaderDiv = styled.div`
justify-content: flex-start;
align-items: center;
// TODO:
svg {
max-height: ${remSize(32)};
padding: ${remSize(4)}

View File

@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="5" y="8" width="22" height="16" rx="2" fill="#333333"/>
<path d="M24 21H14V20H24V21Z" fill="#F0F0F0"/>
<path d="M10.4081 16.0231L8.3676 18.0637C8.27757 18.1537 8.15754 18.1537 8.06752 18.0637C7.97749 17.9736 7.97749 17.8536 8.06752 17.7636L9.95802 15.8731L8.06752 13.9826C7.97749 13.8926 7.97749 13.7725 8.06752 13.6675C8.15754 13.5775 8.27757 13.5775 8.3676 13.6675L10.4081 15.723C10.4532 15.753 10.4832 15.8131 10.4832 15.8731C10.4832 15.9181 10.4532 15.9781 10.4081 16.0231Z" fill="#F0F0F0"/>
</svg>

After

Width:  |  Height:  |  Size: 608 B

View File

@ -1,6 +1,8 @@
import PropTypes from 'prop-types';
import React from 'react';
import { useSelector, useDispatch, connect } from 'react-redux';
import React, { useRef } from 'react';
import { bindActionCreators } from 'redux';
import { useSelector, useDispatch } from 'react-redux';
import classNames from 'classnames';
import { Console as ConsoleFeed } from 'console-feed';
import {
@ -25,172 +27,118 @@ import DownArrowIcon from '../../../images/down-arrow.svg';
import * as IDEActions from '../../IDE/actions/ide';
import * as ConsoleActions from '../../IDE/actions/console';
import { useDidUpdate } from '../../../utils/custom-hooks';
class ConsoleComponent extends React.Component {
componentDidUpdate(prevProps) {
this.consoleMessages.scrollTop = this.consoleMessages.scrollHeight;
if (this.props.theme !== prevProps.theme) {
this.props.clearConsole();
this.props.dispatchConsoleEvent(this.props.consoleEvents);
}
const getConsoleFeedStyle = (theme, times, fontSize) => {
const style = {};
const CONSOLE_FEED_LIGHT_ICONS = {
LOG_WARN_ICON: `url(${warnLightUrl})`,
LOG_ERROR_ICON: `url(${errorLightUrl})`,
LOG_DEBUG_ICON: `url(${debugLightUrl})`,
LOG_INFO_ICON: `url(${infoLightUrl})`
};
const CONSOLE_FEED_DARK_ICONS = {
LOG_WARN_ICON: `url(${warnDarkUrl})`,
LOG_ERROR_ICON: `url(${errorDarkUrl})`,
LOG_DEBUG_ICON: `url(${debugDarkUrl})`,
LOG_INFO_ICON: `url(${infoDarkUrl})`
};
const CONSOLE_FEED_CONTRAST_ICONS = {
LOG_WARN_ICON: `url(${warnContrastUrl})`,
LOG_ERROR_ICON: `url(${errorContrastUrl})`,
LOG_DEBUG_ICON: `url(${debugContrastUrl})`,
LOG_INFO_ICON: `url(${infoContrastUrl})`
};
const CONSOLE_FEED_SIZES = {
TREENODE_LINE_HEIGHT: 1.2,
BASE_FONT_SIZE: fontSize,
ARROW_FONT_SIZE: fontSize,
LOG_ICON_WIDTH: fontSize,
LOG_ICON_HEIGHT: 1.45 * fontSize,
};
if (this.props.fontSize !== prevProps.fontSize) {
this.props.clearConsole();
this.props.dispatchConsoleEvent(this.props.consoleEvents);
}
if (times > 1) {
Object.assign(style, CONSOLE_FEED_WITHOUT_ICONS);
}
getConsoleFeedStyle(theme, times) {
const style = {};
const CONSOLE_FEED_LIGHT_ICONS = {
LOG_WARN_ICON: `url(${warnLightUrl})`,
LOG_ERROR_ICON: `url(${errorLightUrl})`,
LOG_DEBUG_ICON: `url(${debugLightUrl})`,
LOG_INFO_ICON: `url(${infoLightUrl})`
};
const CONSOLE_FEED_DARK_ICONS = {
LOG_WARN_ICON: `url(${warnDarkUrl})`,
LOG_ERROR_ICON: `url(${errorDarkUrl})`,
LOG_DEBUG_ICON: `url(${debugDarkUrl})`,
LOG_INFO_ICON: `url(${infoDarkUrl})`
};
const CONSOLE_FEED_CONTRAST_ICONS = {
LOG_WARN_ICON: `url(${warnContrastUrl})`,
LOG_ERROR_ICON: `url(${errorContrastUrl})`,
LOG_DEBUG_ICON: `url(${debugContrastUrl})`,
LOG_INFO_ICON: `url(${infoContrastUrl})`
};
const CONSOLE_FEED_SIZES = {
TREENODE_LINE_HEIGHT: 1.2,
BASE_FONT_SIZE: this.props.fontSize,
ARROW_FONT_SIZE: this.props.fontSize,
LOG_ICON_WIDTH: this.props.fontSize,
LOG_ICON_HEIGHT: 1.45 * this.props.fontSize,
};
if (times > 1) {
Object.assign(style, CONSOLE_FEED_WITHOUT_ICONS);
}
switch (theme) {
case 'light':
return Object.assign(CONSOLE_FEED_LIGHT_STYLES, CONSOLE_FEED_LIGHT_ICONS, CONSOLE_FEED_SIZES, style);
case 'dark':
return Object.assign(CONSOLE_FEED_DARK_STYLES, CONSOLE_FEED_DARK_ICONS, CONSOLE_FEED_SIZES, style);
case 'contrast':
return Object.assign(CONSOLE_FEED_CONTRAST_STYLES, CONSOLE_FEED_CONTRAST_ICONS, CONSOLE_FEED_SIZES, style);
default:
return '';
}
switch (theme) {
case 'light':
return Object.assign(CONSOLE_FEED_LIGHT_STYLES, CONSOLE_FEED_LIGHT_ICONS, CONSOLE_FEED_SIZES, style);
case 'dark':
return Object.assign(CONSOLE_FEED_DARK_STYLES, CONSOLE_FEED_DARK_ICONS, CONSOLE_FEED_SIZES, style);
case 'contrast':
return Object.assign(CONSOLE_FEED_CONTRAST_STYLES, CONSOLE_FEED_CONTRAST_ICONS, CONSOLE_FEED_SIZES, style);
default:
return '';
}
render() {
const consoleClass = classNames({
'preview-console': true,
'preview-console--collapsed': !this.props.isExpanded
});
return (
<section className={consoleClass} >
<header className="preview-console__header">
<h2 className="preview-console__header-title">Console</h2>
<div className="preview-console__header-buttons">
<button className="preview-console__clear" onClick={this.props.clearConsole} aria-label="Clear console">
Clear
</button>
<button
className="preview-console__collapse"
onClick={this.props.collapseConsole}
aria-label="Close console"
>
<DownArrowIcon focusable="false" aria-hidden="true" />
</button>
<button className="preview-console__expand" onClick={this.props.expandConsole} aria-label="Open console" >
<UpArrowIcon focusable="false" aria-hidden="true" />
</button>
</div>
</header>
<div ref={(element) => { this.consoleMessages = element; }} className="preview-console__messages">
{this.props.consoleEvents.map((consoleEvent) => {
const { method, times } = consoleEvent;
const { theme } = this.props;
return (
<div key={consoleEvent.id} className={`preview-console__message preview-console__message--${method}`}>
{ times > 1 &&
<div
className="preview-console__logged-times"
style={{ fontSize: this.props.fontSize, borderRadius: this.props.fontSize / 2 }}
>
{times}
</div>
}
<ConsoleFeed
styles={this.getConsoleFeedStyle(theme, times)}
logs={[consoleEvent]}
/>
</div>
);
})}
</div>
</section>
);
}
}
ConsoleComponent.propTypes = {
consoleEvents: PropTypes.arrayOf(PropTypes.shape({
method: PropTypes.string.isRequired,
args: PropTypes.arrayOf(PropTypes.string)
})),
isExpanded: PropTypes.bool.isRequired,
collapseConsole: PropTypes.func.isRequired,
expandConsole: PropTypes.func.isRequired,
clearConsole: PropTypes.func.isRequired,
dispatchConsoleEvent: PropTypes.func.isRequired,
theme: PropTypes.string.isRequired,
fontSize: PropTypes.number.isRequired
};
ConsoleComponent.defaultProps = {
consoleEvents: []
};
// TODO: Use Hooks implementation. Requires react-redux 7.1.0
/*
const Console = () => {
const consoleEvents = useSelector(state => state.console);
const { consoleIsExpanded } = useSelector(state => state.ide);
const isExpanded = useSelector(state => state.ide.consoleIsExpanded);
const { theme, fontSize } = useSelector(state => state.preferences);
const dispatch = useDispatch();
const {
collapseConsole, expandConsole, clearConsole, dispatchConsoleEvent
} = bindActionCreators({ ...IDEActions, ...ConsoleActions }, useDispatch());
useDidUpdate(() => {
clearConsole();
dispatchConsoleEvent(consoleEvents);
}, [theme, fontSize]);
const cm = useRef({});
// useDidUpdate(() => { cm.current.scrollTop = cm.current.scrollHeight; });
const consoleClass = classNames({
// 'preview-console': true,
// 'preview-console--collapsed': !isExpanded
});
return (
<ConsoleComponent
consoleEvents={consoleEvents}
isExpanded={consoleIsExpanded}
theme={theme}
fontSize={fontSize}
collapseConsole={() => dispatch({})}
expandConsole={() => dispatch({})}
clearConsole={() => dispatch({})}
dispatchConsoleEvent={() => dispatch({})}
/>
<section className={consoleClass} >
<header className="preview-console__header">
<h2 className="preview-console__header-title">Console</h2>
<div className="preview-console__header-buttons">
<button className="preview-console__clear" onClick={clearConsole} aria-label="Clear console">
Clear
</button>
<button
className="preview-console__collapse"
onClick={collapseConsole}
aria-label="Close console"
>
<DownArrowIcon focusable="false" aria-hidden="true" />
</button>
<button className="preview-console__expand" onClick={expandConsole} aria-label="Open console" >
<UpArrowIcon focusable="false" aria-hidden="true" />
</button>
</div>
</header>
<div ref={cm} className="preview-console__messages">
{consoleEvents.map((consoleEvent) => {
const { method, times } = consoleEvent;
return (
<div key={consoleEvent.id} className={`preview-console__message preview-console__message--${method}`}>
{ times > 1 &&
<div
className="preview-console__logged-times"
style={{ fontSize, borderRadius: fontSize / 2 }}
>
{times}
</div>
}
<ConsoleFeed
styles={getConsoleFeedStyle(theme, times, fontSize)}
logs={[consoleEvent]}
/>
</div>
);
})}
</div>
</section>
);
};
*/
const Console = connect(
state => ({
consoleEvents: state.console,
isExpanded: state.ide.consoleIsExpanded,
theme: state.preferences.theme,
fontSize: state.preferences.fontSize
}),
dispatch => ({
collapseConsole: () => dispatch(IDEActions.collapseConsole()),
expandConsole: () => dispatch(IDEActions.expandConsole()),
clearConsole: () => dispatch(ConsoleActions.clearConsole()),
dispatchConsoleEvent: msgs => dispatch(ConsoleActions.dispatchConsoleEvent(msgs)),
})
)(ConsoleComponent);
export default Console;

View File

@ -48,7 +48,7 @@ class NewFileForm extends React.Component {
NewFileForm.propTypes = {
fields: PropTypes.shape({
name: PropTypes.object.isRequired
name: PropTypes.object.isRequired // eslint-disable-line
}).isRequired,
handleSubmit: PropTypes.func.isRequired,
createFile: PropTypes.func.isRequired,

View File

@ -49,7 +49,7 @@ class NewFolderForm extends React.Component {
NewFolderForm.propTypes = {
fields: PropTypes.shape({
name: PropTypes.object.isRequired
name: PropTypes.object.isRequired // eslint-disable-line
}).isRequired,
handleSubmit: PropTypes.func.isRequired,
createFolder: PropTypes.func.isRequired,

View File

@ -0,0 +1,26 @@
export const optionsOnOff = (name, onLabel = 'On', offLabel = 'Off') => [
{
value: true, label: onLabel, ariaLabel: `${name} on`, name: `${name}`, id: `${name}-on`.replace(' ', '-')
},
{
value: false, label: offLabel, ariaLabel: `${name} off`, name: `${name}`, id: `${name}-off`.replace(' ', '-')
},
];
export const optionsPickOne = (name, ...options) => options.map(option => ({
value: option,
label: option,
ariaLabel: `${option} ${name} on`,
name: `${option} ${name}`,
id: `${option}-${name}-on`.replace(' ', '-')
}));
const nameToValueName = x => (x && x.toLowerCase().replace(/#|_|-/g, ' '));
// preferenceOnOff: name, value and onSelect are mandatory. propname is optional
export const preferenceOnOff = (name, value, onSelect, propname) => ({
title: name,
value,
options: optionsOnOff(propname || nameToValueName(name)),
onSelect
});

View File

@ -7,9 +7,9 @@ import { withTranslation } from 'react-i18next';
// import { connect } from 'react-redux';
// import * as PreferencesActions from '../actions/preferences';
import PlusIcon from '../../../images/plus.svg';
import MinusIcon from '../../../images/minus.svg';
import beepUrl from '../../../sounds/audioAlert.mp3';
import PlusIcon from '../../../../images/plus.svg';
import MinusIcon from '../../../../images/minus.svg';
import beepUrl from '../../../../sounds/audioAlert.mp3';
class Preferences extends React.Component {
constructor(props) {

View File

@ -10,7 +10,7 @@ import Editor from '../components/Editor';
import Sidebar from '../components/Sidebar';
import PreviewFrame from '../components/PreviewFrame';
import Toolbar from '../components/Toolbar';
import Preferences from '../components/Preferences';
import Preferences from '../components/Preferences/index';
import NewFileModal from '../components/NewFileModal';
import NewFolderModal from '../components/NewFolderModal';
import UploadFileModal from '../components/UploadFileModal';
@ -478,7 +478,7 @@ IDEView.propTypes = {
ide: PropTypes.shape({
isPlaying: PropTypes.bool.isRequired,
isAccessibleOutputPlaying: PropTypes.bool.isRequired,
consoleEvent: PropTypes.array,
consoleEvent: PropTypes.array, // eslint-disable-line
modalIsVisible: PropTypes.bool.isRequired,
sidebarIsExpanded: PropTypes.bool.isRequired,
consoleIsExpanded: PropTypes.bool.isRequired,
@ -513,7 +513,7 @@ IDEView.propTypes = {
updatedAt: PropTypes.string
}).isRequired,
editorAccessibility: PropTypes.shape({
lintMessages: PropTypes.array.isRequired,
lintMessages: PropTypes.array.isRequired, // eslint-disable-line
}).isRequired,
updateLintMessage: PropTypes.func.isRequired,
clearLintMessage: PropTypes.func.isRequired,

View File

@ -1,4 +1,4 @@
import React, { useRef, useEffect } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
@ -30,12 +30,13 @@ import IDEWrapper from '../../../components/mobile/IDEWrapper';
import Console from '../components/Console';
import { remSize } from '../../../theme';
import OverlayManager from '../../../components/OverlayManager';
import ActionStrip from '../../../components/mobile/ActionStrip';
const isUserOwner = ({ project, user }) => (project.owner && project.owner.id === user.id);
const BottomBarContent = styled.h2`
padding: ${remSize(12)};
padding-left: ${remSize(32)};
const Expander = styled.div`
height: ${props => (props.expanded ? remSize(160) : remSize(27))};
`;
@ -111,7 +112,8 @@ const MobileIDEView = (props) => {
</IDEWrapper>
<Footer>
<Console />
{ide.consoleIsExpanded && <Expander expanded><Console /></Expander>}
<ActionStrip />
</Footer>
<OverlayManager
@ -123,7 +125,6 @@ const MobileIDEView = (props) => {
);
};
MobileIDEView.propTypes = {
preferences: PropTypes.shape({
@ -142,7 +143,7 @@ MobileIDEView.propTypes = {
ide: PropTypes.shape({
isPlaying: PropTypes.bool.isRequired,
isAccessibleOutputPlaying: PropTypes.bool.isRequired,
consoleEvent: PropTypes.array,
consoleEvent: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
modalIsVisible: PropTypes.bool.isRequired,
sidebarIsExpanded: PropTypes.bool.isRequired,
consoleIsExpanded: PropTypes.bool.isRequired,
@ -168,7 +169,7 @@ MobileIDEView.propTypes = {
}).isRequired,
editorAccessibility: PropTypes.shape({
lintMessages: PropTypes.array.isRequired,
lintMessages: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
}).isRequired,
project: PropTypes.shape({

View File

@ -1,6 +1,6 @@
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { connect, useSelector, useDispatch } from 'react-redux';
import { withRouter } from 'react-router';
import PropTypes from 'prop-types';
import styled from 'styled-components';
@ -14,17 +14,13 @@ import Header from '../../components/mobile/Header';
import PreferencePicker from '../../components/mobile/PreferencePicker';
import { ExitIcon } from '../../common/icons';
import { remSize, prop } from '../../theme';
import { optionsOnOff, optionsPickOne, preferenceOnOff } from '../IDE/components/Preferences/PreferenceCreators';
const Content = styled.div`
z-index: 0;
margin-top: ${remSize(68)};
`;
const SettingsHeader = styled(Header)`
background: transparent;
`;
const SectionHeader = styled.h2`
color: ${prop('primaryTextColor')};
padding-top: ${remSize(32)};
@ -35,143 +31,46 @@ const SectionSubeader = styled.h3`
`;
const MobilePreferences = (props) => {
const MobilePreferences = () => {
// Props
const {
setTheme, setAutosave, setLinewrap, setTextOutput, setGridOutput, setSoundOutput, lineNumbers, lintWarning
} = props;
theme, autosave, linewrap, textOutput, gridOutput, soundOutput, lineNumbers, lintWarning
} = useSelector(state => state.preferences);
// Actions
const {
theme, autosave, linewrap, textOutput, gridOutput, soundOutput, setLineNumbers, setLintWarning
} = props;
setTheme, setAutosave, setLinewrap, setTextOutput, setGridOutput, setSoundOutput, setLineNumbers, setLintWarning,
} = bindActionCreators({ ...PreferencesActions, ...IdeActions }, useDispatch());
const generalSettings = [
{
title: 'Theme',
value: theme,
options: [
{
value: 'light', label: 'light', ariaLabel: 'light theme on', name: 'light theme', id: 'light-theme-on'
},
{
value: 'dark', label: 'dark', ariaLabel: 'dark theme on', name: 'dark theme', id: 'dark-theme-on'
},
{
value: 'contrast',
label: 'contrast',
ariaLabel: 'contrast theme on',
name: 'contrast theme',
id: 'contrast-theme-on'
}
],
options: optionsPickOne('theme', 'light', 'dark', 'contrast'),
onSelect: x => setTheme(x) // setTheme
},
{
title: 'Autosave',
value: autosave,
options: [
{
value: true, label: 'On', ariaLabel: 'autosave on', name: 'autosave', id: 'autosave-on'
},
{
value: false, label: 'Off', ariaLabel: 'autosave off', name: 'autosave', id: 'autosave-off'
},
],
onSelect: x => setAutosave(x) // setAutosave
},
{
title: 'Word Wrap',
value: linewrap,
options: [
{
value: true, label: 'On', ariaLabel: 'linewrap on', name: 'linewrap', id: 'linewrap-on'
},
{
value: false, label: 'Off', ariaLabel: 'linewrap off', name: 'linewrap', id: 'linewrap-off'
},
],
onSelect: x => setLinewrap(x)
}
preferenceOnOff('Autosave', autosave, setAutosave, 'autosave'),
preferenceOnOff('Word Wrap', linewrap, setLinewrap, 'linewrap')
];
const outputSettings = [
{
title: 'Plain-text',
value: textOutput,
options: [
{
value: true, label: 'On', ariaLabel: 'text output on', name: 'text output', id: 'text-output-on'
},
{
value: false, label: 'Off', ariaLabel: 'text output off', name: 'text output', id: 'text-output-off'
},
],
onSelect: x => setTextOutput(x)
},
{
title: 'Table-text',
value: gridOutput,
options: [
{
value: true, label: 'On', ariaLabel: 'table output on', name: 'table output', id: 'table-output-on'
},
{
value: false, label: 'Off', ariaLabel: 'table output off', name: 'table output', id: 'table-output-off'
},
],
onSelect: x => setGridOutput(x)
},
{
title: 'Sound',
value: soundOutput,
options: [
{
value: true, label: 'On', ariaLabel: 'sound output on', name: 'sound output', id: 'sound-output-on'
},
{
value: false, label: 'Off', ariaLabel: 'sound output off', name: 'sound output', id: 'sound-output-off'
},
],
onSelect: x => setSoundOutput(x)
},
preferenceOnOff('Plain-text', textOutput, setTextOutput, 'text output'),
preferenceOnOff('Table-text', gridOutput, setGridOutput, 'table output'),
preferenceOnOff('Lint Warning Sound', soundOutput, setSoundOutput, 'sound output')
];
const accessibilitySettings = [
{
title: 'Line Numbers',
value: lineNumbers,
options: [
{
value: true, label: 'On', ariaLabel: 'line numbers on', name: 'line numbers', id: 'line-numbers-on'
},
{
value: false, label: 'Off', ariaLabel: 'line numbers off', name: 'line numbers', id: 'line-numbers-off'
},
],
onSelect: x => setLineNumbers(x)
},
{
title: 'Lint Warning Sound',
value: lintWarning,
options: [
{
value: true, label: 'On', ariaLabel: 'lint warning on', name: 'lint warning', id: 'lint-warning-on'
},
{
value: false, label: 'Off', ariaLabel: 'lint warning off', name: 'lint warning', id: 'lint-warning-off'
},
],
onSelect: x => setLintWarning(x)
},
preferenceOnOff('Line Numbers', lineNumbers, setLineNumbers),
preferenceOnOff('Lint Warning Sound', lintWarning, setLintWarning)
];
return (
<Screen fullscreen>
<section>
<SettingsHeader transparent title="Preferences">
<Header transparent title="Preferences">
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to ide view" />
</SettingsHeader>
</Header>
<section className="preferences">
<Content>
<SectionHeader>General Settings</SectionHeader>
@ -190,37 +89,4 @@ const MobilePreferences = (props) => {
</Screen>);
};
MobilePreferences.propTypes = {
fontSize: PropTypes.number.isRequired,
lineNumbers: PropTypes.bool.isRequired,
autosave: PropTypes.bool.isRequired,
linewrap: PropTypes.bool.isRequired,
textOutput: PropTypes.bool.isRequired,
gridOutput: PropTypes.bool.isRequired,
soundOutput: PropTypes.bool.isRequired,
lintWarning: PropTypes.bool.isRequired,
theme: PropTypes.string.isRequired,
setLinewrap: PropTypes.func.isRequired,
setLintWarning: PropTypes.func.isRequired,
setTheme: PropTypes.func.isRequired,
setFontSize: PropTypes.func.isRequired,
setLineNumbers: PropTypes.func.isRequired,
setAutosave: PropTypes.func.isRequired,
setTextOutput: PropTypes.func.isRequired,
setGridOutput: PropTypes.func.isRequired,
setSoundOutput: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
...state.preferences,
});
const mapDispatchToProps = dispatch => bindActionCreators({
...PreferencesActions,
...IdeActions
}, dispatch);
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MobilePreferences));
export default withRouter(MobilePreferences);

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { connect, useSelector, useDispatch } from 'react-redux';
import styled from 'styled-components';
import Header from '../../components/mobile/Header';
import IconButton from '../../components/mobile/IconButton';
@ -16,44 +16,35 @@ import * as FilesActions from '../IDE/actions/files';
import { getHTMLFile } from '../IDE/reducers/files';
import { ExitIcon } from '../../common/icons';
import { remSize } from '../../theme';
import Footer from '../../components/mobile/Footer';
const Content = styled.div`
z-index: 0;
margin-top: ${remSize(68)};
`;
const MobileSketchView = (props) => {
// TODO: useSelector requires react-redux ^7.1.0
// const htmlFile = useSelector(state => getHTMLFile(state.files));
// const jsFiles = useSelector(state => getJSFiles(state.files));
// const cssFiles = useSelector(state => getCSSFiles(state.files));
// const files = useSelector(state => state.files);
const { files, ide, preferences } = useSelector(state => state);
const htmlFile = useSelector(state => getHTMLFile(state.files));
const projectName = useSelector(state => state.project.name);
const selectedFile = useSelector(state => state.files.find(file => file.isSelectedFile) ||
state.files.find(file => file.name === 'sketch.js') ||
state.files.find(file => file.name !== 'root'));
const {
htmlFile, files, selectedFile, projectName
} = props;
// Actions
const {
setTextOutput, setGridOutput, setSoundOutput,
endSketchRefresh, stopSketch,
dispatchConsoleEvent, expandConsole, clearConsole,
setBlobUrl,
} = props;
const { preferences, ide } = props;
setTextOutput, setGridOutput, setSoundOutput, dispatchConsoleEvent,
endSketchRefresh, stopSketch, setBlobUrl, expandConsole, clearConsole
} = bindActionCreators({
...ProjectActions, ...IDEActions, ...PreferencesActions, ...ConsoleActions, ...FilesActions
}, useDispatch());
return (
<Screen fullscreen>
<Header
leftButton={
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to original editor" />
}
leftButton={<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to original editor" />}
title={projectName}
/>
<Content>
@ -90,99 +81,4 @@ const MobileSketchView = (props) => {
</Screen>);
};
MobileSketchView.propTypes = {
params: PropTypes.shape({
project_id: PropTypes.string,
username: PropTypes.string
}).isRequired,
htmlFile: PropTypes.shape({
id: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
}).isRequired,
files: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
})).isRequired,
selectedFile: PropTypes.shape({
id: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
}).isRequired,
preferences: PropTypes.shape({
fontSize: PropTypes.number.isRequired,
autosave: PropTypes.bool.isRequired,
linewrap: PropTypes.bool.isRequired,
lineNumbers: PropTypes.bool.isRequired,
lintWarning: PropTypes.bool.isRequired,
textOutput: PropTypes.bool.isRequired,
gridOutput: PropTypes.bool.isRequired,
soundOutput: PropTypes.bool.isRequired,
theme: PropTypes.string.isRequired,
autorefresh: PropTypes.bool.isRequired
}).isRequired,
ide: PropTypes.shape({
isPlaying: PropTypes.bool.isRequired,
isAccessibleOutputPlaying: PropTypes.bool.isRequired,
consoleEvent: PropTypes.array,
modalIsVisible: PropTypes.bool.isRequired,
sidebarIsExpanded: PropTypes.bool.isRequired,
consoleIsExpanded: PropTypes.bool.isRequired,
preferencesIsVisible: PropTypes.bool.isRequired,
projectOptionsVisible: PropTypes.bool.isRequired,
newFolderModalVisible: PropTypes.bool.isRequired,
shareModalVisible: PropTypes.bool.isRequired,
shareModalProjectId: PropTypes.string.isRequired,
shareModalProjectName: PropTypes.string.isRequired,
shareModalProjectUsername: PropTypes.string.isRequired,
editorOptionsVisible: PropTypes.bool.isRequired,
keyboardShortcutVisible: PropTypes.bool.isRequired,
unsavedChanges: PropTypes.bool.isRequired,
infiniteLoop: PropTypes.bool.isRequired,
previewIsRefreshing: PropTypes.bool.isRequired,
infiniteLoopMessage: PropTypes.string.isRequired,
projectSavedTime: PropTypes.string,
previousPath: PropTypes.string.isRequired,
justOpenedProject: PropTypes.bool.isRequired,
errorType: PropTypes.string,
runtimeErrorWarningVisible: PropTypes.bool.isRequired,
}).isRequired,
projectName: PropTypes.string.isRequired,
setTextOutput: PropTypes.func.isRequired,
setGridOutput: PropTypes.func.isRequired,
setSoundOutput: PropTypes.func.isRequired,
dispatchConsoleEvent: PropTypes.func.isRequired,
endSketchRefresh: PropTypes.func.isRequired,
stopSketch: PropTypes.func.isRequired,
setBlobUrl: PropTypes.func.isRequired,
expandConsole: PropTypes.func.isRequired,
clearConsole: PropTypes.func.isRequired,
};
function mapStateToProps(state) {
return {
htmlFile: getHTMLFile(state.files),
projectName: state.project.name,
files: state.files,
ide: state.ide,
preferences: state.preferences,
selectedFile: state.files.find(file => file.isSelectedFile) ||
state.files.find(file => file.name === 'sketch.js') ||
state.files.find(file => file.name !== 'root'),
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
...ProjectActions, ...IDEActions, ...PreferencesActions, ...ConsoleActions, ...FilesActions
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(MobileSketchView);
export default MobileSketchView;

View File

@ -8,8 +8,8 @@ import CopyableInput from '../../IDE/components/CopyableInput';
import APIKeyList from './APIKeyList';
export const APIKeyPropType = PropTypes.shape({
id: PropTypes.object.isRequired,
token: PropTypes.object,
id: PropTypes.object.isRequired, // eslint-disable-line
token: PropTypes.object, // eslint-disable-line
label: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired,
lastUsedAt: PropTypes.string,

View File

@ -103,10 +103,10 @@ function AccountForm(props) {
AccountForm.propTypes = {
fields: PropTypes.shape({
username: PropTypes.object.isRequired,
email: PropTypes.object.isRequired,
currentPassword: PropTypes.object.isRequired,
newPassword: PropTypes.object.isRequired,
username: PropTypes.object.isRequired, // eslint-disable-line
email: PropTypes.object.isRequired, // eslint-disable-line
currentPassword: PropTypes.object.isRequired, // eslint-disable-line
newPassword: PropTypes.object.isRequired, // eslint-disable-line
}).isRequired,
user: PropTypes.shape({
verified: PropTypes.number.isRequired,

View File

@ -44,8 +44,8 @@ function LoginForm(props) {
LoginForm.propTypes = {
fields: PropTypes.shape({
email: PropTypes.object.isRequired,
password: PropTypes.object.isRequired
email: PropTypes.object.isRequired, // eslint-disable-line
password: PropTypes.object.isRequired // eslint-disable-line
}).isRequired,
handleSubmit: PropTypes.func.isRequired,
validateAndLoginUser: PropTypes.func.isRequired,

View File

@ -43,8 +43,8 @@ function NewPasswordForm(props) {
NewPasswordForm.propTypes = {
fields: PropTypes.shape({
password: PropTypes.object.isRequired,
confirmPassword: PropTypes.object.isRequired
password: PropTypes.object.isRequired, // eslint-disable-line
confirmPassword: PropTypes.object.isRequired // eslint-disable-line
}).isRequired,
handleSubmit: PropTypes.func.isRequired,
updatePassword: PropTypes.func.isRequired,

View File

@ -32,7 +32,7 @@ function ResetPasswordForm(props) {
ResetPasswordForm.propTypes = {
fields: PropTypes.shape({
email: PropTypes.object.isRequired
email: PropTypes.object.isRequired // eslint-disable-line
}).isRequired,
handleSubmit: PropTypes.func.isRequired,
initiateResetPassword: PropTypes.func.isRequired,

View File

@ -71,10 +71,10 @@ function SignupForm(props) {
SignupForm.propTypes = {
fields: PropTypes.shape({
username: PropTypes.object.isRequired,
email: PropTypes.object.isRequired,
password: PropTypes.object.isRequired,
confirmPassword: PropTypes.object.isRequired
username: PropTypes.object.isRequired, // eslint-disable-line
email: PropTypes.object.isRequired, // eslint-disable-line
password: PropTypes.object.isRequired, // eslint-disable-line
confirmPassword: PropTypes.object.isRequired // eslint-disable-line
}).isRequired,
handleSubmit: PropTypes.func.isRequired,
signUpUser: PropTypes.func.isRequired,

View File

@ -63,6 +63,7 @@ export default {
colors,
...common,
primaryTextColor: grays.dark,
backgroundColor: grays.lighter,
Button: {
default: {
@ -100,12 +101,14 @@ export default {
Modal: {
background: grays.light,
border: grays.middleLight
}
},
Separator: grays.middleLight,
},
[Theme.dark]: {
colors,
...common,
primaryTextColor: grays.lightest,
backgroundColor: grays.darker,
Button: {
default: {
@ -143,12 +146,14 @@ export default {
Modal: {
background: grays.dark,
border: grays.middleDark
}
},
Separator: grays.middleDark,
},
[Theme.contrast]: {
colors,
...common,
primaryTextColor: grays.lightest,
backgroundColor: grays.darker,
Button: {
default: {
@ -186,6 +191,7 @@ export default {
Modal: {
background: grays.dark,
border: grays.middleDark
}
},
Separator: grays.middleDark,
},
};

View File

@ -0,0 +1,15 @@
import React, { useEffect, useRef } from 'react';
export const noop = () => {};
export const useDidUpdate = (callback, deps) => {
const hasMount = useRef(false);
useEffect(() => {
if (hasMount.current) {
callback();
} else {
hasMount.current = true;
}
}, deps);
};

20347
package-lock.json generated

File diff suppressed because it is too large Load Diff