Merge pull request #3599 from paulkaplan/pictures-of-blocks

Initial "pictures of blocks" implementation for the backpack
This commit is contained in:
Paul Kaplan authored and GitHub committed 2018-11-01 15:48:59 -04:00
commit a5aea97f27
5 files changed
+82 -19

No files matched your search

+1
View File
@@ -45,6 +45,7 @@
"bowser": "1.9.4",
"chromedriver": "2.43.1",
"classnames": "2.2.6",
"computed-style-to-inline-style": "3.0.0",
"copy-webpack-plugin": "^4.5.1",
"core-js": "2.5.7",
"css-loader": "^1.0.0",
+16 -10
View File
@@ -98,14 +98,17 @@ class Backpack extends React.Component {
}
if (!payloader) return;
payloader(dragInfo.payload, this.props.vm)
.then(payload => saveBackpackObject({
host: this.props.host,
token: this.props.token,
username: this.props.username,
...payload
}))
.then(this.refreshContents);
// Creating the payload is async, so set loading before starting
this.setState({loading: true}, () => {
payloader(dragInfo.payload, this.props.vm)
.then(payload => saveBackpackObject({
host: this.props.host,
token: this.props.token,
username: this.props.username,
...payload
}))
.then(this.refreshContents);
});
}
handleDelete (id) {
deleteBackpackObject({
@@ -150,11 +153,14 @@ class Backpack extends React.Component {
blockDragOverBackpack: false
});
}
handleBlockDragEnd (blocks) {
handleBlockDragEnd (blocks, topBlockId) {
if (this.state.blockDragOverBackpack) {
this.handleDrop({
dragType: DragConstants.CODE,
payload: blocks
payload: {
blockObjects: blocks,
topBlockId: topBlockId
}
});
}
this.setState({
+55
View File
@@ -0,0 +1,55 @@
import computedStyleToInlineStyle from 'computed-style-to-inline-style';
import ScratchBlocks from 'scratch-blocks';
/**
* Given a blockId, return a data-uri image that can be used to create a thumbnail.
* @param {string} blockId the ID of the block to imagify
* @return {Promise} resolves to a data-url of a picture of the blocks
*/
export default function (blockId) {
// Not sure any better way to access the scratch-blocks workspace than this...
const block = ScratchBlocks.getMainWorkspace().getBlockById(blockId);
const blockSvg = block.getSvgRoot().cloneNode(true /* deep */);
// Once we have the cloned SVG, do the rest in a setTimeout to prevent
// blocking the drag end from finishing promptly.
return new Promise(resolve => {
setTimeout(() => {
// Create an <svg> element to put the cloned blockSvg inside
const NS = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(NS, 'svg');
svg.appendChild(blockSvg);
// Needs to be on the DOM to get CSS properties and correct sizing
document.body.appendChild(svg);
const padding = 10;
const extraHatPadding = 16;
const topPadding = padding + (blockSvg.getAttribute('data-shapes') === 'hat' ? extraHatPadding : 0);
const leftPadding = padding;
blockSvg.setAttribute('transform', `translate(${leftPadding} ${topPadding})`);
const bounds = blockSvg.getBoundingClientRect();
svg.setAttribute('width', bounds.width + (2 * padding));
svg.setAttribute('height', bounds.height + (2 * padding));
// We need to inline the styles set by CSS rules because
// not all the styles are set directly on the SVG. This makes the
// image styled the same way the block actually appears.
// TODO this doesn't handle images that are xlink:href in the SVG
computedStyleToInlineStyle(svg, {
recursive: true,
// Enumerate the specific properties we need to inline.
// Specifically properties that are set from CSS in scratch-blocks
properties: ['fill', 'font-family', 'font-size', 'font-weight']
});
const svgString = (new XMLSerializer()).serializeToString(svg);
// Once we have the svg as a string, remove it from the DOM
svg.parentNode.removeChild(svg);
resolve(`data:image/svg+xml;utf-8,${encodeURIComponent(svgString)}`);
}, 10);
});
}
+10 -6
View File
@@ -1,16 +1,20 @@
import codeThumbnail from './code-thumbnail';
import blockToImage from './block-to-image';
import jpegThumbnail from './jpeg-thumbnail';
const codePayload = code => {
const codePayload = ({blockObjects, topBlockId}) => {
const payload = {
type: 'script', // Needs to match backpack-server type name
name: 'code', // All code currently gets the same name
mime: 'application/json',
body: btoa(JSON.stringify(code)), // Base64 encode the json
thumbnail: codeThumbnail // TODO make code thumbnail dynamic
body: btoa(JSON.stringify(blockObjects)) // Base64 encode the json
};
// Return a promise to make it consistent with other payload constructors like costume-payload
return new Promise(resolve => resolve(payload));
return blockToImage(topBlockId)
.then(jpegThumbnail)
.then(thumbnail => {
payload.thumbnail = thumbnail.replace('data:image/jpeg;base64,', '');
return payload;
});
};
export default codePayload;
-3
View File
@@ -1,3 +0,0 @@
// image/jpeg base64 encoded code thumbnail image
// eslint-disable-next-line max-len
export default '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 Line truncated