diff --git a/src/lib/backpack/costume-payload.js b/src/lib/backpack/costume-payload.js new file mode 100644 index 000000000..a376c1111 --- /dev/null +++ b/src/lib/backpack/costume-payload.js @@ -0,0 +1,33 @@ +import jpegThumbnail from './jpeg-thumbnail'; +import storage from '../storage'; + +const costumePayload = costume => { + // TODO is it ok to base64 encode SVGs? What about unicode text inside them? + const assetDataUrl = storage.get(costume.assetId).encodeDataURI(); + const assetDataFormat = costume.dataFormat; + const payload = { + type: 'costume', + name: costume.name, + // Params to be filled in below + mime: '', + body: '', + thumbnail: '' + }; + + switch (assetDataFormat) { + case 'svg': + payload.mime = 'image/svg+xml'; + payload.body = assetDataUrl.replace('data:image/svg+xml;base64,', ''); + break; + default: + alert(`Cannot serialize for format: ${assetDataFormat}`); // eslint-disable-line + } + + // TODO we may be able to optimize by not actually generating thumbnails... + return jpegThumbnail(assetDataUrl).then(thumbnail => { + payload.thumbnail = thumbnail.replace('data:image/jpeg;base64,', ''); + return payload; + }); +}; + +export default costumePayload; diff --git a/src/lib/backpack/jpeg-thumbnail.js b/src/lib/backpack/jpeg-thumbnail.js new file mode 100644 index 000000000..623daf3be --- /dev/null +++ b/src/lib/backpack/jpeg-thumbnail.js @@ -0,0 +1,20 @@ +const jpegThumbnail = dataUrl => new Promise(resolve => { + const image = new Image(); + image.onload = () => { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + // TODO we may want to draw to a smaller, fixed size to optimize file size + canvas.width = image.width; + canvas.height = image.height; + + ctx.fillStyle = 'white'; // Create white background, since jpeg doesn't have transparency + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.drawImage(image, 0, 0); + // TODO we can play with the `quality` option here to optimize file size + resolve(canvas.toDataURL('image/jpeg', 0.92 /* quality */)); // Default quality is 0.92 + }; + image.src = dataUrl; +}); + +export default jpegThumbnail; diff --git a/src/lib/backpack/sound-payload.js b/src/lib/backpack/sound-payload.js new file mode 100644 index 000000000..e5e1c992a --- /dev/null +++ b/src/lib/backpack/sound-payload.js @@ -0,0 +1,31 @@ +import soundThumbnail from './sound-thumbnail'; +import storage from '../storage'; + +const soundPayload = sound => { + const assetDataUrl = storage.get(sound.assetId).encodeDataURI(); + const assetDataFormat = sound.dataFormat; + const payload = { + type: 'sound', + name: sound.name, + // Params to be filled in below + mime: '', + body: '', + thumbnail: '' + }; + + switch (assetDataFormat) { + case 'wav': + payload.mime = 'audio/x-wav'; + payload.body = assetDataUrl.replace('data:audio/x-wav;base64,', ''); + break; + default: + alert(`Cannot serialize for format: ${assetDataFormat}`); // eslint-disable-line + } + + payload.thumbnail = soundThumbnail; + + // Return a promise to make it consistent with other payload constructors like costume-payload + return new Promise(resolve => resolve(payload)); +}; + +export default soundPayload; diff --git a/src/lib/backpack/sound-thumbnail.js b/src/lib/backpack/sound-thumbnail.js new file mode 100644 index 000000000..f965cc8e1 --- /dev/null +++ b/src/lib/backpack/sound-thumbnail.js @@ -0,0 +1,3 @@ +// image/jpeg base64 encoded sound thumbnail image +// eslint-disable-next-line max-len +export default '/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAPAAA/+4AJkFkb2JlAGTAAAAAAQMAFQQDBgoNAAACZgAAAwoAAAPwAAAE/P/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoKDBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8IAEQgAQABAAwERAAIRAQMRAf/EALAAAQADAQEBAAAAAAAAAAAAAAAEBQcGAgMBAQEBAQAAAAAAAAAAAAAAAAABAgMQAAEEAgICAwAAAAAAAAAAAAMBAgQFIBEQQDASYCQGEQACAQICBggHAAAAAAAAAAABAgMAESFBEDFRcRIEIDBhoSIyQhNAgZGxwWJTEgEAAAAAAAAAAAAAAAAAAABgEwEAAQIGAgIDAQAAAAAAAAABEQAhECAxQVFhQIEwcZGhsfD/2gAMAwEAAhEDEQAAAdUAAAAAIVk2UACFZz2pwvSa1x19ordLrNGVdsVNDvedjlXppXLQy7ril0He87GKzTSuWgKazOOudU5a9RWaXuaAAAAAAAB//9oACAEBAAEFAvGWbFE/E82KBZt8JGuc5zoa/SOdogV1myZzaKq2HE8b30lAX3ZW7BbcW4iMncTyuZSfnxeqV2z2/M2rjyWHjGAWMPUM8dpY9bWJD6X/2gAIAQIAAQUC6Ws0wXBMFx3hvpf/2gAIAQMAAQUC+Af/2gAIAQICBj8CAf/aAAgBAwIGPwIB/9oACAEBAQY/Aur9uSUK5y6VpZApOVFeV8Tn15D60WY3J1moCf5r9qabWFF8KYcPA6+m98O7TNfbphKnBFjLDaLVNyz4pa4G/A0I/wBmjOmQsMHN1OmEAedUUn5XqbmWwS3CD3mhJtZpD0DgElycfmvakXxZdu6oo5BqRQyndTw+UMLYZUzFuN2wv2fBf//aAAgBAQMBPyH4w+6y888e8wK/Yd2OYJamasJkfQXL6ikQPlLqtEOQX1+tJ6/x3VtNruFuMYphTp9AYwnOihP0s1zyk4sfdqu5Y7GJP6GLvTiVE2cZP3XRH9ointmkOrvq1QQW6QXf65Is2rxfoalPUHFc4PKtckUTcIiUEb+UburVx0sIgbb6+F//2gAIAQIDAT8h+OM0UYHBIx0YmuF0zDXC6ZCk04MvC//aAAgBAwMBPyHwpzuQyOQyxkjwv//aAAwDAQACEQMRAAAQkkkkkhkklIrEhkPEnkPEk6Pkkkkkkkkk/9oACAEBAwE/EPjj8Ql0GhYJP6UIgjI3EylYiQdlZgdxTFjQf2wHCn2aU4Yj6glVdVokAKqA31aJdngG0sDpd3oXX8WyOINm3HOLiiIlvAAejFrIbqFisawfSkBz6gD8RlZ90xKenkOY72Ng0hMc/A2cVHA50nk/5TSE7IQn4yF33UxR3IRz6DFBESRsjSwYJBTwiTd34oYrYAQuoCx6/tIGo6gKAkslIWGBBsIIQO1OlGBFumAquhfo8L//2gAIAQIDAT8Q+MTmE6Vy4amgljJtDF30N6vxK2JvpbVbkIUBJpXoYZ8Of//aAAgBAwMBPxD45zLTgNKWhnHVi6YDXHVi6YDXIypKKSiHhf/Z';