Merge pull request #1507 from ghalestrilo/chore/refactor-mobile-hooks
Refactor mobile components to react hooks
This commit is contained in:
commit
7dbcde452b
14 changed files with 249 additions and 446 deletions
|
@ -12,6 +12,9 @@ import Exit from '../images/exit.svg';
|
||||||
import DropdownArrow from '../images/down-filled-triangle.svg';
|
import DropdownArrow from '../images/down-filled-triangle.svg';
|
||||||
import Preferences from '../images/preferences.svg';
|
import Preferences from '../images/preferences.svg';
|
||||||
import Play from '../images/triangle-arrow-right.svg';
|
import Play from '../images/triangle-arrow-right.svg';
|
||||||
|
import Code from '../images/code.svg';
|
||||||
|
import Terminal from '../images/terminal.svg';
|
||||||
|
|
||||||
|
|
||||||
// HOC that adds the right web accessibility props
|
// HOC that adds the right web accessibility props
|
||||||
// https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html
|
// https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html
|
||||||
|
@ -74,3 +77,4 @@ export const ExitIcon = withLabel(Exit);
|
||||||
export const DropdownArrowIcon = withLabel(DropdownArrow);
|
export const DropdownArrowIcon = withLabel(DropdownArrow);
|
||||||
export const PreferencesIcon = withLabel(Preferences);
|
export const PreferencesIcon = withLabel(Preferences);
|
||||||
export const PlayIcon = withLabel(Play);
|
export const PlayIcon = withLabel(Play);
|
||||||
|
export const TerminalIcon = withLabel(Terminal);
|
||||||
|
|
35
client/components/mobile/ActionStrip.jsx
Normal file
35
client/components/mobile/ActionStrip.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { prop } from '../../theme';
|
import { prop, grays } from '../../theme';
|
||||||
|
|
||||||
|
|
||||||
const background = prop('MobilePanel.default.background');
|
const background = prop('MobilePanel.default.background');
|
||||||
|
@ -12,4 +12,6 @@ export default styled.div`
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: ${background};
|
background: ${background};
|
||||||
color: ${textColor};
|
color: ${textColor};
|
||||||
|
|
||||||
|
& > * + * { border-top: dashed 1px ${prop('Separator')} }
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -3,14 +3,14 @@ import styled from 'styled-components';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { prop, remSize } from '../../theme';
|
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 textColor = prop('primaryTextColor');
|
||||||
|
|
||||||
|
|
||||||
const HeaderDiv = styled.div`
|
const HeaderDiv = styled.div`
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: ${props => (props.transparent ? 'transparent' : background)};
|
background: ${props => background(props.transparent === true)};
|
||||||
color: ${textColor};
|
color: ${textColor};
|
||||||
padding: ${remSize(12)};
|
padding: ${remSize(12)};
|
||||||
padding-left: ${remSize(16)};
|
padding-left: ${remSize(16)};
|
||||||
|
@ -23,7 +23,6 @@ const HeaderDiv = styled.div`
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
// TODO:
|
|
||||||
svg {
|
svg {
|
||||||
max-height: ${remSize(32)};
|
max-height: ${remSize(32)};
|
||||||
padding: ${remSize(4)}
|
padding: ${remSize(4)}
|
||||||
|
|
5
client/images/terminal.svg
Normal file
5
client/images/terminal.svg
Normal 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 |
|
@ -1,6 +1,8 @@
|
||||||
import PropTypes from 'prop-types';
|
import React, { useRef } from 'react';
|
||||||
import React from 'react';
|
|
||||||
import { useSelector, useDispatch, connect } from 'react-redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Console as ConsoleFeed } from 'console-feed';
|
import { Console as ConsoleFeed } from 'console-feed';
|
||||||
import {
|
import {
|
||||||
|
@ -25,172 +27,118 @@ import DownArrowIcon from '../../../images/down-arrow.svg';
|
||||||
|
|
||||||
import * as IDEActions from '../../IDE/actions/ide';
|
import * as IDEActions from '../../IDE/actions/ide';
|
||||||
import * as ConsoleActions from '../../IDE/actions/console';
|
import * as ConsoleActions from '../../IDE/actions/console';
|
||||||
|
import { useDidUpdate } from '../../../utils/custom-hooks';
|
||||||
|
|
||||||
class ConsoleComponent extends React.Component {
|
const getConsoleFeedStyle = (theme, times, fontSize) => {
|
||||||
componentDidUpdate(prevProps) {
|
const style = {};
|
||||||
this.consoleMessages.scrollTop = this.consoleMessages.scrollHeight;
|
const CONSOLE_FEED_LIGHT_ICONS = {
|
||||||
if (this.props.theme !== prevProps.theme) {
|
LOG_WARN_ICON: `url(${warnLightUrl})`,
|
||||||
this.props.clearConsole();
|
LOG_ERROR_ICON: `url(${errorLightUrl})`,
|
||||||
this.props.dispatchConsoleEvent(this.props.consoleEvents);
|
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) {
|
if (times > 1) {
|
||||||
this.props.clearConsole();
|
Object.assign(style, CONSOLE_FEED_WITHOUT_ICONS);
|
||||||
this.props.dispatchConsoleEvent(this.props.consoleEvents);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
switch (theme) {
|
||||||
getConsoleFeedStyle(theme, times) {
|
case 'light':
|
||||||
const style = {};
|
return Object.assign(CONSOLE_FEED_LIGHT_STYLES, CONSOLE_FEED_LIGHT_ICONS, CONSOLE_FEED_SIZES, style);
|
||||||
const CONSOLE_FEED_LIGHT_ICONS = {
|
case 'dark':
|
||||||
LOG_WARN_ICON: `url(${warnLightUrl})`,
|
return Object.assign(CONSOLE_FEED_DARK_STYLES, CONSOLE_FEED_DARK_ICONS, CONSOLE_FEED_SIZES, style);
|
||||||
LOG_ERROR_ICON: `url(${errorLightUrl})`,
|
case 'contrast':
|
||||||
LOG_DEBUG_ICON: `url(${debugLightUrl})`,
|
return Object.assign(CONSOLE_FEED_CONTRAST_STYLES, CONSOLE_FEED_CONTRAST_ICONS, CONSOLE_FEED_SIZES, style);
|
||||||
LOG_INFO_ICON: `url(${infoLightUrl})`
|
default:
|
||||||
};
|
return '';
|
||||||
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 '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 Console = () => {
|
||||||
const consoleEvents = useSelector(state => state.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 { 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 (
|
return (
|
||||||
<ConsoleComponent
|
<section className={consoleClass} >
|
||||||
consoleEvents={consoleEvents}
|
<header className="preview-console__header">
|
||||||
isExpanded={consoleIsExpanded}
|
<h2 className="preview-console__header-title">Console</h2>
|
||||||
theme={theme}
|
<div className="preview-console__header-buttons">
|
||||||
fontSize={fontSize}
|
<button className="preview-console__clear" onClick={clearConsole} aria-label="Clear console">
|
||||||
collapseConsole={() => dispatch({})}
|
Clear
|
||||||
expandConsole={() => dispatch({})}
|
</button>
|
||||||
clearConsole={() => dispatch({})}
|
<button
|
||||||
dispatchConsoleEvent={() => dispatch({})}
|
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;
|
export default Console;
|
||||||
|
|
|
@ -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
|
||||||
|
});
|
|
@ -7,9 +7,9 @@ import { withTranslation } from 'react-i18next';
|
||||||
// import { connect } from 'react-redux';
|
// import { connect } from 'react-redux';
|
||||||
// import * as PreferencesActions from '../actions/preferences';
|
// import * as PreferencesActions from '../actions/preferences';
|
||||||
|
|
||||||
import PlusIcon from '../../../images/plus.svg';
|
import PlusIcon from '../../../../images/plus.svg';
|
||||||
import MinusIcon from '../../../images/minus.svg';
|
import MinusIcon from '../../../../images/minus.svg';
|
||||||
import beepUrl from '../../../sounds/audioAlert.mp3';
|
import beepUrl from '../../../../sounds/audioAlert.mp3';
|
||||||
|
|
||||||
class Preferences extends React.Component {
|
class Preferences extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
|
@ -10,7 +10,7 @@ import Editor from '../components/Editor';
|
||||||
import Sidebar from '../components/Sidebar';
|
import Sidebar from '../components/Sidebar';
|
||||||
import PreviewFrame from '../components/PreviewFrame';
|
import PreviewFrame from '../components/PreviewFrame';
|
||||||
import Toolbar from '../components/Toolbar';
|
import Toolbar from '../components/Toolbar';
|
||||||
import Preferences from '../components/Preferences';
|
import Preferences from '../components/Preferences/index';
|
||||||
import NewFileModal from '../components/NewFileModal';
|
import NewFileModal from '../components/NewFileModal';
|
||||||
import NewFolderModal from '../components/NewFolderModal';
|
import NewFolderModal from '../components/NewFolderModal';
|
||||||
import UploadFileModal from '../components/UploadFileModal';
|
import UploadFileModal from '../components/UploadFileModal';
|
||||||
|
|
|
@ -28,12 +28,13 @@ import Footer from '../../../components/mobile/Footer';
|
||||||
import IDEWrapper from '../../../components/mobile/IDEWrapper';
|
import IDEWrapper from '../../../components/mobile/IDEWrapper';
|
||||||
import Console from '../components/Console';
|
import Console from '../components/Console';
|
||||||
import { remSize } from '../../../theme';
|
import { remSize } from '../../../theme';
|
||||||
|
import ActionStrip from '../../../components/mobile/ActionStrip';
|
||||||
|
|
||||||
const isUserOwner = ({ project, user }) => (project.owner && project.owner.id === user.id);
|
const isUserOwner = ({ project, user }) => (project.owner && project.owner.id === user.id);
|
||||||
|
|
||||||
const BottomBarContent = styled.h2`
|
|
||||||
padding: ${remSize(12)};
|
const Expander = styled.div`
|
||||||
padding-left: ${remSize(32)};
|
height: ${props => (props.expanded ? remSize(160) : remSize(27))};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const MobileIDEView = (props) => {
|
const MobileIDEView = (props) => {
|
||||||
|
@ -103,8 +104,8 @@ const MobileIDEView = (props) => {
|
||||||
/>
|
/>
|
||||||
</IDEWrapper>
|
</IDEWrapper>
|
||||||
<Footer>
|
<Footer>
|
||||||
<Console />
|
{ide.consoleIsExpanded && <Expander expanded><Console /></Expander>}
|
||||||
<BottomBarContent>Bottom Bar</BottomBarContent>
|
<ActionStrip />
|
||||||
</Footer>
|
</Footer>
|
||||||
</Screen>
|
</Screen>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect, useSelector, useDispatch } from 'react-redux';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
@ -14,17 +14,13 @@ import Header from '../../components/mobile/Header';
|
||||||
import PreferencePicker from '../../components/mobile/PreferencePicker';
|
import PreferencePicker from '../../components/mobile/PreferencePicker';
|
||||||
import { ExitIcon } from '../../common/icons';
|
import { ExitIcon } from '../../common/icons';
|
||||||
import { remSize, prop } from '../../theme';
|
import { remSize, prop } from '../../theme';
|
||||||
|
import { optionsOnOff, optionsPickOne, preferenceOnOff } from '../IDE/components/Preferences/PreferenceCreators';
|
||||||
|
|
||||||
const Content = styled.div`
|
const Content = styled.div`
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
margin-top: ${remSize(68)};
|
margin-top: ${remSize(68)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
const SettingsHeader = styled(Header)`
|
|
||||||
background: transparent;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SectionHeader = styled.h2`
|
const SectionHeader = styled.h2`
|
||||||
color: ${prop('primaryTextColor')};
|
color: ${prop('primaryTextColor')};
|
||||||
padding-top: ${remSize(32)};
|
padding-top: ${remSize(32)};
|
||||||
|
@ -35,143 +31,46 @@ const SectionSubeader = styled.h3`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
const MobilePreferences = (props) => {
|
const MobilePreferences = () => {
|
||||||
|
// Props
|
||||||
const {
|
const {
|
||||||
setTheme, setAutosave, setLinewrap, setTextOutput, setGridOutput, setSoundOutput, lineNumbers, lintWarning
|
theme, autosave, linewrap, textOutput, gridOutput, soundOutput, lineNumbers, lintWarning
|
||||||
} = props;
|
} = useSelector(state => state.preferences);
|
||||||
|
|
||||||
|
// Actions
|
||||||
const {
|
const {
|
||||||
theme, autosave, linewrap, textOutput, gridOutput, soundOutput, setLineNumbers, setLintWarning
|
setTheme, setAutosave, setLinewrap, setTextOutput, setGridOutput, setSoundOutput, setLineNumbers, setLintWarning,
|
||||||
} = props;
|
} = bindActionCreators({ ...PreferencesActions, ...IdeActions }, useDispatch());
|
||||||
|
|
||||||
|
|
||||||
const generalSettings = [
|
const generalSettings = [
|
||||||
{
|
{
|
||||||
title: 'Theme',
|
title: 'Theme',
|
||||||
value: theme,
|
value: theme,
|
||||||
options: [
|
options: optionsPickOne('theme', 'light', 'dark', 'contrast'),
|
||||||
{
|
|
||||||
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'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
onSelect: x => setTheme(x) // setTheme
|
onSelect: x => setTheme(x) // setTheme
|
||||||
},
|
},
|
||||||
|
preferenceOnOff('Autosave', autosave, setAutosave, 'autosave'),
|
||||||
{
|
preferenceOnOff('Word Wrap', linewrap, setLinewrap, 'linewrap')
|
||||||
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)
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const outputSettings = [
|
const outputSettings = [
|
||||||
{
|
preferenceOnOff('Plain-text', textOutput, setTextOutput, 'text output'),
|
||||||
title: 'Plain-text',
|
preferenceOnOff('Table-text', gridOutput, setGridOutput, 'table output'),
|
||||||
value: textOutput,
|
preferenceOnOff('Lint Warning Sound', soundOutput, setSoundOutput, 'sound output')
|
||||||
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)
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const accessibilitySettings = [
|
const accessibilitySettings = [
|
||||||
{
|
preferenceOnOff('Line Numbers', lineNumbers, setLineNumbers),
|
||||||
title: 'Line Numbers',
|
preferenceOnOff('Lint Warning Sound', lintWarning, setLintWarning)
|
||||||
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)
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen fullscreen>
|
<Screen fullscreen>
|
||||||
<section>
|
<section>
|
||||||
<SettingsHeader transparent title="Preferences">
|
<Header transparent title="Preferences">
|
||||||
|
|
||||||
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to ide view" />
|
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to ide view" />
|
||||||
</SettingsHeader>
|
</Header>
|
||||||
<section className="preferences">
|
<section className="preferences">
|
||||||
<Content>
|
<Content>
|
||||||
<SectionHeader>General Settings</SectionHeader>
|
<SectionHeader>General Settings</SectionHeader>
|
||||||
|
@ -190,37 +89,4 @@ const MobilePreferences = (props) => {
|
||||||
</Screen>);
|
</Screen>);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default withRouter(MobilePreferences);
|
||||||
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));
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect, useSelector, useDispatch } from 'react-redux';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import Header from '../../components/mobile/Header';
|
import Header from '../../components/mobile/Header';
|
||||||
import IconButton from '../../components/mobile/IconButton';
|
import IconButton from '../../components/mobile/IconButton';
|
||||||
|
@ -16,44 +16,35 @@ import * as FilesActions from '../IDE/actions/files';
|
||||||
|
|
||||||
import { getHTMLFile } from '../IDE/reducers/files';
|
import { getHTMLFile } from '../IDE/reducers/files';
|
||||||
|
|
||||||
|
|
||||||
import { ExitIcon } from '../../common/icons';
|
import { ExitIcon } from '../../common/icons';
|
||||||
import { remSize } from '../../theme';
|
import { remSize } from '../../theme';
|
||||||
import Footer from '../../components/mobile/Footer';
|
import Footer from '../../components/mobile/Footer';
|
||||||
|
|
||||||
|
|
||||||
const Content = styled.div`
|
const Content = styled.div`
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
margin-top: ${remSize(68)};
|
margin-top: ${remSize(68)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const MobileSketchView = (props) => {
|
const MobileSketchView = (props) => {
|
||||||
// TODO: useSelector requires react-redux ^7.1.0
|
const { files, ide, preferences } = useSelector(state => state);
|
||||||
// const htmlFile = useSelector(state => getHTMLFile(state.files));
|
|
||||||
// const jsFiles = useSelector(state => getJSFiles(state.files));
|
const htmlFile = useSelector(state => getHTMLFile(state.files));
|
||||||
// const cssFiles = useSelector(state => getCSSFiles(state.files));
|
const projectName = useSelector(state => state.project.name);
|
||||||
// const files = useSelector(state => state.files);
|
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 {
|
const {
|
||||||
htmlFile, files, selectedFile, projectName
|
setTextOutput, setGridOutput, setSoundOutput, dispatchConsoleEvent,
|
||||||
} = props;
|
endSketchRefresh, stopSketch, setBlobUrl, expandConsole, clearConsole
|
||||||
|
} = bindActionCreators({
|
||||||
// Actions
|
...ProjectActions, ...IDEActions, ...PreferencesActions, ...ConsoleActions, ...FilesActions
|
||||||
const {
|
}, useDispatch());
|
||||||
setTextOutput, setGridOutput, setSoundOutput,
|
|
||||||
endSketchRefresh, stopSketch,
|
|
||||||
dispatchConsoleEvent, expandConsole, clearConsole,
|
|
||||||
setBlobUrl,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const { preferences, ide } = props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen fullscreen>
|
<Screen fullscreen>
|
||||||
<Header
|
<Header
|
||||||
leftButton={
|
leftButton={<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to original editor" />}
|
||||||
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to original editor" />
|
|
||||||
}
|
|
||||||
title={projectName}
|
title={projectName}
|
||||||
/>
|
/>
|
||||||
<Content>
|
<Content>
|
||||||
|
@ -90,99 +81,4 @@ const MobileSketchView = (props) => {
|
||||||
</Screen>);
|
</Screen>);
|
||||||
};
|
};
|
||||||
|
|
||||||
MobileSketchView.propTypes = {
|
export default MobileSketchView;
|
||||||
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);
|
|
||||||
|
|
|
@ -62,6 +62,7 @@ export default {
|
||||||
colors,
|
colors,
|
||||||
...common,
|
...common,
|
||||||
primaryTextColor: grays.dark,
|
primaryTextColor: grays.dark,
|
||||||
|
backgroundColor: grays.lighter,
|
||||||
|
|
||||||
Button: {
|
Button: {
|
||||||
default: {
|
default: {
|
||||||
|
@ -95,12 +96,14 @@ export default {
|
||||||
background: grays.light,
|
background: grays.light,
|
||||||
border: grays.middleLight,
|
border: grays.middleLight,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
Separator: grays.middleLight,
|
||||||
},
|
},
|
||||||
[Theme.dark]: {
|
[Theme.dark]: {
|
||||||
colors,
|
colors,
|
||||||
...common,
|
...common,
|
||||||
primaryTextColor: grays.lightest,
|
primaryTextColor: grays.lightest,
|
||||||
|
backgroundColor: grays.darker,
|
||||||
|
|
||||||
Button: {
|
Button: {
|
||||||
default: {
|
default: {
|
||||||
|
@ -134,12 +137,14 @@ export default {
|
||||||
background: grays.dark,
|
background: grays.dark,
|
||||||
border: grays.middleDark,
|
border: grays.middleDark,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
Separator: grays.middleDark,
|
||||||
},
|
},
|
||||||
[Theme.contrast]: {
|
[Theme.contrast]: {
|
||||||
colors,
|
colors,
|
||||||
...common,
|
...common,
|
||||||
primaryTextColor: grays.lightest,
|
primaryTextColor: grays.lightest,
|
||||||
|
backgroundColor: grays.darker,
|
||||||
|
|
||||||
Button: {
|
Button: {
|
||||||
default: {
|
default: {
|
||||||
|
@ -173,6 +178,7 @@ export default {
|
||||||
background: grays.dark,
|
background: grays.dark,
|
||||||
border: grays.middleDark,
|
border: grays.middleDark,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
Separator: grays.middleDark,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
15
client/utils/custom-hooks.js
Normal file
15
client/utils/custom-hooks.js
Normal 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);
|
||||||
|
};
|
Loading…
Reference in a new issue