diff --git a/package-lock.json b/package-lock.json index 8884150aba3..ce30af175a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2134,9 +2134,9 @@ "integrity": "sha512-9nWywp+0SH7ROVzQPQQO9gMWBikahsqyMWp1Ku8VV0q+q6bnx6dS0aNPTjqTtF2GHAY55hcREsqKzaoUdWBSwg==" }, "node_modules/@turbowarp/jszip": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/@turbowarp/jszip/-/jszip-3.11.0.tgz", - "integrity": "sha512-G+xld2rPv0b7ZKIk4ZZLW8l46+VfdUsreYjWml8bl3IHrmQXgijkmiDwTq17CBf5GPlz4+F/TpJdclq0jhMSVQ==", + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@turbowarp/jszip/-/jszip-3.11.1.tgz", + "integrity": "sha512-1tWXTxAac1T/g0VHC9lIY0Ij7Qyt7sORIaAT4L0/Y+pjU1ZtXD9ti/+RnXzTVHXp6AM8fM2O3mF22/aSEVPXiQ==", "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { "lie": "~3.3.0", @@ -2187,12 +2187,12 @@ "integrity": "sha512-texcM9oxfEsADVlVDR5UhLkYclPKsV9mytJh+9pHHonNcUrxRVGF6FkJTzWO/Hl5NafU1crSdw737nqKE3atSA==" }, "node_modules/@turbowarp/sb3fix": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@turbowarp/sb3fix/-/sb3fix-0.3.0.tgz", - "integrity": "sha512-tZjpPb37UUnr6mxeZ12FLqHFhQ66rIUEIfFMWoLLzmt8VFE6JjM74chyphsB3AwON5ehs678zzXkhRSOZgPK3A==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@turbowarp/sb3fix/-/sb3fix-0.3.6.tgz", + "integrity": "sha512-pKRyJbZYGHFAQNacwJEoz5sh8SnSDRFFle1BcEoyZHRuDE/9TZoBFNfYzoyjKCeOyOBStcS8kmGhak45YabaDg==", "license": "MPL-2.0", "dependencies": { - "@turbowarp/jszip": "^3.11.0" + "@turbowarp/jszip": "^3.11.1" } }, "node_modules/@turbowarp/scratch-svg-renderer": { diff --git a/src/io/mouse.js b/src/io/mouse.js index 169e6af0b67..bc46d907d84 100644 --- a/src/io/mouse.js +++ b/src/io/mouse.js @@ -101,16 +101,19 @@ class Mouse { if (!(data.x > 0 && data.x < data.canvasWidth && data.y > 0 && data.y < data.canvasHeight)) return; + // target will not exist if project is still loading const target = this._pickTarget(data.x, data.y); - const isNewMouseDown = !previousDownState && this._isDown; - const isNewMouseUp = previousDownState && !this._isDown; - - // Draggable targets start click hats on mouse up. - // Non-draggable targets start click hats on mouse down. - if (target.draggable && isNewMouseUp) { - this._activateClickHats(target); - } else if (!target.draggable && isNewMouseDown) { - this._activateClickHats(target); + if (target) { + const isNewMouseDown = !previousDownState && this._isDown; + const isNewMouseUp = previousDownState && !this._isDown; + + // Draggable targets start click hats on mouse up. + // Non-draggable targets start click hats on mouse down. + if (target.draggable && isNewMouseUp) { + this._activateClickHats(target); + } else if (!target.draggable && isNewMouseDown) { + this._activateClickHats(target); + } } } } diff --git a/src/virtual-machine.js b/src/virtual-machine.js index e02d87222a2..14ff8310a2f 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -537,6 +537,22 @@ class VirtualMachine extends EventEmitter { file.date = date; } + // Tell JSZip to only compress file formats where there will be a significant gain. + const COMPRESSABLE_FORMATS = [ + '.json', + '.svg', + '.wav', + '.ttf', + '.otf' + ]; + for (const file of Object.values(zip.files)) { + if (COMPRESSABLE_FORMATS.some(ext => file.name.endsWith(ext))) { + file.options.compression = 'DEFLATE'; + } else { + file.options.compression = 'STORE'; + } + } + return zip; } @@ -546,9 +562,9 @@ class VirtualMachine extends EventEmitter { */ saveProjectSb3 (type) { return this._saveProjectZip().generateAsync({ + // Don't configure compression here. _saveProjectZip() will set it for each file. type: type || 'blob', - mimeType: 'application/x.scratch.sb3', - compression: 'DEFLATE' + mimeType: 'application/x.scratch.sb3' }); } diff --git a/test/fixtures/tw-mixed-file-formats.sb3 b/test/fixtures/tw-mixed-file-formats.sb3 new file mode 100644 index 00000000000..f06c40d680b Binary files /dev/null and b/test/fixtures/tw-mixed-file-formats.sb3 differ diff --git a/test/integration/tw_compression_per_file_type.js b/test/integration/tw_compression_per_file_type.js new file mode 100644 index 00000000000..35f02a68cdb --- /dev/null +++ b/test/integration/tw_compression_per_file_type.js @@ -0,0 +1,31 @@ +const {test} = require('tap'); +const fs = require('fs'); +const path = require('path'); +const VM = require('../../src/virtual-machine'); +const makeTestStorage = require('../fixtures/make-test-storage'); +const JSZip = require('@turbowarp/jszip'); + +test('saveProjectSb3() per-file compression', t => { + const vm = new VM(); + vm.attachStorage(makeTestStorage()); + const fixture = fs.readFileSync(path.join(__dirname, '../fixtures/tw-mixed-file-formats.sb3')); + vm.loadProject(fixture) + .then(() => vm.saveProjectSb3('arraybuffer')) + .then(buffer => JSZip.loadAsync(buffer)) + .then(zip => { + const isCompressed = pathInZip => { + // note: uses JSZip private APIs, not very stable, but it should be okay... + const file = zip.files[pathInZip]; + return file._data.compression.magic === '\x08\x00'; + }; + + t.ok(isCompressed('project.json')); + t.ok(isCompressed('5cb46ddd903fc2c9976ff881df9273c9.wav')); + t.ok(isCompressed('cd21514d0531fdffb22204e0ec5ed84a.svg')); + + t.notOk(isCompressed('0b2e50ca4107ce57416e2ceb840a6347.jpg')); + t.notOk(isCompressed('5c8826d846c06dddeb77590e8792fb7d.png')); + + t.end(); + }); +}); diff --git a/test/unit/tw_mouse.js b/test/unit/tw_mouse.js new file mode 100644 index 00000000000..17aeb200f37 --- /dev/null +++ b/test/unit/tw_mouse.js @@ -0,0 +1,14 @@ +const {test} = require('tap'); +const Runtime = require('../../src/engine/runtime'); + +test('isDown does not error before project loads', t => { + const rt = new Runtime(); + rt.ioDevices.mouse.postData({ + isDown: true, + x: 20, + y: 20, + canvasWidth: 100, + canvasHeight: 100 + }); + t.end(); +});