import React, { PropTypes } from 'react'; import Editor from '../components/Editor'; import Sidebar from '../components/Sidebar'; import PreviewFrame from '../components/PreviewFrame'; import Toolbar from '../components/Toolbar'; import TextOutput from '../components/TextOutput'; import Preferences from '../components/Preferences'; import NewFileModal from '../components/NewFileModal'; import NewFolderModal from '../components/NewFolderModal'; import ShareModal from '../components/ShareModal'; import KeyboardShortcutModal from '../components/KeyboardShortcutModal'; import ForceAuthentication from '../components/ForceAuthentication'; import Nav from '../../../components/Nav'; import Console from '../components/Console'; import Toast from '../components/Toast'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { withRouter } from 'react-router'; 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 { getHTMLFile } from '../reducers/files'; import SplitPane from 'react-split-pane'; import Overlay from '../../App/components/Overlay'; import SketchList from '../components/SketchList'; import About from '../components/About'; import LoginView from '../components/LoginView'; import SignupView from '../components/SignupView'; import ResetPasswordView from '../components/ResetPasswordView'; import NewPasswordView from '../components/NewPasswordView'; class IDEView extends React.Component { constructor(props) { super(props); this._handleConsolePaneOnDragFinished = this._handleConsolePaneOnDragFinished.bind(this); this._handleSidebarPaneOnDragFinished = this._handleSidebarPaneOnDragFinished.bind(this); this.handleGlobalKeydown = this.handleGlobalKeydown.bind(this); this.warnIfUnsavedChanges = this.warnIfUnsavedChanges.bind(this); } componentDidMount() { this.props.stopSketch(); if (this.props.params.project_id) { const id = this.props.params.project_id; if (id !== this.props.project.id) { this.props.getProject(id); } // if autosave is on and the user is the owner of the project if (this.props.preferences.autosave && this.isUserOwner()) { this.autosaveInterval = setInterval(this.props.autosaveProject, 30000); } } this.consoleSize = this.props.ide.consoleIsExpanded ? 180 : 29; this.sidebarSize = this.props.ide.sidebarIsExpanded ? 200 : 25; this.forceUpdate(); this.isMac = navigator.userAgent.toLowerCase().indexOf('mac') !== -1; document.addEventListener('keydown', this.handleGlobalKeydown, false); this.props.router.setRouteLeaveHook(this.props.route, (route) => this.warnIfUnsavedChanges(route)); window.onbeforeunload = () => this.warnIfUnsavedChanges(); document.body.className = this.props.preferences.theme; } componentWillReceiveProps(nextProps) { if (nextProps.location !== this.props.location) { this.props.setPreviousPath(this.props.location.pathname); } } componentWillUpdate(nextProps) { if (this.props.ide.consoleIsExpanded !== nextProps.ide.consoleIsExpanded) { this.consoleSize = nextProps.ide.consoleIsExpanded ? 180 : 29; } if (this.props.ide.sidebarIsExpanded !== nextProps.ide.sidebarIsExpanded) { this.sidebarSize = nextProps.ide.sidebarIsExpanded ? 200 : 25; } 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); } } if (!nextProps.params.project_id && this.props.params.project_id) { this.props.resetProject(); } if (nextProps.preferences.theme !== this.props.preferences.theme) { document.body.className = nextProps.preferences.theme; } } componentDidUpdate(prevProps) { // if user is the owner of the project if (this.isUserOwner()) { // if the user turns on autosave // or the user saves the project for the first time if (!this.autosaveInterval && ((this.props.preferences.autosave && !prevProps.preferences.autosave) || (this.props.project.id && !prevProps.project.id))) { this.autosaveInterval = setInterval(this.props.autosaveProject, 30000); // if user turns off autosave preference } else if (this.autosaveInterval && !this.props.preferences.autosave && prevProps.preferences.autosave) { clearInterval(this.autosaveInterval); this.autosaveInterval = null; } } if (this.autosaveInterval && !this.props.project.id) { clearInterval(this.autosaveInterval); this.autosaveInterval = null; } if (this.props.route.path !== prevProps.route.path) { this.props.router.setRouteLeaveHook(this.props.route, (route) => this.warnIfUnsavedChanges(route)); } } componentWillUnmount() { clearInterval(this.autosaveInterval); this.autosaveInterval = null; this.consoleSize = undefined; this.sidebarSize = undefined; } isUserOwner() { return this.props.project.owner && this.props.project.owner.id === this.props.user.id; } _handleConsolePaneOnDragFinished() { this.consoleSize = this.refs.consolePane.state.draggedSize; this.refs.consolePane.setState({ resized: false, draggedSize: undefined, }); } _handleSidebarPaneOnDragFinished() { this.sidebarSize = this.refs.sidebarPane.state.draggedSize; this.refs.sidebarPane.setState({ resized: false, draggedSize: undefined }); } handleGlobalKeydown(e) { // 83 === s if (e.keyCode === 83 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac))) { e.preventDefault(); e.stopPropagation(); if (this.isUserOwner() || this.props.user.authenticated && !this.props.project.owner) { this.props.saveProject(); } // 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.startSketchAndRefresh(); } else if (e.keyCode === 50 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) && e.shiftKey) { e.preventDefault(); this.props.setTextOutput(0); } else if (e.keyCode === 49 && ((e.metaKey && this.isMac) || (e.ctrlKey && !this.isMac)) && e.shiftKey) { e.preventDefault(); if (this.props.preferences.textOutput === 3) { this.props.preferences.textOutput = 1; } else { this.props.preferences.textOutput += 1; } this.props.setTextOutput(this.props.preferences.textOutput); } } warnIfUnsavedChanges(route) { // eslint-disable-line if (route && (route.action === 'PUSH' && (route.pathname === '/login' || route.pathname === '/signup'))) { // don't warn } else if (route && this.props.location.pathname === '/login' || this.props.location.pathname === '/signup') { // don't warn } else if (this.props.ide.unsavedChanges) { if (!window.confirm('Are you sure you want to leave this page? You have unsaved changes.')) { return false; } this.props.setUnsavedChanges(false); } } render() { return (
{this.props.toast.isVisible && }
); } } IDEView.propTypes = { params: PropTypes.shape({ project_id: PropTypes.string, username: PropTypes.string, reset_password_token: PropTypes.string, }), location: PropTypes.shape({ pathname: PropTypes.string }), getProject: PropTypes.func.isRequired, user: PropTypes.shape({ authenticated: PropTypes.bool.isRequired, id: PropTypes.string, username: PropTypes.string }).isRequired, newProject: PropTypes.func.isRequired, saveProject: PropTypes.func.isRequired, ide: PropTypes.shape({ isPlaying: PropTypes.bool.isRequired, isTextOutputPlaying: 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, 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.isRequired, previousPath: PropTypes.string.isRequired, forceAuthenticationVisible: PropTypes.bool.isRequired }).isRequired, startSketch: PropTypes.func.isRequired, stopSketch: PropTypes.func.isRequired, startTextOutput: PropTypes.func.isRequired, stopTextOutput: PropTypes.func.isRequired, detectInfiniteLoops: PropTypes.func.isRequired, resetInfiniteLoops: PropTypes.func.isRequired, project: PropTypes.shape({ id: PropTypes.string, name: PropTypes.string.isRequired, owner: PropTypes.shape({ username: PropTypes.string, id: PropTypes.string }) }).isRequired, setProjectName: PropTypes.func.isRequired, openPreferences: PropTypes.func.isRequired, editorAccessibility: PropTypes.shape({ lintMessages: PropTypes.array.isRequired, lineNumber: PropTypes.string.isRequired }).isRequired, updateLintMessage: PropTypes.func.isRequired, clearLintMessage: PropTypes.func.isRequired, updateLineNumber: PropTypes.func.isRequired, preferences: PropTypes.shape({ fontSize: PropTypes.number.isRequired, indentationAmount: PropTypes.number.isRequired, isTabIndent: PropTypes.bool.isRequired, autosave: PropTypes.bool.isRequired, lintWarning: PropTypes.bool.isRequired, textOutput: PropTypes.number.isRequired, theme: PropTypes.string.isRequired, autorefresh: PropTypes.bool.isRequired }).isRequired, closePreferences: PropTypes.func.isRequired, setFontSize: PropTypes.func.isRequired, setIndentation: PropTypes.func.isRequired, indentWithTab: PropTypes.func.isRequired, indentWithSpace: PropTypes.func.isRequired, setAutosave: PropTypes.func.isRequired, setLintWarning: PropTypes.func.isRequired, setTextOutput: PropTypes.func.isRequired, files: PropTypes.array.isRequired, updateFileContent: PropTypes.func.isRequired, selectedFile: PropTypes.shape({ id: PropTypes.string.isRequired, content: PropTypes.string.isRequired }), setSelectedFile: PropTypes.func.isRequired, htmlFile: PropTypes.object.isRequired, dispatchConsoleEvent: PropTypes.func.isRequired, newFile: PropTypes.func.isRequired, closeNewFileModal: PropTypes.func.isRequired, expandSidebar: PropTypes.func.isRequired, collapseSidebar: PropTypes.func.isRequired, exportProjectAsZip: PropTypes.func.isRequired, cloneProject: PropTypes.func.isRequired, expandConsole: PropTypes.func.isRequired, collapseConsole: PropTypes.func.isRequired, showFileOptions: PropTypes.func.isRequired, hideFileOptions: PropTypes.func.isRequired, deleteFile: PropTypes.func.isRequired, showEditFileName: PropTypes.func.isRequired, hideEditFileName: PropTypes.func.isRequired, updateFileName: PropTypes.func.isRequired, showEditProjectName: PropTypes.func.isRequired, hideEditProjectName: PropTypes.func.isRequired, logoutUser: PropTypes.func.isRequired, openProjectOptions: PropTypes.func.isRequired, closeProjectOptions: PropTypes.func.isRequired, newFolder: PropTypes.func.isRequired, closeNewFolderModal: PropTypes.func.isRequired, createFolder: PropTypes.func.isRequired, createFile: PropTypes.func.isRequired, showShareModal: PropTypes.func.isRequired, closeShareModal: PropTypes.func.isRequired, showEditorOptions: PropTypes.func.isRequired, closeEditorOptions: PropTypes.func.isRequired, showKeyboardShortcutModal: PropTypes.func.isRequired, closeKeyboardShortcutModal: PropTypes.func.isRequired, toast: PropTypes.shape({ isVisible: PropTypes.bool.isRequired }).isRequired, showToast: PropTypes.func.isRequired, setToastText: PropTypes.func.isRequired, autosaveProject: PropTypes.func.isRequired, router: PropTypes.shape({ setRouteLeaveHook: PropTypes.func }).isRequired, route: PropTypes.object.isRequired, setUnsavedChanges: PropTypes.func.isRequired, setTheme: PropTypes.func.isRequired, setAutorefresh: PropTypes.func.isRequired, startSketchAndRefresh: PropTypes.func.isRequired, endSketchRefresh: PropTypes.func.isRequired, startRefreshSketch: PropTypes.func.isRequired, setBlobUrl: PropTypes.func.isRequired, setPreviousPath: PropTypes.func.isRequired, resetProject: PropTypes.func.isRequired, closeForceAuthentication: PropTypes.func.isRequired, openForceAuthentication: PropTypes.func.isRequired, }; function mapStateToProps(state) { return { files: state.files, selectedFile: state.files.find(file => file.isSelectedFile), htmlFile: getHTMLFile(state.files), ide: state.ide, preferences: state.preferences, editorAccessibility: state.editorAccessibility, user: state.user, project: state.project, toast: state.toast }; } function mapDispatchToProps(dispatch) { return bindActionCreators(Object.assign({}, EditorAccessibilityActions, FileActions, ProjectActions, IDEActions, PreferencesActions, UserActions, ToastActions), dispatch); } export default withRouter(connect(mapStateToProps, mapDispatchToProps)(IDEView));