import PropTypes from 'prop-types'; import React from 'react'; import { connect } from 'react-redux'; import { withRouter } from 'react-router'; import { Link } from 'react-router'; import classNames from 'classnames'; import * as IDEActions from '../modules/IDE/actions/ide'; import * as toastActions from '../modules/IDE/actions/toast'; import * as projectActions from '../modules/IDE/actions/project'; import { setAllAccessibleOutput } from '../modules/IDE/actions/preferences'; import { logoutUser } from '../modules/User/actions'; import getConfig from '../utils/getConfig'; import { metaKeyName, } from '../utils/metaKey'; import CaretLeftIcon from '../images/left-arrow.svg'; import TriangleIcon from '../images/down-filled-triangle.svg'; import LogoIcon from '../images/p5js-logo-small.svg'; class Nav extends React.PureComponent { constructor(props) { super(props); this.state = { dropdownOpen: 'none' }; this.handleFocus = this.handleFocus.bind(this); this.handleBlur = this.handleBlur.bind(this); this.clearHideTimeout = this.clearHideTimeout.bind(this); this.handleClick = this.handleClick.bind(this); this.handleClickOutside = this.handleClickOutside.bind(this); this.handleSave = this.handleSave.bind(this); this.handleNew = this.handleNew.bind(this); this.handleDuplicate = this.handleDuplicate.bind(this); this.handleShare = this.handleShare.bind(this); this.handleDownload = this.handleDownload.bind(this); this.handleFind = this.handleFind.bind(this); this.handleAddFile = this.handleAddFile.bind(this); this.handleAddFolder = this.handleAddFolder.bind(this); this.handleFindNext = this.handleFindNext.bind(this); this.handleRun = this.handleRun.bind(this); this.handleFindPrevious = this.handleFindPrevious.bind(this); this.handleStop = this.handleStop.bind(this); this.handleStartAccessible = this.handleStartAccessible.bind(this); this.handleStopAccessible = this.handleStopAccessible.bind(this); this.handleKeyboardShortcuts = this.handleKeyboardShortcuts.bind(this); this.handleLogout = this.handleLogout.bind(this); this.toggleDropdownForFile = this.toggleDropdown.bind(this, 'file'); this.handleFocusForFile = this.handleFocus.bind(this, 'file'); this.setDropdownForNone = this.setDropdown.bind(this, 'none'); this.toggleDropdownForEdit = this.toggleDropdown.bind(this, 'edit'); this.handleFocusForEdit = this.handleFocus.bind(this, 'edit'); this.toggleDropdownForSketch = this.toggleDropdown.bind(this, 'sketch'); this.handleFocusForSketch = this.handleFocus.bind(this, 'sketch'); this.toggleDropdownForHelp = this.toggleDropdown.bind(this, 'help'); this.handleFocusForHelp = this.handleFocus.bind(this, 'help'); this.toggleDropdownForAccount = this.toggleDropdown.bind(this, 'account'); this.handleFocusForAccount = this.handleFocus.bind(this, 'account'); this.closeDropDown = this.closeDropDown.bind(this); } componentDidMount() { document.addEventListener('mousedown', this.handleClick, false); document.addEventListener('keydown', this.closeDropDown, false); } componentWillUnmount() { document.removeEventListener('mousedown', this.handleClick, false); document.removeEventListener('keydown', this.closeDropDown, false); } setDropdown(dropdown) { this.setState({ dropdownOpen: dropdown }); } closeDropDown(e) { if (e.keyCode === 27) { this.setDropdown('none'); } } handleClick(e) { if (!this.node) { return; } if (this.node && this.node.contains(e.target)) { return; } this.handleClickOutside(); } handleNew() { const { unsavedChanges, warnIfUnsavedChanges } = this.props; if (!unsavedChanges) { this.props.showToast(1500); this.props.setToastText('Opened new sketch.'); this.props.newProject(); } else if (warnIfUnsavedChanges && warnIfUnsavedChanges()) { this.props.showToast(1500); this.props.setToastText('Opened new sketch.'); this.props.newProject(); } this.setDropdown('none'); } handleSave() { if (this.props.user.authenticated) { this.props.saveProject(this.props.cmController.getContent()); } else { this.props.showErrorModal('forceAuthentication'); } this.setDropdown('none'); } handleFind() { this.props.cmController.showFind(); this.setDropdown('none'); } handleFindNext() { this.props.cmController.findNext(); this.setDropdown('none'); } handleFindPrevious() { this.props.cmController.findPrev(); this.setDropdown('none'); } handleAddFile() { this.props.newFile(this.props.rootFile.id); this.setDropdown('none'); } handleAddFolder() { this.props.newFolder(this.props.rootFile.id); this.setDropdown('none'); } handleRun() { this.props.startSketch(); this.setDropdown('none'); } handleStop() { this.props.stopSketch(); this.setDropdown('none'); } handleStartAccessible() { this.props.setAllAccessibleOutput(true); this.setDropdown('none'); } handleStopAccessible() { this.props.setAllAccessibleOutput(false); this.setDropdown('none'); } handleKeyboardShortcuts() { this.props.showKeyboardShortcutModal(); this.setDropdown('none'); } handleLogout() { this.props.logoutUser(); this.setDropdown('none'); } handleDownload() { this.props.autosaveProject(); projectActions.exportProjectAsZip(this.props.project.id); this.setDropdown('none'); } handleDuplicate() { this.props.cloneProject(); this.setDropdown('none'); } handleShare() { const { username } = this.props.params; this.props.showShareModal(this.props.project.id, this.props.project.name, username); this.setDropdown('none'); } handleClickOutside() { this.setState({ dropdownOpen: 'none' }); } toggleDropdown(dropdown) { if (this.state.dropdownOpen === 'none') { this.setState({ dropdownOpen: dropdown }); } else { this.setState({ dropdownOpen: 'none' }); } } isUserOwner() { return this.props.project.owner && this.props.project.owner.id === this.props.user.id; } handleFocus(dropdown) { this.clearHideTimeout(); this.setDropdown(dropdown); } clearHideTimeout() { if (this.timer) { clearTimeout(this.timer); this.timer = null; } } handleBlur() { this.timer = setTimeout(this.setDropdown.bind(this, 'none'), 10); } renderDashboardMenu(navDropdownState) { return ( ); } renderProjectMenu(navDropdownState) { return ( ); } renderUnauthenticatedUserMenu(navDropdownState) { return ( ); } renderAuthenticatedUserMenu(navDropdownState) { return ( ); } renderUserMenu(navDropdownState) { const isLoginEnabled = getConfig('LOGIN_ENABLED'); const isAuthenticated = this.props.user.authenticated; if (isLoginEnabled && isAuthenticated) { return this.renderAuthenticatedUserMenu(navDropdownState); } else if (isLoginEnabled && !isAuthenticated) { return this.renderUnauthenticatedUserMenu(navDropdownState); } return null; } renderLeftLayout(navDropdownState) { switch (this.props.layout) { case 'dashboard': return this.renderDashboardMenu(navDropdownState); case 'project': default: return this.renderProjectMenu(navDropdownState); } } render() { const navDropdownState = { file: classNames({ 'nav__item': true, 'nav__item--open': this.state.dropdownOpen === 'file' }), edit: classNames({ 'nav__item': true, 'nav__item--open': this.state.dropdownOpen === 'edit' }), sketch: classNames({ 'nav__item': true, 'nav__item--open': this.state.dropdownOpen === 'sketch' }), help: classNames({ 'nav__item': true, 'nav__item--open': this.state.dropdownOpen === 'help' }), account: classNames({ 'nav__item': true, 'nav__item--open': this.state.dropdownOpen === 'account' }) }; return (
); } } Nav.propTypes = { newProject: PropTypes.func.isRequired, showToast: PropTypes.func.isRequired, setToastText: PropTypes.func.isRequired, saveProject: PropTypes.func.isRequired, autosaveProject: PropTypes.func.isRequired, cloneProject: PropTypes.func.isRequired, user: PropTypes.shape({ authenticated: PropTypes.bool.isRequired, username: PropTypes.string, id: PropTypes.string }).isRequired, project: PropTypes.shape({ id: PropTypes.string, name: PropTypes.string, owner: PropTypes.shape({ id: PropTypes.string }) }), logoutUser: PropTypes.func.isRequired, showShareModal: PropTypes.func.isRequired, showErrorModal: PropTypes.func.isRequired, unsavedChanges: PropTypes.bool.isRequired, warnIfUnsavedChanges: PropTypes.func, showKeyboardShortcutModal: PropTypes.func.isRequired, cmController: PropTypes.shape({ tidyCode: PropTypes.func, showFind: PropTypes.func, findNext: PropTypes.func, findPrev: PropTypes.func, getContent: PropTypes.func }), startSketch: PropTypes.func.isRequired, stopSketch: PropTypes.func.isRequired, setAllAccessibleOutput: PropTypes.func.isRequired, newFile: PropTypes.func.isRequired, newFolder: PropTypes.func.isRequired, layout: PropTypes.oneOf(['dashboard', 'project']), rootFile: PropTypes.shape({ id: PropTypes.string.isRequired }).isRequired, params: PropTypes.shape({ username: PropTypes.string }) }; Nav.defaultProps = { project: { id: undefined, owner: undefined }, cmController: {}, layout: 'project', warnIfUnsavedChanges: undefined, params: { username: undefined } }; function mapStateToProps(state) { return { project: state.project, user: state.user, unsavedChanges: state.ide.unsavedChanges, rootFile: state.files.filter(file => file.name === 'root')[0] }; } const mapDispatchToProps = { ...IDEActions, ...projectActions, ...toastActions, logoutUser, setAllAccessibleOutput }; export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Nav)); export { Nav as NavComponent };