diff --git a/packages/scratch-gui/src/components/gui/gui.jsx b/packages/scratch-gui/src/components/gui/gui.jsx index 5ca60fb42d..171ef06cca 100644 --- a/packages/scratch-gui/src/components/gui/gui.jsx +++ b/packages/scratch-gui/src/components/gui/gui.jsx @@ -101,6 +101,7 @@ const GUIComponent = props => { isTotallyNormal, loading, logo, + manuallySaveThumbnails, renderLogin, onClickAbout, onClickAccountNav, @@ -128,6 +129,7 @@ const GUIComponent = props => { onTelemetryModalCancel, onTelemetryModalOptIn, onTelemetryModalOptOut, + onUpdateProjectThumbnail, showComingSoon, soundsTabVisible, stageSizeMode, @@ -181,6 +183,11 @@ const GUIComponent = props => { isRendererSupported={isRendererSupported} isRtl={isRtl} loading={loading} + manuallySaveThumbnails={ + manuallySaveThumbnails && + userOwnsProject + } + onUpdateProjectThumbnail={onUpdateProjectThumbnail} stageSize={STAGE_SIZE_MODES.large} vm={vm} > @@ -457,6 +464,7 @@ GUIComponent.propTypes = { isTotallyNormal: PropTypes.bool, loading: PropTypes.bool, logo: PropTypes.string, + manuallySaveThumbnails: PropTypes.bool, onActivateCostumesTab: PropTypes.func, onActivateSoundsTab: PropTypes.func, onActivateTab: PropTypes.func, @@ -481,6 +489,7 @@ GUIComponent.propTypes = { onTelemetryModalOptIn: PropTypes.func, onTelemetryModalOptOut: PropTypes.func, onToggleLoginOpen: PropTypes.func, + onUpdateProjectThumbnail: PropTypes.func, platform: PropTypes.oneOf(Object.keys(PLATFORM)), renderLogin: PropTypes.func, showComingSoon: PropTypes.bool, diff --git a/packages/scratch-gui/src/components/stage-header/stage-header.css b/packages/scratch-gui/src/components/stage-header/stage-header.css index 07ac212138..3b82aae0d7 100644 --- a/packages/scratch-gui/src/components/stage-header/stage-header.css +++ b/packages/scratch-gui/src/components/stage-header/stage-header.css @@ -78,3 +78,23 @@ [dir="rtl"] .stage-button-icon { transform: scaleX(-1); } + +.rightSection { + display: flex; + flex-direction: row; + align-items: center; + gap: 0.5rem; + background-color: transparent; + + .setThumbnailButton { + padding: 0.625rem 0.75rem; + font-size: 0.75rem; + line-height: 0.875rem; + color: $ui-white; + background-color: $motion-primary; + } + + .setThumbnailButton:active { + filter: brightness(90%); + } +} diff --git a/packages/scratch-gui/src/components/stage-header/stage-header.jsx b/packages/scratch-gui/src/components/stage-header/stage-header.jsx index eb43a32389..9245203b88 100644 --- a/packages/scratch-gui/src/components/stage-header/stage-header.jsx +++ b/packages/scratch-gui/src/components/stage-header/stage-header.jsx @@ -1,6 +1,6 @@ -import {defineMessages, injectIntl, intlShape} from 'react-intl'; +import {FormattedMessage, defineMessages, injectIntl, intlShape} from 'react-intl'; import PropTypes from 'prop-types'; -import React from 'react'; +import React, {useCallback} from 'react'; import {connect} from 'react-redux'; import VM from '@scratch/scratch-vm'; @@ -18,6 +18,9 @@ import unFullScreenIcon from './icon--unfullscreen.svg'; import scratchLogo from '../menu-bar/scratch-logo.svg'; import styles from './stage-header.css'; +import {storeProjectThumbnail} from '../../lib/store-project-thumbnail.js'; +import dataURItoBlob from '../../lib/data-uri-to-blob.js'; +import throttle from 'lodash.throttle'; const messages = defineMessages({ largeStageSizeMessage: { @@ -40,6 +43,11 @@ const messages = defineMessages({ description: 'Button to get out of full screen mode', id: 'gui.stageHeader.stageSizeUnFull' }, + setThumbnail: { + defaultMessage: 'Set Thumbnail', + description: 'Manually save project thumbnail', + id: 'gui.stageHeader.saveThumbnail' + }, fullscreenControl: { defaultMessage: 'Full Screen Control', description: 'Button to enter/exit full screen mode', @@ -51,11 +59,14 @@ const StageHeaderComponent = function (props) { const { isFullScreen, isPlayerOnly, + manuallySaveThumbnails, onKeyPress, onSetStageLarge, onSetStageSmall, onSetStageFull, onSetStageUnFull, + onUpdateProjectThumbnail, + projectId, showBranding, stageSizeMode, vm @@ -63,6 +74,22 @@ const StageHeaderComponent = function (props) { let header = null; + const onUpdateThumbnail = useCallback( + throttle( + () => { + if (!onUpdateProjectThumbnail) { + return; + } + + storeProjectThumbnail(vm, dataURI => { + onUpdateProjectThumbnail(projectId, dataURItoBlob(dataURI)); + }); + }, + 3000 + ), + [projectId, onUpdateProjectThumbnail] + ); + if (isFullScreen) { const stageDimensions = getStageDimensions(null, true); const stageButton = showBranding ? ( @@ -138,7 +165,16 @@ const StageHeaderComponent = function (props) {
{stageControls} -
+
+ {manuallySaveThumbnails && ( + + )}