/* eslint-disable react/no-unused-prop-types */ /* eslint-disable no-unused-vars */ import classNames from 'classnames'; import {connect} from 'react-redux'; import {compose} from 'redux'; import {defineMessages, FormattedMessage, injectIntl, intlShape} from 'react-intl'; import PropTypes from 'prop-types'; import bindAll from 'lodash.bindall'; import bowser from 'bowser'; import React from 'react'; import VM from 'scratch-vm'; import Box from '../box/box.jsx'; import Button from '../button/button.jsx'; import CommunityButton from './community-button.jsx'; import ShareButton from './share-button.jsx'; import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx'; import Divider from '../divider/divider.jsx'; // import SaveStatus from './save-status.jsx'; import ProjectWatcher from '../../containers/project-watcher.jsx'; import MenuBarMenu from './menu-bar-menu.jsx'; import MenuLabel from './tw-menu-label.jsx'; import {MenuItem, MenuSection} from '../menu/menu.jsx'; import ProjectTitleInput from './project-title-input.jsx'; import AuthorInfo from './author-info.jsx'; import SB3Downloader from '../../containers/sb3-downloader.jsx'; import DeletionRestorer from '../../containers/deletion-restorer.jsx'; import TurboMode from '../../containers/turbo-mode.jsx'; import MenuBarHOC from '../../containers/menu-bar-hoc.jsx'; import SettingsMenu from './settings-menu.jsx'; import FramerateChanger from '../../containers/tw-framerate-changer.jsx'; import ChangeUsername from '../../containers/tw-change-username.jsx'; import CloudVariablesToggler from '../../containers/tw-cloud-toggler.jsx'; import TWSaveStatus from './tw-save-status.jsx'; import {FEEDBACK_URL} from '../../lib/brand.js'; import {openTipsLibrary, openSettingsModal, openRestorePointModal} from '../../reducers/modals'; import {setPlayer} from '../../reducers/mode'; import { isTimeTravel220022BC, isTimeTravel1920, isTimeTravel1990, isTimeTravel2020, isTimeTravelNow, setTimeTravel } from '../../reducers/time-travel'; import { autoUpdateProject, getIsUpdating, getIsShowingProject, manualUpdateProject, requestNewProject, remixProject, saveProjectAsCopy } from '../../reducers/project-state'; import { openAboutMenu, closeAboutMenu, aboutMenuOpen, openAccountMenu, closeAccountMenu, accountMenuOpen, openFileMenu, closeFileMenu, fileMenuOpen, openEditMenu, closeEditMenu, editMenuOpen, openLoginMenu, closeLoginMenu, loginMenuOpen, openModeMenu, closeModeMenu, modeMenuOpen, settingsMenuOpen, openSettingsMenu, closeSettingsMenu, errorsMenuOpen, openErrorsMenu, closeErrorsMenu } from '../../reducers/menus'; import {setFileHandle} from '../../reducers/tw.js'; import { setAutosaveEnabled, setAutosaveInterval, setAutosaveNotifications } from '../../reducers/autosave.js'; import collectMetadata from '../../lib/collect-metadata'; import AutosaveService from '../../lib/autosave-service.js'; import SettingsStore from '../../addons/settings-store-singleton.js'; import styles from './menu-bar.css'; // import helpIcon from '../../lib/assets/icon--tutorials.svg'; // import mystuffIcon from './icon--mystuff.png'; // import profileIcon from './icon--profile.png'; import remixIcon from './icon--remix.svg'; import dropdownCaret from './dropdown-caret.svg'; import aboutIcon from './icon--about.svg'; import fileIcon from './icon--file.svg'; import editIcon from './icon--edit.svg'; import errorIcon from './tw-error.svg'; import advancedIcon from './tw-advanced.svg'; import ninetiesLogo from './nineties_logo.svg'; import catLogo from './cat_logo.svg'; import prehistoricLogo from './prehistoric-logo.svg'; import oldtimeyLogo from './oldtimey-logo.svg'; import sharedMessages from '../../lib/shared-messages'; import SeeInsideButton from './tw-see-inside.jsx'; import {notScratchDesktop} from '../../lib/isScratchDesktop.js'; import {APP_NAME} from '../../lib/brand.js'; /* const ariaMessages = defineMessages({ tutorials: { id: 'gui.menuBar.tutorialsLibrary', defaultMessage: 'Tutorials', description: 'accessibility text for the tutorials button' } }); */ const twMessages = defineMessages({ compileError: { id: 'tw.menuBar.compileError', defaultMessage: '{sprite}: {error}', description: 'Error message in error menu' } }); const MenuBarItemTooltip = ({ children, className, enable, id, place = 'bottom' }) => { if (enable) { return ( {children} ); } return ( {children} ); }; MenuBarItemTooltip.propTypes = { children: PropTypes.node, className: PropTypes.string, enable: PropTypes.bool, id: PropTypes.string, place: PropTypes.oneOf(['top', 'bottom', 'left', 'right']) }; const MenuItemTooltip = ({id, isRtl, children, className}) => ( {children} ); MenuItemTooltip.propTypes = { children: PropTypes.node, className: PropTypes.string, id: PropTypes.string, isRtl: PropTypes.bool }; const AboutButton = props => ( ); // Show the About button only if we have a handler for it (like in the desktop app) const aboutButton = this.buildAboutMenu(this.props.onClickAbout); return (
{this.props.errors.length > 0 &&
{this.props.errors.map(({id, sprite, error}) => ( {this.props.intl.formatMessage(twMessages.compileError, { sprite, error })} ))}
} {(this.props.canManageFiles) && ( {newProjectMessage} {this.props.onClickNewWindow && ( )} {(this.props.canSave || this.props.canCreateCopy || this.props.canRemix) && ( {this.props.canSave && ( {saveNowMessage} )} {this.props.canCreateCopy && ( {createCopyMessage} )} {this.props.canRemix && ( {remixMessage} )} )} {this.props.intl.formatMessage(sharedMessages.loadFromComputerTitle)} {(_className, downloadProject, extended) => ( {extended.available && ( {extended.name !== null && ( // eslint-disable-next-line max-len )} {/* eslint-disable-next-line max-len */} )} {notScratchDesktop() && ( {extended.available ? ( ) : ( )} )} )} {this.props.onClickPackager && ( )} {this.getAutosaveEnabled() && ( {this.state.autosavePaused ? '⏸' : '✓'} {' '} {this.state.autosavePaused ? ( ) : ( )} {this.getAutosaveTimeRemaining() > 0 && ( ({this.formatTimeRemaining(this.getAutosaveTimeRemaining())}) {this.state.autosavePaused && ' ⏸'} )} )} )} {this.props.isPlayerOnly ? null : ( {(handleRestore, {restorable, deletedItem}) => ( {this.restoreOptionMessage(deletedItem)} )} )} {(toggleTurboMode, {turboMode}) => ( {turboMode ? ( ) : ( )} )} {(changeFramerate, {framerate}) => ( {framerate === 60 ? ( ) : ( )} )} {changeUsername => ( )} {(toggleCloudVariables, {enabled, canUseCloudVariables}) => ( {canUseCloudVariables ? ( enabled ? ( ) : ( ) ) : ( )} )} {this.props.onClickAddonSettings && ( )} {this.props.isTotallyNormal && ( {'✓'} {' '} {'✓'} {' '} )} {(this.props.canChangeTheme || this.props.canChangeLanguage) && ()}
{this.props.canEditTitle ? (
) : ((this.props.authorUsername && this.props.authorUsername !== this.props.username) ? ( ) : null)} {this.props.canShare ? ( (this.props.isShowingProject || this.props.isUpdating) && (
{ waitForUpdate => ( { this.handleClickShare(waitForUpdate); }} /* eslint-enable react/jsx-no-bind */ /> ) }
) ) : this.props.showComingSoon ? (
) : null} {this.props.canRemix && (
{remixButton}
)}
{this.props.enableCommunity ? ( (this.props.isShowingProject || this.props.isUpdating) && ( { waitForUpdate => ( { this.handleClickSeeCommunity(waitForUpdate); }} /* eslint-enable react/jsx-no-bind */ /> ) } ) ) : (this.props.showComingSoon ? ( ) : (this.props.enableSeeInside ? ( ) : []))}
{/* tw: add a feedback button */}
{/* todo: icon */}
{aboutButton}
); } } MenuBar.propTypes = { enableSeeInside: PropTypes.bool, onClickSeeInside: PropTypes.func, aboutMenuOpen: PropTypes.bool, accountMenuOpen: PropTypes.bool, authorId: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), authorThumbnailUrl: PropTypes.string, authorUsername: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), autoUpdateProject: PropTypes.func, autosaveEnabled: PropTypes.bool, autosaveInterval: PropTypes.number, canChangeLanguage: PropTypes.bool, canChangeTheme: PropTypes.bool, canCreateCopy: PropTypes.bool, canCreateNew: PropTypes.bool, canEditTitle: PropTypes.bool, canManageFiles: PropTypes.bool, canRemix: PropTypes.bool, canSave: PropTypes.bool, canShare: PropTypes.bool, className: PropTypes.string, logo: PropTypes.string, errors: PropTypes.arrayOf(PropTypes.shape({ sprite: PropTypes.string, error: PropTypes.string, id: PropTypes.number })), errorsMenuOpen: PropTypes.bool, onClickErrors: PropTypes.func, onRequestCloseErrors: PropTypes.func, confirmReadyToReplaceProject: PropTypes.func, currentLocale: PropTypes.string.isRequired, editMenuOpen: PropTypes.bool, enableCommunity: PropTypes.bool, fileMenuOpen: PropTypes.bool, handleSaveProject: PropTypes.func, intl: intlShape, isPlayerOnly: PropTypes.bool, isRtl: PropTypes.bool, isShared: PropTypes.bool, isShowingProject: PropTypes.bool, isTotallyNormal: PropTypes.bool, isUpdating: PropTypes.bool, locale: PropTypes.string.isRequired, loginMenuOpen: PropTypes.bool, mode1920: PropTypes.bool, mode1990: PropTypes.bool, mode2020: PropTypes.bool, mode220022BC: PropTypes.bool, modeMenuOpen: PropTypes.bool, modeNow: PropTypes.bool, onClickAbout: PropTypes.oneOfType([ PropTypes.func, // button mode: call this callback when the About button is clicked PropTypes.arrayOf( // menu mode: list of items in the About menu PropTypes.shape({ title: PropTypes.string, // text for the menu item onClick: PropTypes.func // call this callback when the menu item is clicked }) ) ]), onClickAccount: PropTypes.func, onClickAddonSettings: PropTypes.func, onClickDesktopSettings: PropTypes.func, onClickPackager: PropTypes.func, onClickRestorePoints: PropTypes.func, onClickEdit: PropTypes.func, onClickFile: PropTypes.func, onClickLogin: PropTypes.func, onClickMode: PropTypes.func, onClickNew: PropTypes.func, onClickNewWindow: PropTypes.func, onClickRemix: PropTypes.func, onClickSave: PropTypes.func, onClickSaveAsCopy: PropTypes.func, onClickSettings: PropTypes.func, onClickSettingsModal: PropTypes.func, onOpenSettingsModal: PropTypes.func, onLogOut: PropTypes.func, onOpenExtensionLibrary: PropTypes.func, onOpenRegistration: PropTypes.func, onOpenTipLibrary: PropTypes.func, onProjectTelemetryEvent: PropTypes.func, onRequestCloseAbout: PropTypes.func, onRequestCloseAccount: PropTypes.func, onRequestCloseEdit: PropTypes.func, onRequestCloseFile: PropTypes.func, onRequestCloseLogin: PropTypes.func, onRequestCloseMode: PropTypes.func, onRequestCloseSettings: PropTypes.func, onRequestOpenAbout: PropTypes.func, onSeeCommunity: PropTypes.func, onSetAutosaveEnabled: PropTypes.func, onSetAutosaveInterval: PropTypes.func, onSetAutosaveNotifications: PropTypes.func, onSetTimeTravelMode: PropTypes.func, onShare: PropTypes.func, onStartSelectingFileUpload: PropTypes.func, onToggleLoginOpen: PropTypes.func, projectId: PropTypes.string, projectTitle: PropTypes.string, renderLogin: PropTypes.func, sessionExists: PropTypes.bool, settingsMenuOpen: PropTypes.bool, shouldSaveBeforeTransition: PropTypes.func, showSaveFilePicker: PropTypes.func, showComingSoon: PropTypes.bool, theme: PropTypes.shape({ menuBarAlign: PropTypes.string }), username: PropTypes.string, userOwnsProject: PropTypes.bool, vm: PropTypes.instanceOf(VM).isRequired }; MenuBar.contextTypes = { store: PropTypes.object }; MenuBar.defaultProps = { onShare: () => {} }; const mapStateToProps = (state, ownProps) => { const loadingState = state.scratchGui.projectState.loadingState; const user = state.session && state.session.session && state.session.session.user; return { authorUsername: state.scratchGui.tw.author.username, authorThumbnailUrl: state.scratchGui.tw.author.thumbnail, projectId: state.scratchGui.projectState.projectId, aboutMenuOpen: aboutMenuOpen(state), accountMenuOpen: accountMenuOpen(state), autosaveEnabled: state.scratchGui.autosave.enabled, autosaveInterval: state.scratchGui.autosave.interval, currentLocale: state.locales.locale, fileMenuOpen: fileMenuOpen(state), editMenuOpen: editMenuOpen(state), errors: state.scratchGui.tw.compileErrors, errorsMenuOpen: errorsMenuOpen(state), isPlayerOnly: state.scratchGui.mode.isPlayerOnly, isRtl: state.locales.isRtl, isUpdating: getIsUpdating(loadingState), isShowingProject: getIsShowingProject(loadingState), locale: state.locales.locale, loginMenuOpen: loginMenuOpen(state), modeMenuOpen: modeMenuOpen(state), projectTitle: state.scratchGui.projectTitle, sessionExists: state.session && typeof state.session.session !== 'undefined', settingsMenuOpen: settingsMenuOpen(state), theme: state.scratchGui.theme.theme, username: user ? user.username : null, userOwnsProject: ownProps.authorUsername && user && (ownProps.authorUsername === user.username), vm: state.scratchGui.vm, mode220022BC: isTimeTravel220022BC(state), mode1920: isTimeTravel1920(state), mode1990: isTimeTravel1990(state), mode2020: isTimeTravel2020(state), modeNow: isTimeTravelNow(state) }; }; const mapDispatchToProps = dispatch => ({ onClickSeeInside: () => dispatch(setPlayer(false)), autoUpdateProject: () => dispatch(autoUpdateProject()), onOpenTipLibrary: () => dispatch(openTipsLibrary()), onClickAccount: () => dispatch(openAccountMenu()), onRequestCloseAccount: () => dispatch(closeAccountMenu()), onClickFile: () => dispatch(openFileMenu()), onRequestCloseFile: () => dispatch(closeFileMenu()), onClickEdit: () => dispatch(openEditMenu()), onRequestCloseEdit: () => dispatch(closeEditMenu()), onClickErrors: () => dispatch(openErrorsMenu()), onRequestCloseErrors: () => dispatch(closeErrorsMenu()), onClickLogin: () => dispatch(openLoginMenu()), onRequestCloseLogin: () => dispatch(closeLoginMenu()), onClickMode: () => dispatch(openModeMenu()), onRequestCloseMode: () => dispatch(closeModeMenu()), onRequestOpenAbout: () => dispatch(openAboutMenu()), onRequestCloseAbout: () => dispatch(closeAboutMenu()), onClickRestorePoints: () => dispatch(openRestorePointModal()), onClickSettings: () => dispatch(openSettingsMenu()), onClickSettingsModal: () => { dispatch(closeEditMenu()); dispatch(openSettingsModal()); }, onOpenSettingsModal: () => dispatch(openSettingsModal()), onRequestCloseSettings: () => dispatch(closeSettingsMenu()), onClickNew: needSave => { dispatch(requestNewProject(needSave)); dispatch(setFileHandle(null)); }, onClickRemix: () => dispatch(remixProject()), onClickSave: () => dispatch(manualUpdateProject()), onClickSaveAsCopy: () => dispatch(saveProjectAsCopy()), onSeeCommunity: () => dispatch(setPlayer(true)), onSetAutosaveEnabled: enabled => dispatch(setAutosaveEnabled(enabled)), onSetAutosaveInterval: interval => dispatch(setAutosaveInterval(interval)), onSetAutosaveNotifications: showNotifications => dispatch(setAutosaveNotifications(showNotifications)), onSetTimeTravelMode: mode => dispatch(setTimeTravel(mode)) }); export default compose( injectIntl, MenuBarHOC, connect( mapStateToProps, mapDispatchToProps ) )(MenuBar);