Skip to content
Snippets Groups Projects
Unverified Commit 8bfaf69d authored by DD Liu's avatar DD Liu Committed by GitHub
Browse files

Inject fonts thumbnails (#2948)

parent 549269db
No related branches found
No related tags found
No related merge requests found
......@@ -6,15 +6,20 @@ import {connect} from 'react-redux';
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 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',
'handleClick',
'handleDelete',
'handleDuplicate',
......@@ -25,6 +30,34 @@ class SpriteSelectorItem extends React.Component {
'handleMouseMove',
'handleMouseUp'
]);
this.svgRenderer = new SVGRenderer();
// Asset ID of the SVG currently in SVGRenderer
this.svgRendererAssetId = null;
}
getCostumeUrl () {
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.svgRendererAssetId === this.props.assetId) {
return this.cachedUrl;
}
const svgString = this.props.vm.runtime.storage.get(this.props.assetId).decodeText();
if (svgString.match(HAS_FONT_REGEXP)) {
this.svgRendererAssetId = this.props.assetId;
this.svgRenderer.loadString(svgString);
const svgText = this.svgRenderer.toString(true /* shouldInjectFonts */);
this.cachedUrl = `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`;
return this.cachedUrl;
}
}
return this.props.vm.runtime.storage.get(this.props.assetId).encodeDataURI();
}
handleMouseUp () {
this.initialOffset = null;
......@@ -49,7 +82,7 @@ class SpriteSelectorItem extends React.Component {
const dy = currentOffset.y - this.initialOffset.y;
if (Math.sqrt((dx * dx) + (dy * dy)) > dragThreshold) {
this.props.onDrag({
img: this.props.costumeURL,
img: this.getCostumeUrl(),
currentOffset: currentOffset,
dragging: true,
dragType: this.props.dragType,
......@@ -103,11 +136,14 @@ class SpriteSelectorItem extends React.Component {
onExportButtonClick,
dragPayload,
receivedBlocks,
costumeURL,
vm,
/* eslint-enable no-unused-vars */
...props
} = this.props;
return (
<SpriteSelectorItemComponent
costumeURL={this.getCostumeUrl()}
onClick={this.handleClick}
onDeleteButtonClick={onDeleteButtonClick ? this.handleDelete : null}
onDuplicateButtonClick={onDuplicateButtonClick ? this.handleDuplicate : null}
......@@ -139,14 +175,15 @@ SpriteSelectorItem.propTypes = {
onDuplicateButtonClick: PropTypes.func,
onExportButtonClick: PropTypes.func,
receivedBlocks: PropTypes.bool.isRequired,
selected: PropTypes.bool
selected: PropTypes.bool,
vm: PropTypes.instanceOf(VM).isRequired
};
const mapStateToProps = (state, {assetId, costumeURL, id}) => ({
costumeURL: costumeURL || (assetId && state.scratchGui.vm.runtime.storage.get(assetId).encodeDataURI()),
const mapStateToProps = (state, {id}) => ({
dragging: state.scratchGui.assetDrag.dragging,
receivedBlocks: state.scratchGui.hoveredTarget.receivedBlocks &&
state.scratchGui.hoveredTarget.sprite === id
state.scratchGui.hoveredTarget.sprite === id,
vm: state.scratchGui.vm
});
const mapDispatchToProps = dispatch => ({
dispatchSetHoveredSprite: spriteId => {
......@@ -155,8 +192,12 @@ const mapDispatchToProps = dispatch => ({
onDrag: data => dispatch(updateAssetDrag(data))
});
export default connect(
const ConnectedComponent = connect(
mapStateToProps,
mapDispatchToProps
)(SpriteSelectorItem);
export {
ConnectedComponent as default,
HAS_FONT_REGEXP // Exposed for testing
};
......@@ -4,6 +4,7 @@ import configureStore from 'redux-mock-store';
import {Provider} from 'react-redux';
import SpriteSelectorItem from '../../../src/containers/sprite-selector-item';
import {HAS_FONT_REGEXP} from '../../../src/containers/sprite-selector-item';
import CloseButton from '../../../src/components/close-button/close-button';
describe('SpriteSelectorItem Container', () => {
......@@ -55,4 +56,12 @@ describe('SpriteSelectorItem Container', () => {
wrapper.find(CloseButton).simulate('click');
expect(onDeleteButtonClick).toHaveBeenCalledWith(1337);
});
test('Has font regexp works', () => {
expect('font-family="Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy();
expect('font-family="none" font-family="Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy();
expect('font-family = "Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy();
expect('font-family="none"'.match(HAS_FONT_REGEXP)).toBeFalsy();
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment