diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css
index a10b08b60..6118b7997 100644
--- a/src/components/gui/gui.css
+++ b/src/components/gui/gui.css
@@ -279,6 +279,20 @@ $fade-out-distance: 15px;
margin-top: 0;
}
+/* Sprite Selection Watermark */
+.watermark {
+ position: absolute;
+ top: 1.25rem;
+}
+
+[dir="ltr"] .watermark {
+ right: 1.25rem;
+}
+
+[dir="rtl"] .watermark {
+ left: 1.25rem;
+}
+
/* Menu */
.menu-bar-position {
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index d39e22833..b37dcbd79 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -20,6 +20,7 @@ import Box from '../box/box.jsx';
import MenuBar from '../menu-bar/menu-bar.jsx';
import CostumeLibrary from '../../containers/costume-library.jsx';
import BackdropLibrary from '../../containers/backdrop-library.jsx';
+import Watermark from '../../containers/watermark.jsx';
import Backpack from '../../containers/backpack.jsx';
import PreviewModal from '../../containers/preview-modal.jsx';
@@ -261,6 +262,9 @@ const GUIComponent = props => {
/>
+
+
+
{costumesTabVisible ? : null}
diff --git a/src/components/watermark/watermark.css b/src/components/watermark/watermark.css
new file mode 100644
index 000000000..f02e3242c
--- /dev/null
+++ b/src/components/watermark/watermark.css
@@ -0,0 +1,9 @@
+
+.sprite-image {
+ margin: auto;
+ user-select: none;
+ max-width: 48px;
+ max-height: 48px;
+ opacity: 0.35;
+ pointer-events: none;
+}
diff --git a/src/components/watermark/watermark.jsx b/src/components/watermark/watermark.jsx
new file mode 100644
index 000000000..d3f5b36e2
--- /dev/null
+++ b/src/components/watermark/watermark.jsx
@@ -0,0 +1,17 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import styles from './watermark.css';
+
+const Watermark = props => (
+
+);
+
+Watermark.propTypes = {
+ costumeURL: PropTypes.string
+};
+
+export default Watermark;
diff --git a/src/containers/sprite-selector-item.jsx b/src/containers/sprite-selector-item.jsx
index 9b154c59b..c33aa41e7 100644
--- a/src/containers/sprite-selector-item.jsx
+++ b/src/containers/sprite-selector-item.jsx
@@ -7,19 +7,17 @@ import {setHoveredSprite} from '../reducers/hovered-target';
import {updateAssetDrag} from '../reducers/asset-drag';
import {getEventXY} from '../lib/touch-utils';
import VM from 'scratch-vm';
-import {SVGRenderer} from 'scratch-svg-renderer';
+import getCostumeUrl from '../lib/get-costume-url';
import SpriteSelectorItemComponent from '../components/sprite-selector-item/sprite-selector-item.jsx';
const dragThreshold = 3; // Same as the block drag threshold
-// Contains 'font-family', but doesn't only contain 'font-family="none"'
-const HAS_FONT_REGEXP = 'font-family(?!="none")';
class SpriteSelectorItem extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
- 'getCostumeUrl',
+ 'getCostumeData',
'handleClick',
'handleDelete',
'handleDuplicate',
@@ -30,8 +28,8 @@ class SpriteSelectorItem extends React.Component {
'handleMouseMove',
'handleMouseUp'
]);
- this.svgRenderer = new SVGRenderer();
- // Asset ID of the SVG currently in SVGRenderer
+
+ // Asset ID of the current decoded costume
this.decodedAssetId = null;
}
shouldComponentUpdate (nextProps) {
@@ -44,32 +42,19 @@ class SpriteSelectorItem extends React.Component {
}
return false;
}
- getCostumeUrl () {
+ getCostumeData () {
if (this.props.costumeURL) return this.props.costumeURL;
if (!this.props.assetId) return null;
- const storage = this.props.vm.runtime.storage;
- const asset = storage.get(this.props.assetId);
- // If the SVG refers to fonts, they must be inlined in order to display correctly in the img tag.
- // Avoid parsing the SVG when possible, since it's expensive.
- if (asset.assetType === storage.AssetType.ImageVector) {
- // If the asset ID has not changed, no need to re-parse
- if (this.decodedAssetId === this.props.assetId) {
- // @todo consider caching more than one URL.
- return this.cachedUrl;
- }
- this.decodedAssetId = this.props.assetId;
- const svgString = this.props.vm.runtime.storage.get(this.props.assetId).decodeText();
- if (svgString.match(HAS_FONT_REGEXP)) {
- this.svgRenderer.loadString(svgString);
- const svgText = this.svgRenderer.toString(true /* shouldInjectFonts */);
- this.cachedUrl = `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`;
- } else {
- this.cachedUrl = this.props.vm.runtime.storage.get(this.props.assetId).encodeDataURI();
- }
+ if (this.decodedAssetId === this.props.assetId) {
+ // @todo consider caching more than one URL.
return this.cachedUrl;
}
- return this.props.vm.runtime.storage.get(this.props.assetId).encodeDataURI();
+
+ this.decodedAssetId = this.props.assetId;
+ this.cachedUrl = getCostumeUrl(this.props.assetId, this.props.vm);
+
+ return this.cachedUrl;
}
handleMouseUp () {
this.initialOffset = null;
@@ -155,7 +140,7 @@ class SpriteSelectorItem extends React.Component {
} = this.props;
return (
+ );
+ }
+}
+
+Watermark.propTypes = {
+ assetId: PropTypes.string,
+ vm: PropTypes.instanceOf(VM).isRequired
+};
+
+const mapStateToProps = state => {
+ const targets = state.scratchGui.targets;
+ const currentTargetId = targets.editingTarget;
+
+ let assetId;
+ if (currentTargetId) {
+ if (targets.stage.id === currentTargetId) {
+ assetId = targets.stage.costume.assetId;
+ } else if (targets.sprites.hasOwnProperty(currentTargetId)) {
+ const currentSprite = targets.sprites[currentTargetId];
+ assetId = currentSprite.costume.assetId;
+ }
+ }
+
+ return {
+ vm: state.scratchGui.vm,
+ assetId: assetId
+ };
+};
+
+const mapDispatchToProps = () => ({});
+
+const ConnectedComponent = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(Watermark);
+
+export default ConnectedComponent;
diff --git a/src/lib/get-costume-url.js b/src/lib/get-costume-url.js
new file mode 100644
index 000000000..b4d779d04
--- /dev/null
+++ b/src/lib/get-costume-url.js
@@ -0,0 +1,28 @@
+import {SVGRenderer} from 'scratch-svg-renderer';
+
+// Contains 'font-family', but doesn't only contain 'font-family="none"'
+const HAS_FONT_REGEXP = 'font-family(?!="none")';
+
+const getCostumeUrl = function (assetId, vm) {
+
+ const storage = vm.runtime.storage;
+ const asset = storage.get(assetId);
+ // If the SVG refers to fonts, they must be inlined in order to display correctly in the img tag.
+ // Avoid parsing the SVG when possible, since it's expensive.
+ if (asset.assetType === storage.AssetType.ImageVector) {
+ // If the asset ID has not changed, no need to re-parse
+
+ const svgRenderer = new SVGRenderer();
+
+ const svgString = vm.runtime.storage.get(assetId).decodeText();
+ if (svgString.match(HAS_FONT_REGEXP)) {
+ svgRenderer.loadString(svgString);
+ const svgText = svgRenderer.toString(true /* shouldInjectFonts */);
+ return `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`;
+ }
+ return vm.runtime.storage.get(assetId).encodeDataURI();
+ }
+ return vm.runtime.storage.get(assetId).encodeDataURI();
+};
+
+export default getCostumeUrl;