🔀 pull from feature/mobile-files-tab
This commit is contained in:
commit
54ae17f29d
17 changed files with 238 additions and 34 deletions
|
@ -17,6 +17,12 @@ import Code from '../images/code.svg';
|
|||
import Save from '../images/save.svg';
|
||||
import Terminal from '../images/terminal.svg';
|
||||
|
||||
import Folder from '../images/folder-padded.svg';
|
||||
|
||||
import CircleTerminal from '../images/circle-terminal.svg';
|
||||
import CircleFolder from '../images/circle-folder.svg';
|
||||
import CircleInfo from '../images/circle-info.svg';
|
||||
|
||||
|
||||
// HOC that adds the right web accessibility props
|
||||
// https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html
|
||||
|
@ -83,3 +89,9 @@ export const MoreIcon = withLabel(More);
|
|||
export const TerminalIcon = withLabel(Terminal);
|
||||
export const CodeIcon = withLabel(Code);
|
||||
export const SaveIcon = withLabel(Save);
|
||||
|
||||
export const FolderIcon = withLabel(Folder);
|
||||
|
||||
export const CircleTerminalIcon = withLabel(CircleTerminal);
|
||||
export const CircleFolderIcon = withLabel(CircleFolder);
|
||||
export const CircleInfoIcon = withLabel(CircleInfo);
|
||||
|
|
|
@ -25,7 +25,7 @@ const DropdownWrapper = styled.ul`
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
z-index: 9999;
|
||||
z-index: 2;
|
||||
border-radius: ${remSize(6)};
|
||||
|
||||
& li:first-child { border-radius: ${remSize(5)} ${remSize(5)} 0 0; }
|
||||
|
|
|
@ -1,23 +1,34 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled from 'styled-components';
|
||||
import { remSize } from '../../theme';
|
||||
import { remSize, prop } from '../../theme';
|
||||
import IconButton from './IconButton';
|
||||
|
||||
const BottomBarContent = styled.div`
|
||||
padding: ${remSize(8)};
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8,1fr);
|
||||
padding: ${remSize(8)};
|
||||
|
||||
|
||||
svg {
|
||||
max-height: ${remSize(32)};
|
||||
}
|
||||
|
||||
path { fill: ${prop('primaryTextColor')} !important }
|
||||
|
||||
.inverted {
|
||||
path { fill: ${prop('backgroundColor')} !important }
|
||||
rect { fill: ${prop('primaryTextColor')} !important }
|
||||
}
|
||||
`;
|
||||
|
||||
const ActionStrip = ({ actions }) => (
|
||||
<BottomBarContent>
|
||||
{actions.map(({ icon, aria, action }) =>
|
||||
{actions.map(({
|
||||
icon, aria, action, inverted
|
||||
}) =>
|
||||
(<IconButton
|
||||
inverted={inverted}
|
||||
className={inverted && 'inverted'}
|
||||
icon={icon}
|
||||
aria-label={aria}
|
||||
key={`bottom-bar-${aria}`}
|
||||
|
@ -29,7 +40,8 @@ ActionStrip.propTypes = {
|
|||
actions: PropTypes.arrayOf(PropTypes.shape({
|
||||
icon: PropTypes.element.isRequired,
|
||||
aria: PropTypes.string.isRequired,
|
||||
action: PropTypes.func.isRequired
|
||||
action: PropTypes.func.isRequired,
|
||||
inverted: PropTypes.bool
|
||||
})).isRequired
|
||||
};
|
||||
|
||||
|
|
24
client/components/mobile/Explorer.jsx
Normal file
24
client/components/mobile/Explorer.jsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import PropTypes from 'prop-types';
|
||||
import Sidebar from './Sidebar';
|
||||
import ConnectedFileNode from '../../modules/IDE/components/FileNode';
|
||||
|
||||
|
||||
const Explorer = ({ id, canEdit, onPressClose }) => (
|
||||
<Sidebar title="Files" onPressClose={onPressClose}>
|
||||
<ConnectedFileNode id={id} canEdit={canEdit} onClickFile={() => onPressClose()} />
|
||||
</Sidebar>
|
||||
);
|
||||
|
||||
Explorer.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
onPressClose: PropTypes.func,
|
||||
canEdit: PropTypes.bool
|
||||
};
|
||||
Explorer.defaultProps = {
|
||||
canEdit: false,
|
||||
onPressClose: () => {}
|
||||
};
|
||||
|
||||
export default Explorer;
|
43
client/components/mobile/FloatingNav.jsx
Normal file
43
client/components/mobile/FloatingNav.jsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled from 'styled-components';
|
||||
import { remSize, prop } from '../../theme';
|
||||
import Button from '../../common/Button';
|
||||
import IconButton from './IconButton';
|
||||
|
||||
const FloatingContainer = styled.div`
|
||||
position: fixed;
|
||||
right: ${remSize(16)};
|
||||
top: ${remSize(80)};
|
||||
|
||||
text-align: right;
|
||||
z-index: 3;
|
||||
|
||||
svg { width: ${remSize(32)}; };
|
||||
svg > path { fill: ${prop('Button.default.background')} !important };
|
||||
`;
|
||||
|
||||
const FloatingNav = ({ items }) => (
|
||||
<FloatingContainer>
|
||||
{ items.map(({ icon, onPress }) =>
|
||||
(
|
||||
<IconButton
|
||||
onClick={onPress}
|
||||
icon={icon}
|
||||
/>
|
||||
))}
|
||||
</FloatingContainer>
|
||||
);
|
||||
|
||||
FloatingNav.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.shape({
|
||||
icon: PropTypes.element,
|
||||
onPress: PropTypes.func
|
||||
}))
|
||||
};
|
||||
|
||||
FloatingNav.defaultProps = {
|
||||
items: []
|
||||
};
|
||||
|
||||
export default FloatingNav;
|
|
@ -14,7 +14,7 @@ const textColor = ({ transparent, inverted }) => prop((transparent === false &&
|
|||
|
||||
|
||||
const HeaderDiv = styled.div`
|
||||
position: fixed;
|
||||
${props => props.fixed && 'position: fixed;'}
|
||||
width: 100%;
|
||||
background: ${props => background(props)};
|
||||
color: ${textColor};
|
||||
|
@ -63,9 +63,9 @@ const TitleContainer = styled.div`
|
|||
|
||||
const Header = ({
|
||||
title, subtitle, leftButton, children,
|
||||
transparent, inverted, slim
|
||||
transparent, inverted, slim, fixed
|
||||
}) => (
|
||||
<HeaderDiv transparent={transparent} slim={slim} inverted={inverted}>
|
||||
<HeaderDiv transparent={transparent} slim={slim} inverted={inverted} fixed={fixed}>
|
||||
{leftButton}
|
||||
<TitleContainer padded={subtitle === null}>
|
||||
{title && <h2>{title}</h2>}
|
||||
|
@ -86,6 +86,7 @@ Header.propTypes = {
|
|||
transparent: PropTypes.bool,
|
||||
inverted: PropTypes.bool,
|
||||
slim: PropTypes.bool,
|
||||
fixed: PropTypes.bool,
|
||||
};
|
||||
|
||||
Header.defaultProps = {
|
||||
|
@ -95,7 +96,8 @@ Header.defaultProps = {
|
|||
children: [],
|
||||
transparent: false,
|
||||
inverted: false,
|
||||
slim: false
|
||||
slim: false,
|
||||
fixed: true
|
||||
};
|
||||
|
||||
export default Header;
|
||||
|
|
|
@ -2,7 +2,10 @@ import React from 'react';
|
|||
import styled from 'styled-components';
|
||||
import { remSize } from '../../theme';
|
||||
|
||||
// Applies padding to top and bottom so editor content is always visible
|
||||
|
||||
export default styled.div`
|
||||
z-index: 0;
|
||||
margin-top: ${remSize(16)};
|
||||
.CodeMirror-sizer > * { padding-bottom: ${remSize(320)}; };
|
||||
`;
|
||||
|
|
46
client/components/mobile/Sidebar.jsx
Normal file
46
client/components/mobile/Sidebar.jsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router';
|
||||
import styled from 'styled-components';
|
||||
import { remSize, prop, common } from '../../theme';
|
||||
import Header from './Header';
|
||||
import IconButton from './IconButton';
|
||||
import { ExitIcon } from '../../common/icons';
|
||||
|
||||
|
||||
const SidebarWrapper = styled.div`
|
||||
height: 100%;
|
||||
width: ${remSize(180)};
|
||||
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
left: 0;
|
||||
|
||||
background: white;
|
||||
box-shadow: 0 6px 6px 0 rgba(0,0,0,0.10);
|
||||
`;
|
||||
|
||||
const Sidebar = ({ title, onPressClose, children }) => (
|
||||
<SidebarWrapper>
|
||||
{title &&
|
||||
<Header slim title={title} fixed={false}>
|
||||
<IconButton onClick={onPressClose} icon={ExitIcon} aria-label="Return to ide view" />
|
||||
</Header>}
|
||||
{children}
|
||||
</SidebarWrapper>
|
||||
);
|
||||
|
||||
Sidebar.propTypes = {
|
||||
title: PropTypes.string,
|
||||
onPressClose: PropTypes.func,
|
||||
children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]),
|
||||
};
|
||||
|
||||
Sidebar.defaultProps = {
|
||||
title: null,
|
||||
children: [],
|
||||
onPressClose: () => {}
|
||||
};
|
||||
|
||||
|
||||
export default Sidebar;
|
|
@ -1,10 +1,29 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useModalBehavior } from '../utils/custom-hooks';
|
||||
|
||||
export default (component) => {
|
||||
const [visible, trigger, setRef] = useModalBehavior();
|
||||
const BackgroundOverlay = styled.div`
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
|
||||
background: black;
|
||||
opacity: 0.3;
|
||||
`;
|
||||
|
||||
const wrapper = () => <div ref={setRef}> {visible && component} </div>; // eslint-disable-line
|
||||
export default (Element, hasOverlay = false) => {
|
||||
const [visible, toggle, setRef] = useModalBehavior();
|
||||
|
||||
return [trigger, wrapper];
|
||||
const wrapper = () => (visible &&
|
||||
<div>
|
||||
{hasOverlay && <BackgroundOverlay />}
|
||||
<div ref={setRef}>
|
||||
{ (typeof (Element) === 'function')
|
||||
? Element(toggle)
|
||||
: Element}
|
||||
</div>
|
||||
</div>);
|
||||
|
||||
return [toggle, wrapper];
|
||||
};
|
||||
|
|
5
client/images/circle-folder.svg
Normal file
5
client/images/circle-folder.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">
|
||||
<circle cx="16" cy="16" r="16" fill="#333333"/>
|
||||
<path d="M25.144 12.0961V22.4321C25.144 22.8801 24.792 23.2321 24.344 23.2321H7.768C7.32 23.2321 7 22.8801 7 22.4321V12.0961C7 11.2321 7.704 10.5281 8.568 10.5281H23.576C24.44 10.5281 25.144 11.2321 25.144 12.0961Z" fill="#F0F0F0"/>
|
||||
<path d="M9.24023 9.6C9.24023 9.6 9.24023 8 10.5842 8H15.1282C16.4402 8 16.4402 9.6 16.4402 9.6H9.24023Z" fill="#F0F0F0"/>
|
||||
</svg>
|
After Width: | Height: | Size: 507 B |
4
client/images/circle-info.svg
Normal file
4
client/images/circle-info.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="16" cy="16" r="16" fill="#333333"/>
|
||||
<path d="M16 7C11.0154 7 7 11.0154 7 16C7 20.95 11.0154 25 16 25C20.95 25 25 20.95 25 16C25 11.0154 20.95 7 16 7ZM17.3846 21.5038H14.6846V13.7154H17.3846V21.5038ZM16 12.9538C15.1 12.9538 14.4077 12.2615 14.4077 11.3615C14.4077 10.4962 15.1 9.80385 16 9.80385C16.9 9.80385 17.5923 10.4962 17.5923 11.3615C17.5923 12.2615 16.9 12.9538 16 12.9538Z" fill="#F0F0F0"/>
|
||||
</svg>
|
After Width: | Height: | Size: 514 B |
6
client/images/circle-terminal.svg
Normal file
6
client/images/circle-terminal.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="16" cy="16" r="16" fill="#333333"/>
|
||||
<rect x="5" y="8" width="22" height="16" rx="2" fill="#F0F0F0"/>
|
||||
<path d="M24 21H14V20H24V21Z" fill="#333333"/>
|
||||
<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="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 656 B |
4
client/images/folder-padded.svg
Normal file
4
client/images/folder-padded.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M25.144 12.0961V22.4321C25.144 22.8801 24.792 23.2321 24.344 23.2321H7.768C7.32 23.2321 7 22.8801 7 22.4321V12.0961C7 11.2321 7.704 10.5281 8.568 10.5281H23.576C24.44 10.5281 25.144 11.2321 25.144 12.0961Z" fill="#F0F0F0"/>
|
||||
<path d="M9.24023 9.6C9.24023 9.6 9.24023 8 10.5842 8H15.1282C16.4402 8 16.4402 9.6 16.4402 9.6H9.24023Z" fill="#F0F0F0"/>
|
||||
</svg>
|
After Width: | Height: | Size: 459 B |
|
@ -19,7 +19,9 @@ class App extends React.Component {
|
|||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const locationWillChange = nextProps.location !== this.props.location;
|
||||
const shouldSkipRemembering = nextProps.location.state && nextProps.location.state.skipSavingPath === true;
|
||||
const shouldSkipRemembering =
|
||||
nextProps.location.state &&
|
||||
nextProps.location.state.skipSavingPath === true;
|
||||
|
||||
if (locationWillChange && !shouldSkipRemembering) {
|
||||
this.props.setPreviousPath(this.props.location.pathname);
|
||||
|
|
|
@ -108,10 +108,15 @@ export class FileNode extends React.Component {
|
|||
handleFileClick = (event) => {
|
||||
event.stopPropagation();
|
||||
const { isDeleting } = this.state;
|
||||
const { id, setSelectedFile, name } = this.props;
|
||||
const {
|
||||
id, setSelectedFile, name, onClickFile
|
||||
} = this.props;
|
||||
if (name !== 'root' && !isDeleting) {
|
||||
setSelectedFile(id);
|
||||
}
|
||||
|
||||
// debugger; // eslint-disable-line
|
||||
if (onClickFile) { onClickFile(); }
|
||||
}
|
||||
|
||||
handleFileNameChange = (event) => {
|
||||
|
@ -214,7 +219,7 @@ export class FileNode extends React.Component {
|
|||
|
||||
renderChild = childId => (
|
||||
<li key={childId}>
|
||||
<ConnectedFileNode id={childId} parentId={this.props.id} canEdit={this.props.canEdit} />
|
||||
<ConnectedFileNode id={childId} parentId={this.props.id} canEdit={this.props.canEdit} onClickFile={this.props.onClickFile} />
|
||||
</li>
|
||||
)
|
||||
|
||||
|
@ -233,7 +238,7 @@ export class FileNode extends React.Component {
|
|||
const isRoot = this.props.name === 'root';
|
||||
|
||||
return (
|
||||
<div className={itemClass}>
|
||||
<div className={itemClass} >
|
||||
{ !isRoot &&
|
||||
<div className="file-item__content" onContextMenu={this.toggleFileOptions}>
|
||||
<span className="file-item__spacer"></span>
|
||||
|
@ -382,10 +387,12 @@ FileNode.propTypes = {
|
|||
hideFolderChildren: PropTypes.func.isRequired,
|
||||
canEdit: PropTypes.bool.isRequired,
|
||||
openUploadFileModal: PropTypes.func.isRequired,
|
||||
authenticated: PropTypes.bool.isRequired
|
||||
authenticated: PropTypes.bool.isRequired,
|
||||
onClickFile: PropTypes.func
|
||||
};
|
||||
|
||||
FileNode.defaultProps = {
|
||||
onClickFile: null,
|
||||
parentId: '0',
|
||||
isSelectedFile: false,
|
||||
isFolderClosed: false,
|
||||
|
|
|
@ -16,7 +16,8 @@ import * as PreferencesActions from '../actions/preferences';
|
|||
|
||||
// Local Imports
|
||||
import Editor from '../components/Editor';
|
||||
import { PlayIcon, MoreIcon } from '../../../common/icons';
|
||||
|
||||
import { PlayIcon, MoreIcon, FolderIcon, PreferencesIcon, TerminalIcon, SaveIcon } from '../../../common/icons';
|
||||
import UnsavedChangesDotIcon from '../../../images/unsaved-changes-dot.svg';
|
||||
|
||||
import IconButton from '../../../components/mobile/IconButton';
|
||||
|
@ -24,12 +25,12 @@ import Header from '../../../components/mobile/Header';
|
|||
import Screen from '../../../components/mobile/MobileScreen';
|
||||
import Footer from '../../../components/mobile/Footer';
|
||||
import IDEWrapper from '../../../components/mobile/IDEWrapper';
|
||||
import MobileExplorer from '../../../components/mobile/Explorer';
|
||||
import Console from '../components/Console';
|
||||
import { remSize } from '../../../theme';
|
||||
// import OverlayManager from '../../../components/OverlayManager';
|
||||
|
||||
import ActionStrip from '../../../components/mobile/ActionStrip';
|
||||
import useAsModal from '../../../components/useAsModal';
|
||||
import { PreferencesIcon, TerminalIcon, SaveIcon } from '../../../common/icons';
|
||||
import Dropdown from '../../../components/Dropdown';
|
||||
|
||||
|
||||
|
@ -46,6 +47,8 @@ const withChangeDot = (title, unsavedChanges = false) => (
|
|||
</span>
|
||||
</span>
|
||||
);
|
||||
const getRootFile = files => files && files.filter(file => file.name === 'root')[0];
|
||||
const getRootFileID = files => (root => root && root.id)(getRootFile(files));
|
||||
|
||||
const Expander = styled.div`
|
||||
height: ${props => (props.expanded ? remSize(160) : remSize(27))};
|
||||
|
@ -55,7 +58,7 @@ const NavItem = styled.li`
|
|||
position: relative;
|
||||
`;
|
||||
|
||||
const getNatOptions = (username = undefined) =>
|
||||
const getNavOptions = (username = undefined) =>
|
||||
(username
|
||||
? [
|
||||
{ icon: PreferencesIcon, title: 'Preferences', href: '/mobile/preferences', },
|
||||
|
@ -170,7 +173,7 @@ const autosave = (autosaveInterval, setAutosaveInterval) => (props, prevProps) =
|
|||
const MobileIDEView = (props) => {
|
||||
const {
|
||||
ide, preferences, project, selectedFile, user, params, unsavedChanges, expandConsole, collapseConsole,
|
||||
stopSketch, startSketch, getProject, clearPersistedState, autosaveProject, saveProject
|
||||
stopSketch, startSketch, getProject, clearPersistedState, autosaveProject, saveProject, files
|
||||
} = props;
|
||||
|
||||
|
||||
|
@ -180,10 +183,6 @@ const MobileIDEView = (props) => {
|
|||
const { consoleIsExpanded } = ide;
|
||||
const { name: filename } = selectedFile;
|
||||
|
||||
const [triggerNavDropdown, NavDropDown] = useAsModal(<Dropdown
|
||||
items={getNatOptions(username)}
|
||||
align="right"
|
||||
/>);
|
||||
|
||||
// Force state reset
|
||||
useEffect(clearPersistedState, []);
|
||||
|
@ -204,6 +203,18 @@ const MobileIDEView = (props) => {
|
|||
setCurrentProjectID(params.project_id);
|
||||
}, [params, project, username]);
|
||||
|
||||
// Screen Modals
|
||||
const [toggleNavDropdown, NavDropDown] = useAsModal(<Dropdown
|
||||
items={getNavOptions(username)}
|
||||
align="right"
|
||||
/>);
|
||||
|
||||
const [toggleExplorer, Explorer] = useAsModal(toggle =>
|
||||
(<MobileExplorer
|
||||
id={getRootFileID(files)}
|
||||
canEdit={false}
|
||||
onPressClose={toggle}
|
||||
/>), true);
|
||||
|
||||
// TODO: This behavior could move to <Editor />
|
||||
const [autosaveInterval, setAutosaveInterval] = useState(null);
|
||||
|
@ -214,19 +225,23 @@ const MobileIDEView = (props) => {
|
|||
useEventListener('keydown', handleGlobalKeydown(props, cmController), false, [props]);
|
||||
|
||||
const projectActions =
|
||||
[{ icon: TerminalIcon, aria: 'Toggle console open/closed', action: consoleIsExpanded ? collapseConsole : expandConsole },
|
||||
{ icon: SaveIcon, aria: 'Save project', action: () => saveProject(cmController.getContent(), false, true) }
|
||||
[{
|
||||
icon: TerminalIcon, aria: 'Toggle console open/closed', action: consoleIsExpanded ? collapseConsole : expandConsole, inverted: true
|
||||
},
|
||||
{ icon: SaveIcon, aria: 'Save project', action: () => saveProject(cmController.getContent(), false, true) },
|
||||
{ icon: FolderIcon, aria: 'Open files explorer', action: toggleExplorer }
|
||||
];
|
||||
|
||||
return (
|
||||
<Screen fullscreen>
|
||||
<Explorer />
|
||||
<Header
|
||||
title={withChangeDot(project.name, unsavedChanges)}
|
||||
subtitle={filename}
|
||||
>
|
||||
<NavItem>
|
||||
<IconButton
|
||||
onClick={triggerNavDropdown}
|
||||
onClick={toggleNavDropdown}
|
||||
icon={MoreIcon}
|
||||
aria-label="Options"
|
||||
/>
|
||||
|
@ -299,6 +314,8 @@ MobileIDEView.propTypes = {
|
|||
username: PropTypes.string,
|
||||
}).isRequired,
|
||||
|
||||
getProject: PropTypes.func.isRequired,
|
||||
clearPersistedState: PropTypes.func.isRequired,
|
||||
params: PropTypes.shape({
|
||||
project_id: PropTypes.string,
|
||||
username: PropTypes.string
|
||||
|
@ -308,8 +325,6 @@ MobileIDEView.propTypes = {
|
|||
|
||||
startSketch: PropTypes.func.isRequired,
|
||||
stopSketch: PropTypes.func.isRequired,
|
||||
getProject: PropTypes.func.isRequired,
|
||||
clearPersistedState: PropTypes.func.isRequired,
|
||||
autosaveProject: PropTypes.func.isRequired,
|
||||
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ export const useModalBehavior = (hideOverlay) => {
|
|||
|
||||
|
||||
const handleClickOutside = ({ target }) => {
|
||||
if (ref && ref.current && !ref.current.contains(target)) {
|
||||
if (ref && ref.current && !(ref.current.contains && ref.current.contains(target))) {
|
||||
hide();
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue