import PropTypes from 'prop-types'; import React from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { withRouter } from 'react-router'; import { withTranslation } from 'react-i18next'; import { Helmet } from 'react-helmet'; import SplitPane from 'react-split-pane'; 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/index'; import NewFileModal from '../components/NewFileModal'; import NewFolderModal from '../components/NewFolderModal'; import UploadFileModal from '../components/UploadFileModal'; import ShareModal from '../components/ShareModal'; import KeyboardShortcutModal from '../components/KeyboardShortcutModal'; import ErrorModal from '../components/ErrorModal'; import Nav from '../../../components/Nav'; import Console from '../components/Console'; import Toast from '../components/Toast'; import * as FileActions from '../actions/files'; import * as IDEActions from '../actions/ide'; import * as ProjectActions from '../actions/project'; import * as EditorAccessibilityActions from '../actions/editorAccessibility'; import * as PreferencesActions from '../actions/preferences'; import * as UserActions from '../../User/actions'; import * as ToastActions from '../actions/toast'; import * as ConsoleActions from '../actions/console'; import { getHTMLFile } from '../reducers/files'; import Overlay from '../../App/components/Overlay'; import About from '../components/About'; import AddToCollectionList from '../components/AddToCollectionList'; import Feedback from '../components/Feedback'; import { CollectionSearchbar } from '../components/Searchbar'; function getTitle(props) { const { id } = props.project; return id ? `p5.js Web Editor | ${props.project.name}` : 'p5.js Web Editor'; } function isUserOwner(props) { return props.project.owner && props.project.owner.id === props.user.id; } function warnIfUnsavedChanges(props) { // eslint-disable-line const { route } = props.route; if ( route && route.action === 'PUSH' && (route.pathname === '/login' || route.pathname === '/signup') ) { // don't warn props.persistState(); window.onbeforeunload = null; } else if ( route && (props.location.pathname === '/login' || props.location.pathname === '/signup') ) { // don't warn props.persistState(); window.onbeforeunload = null; } else if (props.ide.unsavedChanges) { if (!window.confirm(props.t('Nav.WarningUnsavedChanges'))) { return false; } props.setUnsavedChanges(false); return true; } return true; } class IDEView extends React.Component { constructor(props) { super(props); this.handleGlobalKeydown = this.handleGlobalKeydown.bind(this); this.state = { consoleSize: props.ide.consoleIsExpanded ? 150 : 29, sidebarSize: props.ide.sidebarIsExpanded ? 160 : 20, }; } componentDidMount() { // If page doesn't reload after Sign In then we need // to force cleared state to be cleared this.props.clearPersistedState(); this.props.stopSketch(); if (this.props.params.project_id) { const { project_id: id, username } = this.props.params; if (id !== this.props.project.id) { this.props.getProject(id, username); } } this.isMac = navigator.userAgent.toLowerCase().indexOf('mac') !== -1; document.addEventListener('keydown', this.handleGlobalKeydown, false); this.props.router.setRouteLeaveHook( this.props.route, this.handleUnsavedChanges ); window.onbeforeunload = this.handleUnsavedChanges; this.autosaveInterval = null; } componentWillReceiveProps(nextProps) { if (nextProps.location !== this.props.location) { this.props.setPreviousPath(this.props.location.pathname); } if (this.props.ide.consoleIsExpanded !== nextProps.ide.consoleIsExpanded) { this.setState({ consoleSize: nextProps.ide.consoleIsExpanded ? 150 : 29, }); } if (this.props.ide.sidebarIsExpanded !== nextProps.ide.sidebarIsExpanded) { this.setState({ sidebarSize: nextProps.ide.sidebarIsExpanded ? 160 : 20, }); } } componentWillUpdate(nextProps) { if (nextProps.params.project_id && !this.props.params.project_id) { if (nextProps.params.project_id !== nextProps.project.id) { this.props.getProject(nextProps.params.project_id); } } } componentDidUpdate(prevProps) { if (isUserOwner(this.props) && this.props.project.id) { if ( this.props.preferences.autosave && this.props.ide.unsavedChanges && !this.props.ide.justOpenedProject ) { if ( this.props.selectedFile.name === prevProps.selectedFile.name && this.props.selectedFile.content !== prevProps.selectedFile.content ) { if (this.autosaveInterval) { clearTimeout(this.autosaveInterval); } console.log('will save project in 20 seconds'); this.autosaveInterval = setTimeout(this.props.autosaveProject, 20000); } } else if (this.autosaveInterval && !this.props.preferences.autosave) { clearTimeout(this.autosaveInterval); this.autosaveInterval = null; } } else if (this.autosaveInterval) { clearTimeout(this.autosaveInterval); this.autosaveInterval = null; } if (this.props.route.path !== prevProps.route.path) { this.props.router.setRouteLeaveHook(this.props.route, () => warnIfUnsavedChanges(this.props)); } } componentWillUnmount() { document.removeEventListener('keydown', this.handleGlobalKeydown, false); clearTimeout(this.autosaveInterval); this.autosaveInterval = null; } handleGlobalKeydown(e) { // 83 === s if ( e.keyCode === 83 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) ) { e.preventDefault(); e.stopPropagation(); if ( isUserOwner(this.props) || (this.props.user.authenticated && !this.props.project.owner) ) { this.props.saveProject(this.cmController.getContent()); } else if (this.props.user.authenticated) { this.props.cloneProject(); } else { this.props.showErrorModal('forceAuthentication'); } // 13 === enter } else if ( e.keyCode === 13 && e.shiftKey && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) ) { e.preventDefault(); e.stopPropagation(); this.props.stopSketch(); } else if ( e.keyCode === 13 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) ) { e.preventDefault(); e.stopPropagation(); this.props.startSketch(); // 50 === 2 } else if ( e.keyCode === 50 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) && e.shiftKey ) { e.preventDefault(); this.props.setAllAccessibleOutput(false); // 49 === 1 } else if ( e.keyCode === 49 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) && e.shiftKey ) { e.preventDefault(); this.props.setAllAccessibleOutput(true); } else if ( e.keyCode === 66 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) ) { e.preventDefault(); if (!this.props.ide.sidebarIsExpanded) { this.props.expandSidebar(); } else { this.props.collapseSidebar(); } } else if (e.keyCode === 192 && e.ctrlKey) { e.preventDefault(); if (this.props.ide.consoleIsExpanded) { this.props.collapseConsole(); } else { this.props.expandConsole(); } } else if (e.keyCode === 27) { if (this.props.ide.newFolderModalVisible) { this.props.closeNewFolderModal(); } else if (this.props.ide.uploadFileModalVisible) { this.props.closeUploadFileModal(); } else if (this.props.ide.modalIsVisible) { this.props.closeNewFileModal(); } } } handleUnsavedChanges = () => warnIfUnsavedChanges(this.props); render() { return (