diff --git a/package.json b/package.json index 7bbd6c3..90663dc 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "sass": "^1.34.0" }, "dependencies": { - "jquery": "^3.6.0" + "jquery": "^3.6.0", + "jszip": "^3.10.1" } } diff --git a/src/cutter/PreviewPanel.js b/src/cutter/PreviewPanel.js index 3e4cad6..106f56f 100644 --- a/src/cutter/PreviewPanel.js +++ b/src/cutter/PreviewPanel.js @@ -1,6 +1,6 @@ import $ from 'jquery'; +import JSZip from 'jszip'; -import Rect from '../spritecow/Rect'; import InlineEdit from '../spritecow/InlineEdit'; class PreviewPanel { @@ -16,8 +16,8 @@ class PreviewPanel { this.$exportButton = this.$settings.find('#exportButton'); this.$spriteCanvas = spriteCanvas.canvas; - this.fileName = 'sprite.png'; - this.rect = new Rect(0, 0, 0, 0); + this.fileName = 'sprite'; + this.selectedSprites = []; this._addEditEvents(); this.$exportButton.on('click', this.handleExport.bind(this)); @@ -48,17 +48,18 @@ class PreviewPanel { $('
Settings
').appendTo(container); $('
Name:

').appendTo(container); + $('
0 sprite(s) selected!
').appendTo(container); $('
').appendTo(container); return container; } update() { - var rect = this.rect; + var rect = this.selectedSprites[this.selectedSprites.length - 1].rect; const previewCanvas = this.$previewCanvas; - const hiddenCanvas = this.$hiddenExportingCanvas; this.$settings.find('#fileName').text(this.fileName); + this.$settings.find('#selectedSpritesCount').text(this.selectedSprites.length); this.$properties.find('#topX').val(rect.x); this.$properties.find('#topY').val(rect.y); this.$properties.find('#width').val(rect.width); @@ -71,9 +72,35 @@ class PreviewPanel { this.$spriteCanvas, rect.x, rect.y, rect.width, rect.height, 0, 0, previewCanvas.width, previewCanvas.height ); + }; + + handleExport() { + if(this.selectedSprites.length > 1) { + this.createZipExport().then(base64 => { + const dataUrl = "data:application/zip;base64," + base64; + this.downloadExport(dataUrl, '.zip'); + }); + } else { + const image = this.createImageData(this.selectedSprites[0]); + this.downloadExport(image, '.png'); + } + } + + downloadExport(dataUrl, ext) { + var link = document.createElement('a'); + link.download = this.fileName + ext; + link.href = dataUrl; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } - // we need a hidden canvas that will always match the current selected sprite's height/width so when exporting it is the correct size + createImageData(sprite) { + const rect = sprite.rect; + const hiddenCanvas = this.$hiddenExportingCanvas; const hiddenCanvasContext = hiddenCanvas.getContext('2d'); + hiddenCanvasContext.clearRect(0, 0, hiddenCanvas.width, hiddenCanvas.height); hiddenCanvas.width = rect.width; hiddenCanvas.height = rect.height; @@ -81,17 +108,19 @@ class PreviewPanel { this.$spriteCanvas, rect.x, rect.y, rect.width, rect.height, 0, 0, hiddenCanvas.width, hiddenCanvas.height ); - }; - handleExport() { - const image = this.$hiddenExportingCanvas.toDataURL("image/png"); - var link = document.createElement('a'); - link.download = this.fileName; - link.href = image; + return hiddenCanvas.toDataURL("image/png"); + } - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); + createZipExport() { + var zip = new JSZip(); + + this.selectedSprites.forEach((sprite, i) => { + const base64Image = this.createImageData(sprite).split(';base64,')[1]; + zip.file(`${i}.png`, base64Image, {base64: true}); + }); + + return zip.generateAsync({type:"base64"}); } _addEditEvents() { diff --git a/src/spritecow/SpriteCanvasView.js b/src/spritecow/SpriteCanvasView.js index 1d218eb..4a589ce 100644 --- a/src/spritecow/SpriteCanvasView.js +++ b/src/spritecow/SpriteCanvasView.js @@ -263,10 +263,6 @@ class SpriteCanvasView { var SpriteCanvasViewProto = SpriteCanvasView.prototype = new MicroEvent; -SpriteCanvasViewProto._setCurrentRect = function(rect) { - this.trigger('rectChange', rect); -}; - SpriteCanvasViewProto._handleSelectedSprite = function(clickedRect, spriteRect) { if(isKeyDown(SHIFT_KEY)) { const alreadySelectedSpriteIndex = this._selectedSprites.findIndex(sprite => JSON.stringify(sprite.rect) == JSON.stringify(spriteRect)); @@ -281,7 +277,7 @@ SpriteCanvasViewProto._handleSelectedSprite = function(clickedRect, spriteRect) this._selectedSprites = [this._selectSprite(clickedRect, spriteRect)]; } - this._setCurrentRect(spriteRect); + this.trigger('selectedSpritesChange', this._selectedSprites); } SpriteCanvasViewProto._selectSprite = function(clickedRect, spriteRect) { diff --git a/src/spritecow/base.js b/src/spritecow/base.js index a3265ac..79de86e 100644 --- a/src/spritecow/base.js +++ b/src/spritecow/base.js @@ -52,16 +52,22 @@ import PreviewPanel from '../cutter/PreviewPanel'; pageLayout.toAppView(); }); - spriteCanvasView.bind('rectChange', function(rect) { - previewPanel.rect = rect; + spriteCanvasView.bind('selectedSpritesChange', function(selectedSprites) { + if(selectedSprites.length === 0) { + return; + } + + previewPanel.selectedSprites = selectedSprites; previewPanel.update(); - if (rect.width === spriteCanvas.canvas.width && rect.height === spriteCanvas.canvas.height) { - // if the rect is the same size as the whole canvas, - // it's probably because the background is set wrong - // let's be kind... - toolbarTop.feedback( 'Incorrect background colour set?', true ); - } + selectedSprites.forEach(({rect}) => { + if (rect.width === spriteCanvas.canvas.width && rect.height === spriteCanvas.canvas.height) { + // if the rect is the same size as the whole canvas, + // it's probably because the background is set wrong + // let's be kind... + toolbarTop.feedback( 'Incorrect background colour set?', true ); + } + }); }); spriteCanvasView.bind('bgColorHover', function(color) { diff --git a/yarn.lock b/yarn.lock index 6d1b75c..22566c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2802,6 +2802,11 @@ ieee754@^1.1.4: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + import-fresh@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" @@ -3227,6 +3232,16 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jszip@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -3259,6 +3274,13 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + lodash.clone@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" @@ -3686,7 +3708,7 @@ pako@^0.2.5: resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= -pako@~1.0.5: +pako@~1.0.2, pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== @@ -4658,7 +4680,7 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=