mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -05:00
Add upload and download image buttons to the playground
This commit is contained in:
parent
87e01639c0
commit
2e88b6d070
4 changed files with 133 additions and 16 deletions
|
@ -96,9 +96,9 @@ SVGs of up to size 480 x 360 will fit into the view window of the paint editor,
|
|||
|
||||
`imageFormat`: 'svg', 'png', or 'jpg'. Other formats are currently not supported.
|
||||
|
||||
`rotationCenterX`: x coordinate relative to the top left corner of the sprite of the point that should be centered.
|
||||
`rotationCenterX`: x coordinate relative to the top left corner of the sprite of the point that should be centered. If left undefined, image will be horizontally centered.
|
||||
|
||||
`rotationCenterY`: y coordinate relative to the top left corner of the sprite of the point that should be centered.
|
||||
`rotationCenterY`: y coordinate relative to the top left corner of the sprite of the point that should be centered. If left undefined, image will be vertcally centered.
|
||||
|
||||
`rtl`: True if the paint editor should be laid out right to left (meant for right to left languages)
|
||||
|
||||
|
|
|
@ -148,6 +148,13 @@ class PaperCanvas extends React.Component {
|
|||
if (!this.queuedImageToLoad) return;
|
||||
this.queuedImageToLoad = null;
|
||||
|
||||
if (typeof rotationCenterX === 'undefined') {
|
||||
rotationCenterX = imgElement.width / 2;
|
||||
}
|
||||
if (typeof rotationCenterY === 'undefined') {
|
||||
rotationCenterY = imgElement.height / 2;
|
||||
}
|
||||
|
||||
getRaster().drawImage(
|
||||
imgElement,
|
||||
(ART_BOARD_WIDTH / 2) - rotationCenterX,
|
||||
|
|
|
@ -4,7 +4,7 @@ body {
|
|||
margin: 0px;
|
||||
}
|
||||
|
||||
body, html {
|
||||
body, html, .wrapper {
|
||||
height: 100%
|
||||
}
|
||||
|
||||
|
@ -12,4 +12,12 @@ body, html {
|
|||
height: 90%;
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
#fileInput {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.playgroundButton {
|
||||
margin: 4px;
|
||||
}
|
||||
|
|
|
@ -29,50 +29,152 @@ class Playground extends React.Component {
|
|||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'downloadImage',
|
||||
'handleUpdateName',
|
||||
'handleUpdateImage'
|
||||
'handleUpdateImage',
|
||||
'onUploadImage'
|
||||
]);
|
||||
// Append ?dir=rtl to URL to get RTL layout
|
||||
const match = location.search.match(/dir=([^&]+)/);
|
||||
const rtl = match && match[1] == 'rtl';
|
||||
this.id = 0;
|
||||
this.state = {
|
||||
name: 'meow',
|
||||
rotationCenterX: 20,
|
||||
rotationCenterY: 400,
|
||||
imageFormat: 'svg', // 'svg', 'png', or 'jpg'
|
||||
image: svgString, // svg string or data URI
|
||||
rtl: rtl
|
||||
imageId: this.id, // If this changes, the paint editor will reload
|
||||
rtl: rtl,
|
||||
};
|
||||
this.reusableCanvas = document.createElement('canvas');
|
||||
}
|
||||
handleUpdateName (name) {
|
||||
this.setState({name});
|
||||
}
|
||||
handleUpdateImage (isVector, image, rotationCenterX, rotationCenterY) {
|
||||
console.log(image);
|
||||
this.setState({
|
||||
imageFormat: isVector ? 'svg' : 'png'
|
||||
});
|
||||
if (!isVector) {
|
||||
console.log(`Image width: ${image.width} Image height: ${image.height}`);
|
||||
}
|
||||
console.log(`rotationCenterX: ${rotationCenterX} rotationCenterY: ${rotationCenterY}`);
|
||||
if (isVector) {
|
||||
this.setState({image, rotationCenterX, rotationCenterY});
|
||||
} else { // is Bitmap
|
||||
// image parameter has type ImageData
|
||||
// paint editor takes dataURI as input
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
this.reusableCanvas.width = image.width;
|
||||
this.reusableCanvas.height = image.height;
|
||||
const context = this.reusableCanvas.getContext('2d');
|
||||
context.putImageData(image, 0, 0);
|
||||
this.setState({
|
||||
image: canvas.toDataURL('image/png'),
|
||||
image: this.reusableCanvas.toDataURL('image/png'),
|
||||
rotationCenterX: rotationCenterX,
|
||||
rotationCenterY: rotationCenterY
|
||||
});
|
||||
}
|
||||
}
|
||||
downloadImage () {
|
||||
const downloadLink = document.createElement('a');
|
||||
document.body.appendChild(downloadLink);
|
||||
|
||||
const format = this.state.imageFormat;
|
||||
let data = this.state.image;
|
||||
if (format === 'png' || format === 'jpg') {
|
||||
data = this.b64toByteArray(data);
|
||||
} else {
|
||||
data = [data];
|
||||
}
|
||||
const blob = new Blob(data, {type: format});
|
||||
const filename = `${this.state.name}.${format}`;
|
||||
if ('download' in HTMLAnchorElement.prototype) {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
downloadLink.href = url;
|
||||
downloadLink.download = filename;
|
||||
downloadLink.type = blob.type;
|
||||
downloadLink.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
} else {
|
||||
// iOS Safari, open a new page and set href to data-uri
|
||||
let popup = window.open('', '_blank');
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = function () {
|
||||
popup.location.href = reader.result;
|
||||
popup = null;
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
document.body.removeChild(downloadLink);
|
||||
}
|
||||
b64toByteArray (b64Data, sliceSize=512) {
|
||||
// Remove header
|
||||
b64Data = b64Data.substring(b64Data.indexOf('base64,') + 7);
|
||||
|
||||
const byteCharacters = atob(b64Data);
|
||||
const byteArrays = [];
|
||||
|
||||
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
|
||||
const slice = byteCharacters.slice(offset, offset + sliceSize);
|
||||
|
||||
const byteNumbers = new Array(slice.length);
|
||||
for (let i = 0; i < slice.length; i++) {
|
||||
byteNumbers[i] = slice.charCodeAt(i);
|
||||
}
|
||||
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
byteArrays.push(byteArray);
|
||||
}
|
||||
|
||||
return byteArrays;
|
||||
}
|
||||
uploadImage() {
|
||||
document.getElementById(styles.fileInput).click();
|
||||
}
|
||||
onUploadImage(event) {
|
||||
var file = event.target.files[0];
|
||||
var type = file.type === 'image/svg+xml' ? 'svg' :
|
||||
file.type === 'image/png' ? 'png' :
|
||||
file.type === 'image/jpg' ? 'jpg' :
|
||||
file.type === 'image/jpeg' ? 'jpg' :
|
||||
null;
|
||||
|
||||
var reader = new FileReader();
|
||||
if (type === 'svg') {
|
||||
reader.readAsText(file,'UTF-8');
|
||||
} else if (type === 'png' || type === 'jpg'){
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
alert("Couldn't read file type: " + file.type);
|
||||
}
|
||||
|
||||
const that = this;
|
||||
reader.onload = readerEvent => {
|
||||
var content = readerEvent.target.result; // this is the content!
|
||||
|
||||
that.setState({
|
||||
image: content,
|
||||
name: file.name.split('.').slice(0, -1).join('.'),
|
||||
imageId: ++that.id,
|
||||
imageFormat: type,
|
||||
rotationCenterX: undefined,
|
||||
rotationCenterY: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<PaintEditor
|
||||
{...this.state}
|
||||
imageId="meow"
|
||||
onUpdateName={this.handleUpdateName}
|
||||
onUpdateImage={this.handleUpdateImage}
|
||||
/>
|
||||
<div className={styles.wrapper}>
|
||||
<PaintEditor
|
||||
{...this.state}
|
||||
onUpdateName={this.handleUpdateName}
|
||||
onUpdateImage={this.handleUpdateImage}
|
||||
/>
|
||||
<button className={styles.playgroundButton} onClick={this.uploadImage}>Upload</button>
|
||||
<input id={styles.fileInput} type="file" name="name" onChange={this.onUploadImage} />
|
||||
<button className={styles.playgroundButton} onClick={this.downloadImage}>Download</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue