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) {