126 Commits

Author SHA1 Message Date
ef945ef8a6 bump version de 0.15 2018-11-25 18:10:55 +01:00
c5c8184ae3 Fix size picker display for pen size > 4 2018-11-24 21:03:09 +01:00
66452de1e5 Fix hidden frames bug when adding or removing frames 2018-11-24 20:48:47 +01:00
fa62ae511e Fix to avoid double extension when using the dest argument 2018-10-25 18:30:44 +02:00
f106909150 Replace backticks with single quotes 2018-10-25 18:30:44 +02:00
b54efbde21 Add piskel-cli for exporting from .piskel files via command line 2018-10-25 18:30:44 +02:00
5ebf83badf Add inline image option for PixiJS Movie export 2018-10-18 09:03:55 +02:00
3804afbeb5 Enabled inline editing of layer names 2018-10-18 08:14:23 +02:00
0a64d62b84 Add comments for hiddenFrames feature 2018-10-07 14:19:01 +02:00
af9095b934 Move hidden frames info to Piskel model 2018-10-07 14:19:01 +02:00
65f4fd0f27 Refactoring. Create function to get current frame 2018-10-05 23:41:55 +02:00
5e364e984c Add to frame size information about number of current frame and how much frames exists 2018-10-05 23:41:55 +02:00
5a0b6b90e4 Fit typos 2018-09-25 08:35:46 +02:00
717e8dd52a Add keyboard shortcut to toggle grid (alt+G) 2018-09-19 23:24:52 +02:00
bd0478fb35 Extract PreviewActionsController from PreviewController 2018-09-19 23:24:52 +02:00
2bbbfd1219 Add integration test for grid icon in animated preview 2018-09-19 23:24:52 +02:00
da4156912e Remove migration script for 0.12 to 0.13 2018-09-19 23:24:52 +02:00
99c060f4a7 Cleanup local storage before integration tests 2018-09-19 23:24:52 +02:00
52e49a1ed6 Add toggle grid icon to minimap 2018-09-19 23:24:52 +02:00
6acfa2256c Fix in serialization 2018-09-17 22:19:14 +02:00
89629d2939 Implemented with history management. 2018-09-17 22:19:14 +02:00
e5d89104a1 Added frame toggling for preview 2018-09-17 22:19:14 +02:00
6e84f0a2b5 Set color of Alpha icon based on opacity value. 2018-09-17 22:17:19 +02:00
ba0e86dc7e Place opacity value in layer item title. 2018-09-17 22:17:19 +02:00
27497313ff Issue #803 - Add integration test for single png export 2018-09-09 08:47:00 +02:00
8662ab65ad Issue #803 - Reuse downloadCanvas_ to download single frame canvas 2018-09-09 08:47:00 +02:00
594d748146 Issue #803 - Move single frame export to PNG tab and support scale 2018-09-09 08:47:00 +02:00
379c61a11d Fix grid bleeding through from other layers by only enabling support for
grid rendering for current layer.
2018-08-26 10:34:25 +02:00
230a38bf07 Updated urls to use https
Updated urls to use https instead of http when possible to avoid redirects.
2018-05-23 07:12:10 +09:00
0e5d74e1d8 Issue #772 - flash colorpicker cursor when using middle click or alt+click 2018-04-08 17:59:01 +02:00
f462cd3b70 Remove jquery from app.js 2018-04-08 17:39:15 +02:00
45d2245346 Remove jquery from BrowseLocalController.js 2018-04-08 17:39:15 +02:00
e5e85f67c2 Remove jquery from ColorsList.js 2018-04-08 17:39:15 +02:00
4836251bd3 Remove deprecated comment in FramesListController.js 2018-04-08 17:39:15 +02:00
e3182504dd Remove jquery from FramesListController.js 2018-04-08 17:39:15 +02:00
34a88b79a0 Remove jquery from MinimapController.js 2018-04-08 17:39:15 +02:00
7b8978da7a Remove jquery from LayersListController.js 2018-04-08 17:39:15 +02:00
1c3b359b5c Remove unused constant from LayersListController.js 2018-04-08 17:39:15 +02:00
5c4cbbbba1 Remove jquery from NotificationController.js 2018-04-08 17:39:15 +02:00
b9cc2eb4f8 Remove jquery from PaletteController.js 2018-04-08 17:39:15 +02:00
ac13cf13c1 Remove jquery from PalettesListController.js 2018-04-08 17:39:15 +02:00
32600f9e66 Remove jquery from LayersRenderer.js 2018-04-08 17:39:15 +02:00
39eb0d3143 Remove jquery from ProgressBarController.js 2018-04-08 17:39:15 +02:00
6e53bce2cf Remove jquery from SelectionManager.js 2018-04-08 17:39:15 +02:00
090acee11a Remove jquery from PreviewController.js 2018-04-08 17:39:15 +02:00
a1bb5f3b97 Remove jquery from DrawingController.js 2018-04-08 17:39:15 +02:00
f69e502639 Remove jquery from FrameRenderer.js 2018-04-08 17:39:15 +02:00
027cd2e2a6 Remove jquery from BackgroundImageFrameRenderer.js 2018-04-08 17:39:15 +02:00
2726af2d81 Remove jquery from ShortcutService.js 2018-04-08 17:39:15 +02:00
22cec2e8c7 Remove jquery from BaseSelect.js 2018-04-08 17:39:15 +02:00
57ee8e4365 Remove jquery from FrameRenderer.js 2018-04-08 17:39:15 +02:00
a9cdf98281 Remove jquery from ToolController.js 2018-04-08 17:39:15 +02:00
0a9e7dcffb Update the no-colors message for palettes to be more readable 2018-04-08 17:03:07 +02:00
fe3f6996f1 release: update version to 0.15.0-SNAPSHOT 2018-03-11 23:48:06 +01:00
9d32a8c3aa release: bump to version 0.14.0 2018-03-04 13:04:20 +01:00
64cd724139 reverted checkbox class and added method to controller 2018-03-03 16:45:27 +01:00
dae08107e2 Added new checkbox to zip export html template and use layer name as prefix to file names (in ZipExportController) if checkbox is selected. 2018-03-03 16:45:27 +01:00
779761628e Grid spacing: css nits, remove unused variable, string case fix 2018-02-18 13:29:37 +01:00
3e450c3d77 Changes requested in PR 2018-02-18 13:11:08 +01:00
5c7070b01b Add a Grid Spacing option to the GridSettings
Fixes #774
2018-02-18 13:11:08 +01:00
7c215ebcbe Remove use of grunt-open in favor of the option in grunt-contrib-connect 2018-02-07 18:52:03 +01:00
bcee24609f Changed cursor coordinates color from gold to --highlight-color. 2017-11-16 21:20:49 +01:00
f420c6f1fd Made cursor coordinates gold (and whitespace more consistent). 2017-11-16 21:20:49 +01:00
76429dfd86 noloop gif should be -1
repeat	0	repeat count, -1 = no repeat, 0 = forever
https://github.com/jnordberg/gif.js
2017-11-10 10:13:08 +01:00
4ebf43fda4 Display drawing zoom 2017-10-23 11:39:38 +02:00
2f2b4cd9ba release: bump to version 0.13.0 2017-10-22 16:40:09 +02:00
dfb049bbf0 release: bump version to 0.13.0-RC2 2017-10-22 16:40:09 +02:00
8c54108a9b Remove html autocomplete fromt size input fields 2017-10-22 16:40:09 +02:00
3dd72f9781 release: fix minor ui issues for checkbox containers 2017-10-22 16:40:09 +02:00
80001eab0e release: bump version to 0.13.0-RC1 2017-10-22 16:40:09 +02:00
5ecf351e0f Return promise results from PiskelDB and IndexedDBStorageService 2017-10-21 23:48:51 +02:00
407b432227 Show error message if BackupDatabase promise rejected 2017-10-21 23:48:51 +02:00
69cc27557e Add templates for backup database errors 2017-10-21 23:48:51 +02:00
cfd3773a2b Issue #751 - add repeat checkbox to GIF export panel 2017-10-08 19:46:43 +02:00
0eface45f1 Issue #750 - drawing tests: always initialize penSize before starting test 2017-10-08 19:12:04 +02:00
87893bb4ac Issue #750 - Fix mirror pen with even pensizes 2017-10-08 19:12:04 +02:00
77d26bffa9 Issue #727 - add integration test for simple import flow 2017-10-08 18:19:52 +02:00
652027bd3f Issue #727 - update integration tests to wait for color service update 2017-10-08 18:19:52 +02:00
bf4cc3302a Issue #727 - skip import steps if current piskel is empty 2017-10-08 18:19:52 +02:00
95c8df1224 Issue #727 - simplify import: resize and insertion steps 2017-10-08 18:19:52 +02:00
7445357368 Issue #727 - simplify import mode text 2017-10-08 18:19:52 +02:00
a2369cac0c Issue #727 - remove border around meta info in import wizard 2017-10-08 18:19:52 +02:00
51538dff48 Make piskel performance warning less scary 2017-09-24 18:06:37 +02:00
da739e78da Issue #743 - bump color palette cap to 256 2017-09-24 17:39:03 +02:00
dd8217e21b Issue #744 - show notification when exporting to GIF can not preserve colors 2017-09-24 17:37:49 +02:00
d502d3416b Issue #745 - Add https support 2017-09-24 17:37:14 +02:00
d1156954ca Issue #729 - implement custom PNG export viewer instead of opening window to data-uri 2017-09-24 17:36:02 +02:00
dc5209628c fix selectionmanager unit test 2017-09-06 23:05:17 +02:00
8568663949 Move clipboard events to dedicated service and fix tests 2017-09-06 23:05:17 +02:00
fd3d828067 remove unused selection copy cut paste events 2017-09-06 23:05:17 +02:00
e1797b2008 Fix SelectionManagerTest by using a clipboard event mock 2017-09-06 23:05:17 +02:00
0a43f6bbec Fix copy to website script to work if main-partial is missing. 2017-09-06 23:05:17 +02:00
b9423bc831 Issue #645: Support clipboard to paste images 2017-09-06 23:05:17 +02:00
5e6280301d Issue #736 - cleanup selection tool state on SELECTION_DISMISSED event 2017-09-06 00:39:35 +02:00
5671eb4782 Delete all extra backup sessions if MAX is reached 2017-08-06 22:56:43 +02:00
35788b54ba update travis yml to upgrade node and stop downloading casper 2017-08-03 00:44:53 +02:00
629ecf83b4 add comments for values synced between JS and CSS 2017-08-03 00:21:08 +02:00
c037b07693 rename mergeData to backupsData in browse backups wizard 2017-08-03 00:21:08 +02:00
c31b7a351c update piskel mock in BackupServiceTest 2017-08-03 00:21:08 +02:00
7de03f1e73 show snpashot previews in the browse backups dialog 2017-08-03 00:21:08 +02:00
eab21e0839 Show confirmation message when loading snapshot backup 2017-08-03 00:21:08 +02:00
2b3bd02479 improve styling of snapshot list in browse backups dialog 2017-08-03 00:21:08 +02:00
4e86fa1570 dev-environment: add ctrl+alt+R shortcut to reload styles 2017-08-03 00:21:08 +02:00
170a7e4731 skip backups for current session in browse backups dialog 2017-08-03 00:21:08 +02:00
6b7f04b63e browse backups dialog: add styling for empty session list 2017-08-03 00:21:08 +02:00
da2e9f99e4 cleanup: remove title on backup session element 2017-08-03 00:21:08 +02:00
530a949e54 add icon for backup dialog 2017-08-03 00:21:08 +02:00
4377c9e601 add disclaimer in the browse backups dialog 2017-08-03 00:21:08 +02:00
e0bbb88d47 confirm backup session delete, add animation 2017-08-03 00:21:08 +02:00
9ff2ecbb45 improve styling for browse-backups dialog 2017-08-03 00:21:08 +02:00
8beba2088b remove useless console.log 2017-08-03 00:21:08 +02:00
ee45cdcc45 add a browse backups dialog 2017-08-03 00:21:08 +02:00
30ea7fa079 fix migration script for localstorage to indexeddb 2017-08-03 00:21:08 +02:00
e9b39a5c61 add unit test for PiskelDatabase 2017-08-03 00:21:08 +02:00
d0a32b18c5 add unit test for backup database 2017-08-03 00:21:08 +02:00
372ad1f513 add unit test for BackupService 2017-08-03 00:21:08 +02:00
c6e106fe2d add a limit to the number of sessions backed up 2017-08-03 00:21:08 +02:00
f9570ea3c5 Issue #640 - extract database code to dedicated package 2017-08-03 00:21:08 +02:00
f9cb631acb Issue #640 - migrate backup service to indexeddb 2017-08-03 00:21:08 +02:00
ed749a747f Issue #640 - migrate local browser save to indexeddb 2017-08-03 00:21:08 +02:00
30ecd41452 Issue #640 - remove duplicated entries in piskel-script-list 2017-08-03 00:21:08 +02:00
af65344c23 Issue #640 - rename PaletteService pointer to localStorage to localStorageGlobal
PaletteService exposes window.localStorage as this.localStorageService. This is confusing since we also have the LocalStorageService class used to save piskels in local storage.
2017-08-03 00:21:08 +02:00
183133496e Fix #718 - when dropping image, only use import wizard for big images 2017-08-01 01:06:09 +02:00
8a2c0191f9 release: bump version to 0.12.1 2017-07-18 08:06:54 +02:00
a096dcabfd Fix #717: filter invalid colors 2017-07-18 08:05:48 +02:00
96d326ef12 release: bump version to 0.13.0-SNAPSHOT 2017-06-23 21:01:47 +02:00
144 changed files with 5571 additions and 904 deletions

View File

@ -1,14 +1,9 @@
language: node_js language: node_js
node_js: node_js:
- "4.1" - "7.4.0"
before_install: before_install:
- npm update -g npm - npm update -g npm
- npm install -g grunt-cli - npm install -g grunt-cli
- git clone git://github.com/n1k0/casperjs.git ~/casperjs
- cd ~/casperjs
- git checkout tags/1.1.3
- export PATH=$PATH:`pwd`/bin
- cd -
before_script: before_script:
- phantomjs --version - phantomjs --version
- casperjs --version - casperjs --version

View File

@ -47,16 +47,13 @@ module.exports = function(grunt) {
var integrationTestPaths = require('./test/casperjs/integration/IntegrationSuite.js').tests; var integrationTestPaths = require('./test/casperjs/integration/IntegrationSuite.js').tests;
var integrationTests = prefixPaths(integrationTestPaths, "test/casperjs/integration/"); var integrationTests = prefixPaths(integrationTestPaths, "test/casperjs/integration/");
var getConnectConfig = function (base, port, host) { var getConnectConfig = function (base, port, host, open) {
if (typeof base === 'string') {
base = [base];
}
return { return {
options: { options: {
port: port, port: port,
hostname : host, hostname : host,
base: base base: base,
open: open
} }
}; };
}; };
@ -98,18 +95,9 @@ module.exports = function(grunt) {
*/ */
connect: { connect: {
prod: getConnectConfig('dest/prod', PORT.PROD, hostname), prod: getConnectConfig('dest/prod', PORT.PROD, hostname, true),
test: getConnectConfig(['dest/dev', 'test'], PORT.TEST, hostname), test: getConnectConfig(['dest/dev', 'test'], PORT.TEST, hostname, false),
dev: getConnectConfig(['dest/dev', 'test'], PORT.DEV, hostname) dev: getConnectConfig(['dest/dev', 'test'], PORT.DEV, hostname, 'http://' + hostname + ':' + PORT.DEV + '/?debug')
},
open : {
prod : {
path : 'http://' + hostname + ':' + PORT.PROD + '/'
},
dev : {
path : 'http://' + hostname + ':' + PORT.DEV + '/?debug'
}
}, },
watch: { watch: {
@ -343,9 +331,9 @@ module.exports = function(grunt) {
// SERVER TASKS // SERVER TASKS
// Start webserver and watch for changes // Start webserver and watch for changes
grunt.registerTask('serve', ['build', 'connect:prod', 'open:prod', 'watch:prod']); grunt.registerTask('serve', ['build', 'connect:prod', 'watch:prod']);
// Start webserver on src folder, in debug mode // Start webserver on src folder, in debug mode
grunt.registerTask('play', ['build-dev', 'connect:dev', 'open:dev', 'watch:dev']); grunt.registerTask('play', ['build-dev', 'connect:dev', 'watch:dev']);
// ALIASES, kept for backward compatibility // ALIASES, kept for backward compatibility
grunt.registerTask('serve-debug', ['play']); grunt.registerTask('serve-debug', ['play']);

View File

@ -1,10 +1,10 @@
Piskel Piskel
====== ======
[![Travis Status](https://api.travis-ci.org/piskelapp/piskel.png?branch=master)](https://travis-ci.org/piskelapp/piskel) [![Built with Grunt](https://cdn.gruntjs.com/builtwith.png)](http://gruntjs.com/) [![Travis Status](https://api.travis-ci.org/piskelapp/piskel.png?branch=master)](https://travis-ci.org/piskelapp/piskel) [![Built with Grunt](https://cdn.gruntjs.com/builtwith.png)](https://gruntjs.com/)
Piskel is an easy-to-use sprite editor. It can be used to create game sprites, animations, pixel-art... Piskel is an easy-to-use sprite editor. It can be used to create game sprites, animations, pixel-art...
It is the editor used in **[piskelapp.com](http://piskelapp.com)**. It is the editor used in **[piskelapp.com](https://www.piskelapp.com)**.
<img <img
src="https://screenletstore.appspot.com/img/95aaa0f0-37a4-11e7-a652-7b8128ce3e3b.png" src="https://screenletstore.appspot.com/img/95aaa0f0-37a4-11e7-a652-7b8128ce3e3b.png"
@ -19,14 +19,14 @@ The Piskel editor is purely built in **JavaScript, HTML and CSS**.
We also use the following **libraries** : We also use the following **libraries** :
* [spectrum](https://github.com/bgrins/spectrum) : awesome standalone colorpicker * [spectrum](https://github.com/bgrins/spectrum) : awesome standalone colorpicker
* [gifjs](http://jnordberg.github.io/gif.js/) : generate animated GIFs in javascript, using webworkers * [gifjs](https://jnordberg.github.io/gif.js/) : generate animated GIFs in javascript, using webworkers
* [supergif](https://github.com/buzzfeed/libgif-js) : modified version of SuperGif to parse and import GIFs * [supergif](https://github.com/buzzfeed/libgif-js) : modified version of SuperGif to parse and import GIFs
* [jszip](https://github.com/Stuk/jszip) : create, read and edit .zip files with Javascript * [jszip](https://github.com/Stuk/jszip) : create, read and edit .zip files with Javascript
* [canvas-toBlob](https://github.com/eligrey/canvas-toBlob.js/) : shim for canvas toBlob * [canvas-toBlob](https://github.com/eligrey/canvas-toBlob.js/) : shim for canvas toBlob
* [jquery](http://jquery.com/) : used sporadically in the application * [jquery](https://jquery.com/) : used sporadically in the application
* [bootstrap-tooltip](http://getbootstrap.com/javascript/#tooltips) : nice tooltips * [bootstrap-tooltip](https://getbootstrap.com/javascript/#tooltips) : nice tooltips
As well as some **icons** from the [Noun Project](http://thenounproject.com/) : As well as some **icons** from the [Noun Project](https://thenounproject.com/) :
* Folder by Simple Icons from The Noun Project * Folder by Simple Icons from The Noun Project
* (and probably one or two others) * (and probably one or two others)

View File

@ -17,8 +17,17 @@ function onCopy(err) {
console.log('Copied static files to piskel-website...'); console.log('Copied static files to piskel-website...');
let previousPartialPath = path.resolve(PISKELAPP_PATH, 'templates/editor/main-partial.html'); let previousPartialPath = path.resolve(PISKELAPP_PATH, 'templates/editor/main-partial.html');
fs.access(previousPartialPath, fs.constants.F_OK, function (err) {
if (err) {
// File does not exit, call next step directly.
console.error('Previous main partial doesn\'t exist yet.');
onDeletePreviousPartial();
} else {
// File exists, try to delete it before moving on.
fs.unlink(previousPartialPath, onDeletePreviousPartial); fs.unlink(previousPartialPath, onDeletePreviousPartial);
} }
})
}
function onDeletePreviousPartial(err) { function onDeletePreviousPartial(err) {
if (err) { if (err) {

64
cli/README.md Normal file
View File

@ -0,0 +1,64 @@
# Piskel CLI
Wraps the Piskel pixel editing application to enable similar export options via the command line.
## Installation
Option 1: Globally install Piskel
```
npm install -g https://github.com/piskelapp/piskel/tarball/master
```
Option 2: Clone and install Piskel normally and then run npm link inside the installation root
## Usage
**Export provided .piskel file as a png sprite sheet using app defaults**
```
piskel-cli snow-monster.piskel
```
**Export scaled sprite sheet**
```
piskel-cli snow-monster.piskel --scale 5
```
**Export scaled to specific (single frame) width value**
```
piskel-cli snow-monster.piskel --scaledWidth 435
```
**Export scaled to specific (single frame) height value**
```
piskel-cli snow-monster.piskel --scaledHeight 435
```
**Export sprite sheet as a single column**
```
piskel-cli snow-monster.piskel --columns 1
```
**Export sprite sheet as a single row**
```
piskel-cli snow-monster.piskel --rows 1
```
**Export a single frame (0 is first frame)**
```
piskel-cli snow-monster.piskel --frame 3
```
**Export a second file containing the data-uri for the exported png**
```
piskel-cli snow-monster.piskel --dataUri
```
**Export cropped**
```
piskel-cli snow-monster.piskel --crop
```
**Custom output path and/or filename**
```
piskel-cli snow-monster.piskel --dest ./output-folder/snah-monstah.png
```

130
cli/export-png.js Normal file
View File

@ -0,0 +1,130 @@
const fs = require('fs');
function onPageEvaluate(window, options, piskel) {
console.log("\nPiskel name: " + piskel.descriptor.name);
// Setup piskelController
var piskelController = new pskl.controller.piskel.PiskelController(piskel);
pskl.app.piskelController = piskelController;
piskelController.init();
// Apply crop if enabled
if (options.crop) {
// Mock selection manager to avoid errors during crop
pskl.app.selectionManager = {};
// Setup crop tool
var crop = new pskl.tools.transform.Crop();
// Perform crop
crop.applyTransformation();
// Get cropped piskel
piskel = piskelController.getPiskel();
}
// Mock exportController to provide zoom value based on cli args
// and to avoid errors and/or unnecessary bootstrapping
var exportController = {
getExportZoom: function () {
var zoom = options.zoom;
if (options.scaledWidth) {
zoom = options.scaledWidth / piskel.getWidth();
} else if (options.scaledHeight) {
zoom = options.scaledHeight / piskel.getHeight();
}
return zoom;
}
};
// Setup pngExportController
var pngExportController = new pskl.controller.settings.exportimage.PngExportController(piskelController, exportController);
// Mock getColumns and getRows to use values from cli arguments
pngExportController.getColumns_ = function () {
if (options.columns) return options.columns;
if (options.rows) {
return Math.ceil(piskelController.getFrameCount() / pngExportController.getRows_());
} else {
return pngExportController.getBestFit_();
}
};
pngExportController.getRows_ = function () {
if (options.columns && !options.rows) {
return Math.ceil(piskelController.getFrameCount() / pngExportController.getColumns_());
}
return options.rows;
};
// Render to output canvas
var canvas;
if (options.frame > -1) {
// Render a single frame
canvas = piskelController.renderFrameAt(options.frame, true);
var zoom = exportController.getExportZoom();
if (zoom != 1) {
// Scale rendered frame
canvas = pskl.utils.ImageResizer.resize(canvas, canvas.width * zoom, canvas.height * zoom, false);
}
} else {
// Render the sprite sheet
canvas = pngExportController.createPngSpritesheet_();
}
// Add output canvas to DOM
window.document.body.appendChild(canvas);
// Prepare return data
const returnData = {
width: canvas.width,
height: canvas.height
};
// Wait a tick for things to wrap up
setTimeout(function () {
// Exit and pass data to parent process
window.callPhantom(returnData);
}, 0);
}
function onPageExit(page, options, data) {
// Set clip for output image
if (data.width && data.height) {
page.clipRect = { top: 0, left: 0, width: data.width, height: data.height };
}
console.log("\n" + 'Generated file(s):');
const dest = options.dest.replace('.png', '') + '.png';
// Render page to the output image
page.render(dest);
console.log(" " + dest);
if (options.dataUri) {
const dataUriPath = options.dest + '.datauri';
const dataUri = 'data:image/png;base64,' + page.renderBase64('PNG');
// Write data-uri to file
fs.write(dataUriPath, dataUri, 'w');
console.log(" " + dataUriPath);
}
}
module.exports = {
onPageEvaluate: onPageEvaluate,
onPageExit: onPageExit
};

93
cli/index.js Normal file
View File

@ -0,0 +1,93 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const minimist = require('minimist');
const childProcess = require('child_process');
const phantomjs = require('phantomjs');
const binPath = phantomjs.path;
// Parse command args
let args = minimist(process.argv.slice(2), {
default: {
crop: false,
dataUri: false,
debug: false,
scale: 1
},
});
if (args.debug) console.log(args);
// Ensure a path for the src file was passed
if (!args._ || (args._ && !args._.length)) {
console.error('Path to a .piskel file is required');
return;
}
const src = args._[0];
// Ensure the src file exists
if (!fs.existsSync(src)) {
console.error('No such file: ' + src);
return;
}
// Read src piskel file
const piskelFile = fs.readFileSync(src, 'utf-8');
const dest = args.dest || path.basename(src, '.piskel');
console.log('Piskel CLI is exporting...');
// Get path to Piskel's app js bundle
let piskelAppJsDir = path.resolve(__dirname +'/../dest/prod/js/');
let minJsFiles = fs.readdirSync(piskelAppJsDir).filter(filename => filename.indexOf('min') > -1);
let piskelAppJsFileName = minJsFiles[0];
let piskelAppJsPath = (piskelAppJsFileName) ? path.join(piskelAppJsDir, piskelAppJsFileName) : '';
if (!fs.existsSync(piskelAppJsPath)) {
console.error(`Piskel's application JS file not found in: ${piskelAppJsDir}. Run prod build and try again.`);
return;
}
// Prepare args to pass to phantom script
const options = {
dest: dest,
zoom: args.scale,
crop: !!args.crop,
rows: args.rows,
columns: args.columns,
frame: args.frame,
dataUri: !!args.dataUri,
debug: args.debug,
piskelAppJsPath: piskelAppJsPath,
scaledWidth: args.scaledWidth,
scaledHeight: args.scaledHeight
};
const childArgs = [
path.join(__dirname, 'piskel-export.js'),
piskelFile,
JSON.stringify(options)
];
if (args.debug) {
childArgs.unshift(
'--remote-debugger-port=9035',
'--remote-debugger-autorun=yes'
);
}
// Run phantom script
childProcess.execFile(binPath, childArgs, function (err, stdout, stderr) {
// Print any output the from child process
if (err) console.log(err);
if (stderr) console.log(stderr);
if (stdout) console.log(stdout);
console.log('Export complete');
});

45
cli/piskel-export.js Normal file
View File

@ -0,0 +1,45 @@
// PhantomJS system
const system = require('system');
// Exporter
const exporter = require('./export-png');
// Get passed args
const args = system.args;
// Parse input piskel file and options
const piskelFile = JSON.parse(args[1]);
const options = JSON.parse(args[2]);
// Create page w/ canvas
const page = require('webpage').create();
page.content = '<html><body></body></html>';
// Inject Piskel JS
page.injectJs(options.piskelAppJsPath);
// Listen for page console logs
page.onConsoleMessage = function (msg) {
console.log(msg);
};
// Run page logic
page.evaluate(function (piskelFile, options, onPageEvaluate) {
// Zero out default body margin
document.body.style.margin = 0;
// Deserialize piskel file and run exporter's page evaluate task
pskl.utils.serialization.Deserializer.deserialize(piskelFile, function (piskel) {
onPageEvaluate(window, options, piskel);
});
}, piskelFile, options, exporter.onPageEvaluate);
// Wait for page to trigger exit
page.onCallback = function (data) {
// Run exporter page exit task
exporter.onPageExit(page, options, data);
// Exit
phantom.exit(0);
};

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
width="90"
height="90"
viewBox="0 0 89.999997 90"
enable-background="new 0 0 89.231 100"
xml:space="preserve"
inkscape:version="0.92.1 r15371"
sodipodi:docname="common-backup.svg"
inkscape:export-filename="C:\Development\git\piskel\misc\icons\source\tool-rotate.png"
inkscape:export-xdpi="45"
inkscape:export-ydpi="45"><metadata
id="metadata15"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs13" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1148"
id="namedview11"
showgrid="false"
inkscape:zoom="7.75"
inkscape:cx="13.031976"
inkscape:cy="43.272537"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><g
id="g3760"
transform="matrix(0,-0.97677741,0.97203982,0,-2.1261998,91.355253)"
style="fill:#ff00ff;fill-opacity:1"><path
style="fill:#ff00ff;fill-opacity:1"
inkscape:connector-curvature="0"
id="path3"
d="m 29.229405,55.37008 c -0.387431,-1.333059 -0.642506,-2.72161 -0.738881,-4.152895 h -8.675106 c 0.115651,2.460738 0.552554,4.838559 1.260594,7.099021 z" /><path
style="fill:#ff00ff;fill-opacity:1"
inkscape:connector-curvature="0"
id="path5"
d="m 29.023802,70.783821 5.579515,-6.601516 c -1.862622,-1.780814 -3.387929,-3.907969 -4.44999,-6.287065 l -8.152106,2.946124 c 1.604978,3.800815 4.017584,7.185766 7.022581,9.942457 z" /><path
style="fill:#ff00ff;fill-opacity:1"
inkscape:connector-curvature="0"
id="path7"
d="m 47.110967,69.703978 c -3.887799,-0.260871 -7.469766,-1.6322 -10.437498,-3.790608 l -5.577588,6.598963 c 4.487901,3.403448 10.011517,5.524225 16.015086,5.803594 z" /><path
style="fill:#ff00ff;fill-opacity:1;stroke:none"
inkscape:connector-curvature="0"
id="path9"
d="M 48.464084,21.400659 V 14.532532 L 28.981398,25.698341 48.464084,36.86415 v -6.867489 c 11.042093,0 20.024317,8.91683 20.024317,19.877897 0,10.509484 -8.258763,19.134189 -18.671845,19.828145 v 8.611948 c 15.190751,-0.703524 27.330245,-13.189635 27.330245,-28.440093 0,-15.700763 -12.86681,-28.473899 -28.682717,-28.473899 z" /></g><g
id="g4513"
transform="translate(0,-2)"><rect
y="32.516129"
x="42"
height="15.612903"
width="7.9999986"
id="rect4494"
style="fill:#ff00f7;fill-opacity:1;stroke:#ffffed;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:6.04534006;stroke-opacity:1" /><rect
transform="rotate(120)"
y="-76.050484"
x="12.680965"
height="15.612903"
width="7.9999986"
id="rect4494-7"
style="fill:#ff00f7;fill-opacity:1;stroke:#ffffed;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:6.04534006;stroke-opacity:1" /></g></svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -1,6 +1,6 @@
{ {
"name": "piskel", "name": "piskel",
"version": "0.12.0", "version": "0.15.0",
"description": "Pixel art editor", "description": "Pixel art editor",
"author": "Julian Descottes <julian.descottes@gmail.com>", "author": "Julian Descottes <julian.descottes@gmail.com>",
"contributors": [ "contributors": [
@ -17,7 +17,8 @@
"misc/scripts/piskel-root" "misc/scripts/piskel-root"
], ],
"bin": { "bin": {
"piskel-root": "./misc/scripts/piskel-root" "piskel-root": "./misc/scripts/piskel-root",
"piskel-cli": "./cli/index.js"
}, },
"main": "./dest/prod/index.html", "main": "./dest/prod/index.html",
"scripts": { "scripts": {
@ -45,7 +46,6 @@
"grunt-karma": "1.0.0", "grunt-karma": "1.0.0",
"grunt-leading-indent": "0.2.0", "grunt-leading-indent": "0.2.0",
"grunt-nw-builder": "3.1.0", "grunt-nw-builder": "3.1.0",
"grunt-open": "0.2.3",
"grunt-replace": "1.0.1", "grunt-replace": "1.0.1",
"grunt-spritesmith": "6.4.0", "grunt-spritesmith": "6.4.0",
"jasmine-core": "2.6.1", "jasmine-core": "2.6.1",
@ -64,5 +64,8 @@
"toolbar": false, "toolbar": false,
"width": 1000, "width": 1000,
"height": 700 "height": 700
},
"dependencies": {
"minimist": "^1.2.0"
} }
} }

View File

@ -0,0 +1,152 @@
#dialog-container.browse-backups {
width: 700px;
height: 500px;
top : 50%;
left : 50%;
position : absolute;
margin-left: -350px;
}
.backups-step-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.backups-step-content {
width: 100%;
height: 10px;
flex-grow: 1;
background: #444;
padding: 20px;
overflow: auto;
box-sizing: border-box;
}
.backups-step-actions {
flex-grow: 0;
flex-shrink: 1;
height: 60px;
display: flex;
align-items: center;
padding: 0 20px;
}
.show #dialog-container.browse-backups {
margin-top: -250px;
}
.browse-backups .browse-backups-disclaimer {
display: flex;
margin-bottom: 20px;
align-items: center;
}
.browse-backups .browse-backups-disclaimer-content {
padding: 0 20px;
font-size: 13px;
}
.browse-backups .browse-backups-disclaimer .backups-icon {
border: 1px solid gold;
flex-shrink: 0;
width: 90px;
height: 90px;
}
.browse-backups .centered-message {
position: absolute;
left: 50%;
width: 200px;
margin-top: 100px;
margin-left: -130px;
padding: 30px;
font-size: 16px;
text-align: center;
border: 1px solid;
}
.browse-backups .session-list-empty,
.browse-backups .snapshot-list-empty {
color: #bbb;
}
.browse-backups .session-list-error,
.browse-backups .snapshot-list-error {
color: white;
}
.browse-backups .session-item {
/* Transition duration should be kept in sync with SelectSession.DELETE_TRANSITION_DURATION */
transition: all 500ms;
}
/* Hide and slide up next sessions when deleting an item */
.browse-backups .session-item.deleting {
opacity: 0;
margin-bottom: -60px;
}
.browse-backups .session-item,
.browse-backups .snapshot-item {
display: flex;
align-items: center;
width: 100%;
height: 80px;
margin-bottom: 10px;
padding: 0 20px;
border: 1px solid #666;
box-sizing: border-box;
}
.browse-backups .snapshot-preview {
flex-grow: 0;
flex-shrink: 1;
/* Keep synced with SessionDetails.PREVIEW_SIZE */
height: 60px;
width: 60px;
margin-right: 20px;
}
.browse-backups .session-details,
.browse-backups .snapshot-details {
flex-grow: 1;
flex-shrink: 0;
display: flex;
flex-direction: column;
}
.browse-backups .session-details-title,
.browse-backups .snapshot-details-title {
font-size: 13px;
}
.browse-backups .session-details-info,
.browse-backups .snapshot-details-info {
font-size: 11px;
color: #bbb;
line-height: 1.5em;
}
.browse-backups .session-actions,
.browse-backups .snapshot-actions {
flex-grow: 0;
flex-shrink: 1;
display: flex;
}
.browse-backups .session-actions button,
.browse-backups .snapshot-actions button {
margin-left: 10px;
}
.browse-backups .session-item:last-child,
.browse-backups .snapshot-item:last-child {
margin-bottom: 0;
}

View File

@ -183,22 +183,14 @@
.import-meta-value, .import-meta-value,
.import-meta-label { .import-meta-label {
padding: 2px 4px; padding: 2px 4px;
border: 1px solid gold;
} }
.import-meta-label { .import-meta-label {
border-radius: 2px 0 0 2px; border-radius: 2px 0 0 2px;
color: var(--highlight-color); color: var(--highlight-color);
border-right-width: 0;
}
.import-meta-title .import-meta-label {
border-right-width: 1px;
border-radius: 2px;
} }
.import-meta-value { .import-meta-value {
border-radius: 0 2px 2px 0;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
@ -242,7 +234,7 @@
.insert-mode-option { .insert-mode-option {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 5px; margin: 5px 0;
} }
.import-resize-option :checked + span, .import-resize-option :checked + span,
@ -250,11 +242,15 @@
color: var(--highlight-color); color: var(--highlight-color);
} }
.import-resize-option input,
.insert-mode-option input {
margin: 5px;
}
/** /**
* ADJUST SIZE * ADJUST SIZE
*/ */
.import-resize-anchor-info, .import-resize-anchor-info {
.import-resize-option-label {
margin-bottom: 10px; margin-bottom: 10px;
} }

View File

@ -135,6 +135,11 @@
cursor: default; cursor: default;
} }
.preview-tile .tile-overlay.tile-count.toggled {
background-color: gold;
color: black;
}
.preview-tile .tile-overlay.delete-frame-action { .preview-tile .tile-overlay.delete-frame-action {
top: 0; top: 0;
right: 0; right: 0;

View File

@ -25,7 +25,7 @@
.left-column { .left-column {
vertical-align: top; vertical-align: top;
height: 100%; height: 100%;
margin-right: 7px; padding-right: 7px;
flex-shrink: 0; flex-shrink: 0;
} }
@ -44,7 +44,8 @@
flex-direction: column; flex-direction: column;
position: relative; position: relative;
margin-left: 10px; /* Keep in sync with Constants.RIGHT_COLUMN_PADDING_LEFT */
padding-left: 10px;
/* Add some padding for the absolutely positioned .cursor-coordinates */ /* Add some padding for the absolutely positioned .cursor-coordinates */
padding-bottom: 20px; padding-bottom: 20px;
} }
@ -80,12 +81,23 @@
} }
.cursor-coordinates { .cursor-coordinates {
color:#888; color: var(--highlight-color);
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
font-family: monospace; font-family: monospace;
} }
.cursor-coordinates .drawing-zoom {
position: absolute;
top: -20px;
left: 1px;
}
.cursor-coordinates .frame-info {
line-height: 1.5;
text-align: left;
}
/** /**
* Canvases layout * Canvases layout
*/ */

View File

@ -60,22 +60,48 @@
} }
.settings-item-grid-size, .settings-item-grid-size,
.settings-item-grid-spacing,
.settings-item-grid-color { .settings-item-grid-color {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.settings-item-grid-size > label, .settings-item-grid-size > label,
.settings-item-grid-spacing > label,
.settings-item-grid-color > label { .settings-item-grid-color > label {
width: 65px; width: 65px;
flex-shrink: 0; flex-shrink: 0;
} }
.settings-item-grid-size .size-picker-option { .grid-spacing-container .size-picker-option[data-size='1'] {
padding: 7px;
}
.grid-spacing-container .size-picker-option[data-size='2'] {
padding: 6px;
}
.grid-spacing-container .size-picker-option[data-size='4'] {
padding: 5px;
}
.grid-spacing-container .size-picker-option[data-size='8'] {
padding: 4px;
}
.grid-spacing-container .size-picker-option[data-size='16'] {
padding: 3px;
}
.grid-spacing-container .size-picker-option[data-size='32'] {
padding: 2px;
}
.grid-spacing-container .size-picker-option[data-size='64'] {
padding: 1px;
}
.settings-item-grid-size .size-picker-option,
.settings-item-grid-spacing .size-picker-option {
border-color: #888; border-color: #888;
} }
.settings-item-grid-size .size-picker-option:not(.selected):hover { .settings-item-grid-size .size-picker-option:not(.selected):hover,
.settings-item-grid-spacing .size-picker-option:not(.selected):hover {
border-color: white; border-color: white;
} }

View File

@ -43,8 +43,29 @@
float : left; float : left;
} }
.gif-export-warning {
display: none;
}
.gif-export-warning.visible {
display: flex;
align-items: center;
border: 1px solid red;
padding: 5px;
margin: 5px 0;
}
.gif-export-warning-icon {
flex-shrink: 0;
margin-right: 5px;
}
.gif-export-warning-message {
font-weight: normal;
}
.preview-upload-ongoing:before{ .preview-upload-ongoing:before{
content: "Upload ongoing ..."; content: "Upload in progress...";
position: absolute; position: absolute;
display: block; display: block;
height: 100%; height: 100%;
@ -123,7 +144,8 @@
overflow: hidden; overflow: hidden;
} }
.export-panel-gif .button { .export-panel-gif .button,
.export-panel-png .button {
margin-right: 5px; margin-right: 5px;
width: 75px; width: 75px;
flex-shrink: 0; flex-shrink: 0;
@ -133,11 +155,6 @@
width: 50px; width: 50px;
} }
.png-export-dimension-info,
.png-export-datauri-info {
margin-left: 5px;
}
#png-export-columns, #png-export-columns,
#png-export-rows { #png-export-rows {
/* Override default textfield padding-right to keep the number spinners /* Override default textfield padding-right to keep the number spinners

View File

@ -52,7 +52,12 @@ body {
} }
.checkbox-fix { .checkbox-fix {
margin-left: 0; margin: 3px 3px 3px 0;
}
.checkbox-container {
display: flex;
align-items: center;
} }
.hidden { .hidden {

View File

@ -124,6 +124,14 @@
border-color: var(--highlight-color); border-color: var(--highlight-color);
} }
/**
* If the icon represents an enabled state, the border should always be gold.
*/
.preview-contextual-action-enabled {
color: var(--highlight-color);
border-color: var(--highlight-color);
}
/** /**
* Drop-down in preview size selection * Drop-down in preview size selection
*/ */
@ -195,6 +203,7 @@
@media (-webkit-min-device-pixel-ratio: 2), @media (-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi) { (min-resolution: 192dpi) {
.icon-minimap-popup-preview-arrow-white:hover {
background-image: url(../img/icons/minimap/minimap-popup-preview-arrow-gold@2x.png); background-image: url(../img/icons/minimap/minimap-popup-preview-arrow-gold@2x.png);
}
} }

View File

@ -66,12 +66,17 @@
cursor: pointer; cursor: pointer;
} }
.layer-item .layer-name { .layer-item .layer-name,
.layer-item .layer-name-input {
padding: 0 0 0 10px; padding: 0 0 0 10px;
flex: 1 auto; flex: 1 auto;
white-space: nowrap; white-space: nowrap;
} }
.layer-item .layer-name-input {
width: 80%;
}
.layer-item .layer-name.overflowing-name { .layer-item .layer-name.overflowing-name {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -82,7 +87,8 @@
} }
.layer-item-opacity { .layer-item-opacity {
padding-right: 8px; padding: 0 8px 0 8px;
flex: 0 auto;
} }
.current-layer-item, .current-layer-item,

View File

@ -94,7 +94,7 @@
line-height: 35px; line-height: 35px;
width: 100%; width: 100%;
color: gray; color: gray;
font-size: 0.7em; font-size: 0.8em;
font-style: italic; font-style: italic;
text-align: center text-align: center
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

View File

@ -67,6 +67,7 @@
</div> </div>
@@include('templates/misc-templates.html', {}) @@include('templates/misc-templates.html', {})
@@include('templates/data-uri-export.html', {})
@@include('templates/popup-preview.html', {}) @@include('templates/popup-preview.html', {})
<span class="cheatsheet-link icon-common-keyboard-gold" <span class="cheatsheet-link icon-common-keyboard-gold"
@ -75,9 +76,10 @@
rel="tooltip" data-placement="left" title="Performance problem detected, learn more.">&nbsp;</div> rel="tooltip" data-placement="left" title="Performance problem detected, learn more.">&nbsp;</div>
<!-- dialogs partials --> <!-- dialogs partials -->
@@include('templates/dialogs/create-palette.html', {}) @@include('templates/dialogs/browse-backups.html', {})
@@include('templates/dialogs/browse-local.html', {}) @@include('templates/dialogs/browse-local.html', {})
@@include('templates/dialogs/cheatsheet.html', {}) @@include('templates/dialogs/cheatsheet.html', {})
@@include('templates/dialogs/create-palette.html', {})
@@include('templates/dialogs/import.html', {}) @@include('templates/dialogs/import.html', {})
@@include('templates/dialogs/performance-info.html', {}) @@include('templates/dialogs/performance-info.html', {})
@@include('templates/dialogs/unsupported-browser.html', {}) @@include('templates/dialogs/unsupported-browser.html', {})

View File

@ -12,13 +12,15 @@ var Constants = {
MAX_HEIGHT : 1024, MAX_HEIGHT : 1024,
MAX_WIDTH : 1024, MAX_WIDTH : 1024,
MAX_PALETTE_COLORS : 100, MAX_PALETTE_COLORS : 256,
// allow current colors service to get up to 256 colors. // allow current colors service to get up to 256 colors.
// GIF generation is different if the color count goes over 256. // GIF generation is different if the color count goes over 256.
MAX_WORKER_COLORS : 256, MAX_WORKER_COLORS : 256,
PREVIEW_FILM_SIZE : 96, PREVIEW_FILM_SIZE : 96,
ANIMATED_PREVIEW_WIDTH : 200, ANIMATED_PREVIEW_WIDTH : 200,
// Keep in sync with padding-left: 10px in layout.css
RIGHT_COLUMN_PADDING_LEFT : 10,
DEFAULT_PEN_COLOR : '#000000', DEFAULT_PEN_COLOR : '#000000',
TRANSPARENT_COLOR : 'rgba(0, 0, 0, 0)', TRANSPARENT_COLOR : 'rgba(0, 0, 0, 0)',
@ -58,8 +60,11 @@ var Constants = {
// The datastore limit is 1 MiB, which we roughly approximate to 1 million characters. // The datastore limit is 1 MiB, which we roughly approximate to 1 million characters.
APPENGINE_SAVE_LIMIT : 1 * 1024 * 1024, APPENGINE_SAVE_LIMIT : 1 * 1024 * 1024,
// Message displayed when an action will lead to erase the current animation.
CONFIRM_OVERWRITE: 'This will replace your current animation, are you sure you want to continue?',
// SERVICE URLS // SERVICE URLS
APPENGINE_SAVE_URL : 'save', APPENGINE_SAVE_URL : 'save',
IMAGE_SERVICE_UPLOAD_URL : 'http://piskel-imgstore-b.appspot.com/__/upload', IMAGE_SERVICE_UPLOAD_URL : '{{protocol}}://piskel-imgstore-b.appspot.com/__/upload',
IMAGE_SERVICE_GET_URL : 'http://piskel-imgstore-b.appspot.com/img/' IMAGE_SERVICE_GET_URL : '{{protocol}}://piskel-imgstore-b.appspot.com/img/'
}; };

View File

@ -64,9 +64,10 @@ var Events = {
SELECTION_CREATED: 'SELECTION_CREATED', SELECTION_CREATED: 'SELECTION_CREATED',
SELECTION_MOVE_REQUEST: 'SELECTION_MOVE_REQUEST', SELECTION_MOVE_REQUEST: 'SELECTION_MOVE_REQUEST',
SELECTION_DISMISSED: 'SELECTION_DISMISSED', SELECTION_DISMISSED: 'SELECTION_DISMISSED',
SELECTION_COPY: 'SELECTION_COPY',
SELECTION_CUT: 'SELECTION_CUT', CLIPBOARD_COPY: 'CLIPBOARD_COPY',
SELECTION_PASTE: 'SELECTION_PASTE', CLIPBOARD_CUT: 'CLIPBOARD_CUT',
CLIPBOARD_PASTE: 'CLIPBOARD_PASTE',
SHOW_NOTIFICATION: 'SHOW_NOTIFICATION', SHOW_NOTIFICATION: 'SHOW_NOTIFICATION',
HIDE_NOTIFICATION: 'HIDE_NOTIFICATION', HIDE_NOTIFICATION: 'HIDE_NOTIFICATION',

View File

@ -10,14 +10,14 @@
ns.app = { ns.app = {
init : function () { init : function () {
// Run preferences migration scripts for version v0.12.0
pskl.UserSettings.migrate_to_v0_12();
/** /**
* When started from APP Engine, appEngineToken_ (Boolean) should be set on window.pskl * When started from APP Engine, appEngineToken_ (Boolean) should be set on window.pskl
*/ */
this.isAppEngineVersion = !!pskl.appEngineToken_; this.isAppEngineVersion = !!pskl.appEngineToken_;
// This id is used to keep track of sessions in the BackupService.
this.sessionId = pskl.utils.Uuid.generate();
this.shortcutService = new pskl.service.keyboard.ShortcutService(); this.shortcutService = new pskl.service.keyboard.ShortcutService();
this.shortcutService.init(); this.shortcutService.init();
@ -64,24 +64,24 @@
this.drawingController = new pskl.controller.DrawingController( this.drawingController = new pskl.controller.DrawingController(
this.piskelController, this.piskelController,
$('#drawing-canvas-container')); document.querySelector('#drawing-canvas-container'));
this.drawingController.init(); this.drawingController.init();
this.previewController = new pskl.controller.preview.PreviewController( this.previewController = new pskl.controller.preview.PreviewController(
this.piskelController, this.piskelController,
$('#animated-preview-canvas-container')); document.querySelector('#animated-preview-canvas-container'));
this.previewController.init(); this.previewController.init();
this.minimapController = new pskl.controller.MinimapController( this.minimapController = new pskl.controller.MinimapController(
this.piskelController, this.piskelController,
this.previewController, this.previewController,
this.drawingController, this.drawingController,
$('.minimap-container')); document.querySelector('.minimap-container'));
this.minimapController.init(); this.minimapController.init();
this.framesListController = new pskl.controller.FramesListController( this.framesListController = new pskl.controller.FramesListController(
this.piskelController, this.piskelController,
$('#preview-list-wrapper').get(0)); document.querySelector('#preview-list-wrapper'));
this.framesListController.init(); this.framesListController.init();
this.layersListController = new pskl.controller.LayersListController(this.piskelController); this.layersListController = new pskl.controller.LayersListController(this.piskelController);
@ -114,6 +114,9 @@
this.canvasBackgroundController = new pskl.controller.CanvasBackgroundController(); this.canvasBackgroundController = new pskl.controller.CanvasBackgroundController();
this.canvasBackgroundController.init(); this.canvasBackgroundController.init();
this.indexedDbStorageService = new pskl.service.storage.IndexedDbStorageService(this.piskelController);
this.indexedDbStorageService.init();
this.localStorageService = new pskl.service.storage.LocalStorageService(this.piskelController); this.localStorageService = new pskl.service.storage.LocalStorageService(this.piskelController);
this.localStorageService.init(); this.localStorageService.init();
@ -168,6 +171,9 @@
this.currentColorsService); this.currentColorsService);
this.performanceReportService.init(); this.performanceReportService.init();
this.clipboardService = new pskl.service.ClipboardService(this.piskelController);
this.clipboardService.init();
this.drawingLoop = new pskl.rendering.DrawingLoop(); this.drawingLoop = new pskl.rendering.DrawingLoop();
this.drawingLoop.addCallback(this.render, this); this.drawingLoop.addCallback(this.render, this);
this.drawingLoop.start(); this.drawingLoop.start();
@ -195,6 +201,11 @@
dialogId : 'unsupported-browser' dialogId : 'unsupported-browser'
}); });
} }
if (pskl.utils.Environment.isDebug()) {
pskl.app.shortcutService.registerShortcut(pskl.service.keyboard.Shortcuts.DEBUG.RELOAD_STYLES,
window.reloadStyles);
}
}, },
loadPiskel_ : function (piskelData) { loadPiskel_ : function (piskelData) {

View File

@ -17,6 +17,8 @@
$.subscribe(Events.DRAG_START, this.onDragStart_.bind(this)); $.subscribe(Events.DRAG_START, this.onDragStart_.bind(this));
$.subscribe(Events.DRAG_END, this.onDragEnd_.bind(this)); $.subscribe(Events.DRAG_END, this.onDragEnd_.bind(this));
$.subscribe(Events.FRAME_SIZE_CHANGED, this.redraw.bind(this)); $.subscribe(Events.FRAME_SIZE_CHANGED, this.redraw.bind(this));
$.subscribe(Events.ZOOM_CHANGED, this.redraw.bind(this));
$.subscribe(Events.PISKEL_RESET, this.redraw.bind(this));
this.redraw(); this.redraw();
}; };
@ -39,7 +41,18 @@
} }
} }
this.coordinatesContainer.innerHTML = this.getFrameSizeHTML_() + html; if (pskl.app.drawingController) {
var zoom = pskl.app.drawingController.compositeRenderer.getZoom().toFixed(2);
html += '<div class="drawing-zoom">x' + zoom + '</div>';
}
this.coordinatesContainer.innerHTML = this.getFrameSizeHTML_() + html + this.getCurrentFrameIndexHTML_();
};
ns.CursorCoordinatesController.prototype.getCurrentFrameIndexHTML_ = function () {
var currentFrameIndex = this.piskelController.getCurrentFrameIndex() + 1;
var frameCount = this.piskelController.getFrameCount();
return '<div class="frame-info">' + currentFrameIndex + '/' + frameCount + '</div>';
}; };
ns.CursorCoordinatesController.prototype.getFrameSizeHTML_ = function () { ns.CursorCoordinatesController.prototype.getFrameSizeHTML_ = function () {

View File

@ -22,7 +22,7 @@
var cfg = { var cfg = {
'zoom': this.calculateZoom_(), 'zoom': this.calculateZoom_(),
'supportGridRendering' : true, 'supportGridRendering' : false,
'height' : this.getContainerHeight_(), 'height' : this.getContainerHeight_(),
'width' : this.getContainerWidth_(), 'width' : this.getContainerWidth_(),
'xOffset' : 0, 'xOffset' : 0,
@ -30,9 +30,10 @@
}; };
this.overlayRenderer = new pskl.rendering.frame.CachedFrameRenderer(this.container, cfg, ['canvas-overlay']); this.overlayRenderer = new pskl.rendering.frame.CachedFrameRenderer(this.container, cfg, ['canvas-overlay']);
this.renderer = new pskl.rendering.frame.CachedFrameRenderer(this.container, cfg, ['drawing-canvas']);
this.onionSkinRenderer = pskl.rendering.OnionSkinRenderer.createInContainer(this.container, cfg, piskelController); this.onionSkinRenderer = pskl.rendering.OnionSkinRenderer.createInContainer(this.container, cfg, piskelController);
this.layersRenderer = new pskl.rendering.layer.LayersRenderer(this.container, cfg, piskelController); this.layersRenderer = new pskl.rendering.layer.LayersRenderer(this.container, cfg, piskelController);
cfg.supportGridRendering = true;
this.renderer = new pskl.rendering.frame.CachedFrameRenderer(this.container, cfg, ['drawing-canvas']);
this.compositeRenderer = new pskl.rendering.CompositeRenderer(); this.compositeRenderer = new pskl.rendering.CompositeRenderer();
this.compositeRenderer this.compositeRenderer
@ -50,12 +51,12 @@
ns.DrawingController.prototype.init = function () { ns.DrawingController.prototype.init = function () {
this.initMouseBehavior(); this.initMouseBehavior();
$.subscribe(Events.TOOL_SELECTED, $.proxy(function(evt, toolBehavior) { $.subscribe(Events.TOOL_SELECTED, (function(evt, toolBehavior) {
this.currentToolBehavior = toolBehavior; this.currentToolBehavior = toolBehavior;
this.overlayFrame.clear(); this.overlayFrame.clear();
}, this)); }).bind(this));
$(window).resize($.proxy(this.startResizeTimer_, this)); window.addEventListener('resize', this.startResizeTimer_.bind(this));
$.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this)); $.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
$.subscribe(Events.FRAME_SIZE_CHANGED, this.onFrameSizeChange_.bind(this)); $.subscribe(Events.FRAME_SIZE_CHANGED, this.onFrameSizeChange_.bind(this));
@ -76,13 +77,12 @@
}; };
ns.DrawingController.prototype.initMouseBehavior = function() { ns.DrawingController.prototype.initMouseBehavior = function() {
var body = $('body'); this.container.addEventListener('mousedown', this.onMousedown_.bind(this));
this.container.mousedown($.proxy(this.onMousedown_, this));
if (pskl.utils.UserAgent.isChrome || pskl.utils.UserAgent.isIE11) { if (pskl.utils.UserAgent.isChrome || pskl.utils.UserAgent.isIE11) {
this.container.on('mousewheel', $.proxy(this.onMousewheel_, this)); this.container.addEventListener('mousewheel', this.onMousewheel_.bind(this));
} else { } else {
this.container.on('wheel', $.proxy(this.onMousewheel_, this)); this.container.addEventListener('wheel', this.onMousewheel_.bind(this));
} }
window.addEventListener('mouseup', this.onMouseup_.bind(this)); window.addEventListener('mouseup', this.onMouseup_.bind(this));
@ -93,22 +93,20 @@
window.addEventListener('touchend', this.onTouchend_.bind(this)); window.addEventListener('touchend', this.onTouchend_.bind(this));
// Deactivate right click: // Deactivate right click:
body.contextmenu(this.onCanvasContextMenu_); document.body.addEventListener('contextmenu', this.onCanvasContextMenu_.bind(this));
}; };
ns.DrawingController.prototype.startResizeTimer_ = function () { ns.DrawingController.prototype.startResizeTimer_ = function () {
if (this.resizeTimer) { if (this.resizeTimer) {
window.clearInterval(this.resizeTimer); window.clearInterval(this.resizeTimer);
} }
this.resizeTimer = window.setTimeout($.proxy(this.afterWindowResize_, this), 200); this.resizeTimer = window.setTimeout(this.afterWindowResize_.bind(this), 200);
}; };
ns.DrawingController.prototype.afterWindowResize_ = function () { ns.DrawingController.prototype.afterWindowResize_ = function () {
var initialWidth = this.compositeRenderer.getDisplaySize().width; var initialWidth = this.compositeRenderer.getDisplaySize().width;
this.compositeRenderer.setDisplaySize(this.getContainerWidth_(), this.getContainerHeight_()); this.compositeRenderer.setDisplaySize(this.getContainerWidth_(), this.getContainerHeight_());
this.centerColumnWrapperHorizontally_();
var ratio = this.compositeRenderer.getDisplaySize().width / initialWidth; var ratio = this.compositeRenderer.getDisplaySize().width / initialWidth;
var newZoom = ratio * this.compositeRenderer.getZoom(); var newZoom = ratio * this.compositeRenderer.getZoom();
this.compositeRenderer.setZoom(newZoom); this.compositeRenderer.setZoom(newZoom);
@ -133,7 +131,6 @@
ns.DrawingController.prototype.onFrameSizeChange_ = function () { ns.DrawingController.prototype.onFrameSizeChange_ = function () {
this.compositeRenderer.setDisplaySize(this.getContainerWidth_(), this.getContainerHeight_()); this.compositeRenderer.setDisplaySize(this.getContainerWidth_(), this.getContainerHeight_());
this.centerColumnWrapperHorizontally_();
this.compositeRenderer.setZoom(this.calculateZoom_()); this.compositeRenderer.setZoom(this.calculateZoom_());
this.compositeRenderer.setOffset(0, 0); this.compositeRenderer.setOffset(0, 0);
$.publish(Events.ZOOM_CHANGED); $.publish(Events.ZOOM_CHANGED);
@ -241,8 +238,7 @@
$.publish(Events.CURSOR_MOVED, [coords.x, coords.y]); $.publish(Events.CURSOR_MOVED, [coords.x, coords.y]);
}; };
ns.DrawingController.prototype.onMousewheel_ = function (jQueryEvent) { ns.DrawingController.prototype.onMousewheel_ = function (evt) {
var evt = jQueryEvent.originalEvent;
// Ratio between wheelDeltaY (mousewheel event) and deltaY (wheel event) is -40 // Ratio between wheelDeltaY (mousewheel event) and deltaY (wheel event) is -40
var delta; var delta;
if (pskl.utils.UserAgent.isIE11) { if (pskl.utils.UserAgent.isIE11) {
@ -350,6 +346,8 @@
// Picking color after ALT+click or middle mouse button click. // Picking color after ALT+click or middle mouse button click.
this.pickColorAt_(coords); this.pickColorAt_(coords);
this.isPickingColor = false; this.isPickingColor = false;
// Flash the cursor to briefly show the colorpicker cursor.
this.flashColorPicker_();
} else if (isMiddleDrag) { } else if (isMiddleDrag) {
// Stop the drag handler after a middle button drag action. // Stop the drag handler after a middle button drag action.
this.dragHandler.stopDrag(); this.dragHandler.stopDrag();
@ -385,6 +383,16 @@
$.publish(evt, [color]); $.publish(evt, [color]);
}; };
ns.DrawingController.prototype.flashColorPicker_ = function () {
document.body.classList.add('tool-colorpicker');
document.body.classList.remove(this.currentToolBehavior.toolId);
window.clearTimeout(this.flashColorPickerTimer);
this.flashColorPickerTimer = window.setTimeout(function () {
document.body.classList.remove('tool-colorpicker');
document.body.classList.add(this.currentToolBehavior.toolId);
}.bind(this), 200);
};
/** /**
* Translate absolute x,y screen coordinates into sprite coordinates * Translate absolute x,y screen coordinates into sprite coordinates
* @param {Number} screenX * @param {Number} screenX
@ -403,7 +411,8 @@
* @private * @private
*/ */
ns.DrawingController.prototype.onCanvasContextMenu_ = function (event) { ns.DrawingController.prototype.onCanvasContextMenu_ = function (event) {
if ($(event.target).closest('#drawing-canvas-container').length) { // closest() not really available everywhere yet, just skip if missing.
if (event.target.closest && event.target.closest('#drawing-canvas-container')) {
// Deactivate right click on drawing canvas only. // Deactivate right click on drawing canvas only.
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@ -441,17 +450,21 @@
}; };
ns.DrawingController.prototype.getAvailableHeight_ = function () { ns.DrawingController.prototype.getAvailableHeight_ = function () {
return $('#main-wrapper').height(); return document.querySelector('#main-wrapper').getBoundingClientRect().height;
};
ns.DrawingController.prototype.getSelectorWidth_ = function (selector) {
return document.querySelector(selector).getBoundingClientRect().width;
}; };
ns.DrawingController.prototype.getAvailableWidth_ = function () { ns.DrawingController.prototype.getAvailableWidth_ = function () {
var leftSectionWidth = $('.left-column').outerWidth(true); var leftSectionWidth = this.getSelectorWidth_('.left-column');
var rightSectionWidth = $('.right-column').outerWidth(true); var rightSectionWidth = this.getSelectorWidth_('.right-column');
var toolsContainerWidth = $('#tool-section').outerWidth(true); var toolsContainerWidth = this.getSelectorWidth_('#tool-section');
var settingsContainerWidth = $('#application-action-section').outerWidth(true); var settingsContainerWidth = this.getSelectorWidth_('#application-action-section');
var usedWidth = leftSectionWidth + rightSectionWidth + toolsContainerWidth + settingsContainerWidth; var usedWidth = leftSectionWidth + rightSectionWidth + toolsContainerWidth + settingsContainerWidth;
var availableWidth = $('#main-wrapper').width() - usedWidth; var availableWidth = this.getSelectorWidth_('#main-wrapper') - usedWidth;
var comfortMargin = 10; var comfortMargin = 10;
return availableWidth - comfortMargin; return availableWidth - comfortMargin;
@ -465,17 +478,6 @@
return this.getAvailableWidth_(); return this.getAvailableWidth_();
}; };
/**
* @private
*/
ns.DrawingController.prototype.centerColumnWrapperHorizontally_ = function() {
var containerHeight = this.getContainerHeight_();
var verticalGapInPixel = Math.floor(($('#main-wrapper').height() - containerHeight) / 2);
$('#column-wrapper').css({
'top': verticalGapInPixel + 'px'
});
};
ns.DrawingController.prototype.getRenderer = function () { ns.DrawingController.prototype.getRenderer = function () {
return this.compositeRenderer; return this.compositeRenderer;
}; };

View File

@ -5,7 +5,8 @@
SELECT : 'select', SELECT : 'select',
CLONE : 'clone', CLONE : 'clone',
DELETE : 'delete', DELETE : 'delete',
NEW_FRAME : 'newframe' NEW_FRAME : 'newframe',
TOGGLE: 'toggle'
}; };
ns.FramesListController = function (piskelController, container) { ns.FramesListController = function (piskelController, container) {
@ -114,6 +115,8 @@
this.tiles.push(newtile); this.tiles.push(newtile);
this.previewList.insertBefore(newtile, this.addFrameTile); this.previewList.insertBefore(newtile, this.addFrameTile);
this.updateScrollerOverflows(); this.updateScrollerOverflows();
} else if (action == ACTION.TOGGLE) {
this.piskelController.toggleFrameVisibilityAt(index);
} }
this.flagForRedraw_(); this.flagForRedraw_();
@ -127,10 +130,18 @@
// Remove selected class // Remove selected class
this.tiles[i].classList.remove('selected'); this.tiles[i].classList.remove('selected');
// Remove toggle class
this.tiles[i].querySelector('.tile-count').classList.remove('toggled');
// Update tile numbers // Update tile numbers
this.tiles[i].setAttribute('data-tile-number', i); this.tiles[i].setAttribute('data-tile-number', i);
this.tiles[i].querySelector('.tile-count').innerHTML = (i + 1); this.tiles[i].querySelector('.tile-count').innerHTML = (i + 1);
// Update visibility
if (this.piskelController.hasVisibleFrameAt(i)) {
this.tiles[i].querySelector('.tile-count').classList.add('toggled');
}
// Check if any tile is updated // Check if any tile is updated
var hash = this.piskelController.getCurrentLayer().getFrameAt(i).getHash(); var hash = this.piskelController.getCurrentLayer().getFrameAt(i).getHash();
if (this.tiles[i].getAttribute('data-tile-hash') !== hash) { if (this.tiles[i].getAttribute('data-tile-hash') !== hash) {
@ -162,7 +173,10 @@
this.previewList.innerHTML = ''; this.previewList.innerHTML = '';
// Manually remove tooltips since mouseout events were shortcut by the DOM refresh: // Manually remove tooltips since mouseout events were shortcut by the DOM refresh:
$('.tooltip').remove(); var tooltips = document.querySelectorAll('.tooltip');
Array.prototype.forEach.call(tooltips, function (tooltip) {
tooltip.parentNode.removeChild(tooltip);
});
var frameCount = this.piskelController.getFrameCount(); var frameCount = this.piskelController.getFrameCount();
@ -190,8 +204,8 @@
ns.FramesListController.prototype.initDragndropBehavior_ = function () { ns.FramesListController.prototype.initDragndropBehavior_ = function () {
$(this.previewList).sortable({ $(this.previewList).sortable({
placeholder: 'preview-tile preview-tile-drop-proxy', placeholder: 'preview-tile preview-tile-drop-proxy',
update: $.proxy(this.onUpdate_, this), update: this.onUpdate_.bind(this),
stop: $.proxy(this.onSortableStop_, this), stop: this.onSortableStop_.bind(this),
items: '.preview-tile', items: '.preview-tile',
axis: 'y', axis: 'y',
tolerance: 'pointer' tolerance: 'pointer'
@ -203,8 +217,10 @@
* @private * @private
*/ */
ns.FramesListController.prototype.onUpdate_ = function (event, ui) { ns.FramesListController.prototype.onUpdate_ = function (event, ui) {
var originFrameId = parseInt(ui.item.data('tile-number'), 10); var movedItem = ui.item.get(0);
var targetInsertionId = $('.preview-tile').index(ui.item); var originFrameId = parseInt(movedItem.dataset.tileNumber, 10);
var tiles = document.querySelectorAll('.preview-tile');
var targetInsertionId = Array.prototype.indexOf.call(tiles, movedItem);
this.piskelController.moveFrame(originFrameId, targetInsertionId); this.piskelController.moveFrame(originFrameId, targetInsertionId);
this.piskelController.setCurrentFrameIndex(targetInsertionId); this.piskelController.setCurrentFrameIndex(targetInsertionId);
@ -220,14 +236,13 @@
ns.FramesListController.prototype.onSortableStop_ = function (event, ui) { ns.FramesListController.prototype.onSortableStop_ = function (event, ui) {
this.justDropped = true; this.justDropped = true;
this.resizeTimer = window.setTimeout($.proxy(function() { this.resizeTimer = window.setTimeout((function() {
this.justDropped = false; this.justDropped = false;
}, this), 200); }).bind(this), 200);
}; };
/** /**
* @private * @private
* TODO(vincz): clean this giant rendering function & remove listeners.
*/ */
ns.FramesListController.prototype.createPreviewTile_ = function(tileNumber) { ns.FramesListController.prototype.createPreviewTile_ = function(tileNumber) {
var currentFrame = this.piskelController.getCurrentLayer().getFrameAt(tileNumber); var currentFrame = this.piskelController.getCurrentLayer().getFrameAt(tileNumber);
@ -286,8 +301,12 @@
previewTileRoot.appendChild(dndHandle); previewTileRoot.appendChild(dndHandle);
// Add tile count // Add tile count
var tileCount = document.createElement('div'); var tileCount = document.createElement('button');
tileCount.className = 'tile-overlay tile-count'; tileCount.setAttribute('rel', 'tooltip');
tileCount.setAttribute('title', 'Toggle for preview');
tileCount.setAttribute('data-tile-number', tileNumber);
tileCount.setAttribute('data-tile-action', ACTION.TOGGLE);
tileCount.className = 'tile-overlay tile-count toggle-frame-action';
tileCount.innerHTML = tileNumber + 1; tileCount.innerHTML = tileNumber + 1;
previewTileRoot.appendChild(tileCount); previewTileRoot.appendChild(tileCount);

View File

@ -1,15 +1,18 @@
(function () { (function () {
var ns = $.namespace('pskl.controller'); var ns = $.namespace('pskl.controller');
var TOGGLE_LAYER_SHORTCUT = 'alt+L';
ns.LayersListController = function (piskelController) { ns.LayersListController = function (piskelController) {
this.piskelController = piskelController; this.piskelController = piskelController;
this.layerPreviewShortcut = pskl.service.keyboard.Shortcuts.MISC.LAYER_PREVIEW; this.layerPreviewShortcut = pskl.service.keyboard.Shortcuts.MISC.LAYER_PREVIEW;
this.startRenamingCurrentLayer_ = this.startRenamingCurrentLayer_.bind(this);
this.onRenameInput_ = this.onRenameInput_.bind(this);
}; };
ns.LayersListController.prototype.init = function () { ns.LayersListController.prototype.init = function () {
this.isRenaming = false;
this.layerItemTemplate_ = pskl.utils.Template.get('layer-item-template'); this.layerItemTemplate_ = pskl.utils.Template.get('layer-item-template');
this.layerNameInputTemplate_ = pskl.utils.Template.get('layer-name-input-template');
this.rootEl = document.querySelector('.layers-list-container'); this.rootEl = document.querySelector('.layers-list-container');
this.layersListEl = document.querySelector('.layers-list'); this.layersListEl = document.querySelector('.layers-list');
this.toggleLayerPreviewEl = document.querySelector('.layers-toggle-preview'); this.toggleLayerPreviewEl = document.querySelector('.layers-toggle-preview');
@ -24,7 +27,7 @@
this.updateToggleLayerPreview_(); this.updateToggleLayerPreview_();
$.subscribe(Events.PISKEL_RESET, this.renderLayerList_.bind(this)); $.subscribe(Events.PISKEL_RESET, this.renderLayerList_.bind(this));
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this)); $.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
}; };
ns.LayersListController.prototype.renderLayerList_ = function () { ns.LayersListController.prototype.renderLayerList_ = function () {
@ -77,7 +80,6 @@
ns.LayersListController.prototype.updateButtonStatus_ = function () { ns.LayersListController.prototype.updateButtonStatus_ = function () {
var layers = this.piskelController.getLayers(); var layers = this.piskelController.getLayers();
var currentLayer = this.piskelController.getCurrentLayer();
var index = this.piskelController.getCurrentLayerIndex(); var index = this.piskelController.getCurrentLayerIndex();
var isLast = index === 0; var isLast = index === 0;
@ -118,6 +120,7 @@
ns.LayersListController.prototype.addLayerItem = function (layer, index) { ns.LayersListController.prototype.addLayerItem = function (layer, index) {
var isSelected = this.piskelController.getCurrentLayer() === layer; var isSelected = this.piskelController.getCurrentLayer() === layer;
var isRenaming = isSelected && this.isRenaming;
var layerItemHtml = pskl.utils.Template.replace(this.layerItemTemplate_, { var layerItemHtml = pskl.utils.Template.replace(this.layerItemTemplate_, {
'layername' : layer.getName(), 'layername' : layer.getName(),
'layerindex' : index, 'layerindex' : index,
@ -127,10 +130,36 @@
var layerItem = pskl.utils.Template.createFromHTML(layerItemHtml); var layerItem = pskl.utils.Template.createFromHTML(layerItemHtml);
this.layersListEl.insertBefore(layerItem, this.layersListEl.firstChild); this.layersListEl.insertBefore(layerItem, this.layersListEl.firstChild);
if (layerItem.offsetWidth < layerItem.scrollWidth) { if (layerItem.offsetWidth < layerItem.scrollWidth) {
$(layerItem).find('.layer-name') var layerNameEl = layerItem.querySelector('.layer-name');
.addClass('overflowing-name') layerNameEl.classList.add('overflowing-name');
.attr('title', layer.getName()) layerNameEl.setAttribute('title', layer.getName());
.tooltip(); layerNameEl.setAttribute('rel', 'tooltip');
}
if (isSelected) {
layerItem.removeEventListener('dblclick', this.startRenamingCurrentLayer_);
layerItem.addEventListener('dblclick', this.startRenamingCurrentLayer_);
}
if (isRenaming) {
var layerNameInputHtml = pskl.utils.Template.replace(this.layerNameInputTemplate_, {
'layername' : layer.getName()
});
var layerNameInput = pskl.utils.Template.createFromHTML(layerNameInputHtml);
var layerNameEl = layerItem.querySelector('.layer-name');
layerItem.replaceChild(layerNameInput, layerNameEl);
layerNameInput.removeEventListener('blur', this.onRenameInput_);
layerNameInput.removeEventListener('keydown', this.onRenameInput_);
layerNameInput.addEventListener('blur', this.onRenameInput_);
layerNameInput.addEventListener('keydown', this.onRenameInput_);
layerNameInput.focus();
layerNameInput.select();
}
var opacity = layer.getOpacity();
if (opacity == 1) {
layerItem.querySelector('.layer-item-opacity').style.color = '#ffd700';
} else if (opacity == 0) {
layerItem.querySelector('.layer-item-opacity').style.color = '#969696';
} else {
layerItem.querySelector('.layer-item-opacity').style.color = '#ffffff';
} }
}; };
@ -140,8 +169,13 @@
if (el.classList.contains('button')) { if (el.classList.contains('button')) {
this.onButtonClick_(el, evt); this.onButtonClick_(el, evt);
} else if (el.classList.contains('layer-name')) { } else if (el.classList.contains('layer-name')) {
var currentIndex = this.piskelController.getCurrentLayerIndex();
index = pskl.utils.Dom.getData(el, 'layerIndex'); index = pskl.utils.Dom.getData(el, 'layerIndex');
if (index != currentIndex) {
var currentItem = el.parentElement.parentElement.querySelector('.current-layer-item');
currentItem.removeEventListener('dblclick', this.startRenamingCurrentLayer_);
this.piskelController.setCurrentLayerIndex(parseInt(index, 10)); this.piskelController.setCurrentLayerIndex(parseInt(index, 10));
}
} else if (el.classList.contains('layer-item-opacity')) { } else if (el.classList.contains('layer-item-opacity')) {
index = pskl.utils.Dom.getData(el, 'layerIndex'); index = pskl.utils.Dom.getData(el, 'layerIndex');
var layer = this.piskelController.getLayerAt(parseInt(index, 10)); var layer = this.piskelController.getLayerAt(parseInt(index, 10));
@ -150,16 +184,31 @@
} }
}; };
ns.LayersListController.prototype.renameCurrentLayer_ = function () { ns.LayersListController.prototype.startRenamingCurrentLayer_ = function () {
var layer = this.piskelController.getCurrentLayer(); this.isRenaming = true;
var name = window.prompt('Please enter the layer name', layer.getName());
if (name) {
var index = this.piskelController.getCurrentLayerIndex();
this.piskelController.renameLayerAt(index, name);
this.renderLayerList_(); this.renderLayerList_();
};
ns.LayersListController.prototype.onRenameInput_ = function (evt) {
var el = evt.target || evt.srcElement;
if (evt.key === 'Enter') {
this.finishRenamingCurrentLayer_(el, el.value);
} else if (!evt.key || evt.key === 'Escape') {
this.finishRenamingCurrentLayer_(el);
} }
}; };
ns.LayersListController.prototype.finishRenamingCurrentLayer_ = function (input, newName) {
if (newName) {
var index = this.piskelController.getCurrentLayerIndex();
this.piskelController.renameLayerAt(index, newName);
}
input.removeEventListener('blur', this.onRenameInput_);
input.removeEventListener('keydown', this.onRenameInput_);
this.isRenaming = false;
this.renderLayerList_();
};
ns.LayersListController.prototype.mergeDownCurrentLayer_ = function () { ns.LayersListController.prototype.mergeDownCurrentLayer_ = function () {
var index = this.piskelController.getCurrentLayerIndex(); var index = this.piskelController.getCurrentLayerIndex();
this.piskelController.mergeDownLayerAt(index); this.piskelController.mergeDownLayerAt(index);
@ -183,7 +232,7 @@
} else if (action == 'merge') { } else if (action == 'merge') {
this.mergeDownCurrentLayer_(); this.mergeDownCurrentLayer_();
} else if (action == 'edit') { } else if (action == 'edit') {
this.renameCurrentLayer_(); this.startRenamingCurrentLayer_();
} }
}; };

View File

@ -16,14 +16,14 @@
this.minimapEl = document.createElement('DIV'); this.minimapEl = document.createElement('DIV');
this.minimapEl.className = 'minimap-crop-frame'; this.minimapEl.className = 'minimap-crop-frame';
this.minimapEl.style.display = 'none'; this.minimapEl.style.display = 'none';
$(this.container).append(this.minimapEl); this.container.appendChild(this.minimapEl);
// Init mouse events // Init mouse events
$(this.container).mousedown(this.onMinimapMousedown_.bind(this)); this.container.addEventListener('mousedown', this.onMinimapMousedown_.bind(this));
$('body').mousemove(this.onMinimapMousemove_.bind(this)); document.body.addEventListener('mousemove', this.onMinimapMousemove_.bind(this));
$('body').mouseup(this.onMinimapMouseup_.bind(this)); document.body.addEventListener('mouseup', this.onMinimapMouseup_.bind(this));
$.subscribe(Events.ZOOM_CHANGED, $.proxy(this.renderMinimap_, this)); $.subscribe(Events.ZOOM_CHANGED, this.renderMinimap_.bind(this));
}; };
ns.MinimapController.prototype.renderMinimap_ = function () { ns.MinimapController.prototype.renderMinimap_ = function () {
@ -40,8 +40,9 @@
var minimapSize = this.getMinimapSize_(); var minimapSize = this.getMinimapSize_();
var previewSize = this.getPreviewSize_(); var previewSize = this.getPreviewSize_();
var containerHeight = this.container.height(); var containerRect = this.container.getBoundingClientRect();
var containerWidth = this.container.width(); var containerHeight = containerRect.height;
var containerWidth = containerRect.width;
// offset(x, y) in frame pixels // offset(x, y) in frame pixels
var offset = this.drawingController.getRenderer().getOffset(); var offset = this.drawingController.getRenderer().getOffset();
@ -60,7 +61,7 @@
this.minimapEl.style.display = 'block'; this.minimapEl.style.display = 'block';
this.minimapEl.style.width = Math.min(minimapSize.width, containerWidth) + 'px'; this.minimapEl.style.width = Math.min(minimapSize.width, containerWidth) + 'px';
this.minimapEl.style.height = Math.min(minimapSize.height, containerHeight) + 'px'; this.minimapEl.style.height = Math.min(minimapSize.height, containerHeight) + 'px';
this.minimapEl.style.left = Math.max(0, left) + 'px'; this.minimapEl.style.left = (Math.max(0, left) + Constants.RIGHT_COLUMN_PADDING_LEFT) + 'px';
this.minimapEl.style.top = Math.max(0, top) + 'px'; this.minimapEl.style.top = Math.max(0, top) + 'px';
this.isVisible = true; this.isVisible = true;

View File

@ -7,8 +7,8 @@
* @public * @public
*/ */
ns.NotificationController.prototype.init = function() { ns.NotificationController.prototype.init = function() {
$.subscribe(Events.SHOW_NOTIFICATION, $.proxy(this.displayMessage_, this)); $.subscribe(Events.SHOW_NOTIFICATION, this.displayMessage_.bind(this));
$.subscribe(Events.HIDE_NOTIFICATION, $.proxy(this.removeMessage_, this)); $.subscribe(Events.HIDE_NOTIFICATION, this.removeMessage_.bind(this));
}; };
/** /**
@ -35,9 +35,9 @@
* @private * @private
*/ */
ns.NotificationController.prototype.removeMessage_ = function (evt) { ns.NotificationController.prototype.removeMessage_ = function (evt) {
var message = $('#user-message'); var message = document.querySelector('#user-message');
if (message.length) { if (message) {
message.remove(); message.parentNode.removeChild(message);
} }
}; };
})(); })();

View File

@ -35,24 +35,24 @@
// Initialize colorpickers: // Initialize colorpickers:
var colorPicker = $('#color-picker'); var colorPicker = $('#color-picker');
colorPicker.spectrum($.extend({color: Constants.DEFAULT_PEN_COLOR}, spectrumCfg)); colorPicker.spectrum($.extend({color: Constants.DEFAULT_PEN_COLOR}, spectrumCfg));
colorPicker.change({isPrimary : true}, $.proxy(this.onPickerChange_, this)); colorPicker.change({isPrimary : true}, this.onPickerChange_.bind(this));
this.setTitleOnPicker_(Constants.DEFAULT_PEN_COLOR, colorPicker); this.setTitleOnPicker_(Constants.DEFAULT_PEN_COLOR, colorPicker.get(0));
var secondaryColorPicker = $('#secondary-color-picker'); var secondaryColorPicker = $('#secondary-color-picker');
secondaryColorPicker.spectrum($.extend({color: Constants.TRANSPARENT_COLOR}, spectrumCfg)); secondaryColorPicker.spectrum($.extend({color: Constants.TRANSPARENT_COLOR}, spectrumCfg));
secondaryColorPicker.change({isPrimary : false}, $.proxy(this.onPickerChange_, this)); secondaryColorPicker.change({isPrimary : false}, this.onPickerChange_.bind(this));
this.setTitleOnPicker_(Constants.TRANSPARENT_COLOR, secondaryColorPicker); this.setTitleOnPicker_(Constants.TRANSPARENT_COLOR, secondaryColorPicker.get(0));
var swapColorsIcon = $('.swap-colors-button'); var swapColorsIcon = document.querySelector('.swap-colors-button');
swapColorsIcon.click(this.swapColors.bind(this)); swapColorsIcon.addEventListener('click', this.swapColors.bind(this));
}; };
/** /**
* @private * @private
*/ */
ns.PaletteController.prototype.onPickerChange_ = function(evt, isPrimary) { ns.PaletteController.prototype.onPickerChange_ = function(evt) {
var inputPicker = $(evt.target); var inputPicker = evt.target;
var color = inputPicker.val(); var color = inputPicker.value;
if (color != Constants.TRANSPARENT_COLOR) { if (color != Constants.TRANSPARENT_COLOR) {
// Unless the color is TRANSPARENT_COLOR, format it to hexstring, as // Unless the color is TRANSPARENT_COLOR, format it to hexstring, as
@ -71,7 +71,6 @@
* @private * @private
*/ */
ns.PaletteController.prototype.onColorSelected_ = function(args, evt, color) { ns.PaletteController.prototype.onColorSelected_ = function(args, evt, color) {
var inputPicker = $(evt.target);
if (args.isPrimary) { if (args.isPrimary) {
this.setPrimaryColor_(color); this.setPrimaryColor_(color);
} else { } else {
@ -80,12 +79,12 @@
}; };
ns.PaletteController.prototype.setPrimaryColor_ = function (color) { ns.PaletteController.prototype.setPrimaryColor_ = function (color) {
this.updateColorPicker_(color, $('#color-picker')); this.updateColorPicker_(color, document.querySelector('#color-picker'));
$.publish(Events.PRIMARY_COLOR_SELECTED, [color]); $.publish(Events.PRIMARY_COLOR_SELECTED, [color]);
}; };
ns.PaletteController.prototype.setSecondaryColor_ = function (color) { ns.PaletteController.prototype.setSecondaryColor_ = function (color) {
this.updateColorPicker_(color, $('#secondary-color-picker')); this.updateColorPicker_(color, document.querySelector('#secondary-color-picker'));
$.publish(Events.SECONDARY_COLOR_SELECTED, [color]); $.publish(Events.SECONDARY_COLOR_SELECTED, [color]);
}; };
@ -104,6 +103,7 @@
* @private * @private
*/ */
ns.PaletteController.prototype.updateColorPicker_ = function (color, colorPicker) { ns.PaletteController.prototype.updateColorPicker_ = function (color, colorPicker) {
var jqueryColorPicker = $(colorPicker);
if (color == Constants.TRANSPARENT_COLOR) { if (color == Constants.TRANSPARENT_COLOR) {
// We can set the current palette color to transparent. // We can set the current palette color to transparent.
// You can then combine this transparent color with an advanced // You can then combine this transparent color with an advanced
@ -114,17 +114,17 @@
// The colorpicker can't be set to a transparent state. // The colorpicker can't be set to a transparent state.
// We set its background to white and insert the // We set its background to white and insert the
// string "TRANSPARENT" to mimic this state: // string "TRANSPARENT" to mimic this state:
colorPicker.spectrum('set', Constants.TRANSPARENT_COLOR); jqueryColorPicker.spectrum('set', Constants.TRANSPARENT_COLOR);
colorPicker.val(Constants.TRANSPARENT_COLOR); colorPicker.value = Constants.TRANSPARENT_COLOR;
} else { } else {
colorPicker.spectrum('set', color); jqueryColorPicker.spectrum('set', color);
} }
this.setTitleOnPicker_(color, colorPicker); this.setTitleOnPicker_(color, colorPicker);
}; };
ns.PaletteController.prototype.setTitleOnPicker_ = function (title, colorPicker) { ns.PaletteController.prototype.setTitleOnPicker_ = function (title, colorPicker) {
var parent = colorPicker.parent(); var parent = colorPicker.parentNode;
title = parent.data('initial-title') + '<br/>' + title; title = parent.dataset.initialTitle + '<br/>' + title;
parent.attr('data-original-title', title); parent.dataset.originalTitle = title;
}; };
})(); })();

View File

@ -29,7 +29,7 @@
$.subscribe(Events.CURRENT_COLORS_UPDATED, this.fillColorListContainer.bind(this)); $.subscribe(Events.CURRENT_COLORS_UPDATED, this.fillColorListContainer.bind(this));
$.subscribe(Events.PRIMARY_COLOR_SELECTED, this.highlightSelectedColors.bind(this)); $.subscribe(Events.PRIMARY_COLOR_SELECTED, this.highlightSelectedColors.bind(this));
$.subscribe(Events.SECONDARY_COLOR_SELECTED, this.highlightSelectedColors.bind(this)); $.subscribe(Events.SECONDARY_COLOR_SELECTED, this.highlightSelectedColors.bind(this));
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this)); $.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
var shortcuts = pskl.service.keyboard.Shortcuts; var shortcuts = pskl.service.keyboard.Shortcuts;
pskl.app.shortcutService.registerShortcut(shortcuts.COLOR.PREVIOUS_COLOR, this.selectPreviousColor_.bind(this)); pskl.app.shortcutService.registerShortcut(shortcuts.COLOR.PREVIOUS_COLOR, this.selectPreviousColor_.bind(this));
@ -54,7 +54,9 @@
var colors = this.getSelectedPaletteColors_(); var colors = this.getSelectedPaletteColors_();
if (colors.length > 0) { if (colors.length > 0) {
var html = colors.map(function (color, index) { var html = colors.filter(function (color) {
return !!color;
}).map(function (color, index) {
return pskl.utils.Template.replace(this.paletteColorTemplate_, { return pskl.utils.Template.replace(this.paletteColorTemplate_, {
color : color, color : color,
index : index + 1, index : index + 1,

View File

@ -10,9 +10,9 @@
}; };
ns.ProgressBarController.prototype.init = function () { ns.ProgressBarController.prototype.init = function () {
$.subscribe(Events.SHOW_PROGRESS, $.proxy(this.showProgress_, this)); $.subscribe(Events.SHOW_PROGRESS, this.showProgress_.bind(this));
$.subscribe(Events.UPDATE_PROGRESS, $.proxy(this.updateProgress_, this)); $.subscribe(Events.UPDATE_PROGRESS, this.updateProgress_.bind(this));
$.subscribe(Events.HIDE_PROGRESS, $.proxy(this.hideProgress_, this)); $.subscribe(Events.HIDE_PROGRESS, this.hideProgress_.bind(this));
}; };
ns.ProgressBarController.prototype.showProgress_ = function (event, progressInfo) { ns.ProgressBarController.prototype.showProgress_ = function (event, progressInfo) {

View File

@ -35,7 +35,8 @@
// Set SimplePen as default selected tool: // Set SimplePen as default selected tool:
this.selectTool_(this.tools[0]); this.selectTool_(this.tools[0]);
// Activate listener on tool panel: // Activate listener on tool panel:
$('#tool-section').mousedown($.proxy(this.onToolIconClicked_, this)); var toolSection = document.querySelector('#tool-section');
toolSection.addEventListener('mousedown', this.onToolIconClicked_.bind(this));
$.subscribe(Events.SELECT_TOOL, this.onSelectToolEvent_.bind(this)); $.subscribe(Events.SELECT_TOOL, this.onSelectToolEvent_.bind(this));
$.subscribe(Events.SHORTCUTS_CHANGED, this.createToolsDom_.bind(this)); $.subscribe(Events.SHORTCUTS_CHANGED, this.createToolsDom_.bind(this));
@ -45,14 +46,14 @@
* @private * @private
*/ */
ns.ToolController.prototype.activateToolOnStage_ = function(tool) { ns.ToolController.prototype.activateToolOnStage_ = function(tool) {
var stage = $('body'); var stage = document.body;
var previousSelectedToolClass = stage.data('selected-tool-class'); var previousSelectedToolClass = stage.dataset.selectedToolClass;
if (previousSelectedToolClass) { if (previousSelectedToolClass) {
stage.removeClass(previousSelectedToolClass); stage.classList.remove(previousSelectedToolClass);
stage.removeClass(pskl.tools.drawing.Move.TOOL_ID); stage.classList.remove(pskl.tools.drawing.Move.TOOL_ID);
} }
stage.addClass(tool.toolId); stage.classList.add(tool.toolId);
stage.data('selected-tool-class', tool.toolId); stage.dataset.selectedToolClass = tool.toolId;
}; };
ns.ToolController.prototype.onSelectToolEvent_ = function(event, toolId) { ns.ToolController.prototype.onSelectToolEvent_ = function(event, toolId) {
@ -69,11 +70,13 @@
this.currentSelectedTool = tool; this.currentSelectedTool = tool;
this.activateToolOnStage_(this.currentSelectedTool); this.activateToolOnStage_(this.currentSelectedTool);
var selectedToolElement = $('#tool-section .tool-icon.selected'); var selectedToolElement = document.querySelector('#tool-section .tool-icon.selected');
var toolElement = $('[data-tool-id=' + tool.toolId + ']'); if (selectedToolElement) {
selectedToolElement.classList.remove('selected');
}
selectedToolElement.removeClass('selected'); var toolElement = document.querySelector('[data-tool-id=' + tool.toolId + ']');
toolElement.addClass('selected'); toolElement.classList.add('selected');
$.publish(Events.TOOL_SELECTED, [tool]); $.publish(Events.TOOL_SELECTED, [tool]);
}; };
@ -82,11 +85,11 @@
* @private * @private
*/ */
ns.ToolController.prototype.onToolIconClicked_ = function(evt) { ns.ToolController.prototype.onToolIconClicked_ = function(evt) {
var target = $(evt.target); var target = evt.target;
var clickedTool = target.closest('.tool-icon'); var clickedTool = pskl.utils.Dom.getParentWithData(target, 'toolId');
if (clickedTool.length) { if (clickedTool) {
var toolId = clickedTool.data().toolId; var toolId = clickedTool.dataset.toolId;
var tool = this.getToolById_(toolId); var tool = this.getToolById_(toolId);
if (tool) { if (tool) {
this.selectTool_(tool); this.selectTool_(tool);
@ -116,7 +119,7 @@
var tool = this.tools[i]; var tool = this.tools[i];
html += this.toolIconBuilder.createIcon(tool); html += this.toolIconBuilder.createIcon(tool);
} }
$('#tools-container').html(html); document.querySelector('#tools-container').innerHTML = html;
}; };
ns.ToolController.prototype.addKeyboardShortcuts_ = function () { ns.ToolController.prototype.addKeyboardShortcuts_ = function () {

View File

@ -10,13 +10,12 @@
this.localStorageItemTemplate_ = pskl.utils.Template.get('local-storage-item-template'); this.localStorageItemTemplate_ = pskl.utils.Template.get('local-storage-item-template');
this.service_ = pskl.app.localStorageService; this.service_ = pskl.app.indexedDbStorageService;
this.piskelList = $('.local-piskel-list'); this.piskelList = document.querySelector('.local-piskel-list');
this.prevSessionContainer = $('.previous-session');
this.fillLocalPiskelsList_(); this.fillLocalPiskelsList_();
this.piskelList.click(this.onPiskelsListClick_.bind(this)); this.piskelList.addEventListener('click', this.onPiskelsListClick_.bind(this));
}; };
ns.BrowseLocalController.prototype.onPiskelsListClick_ = function (evt) { ns.BrowseLocalController.prototype.onPiskelsListClick_ = function (evt) {
@ -36,9 +35,8 @@
}; };
ns.BrowseLocalController.prototype.fillLocalPiskelsList_ = function () { ns.BrowseLocalController.prototype.fillLocalPiskelsList_ = function () {
this.service_.getKeys().then(function (keys) {
var html = ''; var html = '';
var keys = this.service_.getKeys();
keys.sort(function (k1, k2) { keys.sort(function (k1, k2) {
if (k1.date < k2.date) {return 1;} if (k1.date < k2.date) {return 1;}
if (k1.date > k2.date) {return -1;} if (k1.date > k2.date) {return -1;}
@ -53,7 +51,8 @@
}); });
}).bind(this)); }).bind(this));
var tableBody_ = this.piskelList.get(0).tBodies[0]; var tableBody_ = this.piskelList.tBodies[0];
tableBody_.innerHTML = html; tableBody_.innerHTML = html;
}.bind(this));
}; };
})(); })();

View File

@ -25,6 +25,10 @@
'unsupported-browser' : { 'unsupported-browser' : {
template : 'templates/dialogs/unsupported-browser.html', template : 'templates/dialogs/unsupported-browser.html',
controller : ns.UnsupportedBrowserController controller : ns.UnsupportedBrowserController
},
'browse-backups' : {
template : 'templates/dialogs/browse-backups.html',
controller : ns.backups.BrowseBackups
} }
}; };

View File

@ -0,0 +1,81 @@
(function () {
var ns = $.namespace('pskl.controller.dialogs.backups');
var stepDefinitions = {
'SELECT_SESSION' : {
controller : ns.steps.SelectSession,
template : 'backups-select-session'
},
'SESSION_DETAILS' : {
controller : ns.steps.SessionDetails,
template : 'backups-session-details'
},
};
ns.BrowseBackups = function (piskelController, args) {
this.piskelController = piskelController;
// Backups data object used by steps to communicate and share their
// results.
this.backupsData = {
sessions: [],
selectedSession : null
};
};
pskl.utils.inherit(ns.BrowseBackups, pskl.controller.dialogs.AbstractDialogController);
ns.BrowseBackups.prototype.init = function () {
this.superclass.init.call(this);
// Prepare wizard steps.
this.steps = this.createSteps_();
// Start wizard widget.
var wizardContainer = document.querySelector('.backups-wizard-container');
this.wizard = new pskl.widgets.Wizard(this.steps, wizardContainer);
this.wizard.init();
this.wizard.goTo('SELECT_SESSION');
};
ns.BrowseBackups.prototype.back = function () {
this.wizard.back();
this.wizard.getCurrentStep().instance.onShow();
};
ns.BrowseBackups.prototype.next = function () {
var step = this.wizard.getCurrentStep();
if (step.name === 'SELECT_SESSION') {
this.wizard.goTo('SESSION_DETAILS');
}
};
ns.BrowseBackups.prototype.destroy = function (file) {
Object.keys(this.steps).forEach(function (stepName) {
var step = this.steps[stepName];
step.instance.destroy();
step.instance = null;
step.el = null;
}.bind(this));
this.superclass.destroy.call(this);
};
ns.BrowseBackups.prototype.createSteps_ = function () {
var steps = {};
Object.keys(stepDefinitions).forEach(function (stepName) {
var definition = stepDefinitions[stepName];
var el = pskl.utils.Template.getAsHTML(definition.template);
var instance = new definition.controller(this.piskelController, this, el);
instance.init();
steps[stepName] = {
name: stepName,
el: el,
instance: instance
};
}.bind(this));
return steps;
};
})();

View File

@ -0,0 +1,101 @@
(function () {
var ns = $.namespace('pskl.controller.dialogs.backups.steps');
// Should match the transition duration for.session-item defined in dialogs-browse-backups.css
var DELETE_TRANSITION_DURATION = 500;
/**
* Helper that returns a promise that will resolve after waiting for a
* given time (in ms).
*
* @param {Number} time
* The time to wait.
* @return {Promise} promise that resolves after time.
*/
var wait = function (time) {
var deferred = Q.defer();
setTimeout(function () {
deferred.resolve();
}, time);
return deferred.promise;
};
ns.SelectSession = function (piskelController, backupsController, container) {
this.piskelController = piskelController;
this.backupsController = backupsController;
this.container = container;
};
ns.SelectSession.prototype.addEventListener = function (el, type, cb) {
pskl.utils.Event.addEventListener(el, type, cb, this);
};
ns.SelectSession.prototype.init = function () {
this.addEventListener(this.container, 'click', this.onContainerClick_);
};
ns.SelectSession.prototype.onShow = function () {
this.update();
};
ns.SelectSession.prototype.update = function () {
pskl.app.backupService.list().then(function (sessions) {
var html = this.getMarkupForSessions_(sessions);
this.container.querySelector('.session-list').innerHTML = html;
}.bind(this)).catch(function () {
var html = pskl.utils.Template.get('session-list-error');
this.container.querySelector('.session-list').innerHTML = html;
}.bind(this));
};
ns.SelectSession.prototype.getMarkupForSessions_ = function (sessions) {
if (sessions.length === 0) {
return pskl.utils.Template.get('session-list-empty');
}
var sessionItemTemplate = pskl.utils.Template.get('session-list-item');
return sessions.reduce(function (previous, session) {
if (session.id === pskl.app.sessionId) {
// Do not show backups for the current session.
return previous;
}
var view = {
id: session.id,
name: session.name,
description: session.description ? '- ' + session.description : '',
date: pskl.utils.DateUtils.format(session.endDate, 'the {{Y}}/{{M}}/{{D}} at {{H}}:{{m}}'),
count: session.count === 1 ? '1 snapshot' : session.count + ' snapshots'
};
return previous + pskl.utils.Template.replace(sessionItemTemplate, view);
}, '');
};
ns.SelectSession.prototype.destroy = function () {
pskl.utils.Event.removeAllEventListeners(this);
};
ns.SelectSession.prototype.onContainerClick_ = function (evt) {
var sessionId = evt.target.dataset.sessionId;
if (!sessionId) {
return;
}
var action = evt.target.dataset.action;
if (action == 'view') {
this.backupsController.backupsData.selectedSession = sessionId;
this.backupsController.next();
} else if (action == 'delete') {
if (window.confirm('Are you sure you want to delete this session?')) {
evt.target.closest('.session-item').classList.add('deleting');
Q.all([
pskl.app.backupService.deleteSession(sessionId),
// Wait for 500ms for the .hide opacity transition.
wait(DELETE_TRANSITION_DURATION)
]).then(function () {
// Refresh the list of sessions
this.update();
}.bind(this));
}
}
};
})();

View File

@ -0,0 +1,102 @@
(function () {
var ns = $.namespace('pskl.controller.dialogs.backups.steps');
// Should match the preview dimensions defined in dialogs-browse-backups.css
var PREVIEW_SIZE = 60;
ns.SessionDetails = function (piskelController, backupsController, container) {
this.piskelController = piskelController;
this.backupsController = backupsController;
this.container = container;
};
ns.SessionDetails.prototype.init = function () {
this.backButton = this.container.querySelector('.back-button');
this.addEventListener(this.backButton, 'click', this.onBackClick_);
this.addEventListener(this.container, 'click', this.onContainerClick_);
};
ns.SessionDetails.prototype.destroy = function () {
pskl.utils.Event.removeAllEventListeners(this);
};
ns.SessionDetails.prototype.addEventListener = function (el, type, cb) {
pskl.utils.Event.addEventListener(el, type, cb, this);
};
ns.SessionDetails.prototype.onShow = function () {
var sessionId = this.backupsController.backupsData.selectedSession;
pskl.app.backupService.getSnapshotsBySessionId(sessionId).then(function (snapshots) {
var html = this.getMarkupForSnapshots_(snapshots);
this.container.querySelector('.snapshot-list').innerHTML = html;
// Load the image of the first frame for each sprite and update the list.
snapshots.forEach(function (snapshot) {
this.updateSnapshotPreview_(snapshot);
}.bind(this));
}.bind(this)).catch(function () {
var html = pskl.utils.Template.get('snapshot-list-error');
this.container.querySelector('.snapshot-list').innerHTML = html;
}.bind(this));
};
ns.SessionDetails.prototype.getMarkupForSnapshots_ = function (snapshots) {
if (snapshots.length === 0) {
// This should normally never happen, all sessions have at least one snapshot and snapshots
// can not be individually deleted.
console.warn('Could not retrieve snapshots for a session');
return pskl.utils.Template.get('snapshot-list-empty');
}
var sessionItemTemplate = pskl.utils.Template.get('snapshot-list-item');
return snapshots.reduce(function (previous, snapshot) {
var view = {
id: snapshot.id,
name: snapshot.name,
description: snapshot.description ? '- ' + snapshot.description : '',
date: pskl.utils.DateUtils.format(snapshot.date, 'the {{Y}}/{{M}}/{{D}} at {{H}}:{{m}}'),
frames: snapshot.frames === 1 ? '1 frame' : snapshot.frames + ' frames',
resolution: pskl.utils.StringUtils.formatSize(snapshot.width, snapshot.height),
fps: snapshot.fps
};
return previous + pskl.utils.Template.replace(sessionItemTemplate, view);
}, '');
};
ns.SessionDetails.prototype.updateSnapshotPreview_ = function (snapshot) {
pskl.utils.serialization.Deserializer.deserialize(
JSON.parse(snapshot.serialized),
function (piskel) {
var selector = '.snapshot-item[data-snapshot-id="' + snapshot.id + '"] .snapshot-preview';
var previewContainer = this.container.querySelector(selector);
if (!previewContainer) {
return;
}
var image = this.getFirstFrameAsImage_(piskel);
previewContainer.appendChild(image);
}.bind(this)
);
};
ns.SessionDetails.prototype.getFirstFrameAsImage_ = function (piskel) {
var frame = pskl.utils.LayerUtils.mergeFrameAt(piskel.getLayers(), 0);
var wZoom = PREVIEW_SIZE / piskel.width;
var hZoom = PREVIEW_SIZE / piskel.height;
var zoom = Math.min(hZoom, wZoom);
return pskl.utils.FrameUtils.toImage(frame, zoom);
};
ns.SessionDetails.prototype.onBackClick_ = function () {
this.backupsController.back(this);
};
ns.SessionDetails.prototype.onContainerClick_ = function (evt) {
var action = evt.target.dataset.action;
if (action == 'load' && window.confirm(Constants.CONFIRM_OVERWRITE)) {
var snapshotId = evt.target.dataset.snapshotId * 1;
pskl.app.backupService.loadSnapshotById(snapshotId).then(function () {
$.publish(Events.DIALOG_HIDE);
});
}
};
})();

View File

@ -80,7 +80,13 @@
var step = this.wizard.getCurrentStep(); var step = this.wizard.getCurrentStep();
if (step.name === 'IMAGE_IMPORT') { if (step.name === 'IMAGE_IMPORT') {
if (this.piskelController.isEmpty()) {
// If the current sprite is empty finalize immediately and replace the current sprite.
this.mergeData.importMode = ns.steps.SelectMode.MODES.REPLACE;
this.finalizeImport_();
} else {
this.wizard.goTo('SELECT_MODE'); this.wizard.goTo('SELECT_MODE');
}
} else if (step.name === 'SELECT_MODE') { } else if (step.name === 'SELECT_MODE') {
if (this.mergeData.importMode === ns.steps.SelectMode.MODES.REPLACE) { if (this.mergeData.importMode === ns.steps.SelectMode.MODES.REPLACE) {
this.finalizeImport_(); this.finalizeImport_();
@ -144,9 +150,7 @@
if (mode === ns.steps.SelectMode.MODES.REPLACE) { if (mode === ns.steps.SelectMode.MODES.REPLACE) {
// Replace the current piskel and close the dialog. // Replace the current piskel and close the dialog.
var message = 'This will replace your current animation,' + if (window.confirm(Constants.CONFIRM_OVERWRITE)) {
' are you sure you want to continue?';
if (window.confirm(message)) {
this.piskelController.setPiskel(piskel); this.piskelController.setPiskel(piskel);
this.closeDialog(); this.closeDialog();
} }

View File

@ -69,16 +69,15 @@
var anchorInfo = this.container.querySelector('.import-resize-anchor-info'); var anchorInfo = this.container.querySelector('.import-resize-anchor-info');
if (isBigger && keep) { if (isBigger && keep) {
anchorInfo.innerHTML = [ anchorInfo.innerHTML = [
'<span class="import-resize-warning">', '<div class="import-resize-warning">',
' Imported content will be cropped!', ' Imported content will be cropped!',
'</span>', '</div>',
' ', 'Select crop anchor:'
'Select crop origin'
].join(''); ].join('');
} else if (isBigger) { } else if (isBigger) {
anchorInfo.innerHTML = 'Select the anchor for resizing the canvas'; anchorInfo.innerHTML = 'Select resize anchor:';
} else { } else {
anchorInfo.innerHTML = 'Select where the import should be positioned'; anchorInfo.innerHTML = 'Select position anchor:';
} }
}; };

View File

@ -42,12 +42,17 @@
this.addEventListener(this.frameOffsetY, 'keyup', this.onFrameInputKeyUp_); this.addEventListener(this.frameOffsetY, 'keyup', this.onFrameInputKeyUp_);
pskl.utils.FileUtils.readImageFile(this.file_, this.onImageLoaded_.bind(this)); pskl.utils.FileUtils.readImageFile(this.file_, this.onImageLoaded_.bind(this));
if (this.piskelController.isEmpty()) {
this.nextButton.textContent = 'import';
}
}; };
ns.ImageImport.prototype.onNextClick = function () { ns.ImageImport.prototype.onNextClick = function () {
this.container.classList.add('import-image-loading'); this.container.classList.add('import-image-loading');
this.createPiskelFromImage().then(function (piskel) { this.createPiskelFromImage().then(function (piskel) {
this.mergeData.mergePiskel = piskel; this.mergeData.mergePiskel = piskel;
this.container.classList.remove('import-image-loading');
this.superclass.onNextClick.call(this); this.superclass.onNextClick.call(this);
}.bind(this)).catch(function (e) { }.bind(this)).catch(function (e) {
console.error(e); console.error(e);
@ -257,9 +262,7 @@
context.lineTo(maxWidth * scaleX, y * scaleY); context.lineTo(maxWidth * scaleX, y * scaleY);
} }
// Set the line style to dashed
context.lineWidth = 1; context.lineWidth = 1;
// context.setLineDash([2, 1]);
context.strokeStyle = 'gold'; context.strokeStyle = 'gold';
context.stroke(); context.stroke();

View File

@ -122,6 +122,17 @@
l.addFrameAt(this.createEmptyFrame_(), index); l.addFrameAt(this.createEmptyFrame_(), index);
}.bind(this)); }.bind(this));
this.onFrameAddedAt_(index);
};
ns.PiskelController.prototype.onFrameAddedAt_ = function (index) {
this.piskel.hiddenFrames = this.piskel.hiddenFrames.map(function (hiddenIndex) {
if (hiddenIndex >= index) {
return hiddenIndex + 1;
}
return hiddenIndex;
});
this.setCurrentFrameIndex(index); this.setCurrentFrameIndex(index);
}; };
@ -135,6 +146,15 @@
this.getLayers().forEach(function (l) { this.getLayers().forEach(function (l) {
l.removeFrameAt(index); l.removeFrameAt(index);
}); });
// Update the array of hidden frames since some hidden indexes might have shifted.
this.piskel.hiddenFrames = this.piskel.hiddenFrames.map(function (hiddenIndex) {
if (hiddenIndex > index) {
return hiddenIndex - 1;
}
return hiddenIndex;
});
// Current frame index is impacted if the removed frame was before the current frame // Current frame index is impacted if the removed frame was before the current frame
if (this.currentFrameIndex >= index && this.currentFrameIndex > 0) { if (this.currentFrameIndex >= index && this.currentFrameIndex > 0) {
this.setCurrentFrameIndex(this.currentFrameIndex - 1); this.setCurrentFrameIndex(this.currentFrameIndex - 1);
@ -149,13 +169,64 @@
this.getLayers().forEach(function (l) { this.getLayers().forEach(function (l) {
l.duplicateFrameAt(index); l.duplicateFrameAt(index);
}); });
this.setCurrentFrameIndex(index + 1); this.onFrameAddedAt_(index + 1);
};
/**
* Toggle frame visibility for the frame at the provided index.
* A visible frame will be included in the animated preview.
*/
ns.PiskelController.prototype.toggleFrameVisibilityAt = function (index) {
var hiddenFrames = this.piskel.hiddenFrames;
if (hiddenFrames.indexOf(index) === -1) {
hiddenFrames.push(index);
} else {
hiddenFrames = hiddenFrames.filter(function (i) {
return i !== index;
});
}
// Keep the hiddenFrames array sorted.
this.piskel.hiddenFrames = hiddenFrames.sort();
}; };
ns.PiskelController.prototype.moveFrame = function (fromIndex, toIndex) { ns.PiskelController.prototype.moveFrame = function (fromIndex, toIndex) {
this.getLayers().forEach(function (l) { this.getLayers().forEach(function (l) {
l.moveFrame(fromIndex, toIndex); l.moveFrame(fromIndex, toIndex);
}); });
// Update the array of hidden frames since some hidden indexes might have shifted.
this.piskel.hiddenFrames = this.piskel.hiddenFrames.map(function (index) {
if (index === fromIndex) {
return toIndex;
}
// All the frames between fromIndex and toIndex changed their index.
var isImpacted = index >= Math.min(fromIndex, toIndex) &&
index <= Math.max(fromIndex, toIndex);
if (isImpacted) {
if (fromIndex < toIndex) {
// If the frame moved to a higher index, all impacted frames had their index
// reduced by 1.
return index - 1;
} else {
// Otherwise, they had their index increased by 1.
return index + 1;
}
}
});
};
ns.PiskelController.prototype.hasVisibleFrameAt = function (index) {
return this.piskel.hiddenFrames.indexOf(index) === -1;
};
ns.PiskelController.prototype.getVisibleFrameIndexes = function () {
return this.getCurrentLayer().getFrames().map(function (frame, index) {
return index;
}).filter(function (index) {
return this.piskel.hiddenFrames.indexOf(index) === -1;
}.bind(this));
}; };
ns.PiskelController.prototype.getFrameCount = function () { ns.PiskelController.prototype.getFrameCount = function () {
@ -297,4 +368,12 @@
ns.PiskelController.prototype.serialize = function () { ns.PiskelController.prototype.serialize = function () {
return pskl.utils.serialization.Serializer.serialize(this.piskel); return pskl.utils.serialization.Serializer.serialize(this.piskel);
}; };
/**
* Check if the current sprite is empty. Emptiness here means no pixel has been filled
* on any layer or frame for the current sprite.
*/
ns.PiskelController.prototype.isEmpty = function () {
return pskl.app.currentColorsService.getCurrentColors().length === 0;
};
})(); })();

View File

@ -37,6 +37,7 @@
this.saveWrap_('moveLayerDown', true); this.saveWrap_('moveLayerDown', true);
this.saveWrap_('removeCurrentLayer', true); this.saveWrap_('removeCurrentLayer', true);
this.saveWrap_('setLayerOpacityAt', true); this.saveWrap_('setLayerOpacityAt', true);
this.saveWrap_('toggleFrameVisibilityAt', true);
var shortcuts = pskl.service.keyboard.Shortcuts; var shortcuts = pskl.service.keyboard.Shortcuts;
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.PREVIOUS_FRAME, this.selectPreviousFrame.bind(this)); pskl.app.shortcutService.registerShortcut(shortcuts.MISC.PREVIOUS_FRAME, this.selectPreviousFrame.bind(this));

View File

@ -33,7 +33,7 @@
pskl.utils.Event.addEventListener(this.popup, 'resize', this.onWindowResize_, this); pskl.utils.Event.addEventListener(this.popup, 'resize', this.onWindowResize_, this);
pskl.utils.Event.addEventListener(this.popup, 'unload', this.onPopupClosed_, this); pskl.utils.Event.addEventListener(this.popup, 'unload', this.onPopupClosed_, this);
var container = this.popup.document.querySelector('.preview-container'); var container = this.popup.document.querySelector('.preview-container');
this.renderer = new pskl.rendering.frame.BackgroundImageFrameRenderer($(container)); this.renderer = new pskl.rendering.frame.BackgroundImageFrameRenderer(container);
this.updateZoom_(); this.updateZoom_();
this.renderFlag = true; this.renderFlag = true;
}; };

View File

@ -0,0 +1,232 @@
(function () {
var ns = $.namespace('pskl.controller.preview');
ns.PreviewActionsController = function (previewController, container) {
this.previewController = previewController;
this.piskelController = previewController.piskelController;
this.container = container;
this.onionSkinShortcut = pskl.service.keyboard.Shortcuts.MISC.ONION_SKIN;
this.toggleGridShortcut = pskl.service.keyboard.Shortcuts.MISC.TOGGLE_GRID;
this.fpsRangeInput = document.querySelector('#preview-fps');
this.fpsCounterDisplay = document.querySelector('#display-fps');
this.openPopupPreview = document.querySelector('.open-popup-preview-button');
this.toggleGridButton = document.querySelector('.toggle-grid-button');
this.previewSizeDropdown = document.querySelector('.preview-drop-down');
this.previewSizes = {
original: {
button: document.querySelector('.original-size-button'),
shortcut: pskl.service.keyboard.Shortcuts.MISC.X1_PREVIEW,
tooltip: 'Original size preview'
},
best: {
button: document.querySelector('.best-size-button'),
shortcut: pskl.service.keyboard.Shortcuts.MISC.BEST_PREVIEW,
tooltip: 'Best size preview'
},
full: {
button: document.querySelector('.full-size-button'),
shortcut: pskl.service.keyboard.Shortcuts.MISC.FULL_PREVIEW,
tooltip: 'Full size preview'
}
};
this.toggleOnionSkinButton = document.querySelector('.preview-toggle-onion-skin');
};
ns.PreviewActionsController.prototype.init = function () {
this.fpsRangeInput.addEventListener('change', this.onFpsRangeInputUpdate_.bind(this));
this.fpsRangeInput.addEventListener('input', this.onFpsRangeInputUpdate_.bind(this));
var addEvent = pskl.utils.Event.addEventListener;
addEvent(this.toggleOnionSkinButton, 'click', this.toggleOnionSkin_, this);
addEvent(this.openPopupPreview, 'click', this.onOpenPopupPreviewClick_, this);
addEvent(this.toggleGridButton, 'click', this.toggleGrid_, this);
var registerShortcut = pskl.app.shortcutService.registerShortcut.bind(pskl.app.shortcutService);
registerShortcut(this.onionSkinShortcut, this.toggleOnionSkin_.bind(this));
registerShortcut(this.toggleGridShortcut, this.toggleGrid_.bind(this));
var onionSkinTooltip = pskl.utils.TooltipFormatter.format('Toggle onion skin', this.onionSkinShortcut);
this.toggleOnionSkinButton.setAttribute('title', onionSkinTooltip);
for (var size in this.previewSizes) {
if (this.previewSizes.hasOwnProperty(size)) {
var previewSize = this.previewSizes[size];
addEvent(previewSize.button, 'click', this.onChangePreviewSize_, this, size);
registerShortcut(previewSize.shortcut, this.onChangePreviewSize_.bind(this, size));
var tooltip = pskl.utils.TooltipFormatter.format(previewSize.tooltip, previewSize.shortcut);
previewSize.button.setAttribute('title', tooltip);
}
}
$.subscribe(Events.FRAME_SIZE_CHANGED, this.updatePreviewSizeButtons_.bind(this));
$.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
$.subscribe(Events.FPS_CHANGED, this.updateFPS_.bind(this));
$.subscribe(Events.PISKEL_RESET, this.updateFPS_.bind(this));
this.updatePreviewSizeButtons_();
this.updateOnionSkinPreview_();
this.selectPreviewSizeButton_();
this.updateFPS_();
this.updateMaxFPS_();
this.updateToggleGridButton_();
};
ns.PreviewActionsController.prototype.updateToggleGridButton_ = function () {
var gridEnabled = pskl.UserSettings.get(pskl.UserSettings.GRID_ENABLED);
this.toggleGridButton.classList.toggle('icon-minimap-grid-white', !gridEnabled);
this.toggleGridButton.classList.toggle('icon-minimap-grid-gold', gridEnabled);
this.toggleGridButton.classList.toggle('preview-contextual-action-enabled', gridEnabled);
};
ns.PreviewActionsController.prototype.toggleGrid_ = function () {
var gridEnabled = pskl.UserSettings.get(pskl.UserSettings.GRID_ENABLED);
pskl.UserSettings.set(pskl.UserSettings.GRID_ENABLED, !gridEnabled);
};
ns.PreviewActionsController.prototype.updatePreviewSizeButtons_ = function () {
var fullZoom = this.previewController.getZoom();
var bestZoom = Math.floor(fullZoom);
var seamlessModeEnabled = pskl.UserSettings.get(pskl.UserSettings.SEAMLESS_MODE);
var validSizes;
if (fullZoom < 1) {
this.disablePreviewSizeWidget_('No other option available');
validSizes = ['full'];
} else if (fullZoom === 1) {
this.disablePreviewSizeWidget_('No other option available');
validSizes = ['original'];
} else if (seamlessModeEnabled) {
this.disablePreviewSizeWidget_('Disabled in tile mode');
validSizes = ['original'];
} else {
this.enablePreviewSizeWidget_();
if (fullZoom === bestZoom) {
// If the full zoom is the same as the best zoom, display the best option only as
// it gives the exact factor information.
validSizes = ['original', 'best'];
} else if (bestZoom === 1) {
// If best zoom is 1x, remove it as it is redundant with the original option.
validSizes = ['full', 'original'];
} else {
validSizes = ['full', 'original', 'best'];
}
}
// Update buttons content and status.
this.previewSizes.best.button.textContent = Math.floor(fullZoom) + 'x';
for (var size in this.previewSizes) {
if (this.previewSizes.hasOwnProperty(size)) {
var previewSize = this.previewSizes[size];
var isSizeEnabled = validSizes.indexOf(size) != -1;
// classList.toggle is not available on IE11.
if (isSizeEnabled) {
previewSize.button.classList.remove('preview-contextual-action-hidden');
} else {
previewSize.button.classList.add('preview-contextual-action-hidden');
}
}
}
// Update the selected preview size if the currently selected size is not valid.
var selectedSize = pskl.UserSettings.get(pskl.UserSettings.PREVIEW_SIZE);
if (validSizes.indexOf(selectedSize) === -1) {
this.onChangePreviewSize_(validSizes[0]);
}
};
ns.PreviewActionsController.prototype.enablePreviewSizeWidget_ = function () {
this.previewSizeDropdown.classList.remove('preview-drop-down-disabled');
};
ns.PreviewActionsController.prototype.disablePreviewSizeWidget_ = function (reason) {
// The .preview-disable-overlay is displayed on top of the preview size widget
document.querySelector('.preview-disable-overlay').setAttribute('data-original-title', reason);
this.previewSizeDropdown.classList.add('preview-drop-down-disabled');
};
ns.PreviewActionsController.prototype.onOpenPopupPreviewClick_ = function () {
this.previewController.openPopupPreview();
};
ns.PreviewActionsController.prototype.onChangePreviewSize_ = function (size) {
var previewSize = this.previewSizes[size];
var isEnabled = !previewSize.button.classList.contains('preview-contextual-action-hidden');
if (isEnabled) {
pskl.UserSettings.set(pskl.UserSettings.PREVIEW_SIZE, size);
}
};
ns.PreviewActionsController.prototype.onUserSettingsChange_ = function (evt, name, value) {
if (name == pskl.UserSettings.ONION_SKIN) {
this.updateOnionSkinPreview_();
} else if (name == pskl.UserSettings.MAX_FPS) {
this.updateMaxFPS_();
} else if (name === pskl.UserSettings.SEAMLESS_MODE) {
this.updatePreviewSizeButtons_();
} else if (name === pskl.UserSettings.GRID_ENABLED) {
this.updateToggleGridButton_();
} else {
this.selectPreviewSizeButton_();
}
};
ns.PreviewActionsController.prototype.updateOnionSkinPreview_ = function () {
var enabledClassname = 'preview-toggle-onion-skin-enabled';
var isEnabled = pskl.UserSettings.get(pskl.UserSettings.ONION_SKIN);
// classList.toggle is not available on IE11.
if (isEnabled) {
this.toggleOnionSkinButton.classList.add(enabledClassname);
} else {
this.toggleOnionSkinButton.classList.remove(enabledClassname);
}
};
ns.PreviewActionsController.prototype.selectPreviewSizeButton_ = function () {
var currentlySelected = document.querySelector('.size-button-selected');
if (currentlySelected) {
currentlySelected.classList.remove('size-button-selected');
}
var selectedSize = pskl.UserSettings.get(pskl.UserSettings.PREVIEW_SIZE);
var previewSize = this.previewSizes[selectedSize];
previewSize.button.classList.add('size-button-selected');
};
ns.PreviewActionsController.prototype.updateMaxFPS_ = function () {
var maxFps = pskl.UserSettings.get(pskl.UserSettings.MAX_FPS);
this.fpsRangeInput.setAttribute('max', maxFps);
this.piskelController.setFPS(Math.min(maxFps, this.piskelController.getFPS()));
};
/**
* Event handler triggered on 'input' or 'change' events.
*/
ns.PreviewActionsController.prototype.onFpsRangeInputUpdate_ = function (evt) {
var fps = parseInt(this.fpsRangeInput.value, 10);
this.piskelController.setFPS(fps);
// blur only on 'change' events, as blurring on 'input' breaks on Firefox
if (evt.type === 'change') {
this.fpsRangeInput.blur();
}
};
ns.PreviewActionsController.prototype.updateFPS_ = function () {
var fps = this.piskelController.getFPS();
if (fps !== this.fpsRangeInput.value) {
// reset
this.fpsRangeInput.value = 0;
// set proper value
this.fpsRangeInput.value = fps;
this.fpsCounterDisplay.innerHTML = fps + ' FPS';
}
};
ns.PreviewActionsController.prototype.toggleOnionSkin_ = function () {
var currentValue = pskl.UserSettings.get(pskl.UserSettings.ONION_SKIN);
pskl.UserSettings.set(pskl.UserSettings.ONION_SKIN, !currentValue);
};
})();

View File

@ -11,201 +11,43 @@
this.elapsedTime = 0; this.elapsedTime = 0;
this.currentIndex = 0; this.currentIndex = 0;
this.onionSkinShortcut = pskl.service.keyboard.Shortcuts.MISC.ONION_SKIN;
this.lastRenderTime = 0; this.lastRenderTime = 0;
this.renderFlag = true; this.renderFlag = true;
this.fpsRangeInput = document.querySelector('#preview-fps');
this.fpsCounterDisplay = document.querySelector('#display-fps');
this.openPopupPreview = document.querySelector('.open-popup-preview-button');
this.previewSizeDropdown = document.querySelector('.preview-drop-down');
this.previewSizes = {
original: {
button: document.querySelector('.original-size-button'),
shortcut: pskl.service.keyboard.Shortcuts.MISC.X1_PREVIEW,
tooltip: 'Original size preview'
},
best: {
button: document.querySelector('.best-size-button'),
shortcut: pskl.service.keyboard.Shortcuts.MISC.BEST_PREVIEW,
tooltip: 'Best size preview'
},
full: {
button: document.querySelector('.full-size-button'),
shortcut: pskl.service.keyboard.Shortcuts.MISC.FULL_PREVIEW,
tooltip: 'Full size preview'
}
};
this.toggleOnionSkinButton = document.querySelector('.preview-toggle-onion-skin');
this.renderer = new pskl.rendering.frame.BackgroundImageFrameRenderer(this.container); this.renderer = new pskl.rendering.frame.BackgroundImageFrameRenderer(this.container);
this.popupPreviewController = new ns.PopupPreviewController(piskelController); this.popupPreviewController = new ns.PopupPreviewController(piskelController);
this.previewActionsController = new ns.PreviewActionsController(this, container);
}; };
ns.PreviewController.prototype.init = function () { ns.PreviewController.prototype.init = function () {
this.fpsRangeInput.addEventListener('change', this.onFpsRangeInputUpdate_.bind(this)); var width = Constants.ANIMATED_PREVIEW_WIDTH + Constants.RIGHT_COLUMN_PADDING_LEFT;
this.fpsRangeInput.addEventListener('input', this.onFpsRangeInputUpdate_.bind(this)); document.querySelector('.right-column').style.width = width + 'px';
document.querySelector('.right-column').style.width = Constants.ANIMATED_PREVIEW_WIDTH + 'px';
var addEvent = pskl.utils.Event.addEventListener;
addEvent(this.toggleOnionSkinButton, 'click', this.toggleOnionSkin_, this);
addEvent(this.openPopupPreview, 'click', this.onOpenPopupPreviewClick_, this);
var registerShortcut = pskl.app.shortcutService.registerShortcut.bind(pskl.app.shortcutService);
registerShortcut(this.onionSkinShortcut, this.toggleOnionSkin_.bind(this));
var onionSkinTooltip = pskl.utils.TooltipFormatter.format('Toggle onion skin', this.onionSkinShortcut);
this.toggleOnionSkinButton.setAttribute('title', onionSkinTooltip);
for (var size in this.previewSizes) {
if (this.previewSizes.hasOwnProperty(size)) {
var previewSize = this.previewSizes[size];
addEvent(previewSize.button, 'click', this.onChangePreviewSize_, this, size);
registerShortcut(previewSize.shortcut, this.onChangePreviewSize_.bind(this, size));
var tooltip = pskl.utils.TooltipFormatter.format(previewSize.tooltip, previewSize.shortcut);
previewSize.button.setAttribute('title', tooltip);
}
}
$.subscribe(Events.FRAME_SIZE_CHANGED, this.onFrameSizeChange_.bind(this)); $.subscribe(Events.FRAME_SIZE_CHANGED, this.onFrameSizeChange_.bind(this));
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this)); $.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
$.subscribe(Events.PISKEL_SAVE_STATE, this.setRenderFlag_.bind(this, true)); $.subscribe(Events.PISKEL_SAVE_STATE, this.setRenderFlag_.bind(this, true));
$.subscribe(Events.FPS_CHANGED, this.updateFPS_.bind(this));
// On PISKEL_RESET, set the render flag and update the FPS input
$.subscribe(Events.PISKEL_RESET, this.setRenderFlag_.bind(this, true)); $.subscribe(Events.PISKEL_RESET, this.setRenderFlag_.bind(this, true));
$.subscribe(Events.PISKEL_RESET, this.updateFPS_.bind(this));
this.updatePreviewSizeButtons_();
this.popupPreviewController.init(); this.popupPreviewController.init();
this.previewActionsController.init();
this.updateZoom_(); this.updateZoom_();
this.updateOnionSkinPreview_();
this.selectPreviewSizeButton_();
this.updateFPS_();
this.updateMaxFPS_();
this.updateContainerDimensions_(); this.updateContainerDimensions_();
}; };
ns.PreviewController.prototype.updatePreviewSizeButtons_ = function () { ns.PreviewController.prototype.openPopupPreview = function () {
var fullZoom = this.calculateZoom_();
var bestZoom = Math.floor(fullZoom);
var seamlessModeEnabled = pskl.UserSettings.get(pskl.UserSettings.SEAMLESS_MODE);
var validSizes;
if (fullZoom < 1) {
this.disablePreviewSizeWidget_('No other option available');
validSizes = ['full'];
} else if (fullZoom === 1) {
this.disablePreviewSizeWidget_('No other option available');
validSizes = ['original'];
} else if (seamlessModeEnabled) {
this.disablePreviewSizeWidget_('Disabled in tile mode');
validSizes = ['original'];
} else {
this.enablePreviewSizeWidget_();
if (fullZoom === bestZoom) {
// If the full zoom is the same as the best zoom, display the best option only as
// it gives the exact factor information.
validSizes = ['original', 'best'];
} else if (bestZoom === 1) {
// If best zoom is 1x, remove it as it is redundant with the original option.
validSizes = ['full', 'original'];
} else {
validSizes = ['full', 'original', 'best'];
}
}
// Update buttons content and status.
this.previewSizes.best.button.textContent = Math.floor(fullZoom) + 'x';
for (var size in this.previewSizes) {
if (this.previewSizes.hasOwnProperty(size)) {
var previewSize = this.previewSizes[size];
var isSizeEnabled = validSizes.indexOf(size) != -1;
// classList.toggle is not available on IE11.
if (isSizeEnabled) {
previewSize.button.classList.remove('preview-contextual-action-hidden');
} else {
previewSize.button.classList.add('preview-contextual-action-hidden');
}
}
}
// Update the selected preview size if the currently selected size is not valid.
var selectedSize = pskl.UserSettings.get(pskl.UserSettings.PREVIEW_SIZE);
if (validSizes.indexOf(selectedSize) === -1) {
this.onChangePreviewSize_(validSizes[0]);
}
};
ns.PreviewController.prototype.enablePreviewSizeWidget_ = function () {
this.previewSizeDropdown.classList.remove('preview-drop-down-disabled');
};
ns.PreviewController.prototype.disablePreviewSizeWidget_ = function (reason) {
// The .preview-disable-overlay is displayed on top of the preview size widget
document.querySelector('.preview-disable-overlay').setAttribute('data-original-title', reason);
this.previewSizeDropdown.classList.add('preview-drop-down-disabled');
};
ns.PreviewController.prototype.onOpenPopupPreviewClick_ = function () {
this.popupPreviewController.open(); this.popupPreviewController.open();
}; };
ns.PreviewController.prototype.onChangePreviewSize_ = function (size) {
var previewSize = this.previewSizes[size];
var isEnabled = !previewSize.button.classList.contains('preview-contextual-action-hidden');
if (isEnabled) {
pskl.UserSettings.set(pskl.UserSettings.PREVIEW_SIZE, size);
}
};
ns.PreviewController.prototype.onUserSettingsChange_ = function (evt, name, value) { ns.PreviewController.prototype.onUserSettingsChange_ = function (evt, name, value) {
if (name == pskl.UserSettings.ONION_SKIN) { if (name === pskl.UserSettings.SEAMLESS_MODE) {
this.updateOnionSkinPreview_();
} else if (name == pskl.UserSettings.MAX_FPS) {
this.updateMaxFPS_();
} else if (name === pskl.UserSettings.SEAMLESS_MODE) {
this.onFrameSizeChange_(); this.onFrameSizeChange_();
} else { } else {
this.updateZoom_(); this.updateZoom_();
this.selectPreviewSizeButton_();
this.updateContainerDimensions_(); this.updateContainerDimensions_();
} }
}; };
ns.PreviewController.prototype.updateOnionSkinPreview_ = function () {
var enabledClassname = 'preview-toggle-onion-skin-enabled';
var isEnabled = pskl.UserSettings.get(pskl.UserSettings.ONION_SKIN);
// classList.toggle is not available on IE11.
if (isEnabled) {
this.toggleOnionSkinButton.classList.add(enabledClassname);
} else {
this.toggleOnionSkinButton.classList.remove(enabledClassname);
}
};
ns.PreviewController.prototype.selectPreviewSizeButton_ = function () {
var currentlySelected = document.querySelector('.size-button-selected');
if (currentlySelected) {
currentlySelected.classList.remove('size-button-selected');
}
var selectedSize = pskl.UserSettings.get(pskl.UserSettings.PREVIEW_SIZE);
var previewSize = this.previewSizes[selectedSize];
previewSize.button.classList.add('size-button-selected');
};
ns.PreviewController.prototype.updateMaxFPS_ = function () {
var maxFps = pskl.UserSettings.get(pskl.UserSettings.MAX_FPS);
this.fpsRangeInput.setAttribute('max', maxFps);
this.piskelController.setFPS(Math.min(maxFps, this.piskelController.getFPS()));
};
ns.PreviewController.prototype.updateZoom_ = function () { ns.PreviewController.prototype.updateZoom_ = function () {
var previewSize = pskl.UserSettings.get(pskl.UserSettings.PREVIEW_SIZE); var previewSize = pskl.UserSettings.get(pskl.UserSettings.PREVIEW_SIZE);
@ -227,9 +69,9 @@
}; };
ns.PreviewController.prototype.getCoordinates = function(x, y) { ns.PreviewController.prototype.getCoordinates = function(x, y) {
var containerOffset = this.container.offset(); var containerRect = this.container.getBoundingClientRect();
x = x - containerOffset.left; x = x - containerRect.left;
y = y - containerOffset.top; y = y - containerRect.top;
var zoom = this.getZoom(); var zoom = this.getZoom();
return { return {
x : Math.floor(x / zoom), x : Math.floor(x / zoom),
@ -237,30 +79,6 @@
}; };
}; };
/**
* Event handler triggered on 'input' or 'change' events.
*/
ns.PreviewController.prototype.onFpsRangeInputUpdate_ = function (evt) {
var fps = parseInt(this.fpsRangeInput.value, 10);
this.piskelController.setFPS(fps);
// blur only on 'change' events, as blurring on 'input' breaks on Firefox
if (evt.type === 'change') {
this.fpsRangeInput.blur();
}
};
ns.PreviewController.prototype.updateFPS_ = function () {
var fps = this.piskelController.getFPS();
if (fps !== this.fps) {
this.fps = fps;
// reset
this.fpsRangeInput.value = 0;
// set proper value
this.fpsRangeInput.value = this.fps;
this.fpsCounterDisplay.innerHTML = this.fps + ' FPS';
}
};
ns.PreviewController.prototype.render = function (delta) { ns.PreviewController.prototype.render = function (delta) {
this.elapsedTime += delta; this.elapsedTime += delta;
var index = this.getNextIndex_(delta); var index = this.getNextIndex_(delta);
@ -276,16 +94,19 @@
}; };
ns.PreviewController.prototype.getNextIndex_ = function (delta) { ns.PreviewController.prototype.getNextIndex_ = function (delta) {
if (this.fps === 0) { var fps = this.piskelController.getFPS();
if (fps === 0) {
return this.piskelController.getCurrentFrameIndex(); return this.piskelController.getCurrentFrameIndex();
} else { } else {
var index = Math.floor(this.elapsedTime / (1000 / this.fps)); var index = Math.floor(this.elapsedTime / (1000 / fps));
if (!this.piskelController.hasFrameAt(index)) { var frameIndexes = this.piskelController.getVisibleFrameIndexes();
if (frameIndexes.length <= index) {
this.elapsedTime = 0; this.elapsedTime = 0;
index = 0; index = (frameIndexes.length) ? frameIndexes[0] : this.piskelController.getCurrentFrameIndex();
}
return index; return index;
} }
return frameIndexes[index];
}
}; };
/** /**
@ -302,7 +123,6 @@
ns.PreviewController.prototype.onFrameSizeChange_ = function () { ns.PreviewController.prototype.onFrameSizeChange_ = function () {
this.updateZoom_(); this.updateZoom_();
this.updateContainerDimensions_(); this.updateContainerDimensions_();
this.updatePreviewSizeButtons_();
}; };
ns.PreviewController.prototype.updateContainerDimensions_ = function () { ns.PreviewController.prototype.updateContainerDimensions_ = function () {
@ -322,7 +142,7 @@
width = frame.getWidth() * zoom; width = frame.getWidth() * zoom;
} }
var containerEl = this.container.get(0); var containerEl = this.container;
containerEl.style.height = height + 'px'; containerEl.style.height = height + 'px';
containerEl.style.width = width + 'px'; containerEl.style.width = width + 'px';
@ -343,9 +163,4 @@
return (this.renderFlag || this.popupPreviewController.renderFlag) && return (this.renderFlag || this.popupPreviewController.renderFlag) &&
(Date.now() - this.lastRenderTime > RENDER_MINIMUM_DELAY); (Date.now() - this.lastRenderTime > RENDER_MINIMUM_DELAY);
}; };
ns.PreviewController.prototype.toggleOnionSkin_ = function () {
var currentValue = pskl.UserSettings.get(pskl.UserSettings.ONION_SKIN);
pskl.UserSettings.set(pskl.UserSettings.ONION_SKIN, !currentValue);
};
})(); })();

View File

@ -14,6 +14,7 @@
this.hiddenOpenPiskelInput = document.querySelector('[name="open-piskel-input"]'); this.hiddenOpenPiskelInput = document.querySelector('[name="open-piskel-input"]');
this.addEventListener('.browse-local-button', 'click', this.onBrowseLocalClick_); this.addEventListener('.browse-local-button', 'click', this.onBrowseLocalClick_);
this.addEventListener('.browse-backups-button', 'click', this.onBrowseBackupsClick_);
this.addEventListener('.file-input-button', 'click', this.onFileInputClick_); this.addEventListener('.file-input-button', 'click', this.onFileInputClick_);
// different handlers, depending on the Environment // different handlers, depending on the Environment
@ -23,24 +24,6 @@
this.addEventListener(this.hiddenOpenPiskelInput, 'change', this.onOpenPiskelChange_); this.addEventListener(this.hiddenOpenPiskelInput, 'change', this.onOpenPiskelChange_);
this.addEventListener('.open-piskel-button', 'click', this.onOpenPiskelClick_); this.addEventListener('.open-piskel-button', 'click', this.onOpenPiskelClick_);
} }
this.initRestoreSession_();
};
ns.ImportController.prototype.initRestoreSession_ = function () {
var previousSessionContainer = document.querySelector('.previous-session');
var previousInfo = pskl.app.backupService.getPreviousPiskelInfo();
if (previousInfo) {
var previousSessionTemplate_ = pskl.utils.Template.get('previous-session-info-template');
var date = pskl.utils.DateUtils.format(previousInfo.date, '{{H}}:{{m}} - {{Y}}/{{M}}/{{D}}');
previousSessionContainer.innerHTML = pskl.utils.Template.replace(previousSessionTemplate_, {
name : previousInfo.name,
date : date
});
this.addEventListener('.restore-session-button', 'click', this.onRestorePreviousSessionClick_);
} else {
previousSessionContainer.innerHTML = 'No piskel backup was found on this browser.';
}
}; };
ns.ImportController.prototype.closeDrawer_ = function () { ns.ImportController.prototype.closeDrawer_ = function () {
@ -77,6 +60,13 @@
this.closeDrawer_(); this.closeDrawer_();
}; };
ns.ImportController.prototype.onBrowseBackupsClick_ = function (evt) {
$.publish(Events.DIALOG_SHOW, {
dialogId : 'browse-backups'
});
this.closeDrawer_();
};
ns.ImportController.prototype.openPiskelFile_ = function (file) { ns.ImportController.prototype.openPiskelFile_ = function (file) {
if (this.isPiskel_(file)) { if (this.isPiskel_(file)) {
$.publish(Events.DIALOG_SHOW, { $.publish(Events.DIALOG_SHOW, {

View File

@ -34,7 +34,7 @@
this.saveDesktopAsNewButton = document.querySelector('#save-desktop-as-new-button'); this.saveDesktopAsNewButton = document.querySelector('#save-desktop-as-new-button');
this.saveFileDownloadButton = document.querySelector('#save-file-download-button'); this.saveFileDownloadButton = document.querySelector('#save-file-download-button');
this.safeAddEventListener_(this.saveLocalStorageButton, 'click', this.saveToLocalStorage_); this.safeAddEventListener_(this.saveLocalStorageButton, 'click', this.saveToIndexedDb_);
this.safeAddEventListener_(this.saveGalleryButton, 'click', this.saveToGallery_); this.safeAddEventListener_(this.saveGalleryButton, 'click', this.saveToGallery_);
this.safeAddEventListener_(this.saveDesktopButton, 'click', this.saveToDesktop_); this.safeAddEventListener_(this.saveDesktopButton, 'click', this.saveToDesktop_);
this.safeAddEventListener_(this.saveDesktopAsNewButton, 'click', this.saveToDesktopAsNew_); this.safeAddEventListener_(this.saveDesktopAsNewButton, 'click', this.saveToDesktopAsNew_);
@ -99,7 +99,7 @@
if (pskl.app.isLoggedIn()) { if (pskl.app.isLoggedIn()) {
this.saveToGallery_(); this.saveToGallery_();
} else { } else {
this.saveToLocalStorage_(); this.saveToIndexedDb_();
} }
}; };
@ -111,8 +111,8 @@
this.saveTo_('saveToGallery', false); this.saveTo_('saveToGallery', false);
}; };
ns.SaveController.prototype.saveToLocalStorage_ = function () { ns.SaveController.prototype.saveToIndexedDb_ = function () {
this.saveTo_('saveToLocalStorage', false); this.saveTo_('saveToIndexedDb', false);
}; };
ns.SaveController.prototype.saveToDesktop_ = function () { ns.SaveController.prototype.saveToDesktop_ = function () {

View File

@ -14,14 +14,22 @@
pskl.utils.inherit(ns.GifExportController, pskl.controller.settings.AbstractSettingController); pskl.utils.inherit(ns.GifExportController, pskl.controller.settings.AbstractSettingController);
ns.GifExportController.prototype.init = function () { ns.GifExportController.prototype.init = function () {
this.uploadStatusContainerEl = document.querySelector('.gif-upload-status'); this.uploadStatusContainerEl = document.querySelector('.gif-upload-status');
this.previewContainerEl = document.querySelector('.gif-export-preview'); this.previewContainerEl = document.querySelector('.gif-export-preview');
this.uploadButton = document.querySelector('.gif-upload-button'); this.uploadButton = document.querySelector('.gif-upload-button');
this.downloadButton = document.querySelector('.gif-download-button'); this.downloadButton = document.querySelector('.gif-download-button');
this.repeatCheckbox = document.querySelector('.gif-repeat-checkbox');
// Initialize repeatCheckbox state
this.repeatCheckbox.checked = this.getRepeatSetting_();
this.addEventListener(this.uploadButton, 'click', this.onUploadButtonClick_); this.addEventListener(this.uploadButton, 'click', this.onUploadButtonClick_);
this.addEventListener(this.downloadButton, 'click', this.onDownloadButtonClick_); this.addEventListener(this.downloadButton, 'click', this.onDownloadButtonClick_);
this.addEventListener(this.repeatCheckbox, 'change', this.onRepeatCheckboxChange_);
var currentColors = pskl.app.currentColorsService.getCurrentColors();
var tooManyColors = currentColors.length >= MAX_GIF_COLORS;
document.querySelector('.gif-export-warning').classList.toggle('visible', tooManyColors);
}; };
ns.GifExportController.prototype.getZoom_ = function () { ns.GifExportController.prototype.getZoom_ = function () {
@ -105,6 +113,7 @@
width: width * zoom, width: width * zoom,
height: height * zoom, height: height * zoom,
preserveColors : preserveColors, preserveColors : preserveColors,
repeat: this.getRepeatSetting_() ? 0 : -1,
transparent : transparent transparent : transparent
}); });
@ -149,6 +158,15 @@
return transparentColor; return transparentColor;
}; };
ns.GifExportController.prototype.onRepeatCheckboxChange_ = function () {
var checked = this.repeatCheckbox.checked;
pskl.UserSettings.set(pskl.UserSettings.EXPORT_GIF_REPEAT, checked);
};
ns.GifExportController.prototype.getRepeatSetting_ = function () {
return pskl.UserSettings.get(pskl.UserSettings.EXPORT_GIF_REPEAT);
};
ns.GifExportController.prototype.updateStatus_ = function (imageUrl, error) { ns.GifExportController.prototype.updateStatus_ = function (imageUrl, error) {
if (imageUrl) { if (imageUrl) {
var linkTpl = '<a class="highlight" href="{{link}}" target="_blank">{{shortLink}}</a>'; var linkTpl = '<a class="highlight" href="{{link}}" target="_blank">{{shortLink}}</a>';

View File

@ -12,9 +12,6 @@
ns.MiscExportController.prototype.init = function () { ns.MiscExportController.prototype.init = function () {
var cDownloadButton = document.querySelector('.c-download-button'); var cDownloadButton = document.querySelector('.c-download-button');
this.addEventListener(cDownloadButton, 'click', this.onDownloadCFileClick_); this.addEventListener(cDownloadButton, 'click', this.onDownloadCFileClick_);
var selectedFrameDownloadButton = document.querySelector('.selected-frame-download-button');
this.addEventListener(selectedFrameDownloadButton, 'click', this.onDownloadSelectedFrameClick_);
}; };
ns.MiscExportController.prototype.onDownloadCFileClick_ = function (evt) { ns.MiscExportController.prototype.onDownloadCFileClick_ = function (evt) {
@ -76,14 +73,4 @@
hexStr += ('00' + r.toString(16)).substr(-2); hexStr += ('00' + r.toString(16)).substr(-2);
return hexStr; return hexStr;
}; };
ns.MiscExportController.prototype.onDownloadSelectedFrameClick_ = function (evt) {
var frameIndex = this.piskelController.getCurrentFrameIndex();
var fileName = this.getPiskelName_() + '-' + (frameIndex + 1) + '.png';
var canvas = this.piskelController.renderFrameAt(frameIndex, true);
pskl.utils.BlobUtils.canvasToBlob(canvas, function(blob) {
pskl.utils.FileUtils.downloadAsFile(blob, fileName);
});
};
})(); })();

View File

@ -31,6 +31,9 @@
var downloadButton = document.querySelector('.png-download-button'); var downloadButton = document.querySelector('.png-download-button');
var downloadPixiButton = document.querySelector('.png-pixi-download-button'); var downloadPixiButton = document.querySelector('.png-pixi-download-button');
var dataUriButton = document.querySelector('.datauri-open-button'); var dataUriButton = document.querySelector('.datauri-open-button');
var selectedFrameDownloadButton = document.querySelector('.selected-frame-download-button');
this.pixiInlineImageCheckbox = document.querySelector('.png-pixi-inline-image-checkbox');
this.initLayoutSection_(); this.initLayoutSection_();
this.updateDimensionLabel_(); this.updateDimensionLabel_();
@ -39,6 +42,7 @@
this.addEventListener(downloadButton, 'click', this.onDownloadClick_); this.addEventListener(downloadButton, 'click', this.onDownloadClick_);
this.addEventListener(downloadPixiButton, 'click', this.onPixiDownloadClick_); this.addEventListener(downloadPixiButton, 'click', this.onPixiDownloadClick_);
this.addEventListener(dataUriButton, 'click', this.onDataUriClick_); this.addEventListener(dataUriButton, 'click', this.onDataUriClick_);
this.addEventListener(selectedFrameDownloadButton, 'click', this.onDownloadSelectedFrameClick_);
$.subscribe(Events.EXPORT_SCALE_CHANGED, this.onScaleChanged_); $.subscribe(Events.EXPORT_SCALE_CHANGED, this.onScaleChanged_);
}; };
@ -149,9 +153,9 @@
}; };
// Used and overridden in casper integration tests. // Used and overridden in casper integration tests.
ns.PngExportController.prototype.downloadCanvas_ = function (canvas) { ns.PngExportController.prototype.downloadCanvas_ = function (canvas, name) {
// Generate file name // Generate file name
var name = this.piskelController.getPiskel().getDescriptor().name; name = name || this.piskelController.getPiskel().getDescriptor().name;
var fileName = name + '.png'; var fileName = name + '.png';
// Transform to blob and start download. // Transform to blob and start download.
@ -167,7 +171,15 @@
var canvas = this.createPngSpritesheet_(); var canvas = this.createPngSpritesheet_();
var name = this.piskelController.getPiskel().getDescriptor().name; var name = this.piskelController.getPiskel().getDescriptor().name;
zip.file(name + '.png', pskl.utils.CanvasUtils.getBase64FromCanvas(canvas) + '\n', {base64: true}); var image;
if (this.pixiInlineImageCheckbox.checked) {
image = canvas.toDataURL('image/png');
} else {
image = name + '.png';
zip.file(image, pskl.utils.CanvasUtils.getBase64FromCanvas(canvas) + '\n', {base64: true});
}
var width = canvas.width / this.getColumns_(); var width = canvas.width / this.getColumns_();
var height = canvas.height / this.getRows_(); var height = canvas.height / this.getRows_();
@ -192,7 +204,7 @@
'meta': { 'meta': {
'app': 'https://github.com/piskelapp/piskel/', 'app': 'https://github.com/piskelapp/piskel/',
'version': '1.0', 'version': '1.0',
'image': name + '.png', 'image': image,
'format': 'RGBA8888', 'format': 'RGBA8888',
'size': {'w': canvas.width,'h': canvas.height} 'size': {'w': canvas.width,'h': canvas.height}
} }
@ -207,6 +219,27 @@
}; };
ns.PngExportController.prototype.onDataUriClick_ = function (evt) { ns.PngExportController.prototype.onDataUriClick_ = function (evt) {
window.open(this.createPngSpritesheet_().toDataURL('image/png')); var popup = window.open('about:blank');
var dataUri = this.createPngSpritesheet_().toDataURL('image/png');
window.setTimeout(function () {
var html = pskl.utils.Template.getAndReplace('data-uri-export-partial', {
src: dataUri
});
popup.document.title = dataUri;
popup.document.body.innerHTML = html;
}.bind(this), 500);
};
ns.PngExportController.prototype.onDownloadSelectedFrameClick_ = function (evt) {
var frameIndex = this.piskelController.getCurrentFrameIndex();
var name = this.piskelController.getPiskel().getDescriptor().name;
var canvas = this.piskelController.renderFrameAt(frameIndex, true);
var zoom = this.exportController.getExportZoom();
if (zoom != 1) {
canvas = pskl.utils.ImageResizer.resize(canvas, canvas.width * zoom, canvas.height * zoom, false);
}
var fileName = name + '-' + (frameIndex + 1) + '.png';
this.downloadCanvas_(canvas, fileName);
}; };
})(); })();

View File

@ -13,11 +13,24 @@
this.pngFilePrefixInput.value = 'sprite_'; this.pngFilePrefixInput.value = 'sprite_';
this.splitByLayersCheckbox = document.querySelector('.zip-split-layers-checkbox'); this.splitByLayersCheckbox = document.querySelector('.zip-split-layers-checkbox');
this.addEventListener(this.splitByLayersCheckbox, 'change', this.onSplitLayersClick_);
this.useLayerNamesContainer = document.querySelector('.use-layer-names-container');
this.useLayerNamesCheckbox = document.querySelector('.zip-use-layer-names-checkbox');
this.toggleHideUseLayerNamesCheckbox();
var zipButton = document.querySelector('.zip-generate-button'); var zipButton = document.querySelector('.zip-generate-button');
this.addEventListener(zipButton, 'click', this.onZipButtonClick_); this.addEventListener(zipButton, 'click', this.onZipButtonClick_);
}; };
ns.ZipExportController.prototype.toggleHideUseLayerNamesCheckbox = function () {
this.useLayerNamesContainer.style.display = (this.splitByLayersCheckbox.checked ? 'block' : 'none');
};
ns.ZipExportController.prototype.onSplitLayersClick_ = function () {
this.toggleHideUseLayerNamesCheckbox();
};
ns.ZipExportController.prototype.onZipButtonClick_ = function () { ns.ZipExportController.prototype.onZipButtonClick_ = function () {
var zip = new window.JSZip(); var zip = new window.JSZip();
@ -63,6 +76,9 @@
var basename = this.pngFilePrefixInput.value; var basename = this.pngFilePrefixInput.value;
var frameid = pskl.utils.StringUtils.leftPad(i + 1, framePaddingLength, '0'); var frameid = pskl.utils.StringUtils.leftPad(i + 1, framePaddingLength, '0');
var filename = 'l' + layerid + '_' + basename + frameid + '.png'; var filename = 'l' + layerid + '_' + basename + frameid + '.png';
if (this.useLayerNamesCheckbox.checked) {
filename = layer.getName() + '_' + basename + frameid + '.png';
}
zip.file(filename, pskl.utils.CanvasUtils.getBase64FromCanvas(canvas) + '\n', {base64: true}); zip.file(filename, pskl.utils.CanvasUtils.getBase64FromCanvas(canvas) + '\n', {base64: true});
} }
} }

View File

@ -22,6 +22,7 @@
this.piskelController = piskelController; this.piskelController = piskelController;
this.preferencesController = preferencesController; this.preferencesController = preferencesController;
this.sizePicker = new pskl.widgets.SizePicker(this.onSizePickerChanged_.bind(this)); this.sizePicker = new pskl.widgets.SizePicker(this.onSizePickerChanged_.bind(this));
this.spacingPicker = new pskl.widgets.SizePicker(this.onSpacingPickerChanged_.bind(this));
}; };
pskl.utils.inherit(ns.GridPreferencesController, pskl.controller.settings.AbstractSettingController); pskl.utils.inherit(ns.GridPreferencesController, pskl.controller.settings.AbstractSettingController);
@ -40,6 +41,11 @@
this.sizePicker.init(document.querySelector('.grid-size-container')); this.sizePicker.init(document.querySelector('.grid-size-container'));
this.sizePicker.setSize(gridWidth); this.sizePicker.setSize(gridWidth);
//Grid Spacing
var gridSpacing = pskl.UserSettings.get(pskl.UserSettings.GRID_SPACING);
this.spacingPicker.init(document.querySelector('.grid-spacing-container'));
this.spacingPicker.setSize(gridSpacing);
// Grid color // Grid color
var colorListItemTemplate = pskl.utils.Template.get('color-list-item-template'); var colorListItemTemplate = pskl.utils.Template.get('color-list-item-template');
@ -68,6 +74,7 @@
ns.GridPreferencesController.prototype.destroy = function () { ns.GridPreferencesController.prototype.destroy = function () {
this.sizePicker.destroy(); this.sizePicker.destroy();
this.spacingPicker.destroy();
this.superclass.destroy.call(this); this.superclass.destroy.call(this);
}; };
@ -75,6 +82,10 @@
pskl.UserSettings.set(pskl.UserSettings.GRID_WIDTH, size); pskl.UserSettings.set(pskl.UserSettings.GRID_WIDTH, size);
}; };
ns.GridPreferencesController.prototype.onSpacingPickerChanged_ = function (size) {
pskl.UserSettings.set(pskl.UserSettings.GRID_SPACING, size);
};
ns.GridPreferencesController.prototype.onEnableGridChange_ = function (evt) { ns.GridPreferencesController.prototype.onEnableGridChange_ = function (evt) {
pskl.UserSettings.set(pskl.UserSettings.GRID_ENABLED, evt.currentTarget.checked); pskl.UserSettings.set(pskl.UserSettings.GRID_ENABLED, evt.currentTarget.checked);
}; };

View File

@ -0,0 +1,258 @@
(function () {
var ns = $.namespace('pskl.database');
var DB_NAME = 'PiskelSessionsDatabase';
var DB_VERSION = 1;
// Simple wrapper to promisify a request.
var _requestPromise = function (req) {
var deferred = Q.defer();
req.onsuccess = deferred.resolve.bind(deferred);
req.onerror = deferred.reject.bind(deferred);
return deferred.promise;
};
/**
* The BackupDatabase handles all the database interactions related
* to piskel snapshots continuously saved while during the usage of
* Piskel.
*/
ns.BackupDatabase = function () {
this.db = null;
};
ns.BackupDatabase.DB_NAME = DB_NAME;
/**
* Open and initialize the database.
* Returns a promise that resolves when the database is opened.
*/
ns.BackupDatabase.prototype.init = function () {
var request = window.indexedDB.open(DB_NAME, DB_VERSION);
request.onupgradeneeded = this.onUpgradeNeeded_.bind(this);
return _requestPromise(request).then(function (event) {
this.db = event.target.result;
return this.db;
}.bind(this)).catch(function (e) {
console.log('Could not initialize the piskel backup database');
});
};
ns.BackupDatabase.prototype.onUpgradeNeeded_ = function (event) {
// Set this.db early to allow migration scripts to access it in oncomplete.
this.db = event.target.result;
// Create an object store "piskels" with the autoIncrement flag set as true.
var objectStore = this.db.createObjectStore('snapshots', { keyPath: 'id', autoIncrement : true });
objectStore.createIndex('session_id', 'session_id', { unique: false });
objectStore.createIndex('date', 'date', { unique: false });
objectStore.createIndex('session_id, date', ['session_id', 'date'], { unique: false });
objectStore.transaction.oncomplete = function(event) {
// Nothing to do at the moment!
}.bind(this);
};
ns.BackupDatabase.prototype.openObjectStore_ = function () {
return this.db.transaction(['snapshots'], 'readwrite').objectStore('snapshots');
};
/**
* Send an add request for the provided snapshot.
* Returns a promise that resolves the request event.
*/
ns.BackupDatabase.prototype.createSnapshot = function (snapshot) {
var objectStore = this.openObjectStore_();
var request = objectStore.add(snapshot);
return _requestPromise(request);
};
/**
* Send a put request for the provided snapshot.
* Returns a promise that resolves the request event.
*/
ns.BackupDatabase.prototype.updateSnapshot = function (snapshot) {
var objectStore = this.openObjectStore_();
var request = objectStore.put(snapshot);
return _requestPromise(request);
};
/**
* Send a delete request for the provided snapshot.
* Returns a promise that resolves the request event.
*/
ns.BackupDatabase.prototype.deleteSnapshot = function (snapshot) {
var objectStore = this.openObjectStore_();
var request = objectStore.delete(snapshot.id);
return _requestPromise(request);
};
/**
* Send a get request for the provided snapshotId.
* Returns a promise that resolves the request event.
*/
ns.BackupDatabase.prototype.getSnapshot = function (snapshotId) {
var objectStore = this.openObjectStore_();
var request = objectStore.get(snapshotId);
return _requestPromise(request).then(function (event) {
return event.target.result;
});
};
/**
* Get the last (most recent) snapshot that satisfies the accept filter provided.
* Returns a promise that will resolve with the first matching snapshot (or null
* if no valid snapshot is found).
*
* @param {Function} accept:
* Filter method that takes a snapshot as argument and should return true
* if the snapshot is valid.
*/
ns.BackupDatabase.prototype.findLastSnapshot = function (accept) {
// Create the backup promise.
var deferred = Q.defer();
// Open a transaction to the snapshots object store.
var objectStore = this.db.transaction(['snapshots']).objectStore('snapshots');
var index = objectStore.index('date');
var range = IDBKeyRange.upperBound(Infinity);
index.openCursor(range, 'prev').onsuccess = function(event) {
var cursor = event.target.result;
var snapshot = cursor && cursor.value;
// Resolve null if we couldn't find a matching snapshot.
if (!snapshot) {
deferred.resolve(null);
} else if (accept(snapshot)) {
deferred.resolve(snapshot);
} else {
cursor.continue();
}
};
return deferred.promise;
};
/**
* Retrieve all the snapshots for a given session id, sorted by descending date order.
* Returns a promise that resolves with an array of snapshots.
*
* @param {String} sessionId
* The session id
*/
ns.BackupDatabase.prototype.getSnapshotsBySessionId = function (sessionId) {
// Create the backup promise.
var deferred = Q.defer();
// Open a transaction to the snapshots object store.
var objectStore = this.db.transaction(['snapshots']).objectStore('snapshots');
// Loop on all the saved snapshots for the provided piskel id
var index = objectStore.index('session_id, date');
var keyRange = IDBKeyRange.bound(
[sessionId, 0],
[sessionId, Infinity]
);
var snapshots = [];
// Ordered by date in descending order.
index.openCursor(keyRange, 'prev').onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
snapshots.push(cursor.value);
cursor.continue();
} else {
// Consumed all piskel snapshots
deferred.resolve(snapshots);
}
};
return deferred.promise;
};
ns.BackupDatabase.prototype.getSessions = function () {
// Create the backup promise.
var deferred = Q.defer();
// Open a transaction to the snapshots object store.
var objectStore = this.db.transaction(['snapshots']).objectStore('snapshots');
var sessions = {};
var _createSession = function (snapshot) {
sessions[snapshot.session_id] = {
startDate: snapshot.date,
endDate: snapshot.date,
name: snapshot.name,
description: snapshot.description,
id: snapshot.session_id,
count: 1
};
};
var _updateSession = function (snapshot) {
var s = sessions[snapshot.session_id];
s.startDate = Math.min(s.startDate, snapshot.date);
s.endDate = Math.max(s.endDate, snapshot.date);
s.count++;
if (s.endDate === snapshot.date) {
// If the endDate was updated, update also the session metadata to
// reflect the latest state.
s.name = snapshot.name;
s.description = snapshot.description;
}
};
var index = objectStore.index('date');
var range = IDBKeyRange.upperBound(Infinity);
index.openCursor(range, 'prev').onsuccess = function(event) {
var cursor = event.target.result;
var snapshot = cursor && cursor.value;
if (!snapshot) {
deferred.resolve(sessions);
} else {
if (sessions[snapshot.session_id]) {
_updateSession(snapshot);
} else {
_createSession(snapshot);
}
cursor.continue();
}
};
return deferred.promise.then(function (sessions) {
// Convert the sessions map to an array.
return Object.keys(sessions).map(function (key) {
return sessions[key];
});
});
};
ns.BackupDatabase.prototype.deleteSnapshotsForSession = function (sessionId) {
// Create the backup promise.
var deferred = Q.defer();
// Open a transaction to the snapshots object store.
var objectStore = this.openObjectStore_();
// Loop on all the saved snapshots for the provided piskel id
var index = objectStore.index('session_id');
var keyRange = IDBKeyRange.only(sessionId);
index.openCursor(keyRange).onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
cursor.delete();
cursor.continue();
} else {
deferred.resolve();
}
};
return deferred.promise;
};
})();

View File

@ -0,0 +1,139 @@
(function () {
var ns = $.namespace('pskl.database');
var DB_NAME = 'PiskelDatabase';
var DB_VERSION = 1;
// Simple wrapper to promisify a request.
var _requestPromise = function (req) {
var deferred = Q.defer();
req.onsuccess = deferred.resolve.bind(deferred);
req.onerror = deferred.reject.bind(deferred);
return deferred.promise;
};
/**
* The PiskelDatabase handles all the database interactions related
* to the local piskel saved that can be performed in-browser.
*/
ns.PiskelDatabase = function (options) {
this.db = null;
};
ns.PiskelDatabase.DB_NAME = DB_NAME;
ns.PiskelDatabase.prototype.init = function () {
var request = window.indexedDB.open(DB_NAME, DB_VERSION);
request.onupgradeneeded = this.onUpgradeNeeded_.bind(this);
return _requestPromise(request).then(function (event) {
this.db = event.target.result;
return this.db;
}.bind(this));
};
ns.PiskelDatabase.prototype.onUpgradeNeeded_ = function (event) {
// Set this.db early to allow migration scripts to access it in oncomplete.
this.db = event.target.result;
// Create an object store "piskels" with the autoIncrement flag set as true.
var objectStore = this.db.createObjectStore('piskels', { keyPath : 'name' });
objectStore.transaction.oncomplete = function(event) {
pskl.database.migrate.MigrateLocalStorageToIndexedDb.migrate(this);
}.bind(this);
};
ns.PiskelDatabase.prototype.openObjectStore_ = function () {
return this.db.transaction(['piskels'], 'readwrite').objectStore('piskels');
};
/**
* Send a get request for the provided name.
* Returns a promise that resolves the request event.
*/
ns.PiskelDatabase.prototype.get = function (name) {
var objectStore = this.openObjectStore_();
return _requestPromise(objectStore.get(name)).then(function (event) {
return event.target.result;
});
};
/**
* List all locally saved piskels.
* Returns a promise that resolves an array of objects:
* - name: name of the piskel
* - description: description of the piskel
* - date: save date
*
* The sprite content is not contained in the object and
* needs to be retrieved with a separate get.
*/
ns.PiskelDatabase.prototype.list = function () {
var deferred = Q.defer();
var piskels = [];
var objectStore = this.openObjectStore_();
var cursor = objectStore.openCursor();
cursor.onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
piskels.push({
name: cursor.value.name,
date: cursor.value.date,
description: cursor.value.description
});
cursor.continue();
} else {
// Cursor consumed all availabled piskels
deferred.resolve(piskels);
}
};
cursor.onerror = function () {
deferred.reject();
};
return deferred.promise;
};
/**
* Send an put request for the provided args.
* Returns a promise that resolves the request event.
*/
ns.PiskelDatabase.prototype.update = function (name, description, date, serialized) {
var data = {};
data.name = name;
data.serialized = serialized;
data.date = date;
data.description = description;
var objectStore = this.openObjectStore_();
return _requestPromise(objectStore.put(data));
};
/**
* Send an add request for the provided args.
* Returns a promise that resolves the request event.
*/
ns.PiskelDatabase.prototype.create = function (name, description, date, serialized) {
var data = {};
data.name = name;
data.serialized = serialized;
data.date = date;
data.description = description;
var objectStore = this.openObjectStore_();
return _requestPromise(objectStore.add(data));
};
/**
* Delete a saved piskel for the provided name.
* Returns a promise that resolves the request event.
*/
ns.PiskelDatabase.prototype.delete = function (name) {
var objectStore = this.openObjectStore_();
return _requestPromise(objectStore.delete(name));
};
})();

View File

@ -0,0 +1,76 @@
(function () {
var ns = $.namespace('pskl.database.migrate');
// Simple migration helper to move local storage saves to indexed db.
ns.MigrateLocalStorageToIndexedDb = {};
ns.MigrateLocalStorageToIndexedDb.migrate = function (piskelDatabase) {
var deferred = Q.defer();
var localStorageService = pskl.app.localStorageService;
var localStorageKeys = localStorageService.getKeys();
var migrationData = localStorageKeys.map(function (key) {
return {
name: key.name,
description: key.description,
date: key.date,
serialized: localStorageService.getPiskel(key.name)
};
});
// Define the sequential migration process.
// Wait for each sprite to be saved before saving the next one.
var success = true;
var migrateSprite = function (index) {
var data = migrationData[index];
if (!data) {
console.log('Data migration from local storage to indexed db finished.');
if (success) {
console.log('Local storage piskels successfully migrated. Old copies will be deleted.');
ns.MigrateLocalStorageToIndexedDb.deleteLocalStoragePiskels();
}
deferred.resolve();
} else {
ns.MigrateLocalStorageToIndexedDb.save_(piskelDatabase, data)
.then(function () {
migrateSprite(index + 1);
})
.catch(function (e) {
var success = false;
console.error('Failed to migrate local storage sprite for name: ' + data.name);
migrateSprite(index + 1);
});
}
};
// Start the migration.
migrateSprite(0);
return deferred.promise;
};
ns.MigrateLocalStorageToIndexedDb.save_ = function (piskelDatabase, piskelData) {
return piskelDatabase.get(piskelData.name).then(function (data) {
if (typeof data !== 'undefined') {
return piskelDatabase.update(piskelData.name, piskelData.description, piskelData.date, piskelData.serialized);
} else {
return piskelDatabase.create(piskelData.name, piskelData.description, piskelData.date, piskelData.serialized);
}
});
};
ns.MigrateLocalStorageToIndexedDb.deleteLocalStoragePiskels = function () {
var localStorageKeys = pskl.app.localStorageService.getKeys();
// Remove all sprites.
localStorageKeys.forEach(function (key) {
window.localStorage.removeItem('piskel.' + key.name);
});
// Remove keys.
window.localStorage.removeItem('piskel.keys');
};
})();

View File

@ -32,6 +32,7 @@
}; };
ns.DrawingTestPlayer.prototype.setupInitialState_ = function () { ns.DrawingTestPlayer.prototype.setupInitialState_ = function () {
var size = this.initialState.size; var size = this.initialState.size;
var piskel = this.createPiskel_(size.width, size.height); var piskel = this.createPiskel_(size.width, size.height);
pskl.app.piskelController.setPiskel(piskel); pskl.app.piskelController.setPiskel(piskel);
@ -39,9 +40,10 @@
$.publish(Events.SELECT_PRIMARY_COLOR, [this.initialState.primaryColor]); $.publish(Events.SELECT_PRIMARY_COLOR, [this.initialState.primaryColor]);
$.publish(Events.SELECT_SECONDARY_COLOR, [this.initialState.secondaryColor]); $.publish(Events.SELECT_SECONDARY_COLOR, [this.initialState.secondaryColor]);
$.publish(Events.SELECT_TOOL, [this.initialState.selectedTool]); $.publish(Events.SELECT_TOOL, [this.initialState.selectedTool]);
if (this.initialState.penSize) {
// Old tests do not have penSize stored in initialState, fallback to 1.
var penSize = this.initialState.penSize || 1;
pskl.app.penSizeService.setPenSize(this.initialState.penSize); pskl.app.penSizeService.setPenSize(this.initialState.penSize);
}
}; };
ns.DrawingTestPlayer.prototype.createPiskel_ = function (width, height) { ns.DrawingTestPlayer.prototype.createPiskel_ = function (width, height) {
@ -108,6 +110,8 @@
this.playTransformToolEvent_(recordEvent); this.playTransformToolEvent_(recordEvent);
} else if (recordEvent.type === 'instrumented-event') { } else if (recordEvent.type === 'instrumented-event') {
this.playInstrumentedEvent_(recordEvent); this.playInstrumentedEvent_(recordEvent);
} else if (recordEvent.type === 'clipboard-event') {
this.playClipboardEvent_(recordEvent);
} }
// Record the time spent replaying the event // Record the time spent replaying the event
@ -169,6 +173,16 @@
pskl.app.piskelController[recordEvent.methodName].apply(pskl.app.piskelController, recordEvent.args); pskl.app.piskelController[recordEvent.methodName].apply(pskl.app.piskelController, recordEvent.args);
}; };
ns.DrawingTestPlayer.prototype.playClipboardEvent_ = function (recordEvent) {
$.publish(recordEvent.event.type, {
preventDefault: function () {},
clipboardData: {
items: [],
setData: function () {}
}
});
};
ns.DrawingTestPlayer.prototype.onTestEnd_ = function () { ns.DrawingTestPlayer.prototype.onTestEnd_ = function () {
this.removeMouseShim_(); this.removeMouseShim_();
// Restore the original drawing loop. // Restore the original drawing loop.

View File

@ -15,6 +15,10 @@
$.subscribe(Events.TRANSFORMATION_EVENT, this.onTransformationEvent_.bind(this)); $.subscribe(Events.TRANSFORMATION_EVENT, this.onTransformationEvent_.bind(this));
$.subscribe(Events.PRIMARY_COLOR_SELECTED, this.onColorEvent_.bind(this, true)); $.subscribe(Events.PRIMARY_COLOR_SELECTED, this.onColorEvent_.bind(this, true));
$.subscribe(Events.SECONDARY_COLOR_SELECTED, this.onColorEvent_.bind(this, false)); $.subscribe(Events.SECONDARY_COLOR_SELECTED, this.onColorEvent_.bind(this, false));
$.subscribe(Events.CLIPBOARD_COPY, this.onClipboardEvent_.bind(this));
$.subscribe(Events.CLIPBOARD_CUT, this.onClipboardEvent_.bind(this));
$.subscribe(Events.CLIPBOARD_PASTE, this.onClipboardEvent_.bind(this));
for (var key in this.piskelController) { for (var key in this.piskelController) {
if (typeof this.piskelController[key] == 'function') { if (typeof this.piskelController[key] == 'function') {
@ -136,6 +140,15 @@
} }
}; };
ns.DrawingTestRecorder.prototype.onClipboardEvent_ = function (evt) {
if (this.isRecording) {
var recordEvent = {};
recordEvent.type = 'clipboard-event';
recordEvent.event = evt;
this.events.push(recordEvent);
}
};
ns.DrawingTestRecorder.prototype.onInstrumentedMethod_ = function (callee, methodName, args) { ns.DrawingTestRecorder.prototype.onInstrumentedMethod_ = function (callee, methodName, args) {
if (this.isRecording) { if (this.isRecording) {
var recordEvent = {}; var recordEvent = {};

View File

@ -16,7 +16,7 @@
this.descriptor = descriptor; this.descriptor = descriptor;
this.savePath = null; this.savePath = null;
this.fps = fps; this.fps = fps;
this.hiddenFrames = [];
} else { } else {
throw 'Missing arguments in Piskel constructor : ' + Array.prototype.join.call(arguments, ','); throw 'Missing arguments in Piskel constructor : ' + Array.prototype.join.call(arguments, ',');
} }

View File

@ -5,11 +5,10 @@
this.container = container; this.container = container;
this.setZoom(zoom); this.setZoom(zoom);
var containerEl = container.get(0); var containerDocument = container.ownerDocument;
var containerDocument = containerEl.ownerDocument;
this.frameContainer = containerDocument.createElement('div'); this.frameContainer = containerDocument.createElement('div');
this.frameContainer.classList.add('background-image-frame-container'); this.frameContainer.classList.add('background-image-frame-container');
container.get(0).appendChild(this.frameContainer); container.appendChild(this.frameContainer);
this.cachedFrameProcessor = new pskl.model.frame.CachedFrameProcessor(); this.cachedFrameProcessor = new pskl.model.frame.CachedFrameProcessor();
this.cachedFrameProcessor.setFrameProcessor(this.frameToDataUrl_.bind(this)); this.cachedFrameProcessor.setFrameProcessor(this.frameToDataUrl_.bind(this));

View File

@ -34,6 +34,7 @@
var serializedFrame = [ var serializedFrame = [
this.getZoom(), this.getZoom(),
this.getGridWidth(), this.getGridWidth(),
this.getGridSpacing(),
this.getGridColor(), this.getGridColor(),
pskl.UserSettings.get('SEAMLESS_MODE'), pskl.UserSettings.get('SEAMLESS_MODE'),
pskl.UserSettings.get('SEAMLESS_OPACITY'), pskl.UserSettings.get('SEAMLESS_OPACITY'),

View File

@ -56,6 +56,7 @@
this.setDisplaySize(renderingOptions.width, renderingOptions.height); this.setDisplaySize(renderingOptions.width, renderingOptions.height);
this.setGridWidth(this.getUserGridWidth_()); this.setGridWidth(this.getUserGridWidth_());
this.setGridSpacing(this.getUserGridSpacing_());
$.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this)); $.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
}; };
@ -106,7 +107,7 @@
this.displayWidth = width; this.displayWidth = width;
this.displayHeight = height; this.displayHeight = height;
if (this.displayCanvas) { if (this.displayCanvas) {
$(this.displayCanvas).remove(); this.displayCanvas.parentNode.removeChild(this.displayCanvas);
this.displayCanvas = null; this.displayCanvas = null;
} }
this.createDisplayCanvas_(); this.createDisplayCanvas_();
@ -146,6 +147,10 @@
this.gridWidth_ = value; this.gridWidth_ = value;
}; };
ns.FrameRenderer.prototype.setGridSpacing = function (value) {
this.gridSpacing_ = value;
};
ns.FrameRenderer.prototype.getGridWidth = function () { ns.FrameRenderer.prototype.getGridWidth = function () {
if (!this.supportGridRendering) { if (!this.supportGridRendering) {
return 0; return 0;
@ -154,15 +159,29 @@
return this.gridWidth_; return this.gridWidth_;
}; };
ns.FrameRenderer.prototype.getGridSpacing = function () {
if (!this.supportGridRendering) {
return 0;
}
return this.gridSpacing_;
};
/** /**
* Compute a grid width value best suited to the current display context, * Compute a grid width value best suited to the current display context,
* particularly for the current zoom level * particularly for the current zoom level
*/ */
ns.FrameRenderer.prototype.computeGridWidthForDisplay_ = function () { ns.FrameRenderer.prototype.computeGridWidthForDisplay_ = function () {
var gridSpacing = this.getGridSpacing();
if (this.zoom * gridSpacing < 6) {
return 0;
}
var gridWidth = this.getGridWidth(); var gridWidth = this.getGridWidth();
while (this.zoom < 6 * gridWidth) { while (gridWidth > 1 && this.zoom < 6 * gridWidth) {
gridWidth--; gridWidth--;
} }
return gridWidth; return gridWidth;
}; };
@ -180,13 +199,16 @@
this.displayCanvas = pskl.utils.CanvasUtils.createCanvas(width, height, this.classList); this.displayCanvas = pskl.utils.CanvasUtils.createCanvas(width, height, this.classList);
pskl.utils.CanvasUtils.disableImageSmoothing(this.displayCanvas); pskl.utils.CanvasUtils.disableImageSmoothing(this.displayCanvas);
this.container.append(this.displayCanvas); this.container.appendChild(this.displayCanvas);
}; };
ns.FrameRenderer.prototype.onUserSettingsChange_ = function (evt, settingName, settingValue) { ns.FrameRenderer.prototype.onUserSettingsChange_ = function (evt, settingName, settingValue) {
var settings = pskl.UserSettings; var settings = pskl.UserSettings;
if (settingName == settings.GRID_WIDTH || settingName == settings.GRID_ENABLED) { if (settingName == settings.GRID_WIDTH ||
settingName == settings.GRID_SPACING ||
settingName == settings.GRID_ENABLED) {
this.setGridWidth(this.getUserGridWidth_()); this.setGridWidth(this.getUserGridWidth_());
this.setGridSpacing(this.getUserGridSpacing_());
} }
}; };
@ -196,15 +218,21 @@
return gridEnabled ? width : 0; return gridEnabled ? width : 0;
}; };
ns.FrameRenderer.prototype.getUserGridSpacing_ = function () {
var gridEnabled = pskl.UserSettings.get(pskl.UserSettings.GRID_ENABLED);
var spacing = pskl.UserSettings.get(pskl.UserSettings.GRID_SPACING);
return gridEnabled ? spacing : 0;
};
/** /**
* Transform a screen pixel-based coordinate (relative to the top-left corner of the rendered * Transform a screen pixel-based coordinate (relative to the top-left corner of the rendered
* frame) into a sprite coordinate in column and row. * frame) into a sprite coordinate in column and row.
* @public * @public
*/ */
ns.FrameRenderer.prototype.getCoordinates = function(x, y) { ns.FrameRenderer.prototype.getCoordinates = function(x, y) {
var containerOffset = this.container.offset(); var containerRect = this.container.getBoundingClientRect();
x = x - containerOffset.left; x = x - containerRect.left;
y = y - containerOffset.top; y = y - containerRect.top;
// apply margins // apply margins
x = x - this.margin.x; x = x - this.margin.x;
@ -233,9 +261,9 @@
x = x + this.margin.x; x = x + this.margin.x;
y = y + this.margin.y; y = y + this.margin.y;
var containerOffset = this.container.offset(); var containerRect = this.container.getBoundingClientRect();
x = x + containerOffset.left; x = x + containerRect.left;
y = y + containerOffset.top; y = y + containerRect.top;
return { return {
x : x + (cellSize / 2), x : x + (cellSize / 2),
@ -298,6 +326,7 @@
// Draw grid. // Draw grid.
var gridWidth = this.computeGridWidthForDisplay_(); var gridWidth = this.computeGridWidthForDisplay_();
var gridSpacing = this.getGridSpacing();
if (gridWidth > 0) { if (gridWidth > 0) {
var gridColor = this.getGridColor(); var gridColor = this.getGridColor();
// Scale out before drawing the grid. // Scale out before drawing the grid.
@ -313,13 +342,17 @@
// Draw or clear vertical lines. // Draw or clear vertical lines.
for (var i = 1 ; i < frame.getWidth() ; i++) { for (var i = 1 ; i < frame.getWidth() ; i++) {
if (i % gridSpacing == 0) {
drawOrClear((i * z) - (gridWidth / 2), 0, gridWidth, h * z); drawOrClear((i * z) - (gridWidth / 2), 0, gridWidth, h * z);
} }
}
// Draw or clear horizontal lines. // Draw or clear horizontal lines.
for (var j = 1 ; j < frame.getHeight() ; j++) { for (var j = 1 ; j < frame.getHeight() ; j++) {
if (j % gridSpacing == 0) {
drawOrClear(0, (j * z) - (gridWidth / 2), w * z, gridWidth); drawOrClear(0, (j * z) - (gridWidth / 2), w * z, gridWidth);
} }
} }
}
displayContext.restore(); displayContext.restore();
}; };

View File

@ -23,7 +23,7 @@
this.updateLayersCanvasOpacity_(pskl.UserSettings.get(pskl.UserSettings.LAYER_OPACITY)); this.updateLayersCanvasOpacity_(pskl.UserSettings.get(pskl.UserSettings.LAYER_OPACITY));
$.subscribe(Events.PISKEL_RESET, this.flush.bind(this)); $.subscribe(Events.PISKEL_RESET, this.flush.bind(this));
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this)); $.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
}; };
pskl.utils.inherit(pskl.rendering.layer.LayersRenderer, pskl.rendering.CompositeRenderer); pskl.utils.inherit(pskl.rendering.layer.LayersRenderer, pskl.rendering.CompositeRenderer);

View File

@ -5,9 +5,23 @@
this.reset(); this.reset();
}; };
ns.BaseSelection.prototype.stringify = function () {
return JSON.stringify({
pixels: this.pixels,
time: this.time
});
};
ns.BaseSelection.prototype.parse = function (str) {
var selectionData = JSON.parse(str);
this.pixels = selectionData.pixels;
this.time = selectionData.time;
};
ns.BaseSelection.prototype.reset = function () { ns.BaseSelection.prototype.reset = function () {
this.pixels = []; this.pixels = [];
this.hasPastedContent = false; this.hasPastedContent = false;
this.time = -1;
}; };
ns.BaseSelection.prototype.move = function (colDiff, rowDiff) { ns.BaseSelection.prototype.move = function (colDiff, rowDiff) {
@ -30,5 +44,8 @@
}); });
this.hasPastedContent = true; this.hasPastedContent = true;
// Keep track of the selection time to compare between local selection and
// paste event selections.
this.time = Date.now();
}; };
})(); })();

View File

@ -14,21 +14,18 @@
}; };
ns.SelectionManager.prototype.init = function () { ns.SelectionManager.prototype.init = function () {
$.subscribe(Events.SELECTION_CREATED, $.proxy(this.onSelectionCreated_, this)); $.subscribe(Events.SELECTION_CREATED, this.onSelectionCreated_.bind(this));
$.subscribe(Events.SELECTION_DISMISSED, $.proxy(this.onSelectionDismissed_, this)); $.subscribe(Events.SELECTION_DISMISSED, this.onSelectionDismissed_.bind(this));
$.subscribe(Events.SELECTION_MOVE_REQUEST, $.proxy(this.onSelectionMoved_, this)); $.subscribe(Events.SELECTION_MOVE_REQUEST, this.onSelectionMoved_.bind(this));
$.subscribe(Events.SELECTION_COPY, this.copy.bind(this)); $.subscribe(Events.CLIPBOARD_COPY, this.copy.bind(this));
$.subscribe(Events.SELECTION_CUT, this.cut.bind(this)); $.subscribe(Events.CLIPBOARD_CUT, this.copy.bind(this));
$.subscribe(Events.SELECTION_PASTE, this.paste.bind(this)); $.subscribe(Events.CLIPBOARD_PASTE, this.paste.bind(this));
var shortcuts = pskl.service.keyboard.Shortcuts; var shortcuts = pskl.service.keyboard.Shortcuts;
pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.PASTE, this.paste.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.CUT, this.cut.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.COPY, this.copy.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.DELETE, this.onDeleteShortcut_.bind(this)); pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.DELETE, this.onDeleteShortcut_.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.COMMIT, this.commit.bind(this)); pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.COMMIT, this.commit.bind(this));
$.subscribe(Events.TOOL_SELECTED, $.proxy(this.onToolSelected_, this)); $.subscribe(Events.TOOL_SELECTED, this.onToolSelected_.bind(this));
}; };
/** /**
@ -78,29 +75,85 @@
scope : this, scope : this,
replay : { replay : {
type : SELECTION_REPLAY.ERASE, type : SELECTION_REPLAY.ERASE,
pixels : JSON.parse(JSON.stringify(pixels.slice(0))) pixels : JSON.parse(JSON.stringify(pixels))
} }
}); });
}; };
ns.SelectionManager.prototype.cut = function() { ns.SelectionManager.prototype.copy = function(event, domEvent) {
if (this.currentSelection) { if (this.currentSelection && this.piskelController.getCurrentFrame()) {
// Put cut target into the selection:
this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame()); this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame());
if (domEvent) {
domEvent.clipboardData.setData('text/plain', this.currentSelection.stringify());
domEvent.preventDefault();
}
if (event.type === Events.CLIPBOARD_CUT) {
this.erase(); this.erase();
} }
}
}; };
ns.SelectionManager.prototype.paste = function() { ns.SelectionManager.prototype.paste = function(event, domEvent) {
if (!this.currentSelection || !this.currentSelection.hasPastedContent) { var items = domEvent ? domEvent.clipboardData.items : [];
if (window.localStorage.getItem('piskel.clipboard')) {
this.currentSelection = JSON.parse(window.localStorage.getItem('piskel.clipboard')); try {
} else { for (var i = 0 ; i < items.length ; i++) {
var item = items[i];
if (/^image/i.test(item.type)) {
this.pasteImage_(item);
event.stopPropagation();
return;
}
if (/^text\/plain/i.test(item.type)) {
this.pasteText_(item);
event.stopPropagation();
return; return;
} }
} }
} catch (e) {
// Some of the clipboard APIs are not available on Safari/IE
// Allow Piskel to fallback on local currentSelection pasting.
}
var pixels = this.currentSelection.pixels; // temporarily keeping this code path for tests and fallbacks.
if (this.currentSelection && this.currentSelection.hasPastedContent) {
this.pastePixelsOnCurrentFrame_(this.currentSelection.pixels);
}
};
ns.SelectionManager.prototype.pasteImage_ = function(clipboardItem) {
var blob = clipboardItem.getAsFile();
pskl.utils.FileUtils.readImageFile(blob, function (image) {
pskl.app.fileDropperService.dropPosition_ = {x: 0, y: 0};
pskl.app.fileDropperService.onImageLoaded_(image, blob);
}.bind(this));
};
ns.SelectionManager.prototype.pasteText_ = function(clipboardItem) {
var blob = clipboardItem.getAsString(function (selectionString) {
var selectionData = JSON.parse(selectionString);
var time = selectionData.time;
var pixels = selectionData.pixels;
if (this.currentSelection && this.currentSelection.time >= time) {
// If the local selection is newer or equal to the one coming from the clipboard event
// use the local one. The reason is that the "move" information is only updated locally
// without synchronizing it to the clipboard.
// TODO: the selection should store the origin of the selection and the selection itself
// separately.
pixels = this.currentSelection.pixels;
}
if (pixels) {
// If the current clipboard data is some random text, pixels will not be defined.
this.pastePixelsOnCurrentFrame_(pixels);
}
}.bind(this));
};
ns.SelectionManager.prototype.pastePixelsOnCurrentFrame_ = function (pixels) {
var frame = this.piskelController.getCurrentFrame(); var frame = this.piskelController.getCurrentFrame();
this.pastePixels_(frame, pixels); this.pastePixels_(frame, pixels);
@ -123,8 +176,7 @@
var tool = pskl.app.drawingController.currentToolBehavior; var tool = pskl.app.drawingController.currentToolBehavior;
var isSelectionTool = tool instanceof pskl.tools.drawing.selection.BaseSelect; var isSelectionTool = tool instanceof pskl.tools.drawing.selection.BaseSelect;
if (isSelectionTool) { if (isSelectionTool) {
var overlay = pskl.app.drawingController.overlayFrame; tool.commitSelection();
tool.commitSelection(overlay);
} }
}; };
@ -147,13 +199,6 @@
}); });
}; };
ns.SelectionManager.prototype.copy = function() {
if (this.currentSelection && this.piskelController.getCurrentFrame()) {
this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame());
window.localStorage.setItem('piskel.clipboard', JSON.stringify(this.currentSelection));
}
};
/** /**
* @private * @private
*/ */

View File

@ -1,65 +1,161 @@
(function () { (function () {
var ns = $.namespace('pskl.service'); var ns = $.namespace('pskl.service');
// 1 minute = 1000 * 60 var ONE_SECOND = 1000;
var BACKUP_INTERVAL = 1000 * 60; var ONE_MINUTE = 60 * ONE_SECOND;
ns.BackupService = function (piskelController) { // Save every minute = 1000 * 60
var BACKUP_INTERVAL = ONE_MINUTE;
// Store a new snapshot every 5 minutes.
var SNAPSHOT_INTERVAL = ONE_MINUTE * 5;
// Store up to 12 snapshots for a piskel session, min. 1 hour of work
var MAX_SNAPSHOTS_PER_SESSION = 12;
var MAX_SESSIONS = 10;
ns.BackupService = function (piskelController, backupDatabase) {
this.piskelController = piskelController; this.piskelController = piskelController;
this.lastHash = null; // Immediately store the current when initializing the Service to avoid storing
// empty sessions.
this.lastHash = this.piskelController.getPiskel().getHash();
this.nextSnapshotDate = -1;
// backupDatabase can be provided for testing purposes.
this.backupDatabase = backupDatabase || new pskl.database.BackupDatabase();
}; };
ns.BackupService.prototype.init = function () { ns.BackupService.prototype.init = function () {
var previousPiskel = window.localStorage.getItem('bkp.next.piskel'); this.backupDatabase.init().then(function () {
var previousInfo = window.localStorage.getItem('bkp.next.info');
if (previousPiskel && previousInfo) {
this.savePiskel_('prev', previousPiskel, previousInfo);
}
window.setInterval(this.backup.bind(this), BACKUP_INTERVAL); window.setInterval(this.backup.bind(this), BACKUP_INTERVAL);
}.bind(this));
};
// This is purely exposed for testing, so that backup dates can be set programmatically.
ns.BackupService.prototype.currentDate_ = function () {
return Date.now();
}; };
ns.BackupService.prototype.backup = function () { ns.BackupService.prototype.backup = function () {
var piskel = this.piskelController.getPiskel(); var piskel = this.piskelController.getPiskel();
var descriptor = piskel.getDescriptor();
var hash = piskel.getHash(); var hash = piskel.getHash();
var info = {
name : descriptor.name,
description : descriptor.info,
date : Date.now(),
hash : hash
};
// Do not save an unchanged piskel // Do not save an unchanged piskel
if (hash !== this.lastHash) { if (hash === this.lastHash) {
return Q.resolve();
}
// Update the hash
// TODO: should only be done after a successful save.
this.lastHash = hash; this.lastHash = hash;
var serializedPiskel = pskl.utils.serialization.Serializer.serialize(piskel);
this.savePiskel_('next', serializedPiskel, JSON.stringify(info)); // Prepare the backup snapshot.
} var descriptor = piskel.getDescriptor();
var date = this.currentDate_();
var snapshot = {
session_id: pskl.app.sessionId,
date: date,
name: descriptor.name,
description: descriptor.description,
frames: piskel.getFrameCount(),
width: piskel.getWidth(),
height: piskel.getHeight(),
fps: piskel.getFPS(),
serialized: pskl.utils.serialization.Serializer.serialize(piskel)
}; };
ns.BackupService.prototype.getPreviousPiskelInfo = function () { return this.getSnapshotsBySessionId(pskl.app.sessionId).then(function (snapshots) {
var previousInfo = window.localStorage.getItem('bkp.prev.info'); var latest = snapshots[0];
if (previousInfo) {
return JSON.parse(previousInfo); if (latest && date < this.nextSnapshotDate) {
// update the latest snapshot
snapshot.id = latest.id;
return this.backupDatabase.updateSnapshot(snapshot);
} else {
// add a new snapshot
this.nextSnapshotDate = date + SNAPSHOT_INTERVAL;
return this.backupDatabase.createSnapshot(snapshot).then(function () {
if (snapshots.length >= MAX_SNAPSHOTS_PER_SESSION) {
// remove oldest snapshot
return this.backupDatabase.deleteSnapshot(snapshots[snapshots.length - 1]);
}
}.bind(this)).then(function () {
var isNewSession = !latest;
if (!isNewSession) {
return;
}
return this.backupDatabase.getSessions().then(function (sessions) {
if (sessions.length <= MAX_SESSIONS) {
// If MAX_SESSIONS has not been reached, no need to delete
// previous sessions.
return;
} }
};
ns.BackupService.prototype.load = function() { // Prepare an array containing all the ids of the sessions to be deleted.
var previousPiskel = window.localStorage.getItem('bkp.prev.piskel'); var sessionIdsToDelete = sessions.sort(function (s1, s2) {
previousPiskel = JSON.parse(previousPiskel); return s1.startDate - s2.startDate;
}).map(function (s) {
return s.id;
}).slice(0, sessions.length - MAX_SESSIONS);
pskl.utils.serialization.Deserializer.deserialize(previousPiskel, function (piskel) { // Delete all the extra sessions.
pskl.app.piskelController.setPiskel(piskel); return Q.all(sessionIdsToDelete.map(function (id) {
return this.deleteSession(id);
}.bind(this)));
}.bind(this));
}.bind(this));
}
}.bind(this)).catch(function (e) {
console.error(e);
}); });
}; };
ns.BackupService.prototype.savePiskel_ = function (type, piskel, info) { ns.BackupService.prototype.getSnapshotsBySessionId = function (sessionId) {
try { return this.backupDatabase.getSnapshotsBySessionId(sessionId);
window.localStorage.setItem('bkp.' + type + '.piskel', piskel); };
window.localStorage.setItem('bkp.' + type + '.info', info);
} catch (e) { ns.BackupService.prototype.deleteSession = function (sessionId) {
console.error('Could not save piskel backup in localStorage.', e); return this.backupDatabase.deleteSnapshotsForSession(sessionId);
};
ns.BackupService.prototype.getPreviousPiskelInfo = function () {
return this.backupDatabase.findLastSnapshot(function (snapshot) {
return snapshot.session_id !== pskl.app.sessionId;
});
};
ns.BackupService.prototype.list = function() {
return this.backupDatabase.getSessions();
};
ns.BackupService.prototype.loadSnapshotById = function(snapshotId) {
var deferred = Q.defer();
this.backupDatabase.getSnapshot(snapshotId).then(function (snapshot) {
pskl.utils.serialization.Deserializer.deserialize(
JSON.parse(snapshot.serialized),
function (piskel) {
pskl.app.piskelController.setPiskel(piskel);
deferred.resolve();
} }
);
});
return deferred.promise;
};
// Load "latest" backup snapshot.
ns.BackupService.prototype.load = function() {
var deferred = Q.defer();
this.getPreviousPiskelInfo().then(function (snapshot) {
pskl.utils.serialization.Deserializer.deserialize(
JSON.parse(snapshot.serialized),
function (piskel) {
pskl.app.piskelController.setPiskel(piskel);
deferred.resolve();
}
);
});
return deferred.promise;
}; };
})(); })();

View File

@ -30,6 +30,8 @@
}; };
ns.BeforeUnloadService.prototype.onBeforeUnload = function (evt) { ns.BeforeUnloadService.prototype.onBeforeUnload = function (evt) {
// Attempt one last backup. Some of it may fail due to the asynchronous
// nature of IndexedDB.
pskl.app.backupService.backup(); pskl.app.backupService.backup();
if (pskl.app.savedStatusService.isDirty()) { if (pskl.app.savedStatusService.isDirty()) {
var confirmationMessage = 'Your current sprite has unsaved changes. Are you sure you want to quit?'; var confirmationMessage = 'Your current sprite has unsaved changes. Are you sure you want to quit?';

View File

@ -0,0 +1,25 @@
(function () {
var ns = $.namespace('pskl.service');
ns.ClipboardService = function (piskelController) {
this.piskelController = piskelController;
};
ns.ClipboardService.prototype.init = function () {
window.addEventListener('copy', this._onCopy.bind(this), true);
window.addEventListener('cut', this._onCut.bind(this), true);
window.addEventListener('paste', this._onPaste.bind(this), true);
};
ns.ClipboardService.prototype._onCut = function (event) {
$.publish(Events.CLIPBOARD_CUT, event);
};
ns.ClipboardService.prototype._onCopy = function (event) {
$.publish(Events.CLIPBOARD_COPY, event);
};
ns.ClipboardService.prototype._onPaste = function (event) {
$.publish(Events.CLIPBOARD_PASTE, event);
};
})();

View File

@ -35,13 +35,9 @@
var isPiskel = /\.piskel$/i.test(file.name); var isPiskel = /\.piskel$/i.test(file.name);
var isPalette = /\.(gpl|txt|pal)$/i.test(file.name); var isPalette = /\.(gpl|txt|pal)$/i.test(file.name);
if (isImage) { if (isImage) {
$.publish(Events.DIALOG_SHOW, { pskl.utils.FileUtils.readImageFile(file, function (image) {
dialogId : 'import', this.onImageLoaded_(image, file);
initArgs : { }.bind(this));
rawFiles: [file]
}
});
// pskl.utils.FileUtils.readImageFile(file, this.onImageLoaded_.bind(this));
} else if (isPiskel) { } else if (isPiskel) {
pskl.utils.PiskelFileUtils.loadFromFile(file, this.onPiskelFileLoaded_, this.onPiskelFileError_); pskl.utils.PiskelFileUtils.loadFromFile(file, this.onPiskelFileLoaded_, this.onPiskelFileError_);
} else if (isPalette) { } else if (isPalette) {
@ -56,7 +52,7 @@
}; };
ns.FileDropperService.prototype.onPiskelFileLoaded_ = function (piskel) { ns.FileDropperService.prototype.onPiskelFileLoaded_ = function (piskel) {
if (window.confirm('This will replace your current animation')) { if (window.confirm(Constants.CONFIRM_OVERWRITE)) {
pskl.app.piskelController.setPiskel(piskel); pskl.app.piskelController.setPiskel(piskel);
} }
}; };
@ -65,10 +61,23 @@
$.publish(Events.PISKEL_FILE_IMPORT_FAILED, [reason]); $.publish(Events.PISKEL_FILE_IMPORT_FAILED, [reason]);
}; };
ns.FileDropperService.prototype.onImageLoaded_ = function (importedImage) { ns.FileDropperService.prototype.onImageLoaded_ = function (importedImage, file) {
var piskelWidth = pskl.app.piskelController.getWidth();
var piskelHeight = pskl.app.piskelController.getHeight();
if (this.isMultipleFiles_) { if (this.isMultipleFiles_) {
this.piskelController.addFrameAtCurrentIndex(); this.piskelController.addFrameAtCurrentIndex();
this.piskelController.selectNextFrame(); this.piskelController.selectNextFrame();
} else if (importedImage.width > piskelWidth || importedImage.height > piskelHeight) {
// For single file imports, if the file is too big, trigger the import wizard.
$.publish(Events.DIALOG_SHOW, {
dialogId : 'import',
initArgs : {
rawFiles: [file]
}
});
return;
} }
var currentFrame = this.piskelController.getCurrentFrame(); var currentFrame = this.piskelController.getCurrentFrame();

View File

@ -15,10 +15,17 @@
data : imageData data : imageData
}; };
var protocol = pskl.utils.Environment.isHttps() ? 'https' : 'http';
var wrappedSuccess = function (response) { var wrappedSuccess = function (response) {
success(Constants.IMAGE_SERVICE_GET_URL + response.responseText); var getUrl = pskl.utils.Template.replace(Constants.IMAGE_SERVICE_GET_URL, {
protocol: protocol
});
success(getUrl + response.responseText);
}; };
pskl.utils.Xhr.post(Constants.IMAGE_SERVICE_UPLOAD_URL, data, wrappedSuccess, error); var uploadUrl = pskl.utils.Template.replace(Constants.IMAGE_SERVICE_UPLOAD_URL, {
protocol: protocol
});
pskl.utils.Xhr.post(uploadUrl, data, wrappedSuccess, error);
}; };
})(); })();

View File

@ -9,7 +9,7 @@
* @public * @public
*/ */
ns.ShortcutService.prototype.init = function() { ns.ShortcutService.prototype.init = function() {
$(document.body).keydown($.proxy(this.onKeyDown_, this)); document.body.addEventListener('keydown', this.onKeyDown_.bind(this));
}; };
/** /**

View File

@ -59,6 +59,7 @@
BEST_PREVIEW : createShortcut('best-preview', 'Select best size preview', 'alt+2'), BEST_PREVIEW : createShortcut('best-preview', 'Select best size preview', 'alt+2'),
FULL_PREVIEW : createShortcut('full-preview', 'Select full size preview', 'alt+3'), FULL_PREVIEW : createShortcut('full-preview', 'Select full size preview', 'alt+3'),
ONION_SKIN : createShortcut('onion-skin', 'Toggle onion skin', 'alt+O'), ONION_SKIN : createShortcut('onion-skin', 'Toggle onion skin', 'alt+O'),
TOGGLE_GRID : createShortcut('toggle-grid', 'Show/Hide grid', 'alt+G'),
LAYER_PREVIEW : createShortcut('layer-preview', 'Toggle layer preview', 'alt+L'), LAYER_PREVIEW : createShortcut('layer-preview', 'Toggle layer preview', 'alt+L'),
MERGE_ANIMATION : createShortcut('import-animation', 'Open merge animation popup', 'ctrl+shift+M'), MERGE_ANIMATION : createShortcut('import-animation', 'Open merge animation popup', 'ctrl+shift+M'),
CLOSE_POPUP : createShortcut('close-popup', 'Close an opened popup', 'ESC'), CLOSE_POPUP : createShortcut('close-popup', 'Close an opened popup', 'ESC'),
@ -84,6 +85,10 @@
'123456789'.split(''), '1 to 9') '123456789'.split(''), '1 to 9')
}, },
DEBUG : {
RELOAD_STYLES : createShortcut('move-left', 'Move viewport left', 'ctrl+alt+R'),
},
CATEGORIES : ['TOOL', 'SELECTION', 'MISC', 'STORAGE', 'COLOR'] CATEGORIES : ['TOOL', 'SELECTION', 'MISC', 'STORAGE', 'COLOR']
}; };
})(); })();

View File

@ -3,11 +3,12 @@
ns.PaletteService = function () { ns.PaletteService = function () {
this.dynamicPalettes = []; this.dynamicPalettes = [];
this.localStorageService = window.localStorage; // Exposed for tests.
this.localStorageGlobal = window.localStorage;
}; };
ns.PaletteService.prototype.getPalettes = function () { ns.PaletteService.prototype.getPalettes = function () {
var palettesString = this.localStorageService.getItem('piskel.palettes'); var palettesString = this.localStorageGlobal.getItem('piskel.palettes');
var palettes = JSON.parse(palettesString) || []; var palettes = JSON.parse(palettesString) || [];
palettes = palettes.map(function (palette) { palettes = palettes.map(function (palette) {
return pskl.model.Palette.fromObject(palette); return pskl.model.Palette.fromObject(palette);
@ -54,7 +55,7 @@
palettes = palettes.filter(function (palette) { palettes = palettes.filter(function (palette) {
return this.dynamicPalettes.indexOf(palette) === -1; return this.dynamicPalettes.indexOf(palette) === -1;
}.bind(this)); }.bind(this));
this.localStorageService.setItem('piskel.palettes', JSON.stringify(palettes)); this.localStorageGlobal.setItem('piskel.palettes', JSON.stringify(palettes));
$.publish(Events.PALETTE_LIST_UPDATED); $.publish(Events.PALETTE_LIST_UPDATED);
}; };

View File

@ -16,7 +16,7 @@
*/ */
ns.PerformanceReport = function (piskel, colorsCount) { ns.PerformanceReport = function (piskel, colorsCount) {
var pixels = piskel.getWidth() * piskel.getHeight(); var pixels = piskel.getWidth() * piskel.getHeight();
this.resolution = pixels > (500 * 500); this.resolution = pixels > (512 * 512);
var layersCount = piskel.getLayers().length; var layersCount = piskel.getLayers().length;
this.layers = layersCount > 25; this.layers = layersCount > 25;
@ -24,10 +24,10 @@
var framesCount = piskel.getLayerAt(0).size(); var framesCount = piskel.getLayerAt(0).size();
this.frames = framesCount > 100; this.frames = framesCount > 100;
this.colors = colorsCount > 100; this.colors = colorsCount >= 256;
var overallScore = (pixels / 2500) + (layersCount * 4) + framesCount + colorsCount; var overallScore = (pixels / 2620) + (layersCount * 4) + framesCount + (colorsCount * 100 / 256);
this.overall = overallScore > 100; this.overall = overallScore > 200;
}; };
ns.PerformanceReport.prototype.equals = function (report) { ns.PerformanceReport.prototype.equals = function (report) {

View File

@ -0,0 +1,57 @@
(function () {
var ns = $.namespace('pskl.service.storage');
ns.IndexedDbStorageService = function (piskelController) {
this.piskelController = piskelController;
this.piskelDatabase = new pskl.database.PiskelDatabase();
};
ns.IndexedDbStorageService.prototype.init = function () {
this.piskelDatabase.init().catch(function (e) {
console.log('Failed to initialize PiskelDatabase, local browser saves will be unavailable.');
});
};
ns.IndexedDbStorageService.prototype.save = function (piskel) {
var name = piskel.getDescriptor().name;
var description = piskel.getDescriptor().description;
var date = Date.now();
var serialized = pskl.utils.serialization.Serializer.serialize(piskel);
return this.save_(name, description, date, serialized);
};
ns.IndexedDbStorageService.prototype.save_ = function (name, description, date, serialized) {
return this.piskelDatabase.get(name).then(function (piskelData) {
if (typeof piskelData !== 'undefined') {
return this.piskelDatabase.update(name, description, date, serialized);
} else {
return this.piskelDatabase.create(name, description, date, serialized);
}
}.bind(this));
};
ns.IndexedDbStorageService.prototype.load = function (name) {
return this.piskelDatabase.get(name).then(function (piskelData) {
if (typeof piskelData !== 'undefined') {
var serialized = piskelData.serialized;
pskl.utils.serialization.Deserializer.deserialize(
JSON.parse(serialized),
function (piskel) {
pskl.app.piskelController.setPiskel(piskel);
}
);
} else {
console.log('no local browser save found for name: ' + name);
}
});
};
ns.IndexedDbStorageService.prototype.remove = function (name) {
return this.piskelDatabase.delete(name);
};
ns.IndexedDbStorageService.prototype.getKeys = function () {
return this.piskelDatabase.list();
};
})();

View File

@ -27,10 +27,15 @@
return this.delegateSave_(pskl.app.galleryStorageService, piskel); return this.delegateSave_(pskl.app.galleryStorageService, piskel);
}; };
// @deprecated, use saveToIndexedDb unless indexedDb is not available.
ns.StorageService.prototype.saveToLocalStorage = function (piskel) { ns.StorageService.prototype.saveToLocalStorage = function (piskel) {
return this.delegateSave_(pskl.app.localStorageService, piskel); return this.delegateSave_(pskl.app.localStorageService, piskel);
}; };
ns.StorageService.prototype.saveToIndexedDb = function (piskel) {
return this.delegateSave_(pskl.app.indexedDbStorageService, piskel);
};
ns.StorageService.prototype.saveToFileDownload = function (piskel) { ns.StorageService.prototype.saveToFileDownload = function (piskel) {
return this.delegateSave_(pskl.app.fileDownloadStorageService, piskel); return this.delegateSave_(pskl.app.fileDownloadStorageService, piskel);
}; };
@ -67,7 +72,7 @@
// wrap in timeout in order to start saving only after event.preventDefault // wrap in timeout in order to start saving only after event.preventDefault
// has been done // has been done
window.setTimeout(function () { window.setTimeout(function () {
this.saveToLocalStorage(this.piskelController.getPiskel()); this.saveToIndexedDb(this.piskelController.getPiskel());
}.bind(this), 0); }.bind(this), 0);
} }
}; };

View File

@ -44,10 +44,17 @@
}; };
ns.VerticalMirrorPen.prototype.getSymmetricCol_ = function(col, frame) { ns.VerticalMirrorPen.prototype.getSymmetricCol_ = function(col, frame) {
return frame.getWidth() - col - 1; return frame.getWidth() - col - this.getPenSizeOffset_();
}; };
ns.VerticalMirrorPen.prototype.getSymmetricRow_ = function(row, frame) { ns.VerticalMirrorPen.prototype.getSymmetricRow_ = function(row, frame) {
return frame.getHeight() - row - 1; return frame.getHeight() - row - this.getPenSizeOffset_();
};
/**
* Depending on the pen size, the mirrored index need to have an offset of 1 pixel.
*/
ns.VerticalMirrorPen.prototype.getPenSizeOffset_ = function(row, frame) {
return pskl.app.penSizeService.getPenSize() % 2;
}; };
})(); })();

View File

@ -16,7 +16,7 @@
ns.AbstractDragSelect.prototype.onSelectStart_ = function (col, row, frame, overlay) { ns.AbstractDragSelect.prototype.onSelectStart_ = function (col, row, frame, overlay) {
if (this.hasSelection) { if (this.hasSelection) {
this.hasSelection = false; this.hasSelection = false;
this.commitSelection(overlay); this.commitSelection();
} else { } else {
this.hasSelection = true; this.hasSelection = true;
this.onDragSelectStart_(col, row); this.onDragSelectStart_(col, row);

View File

@ -8,7 +8,6 @@
ns.BaseSelect = function() { ns.BaseSelect = function() {
this.secondaryToolId = pskl.tools.drawing.Move.TOOL_ID; this.secondaryToolId = pskl.tools.drawing.Move.TOOL_ID;
this.bodyRoot = $('body');
// Select's first point coordinates (set in applyToolAt) // Select's first point coordinates (set in applyToolAt)
this.startCol = null; this.startCol = null;
@ -26,6 +25,8 @@
{key : 'ctrl+v', description : 'Paste the copied area'}, {key : 'ctrl+v', description : 'Paste the copied area'},
{key : 'shift', description : 'Hold to move the content'} {key : 'shift', description : 'Hold to move the content'}
]; ];
$.subscribe(Events.SELECTION_DISMISSED, this.onSelectionDismissed_.bind(this));
}; };
pskl.utils.inherit(ns.BaseSelect, pskl.tools.drawing.BaseTool); pskl.utils.inherit(ns.BaseSelect, pskl.tools.drawing.BaseTool);
@ -41,7 +42,7 @@
this.lastMoveRow = row; this.lastMoveRow = row;
// The select tool can be in two different state. // The select tool can be in two different state.
// If the inital click of the tool is not on a selection, we go in 'select' // If the initial click of the tool is not on a selection, we go in 'select'
// mode to create a selection. // mode to create a selection.
// If the initial click is on a previous selection, we go in 'moveSelection' // If the initial click is on a previous selection, we go in 'moveSelection'
// mode to allow to move the selection by drag'n dropping it. // mode to allow to move the selection by drag'n dropping it.
@ -52,7 +53,7 @@
this.mode = 'moveSelection'; this.mode = 'moveSelection';
if (event.shiftKey && !this.isMovingContent_) { if (event.shiftKey && !this.isMovingContent_) {
this.isMovingContent_ = true; this.isMovingContent_ = true;
$.publish(Events.SELECTION_CUT); $.publish(Events.CLIPBOARD_CUT);
this.drawSelectionOnOverlay_(overlay); this.drawSelectionOnOverlay_(overlay);
} }
this.onSelectionMoveStart_(col, row, frame, overlay); this.onSelectionMoveStart_(col, row, frame, overlay);
@ -90,12 +91,12 @@
if (overlay.containsPixel(col, row)) { if (overlay.containsPixel(col, row)) {
if (this.isInSelection(col, row)) { if (this.isInSelection(col, row)) {
// We're hovering the selection, show the move tool: // We're hovering the selection, show the move tool:
this.bodyRoot.addClass(this.secondaryToolId); document.body.classList.add(this.secondaryToolId);
this.bodyRoot.removeClass(this.toolId); document.body.classList.remove(this.toolId);
} else { } else {
// We're not hovering the selection, show create selection tool: // We're not hovering the selection, show create selection tool:
this.bodyRoot.addClass(this.toolId); document.body.classList.add(this.toolId);
this.bodyRoot.removeClass(this.secondaryToolId); document.body.classList.remove(this.secondaryToolId);
} }
} }
@ -111,16 +112,24 @@
}; };
/** /**
* Protected method, should be called when the selection is dismissed. * Protected method, should be called when the selection is committed,
* typically by clicking outside of the selected area.
*/ */
ns.BaseSelect.prototype.commitSelection = function (overlay) { ns.BaseSelect.prototype.commitSelection = function () {
if (this.isMovingContent_) { if (this.isMovingContent_) {
$.publish(Events.SELECTION_PASTE); $.publish(Events.CLIPBOARD_PASTE);
this.isMovingContent_ = false; this.isMovingContent_ = false;
} }
// Clean previous selection: // Clean previous selection:
$.publish(Events.SELECTION_DISMISSED); $.publish(Events.SELECTION_DISMISSED);
};
/**
* Protected method, should be called when the selection is dismissed.
*/
ns.BaseSelect.prototype.onSelectionDismissed_ = function () {
var overlay = pskl.app.drawingController.overlayFrame;
overlay.clear(); overlay.clear();
this.hasSelection = false; this.hasSelection = false;
}; };

View File

@ -24,8 +24,8 @@
/** /**
* When creating the rectangle selection, we clear the current overlayFrame and * When creating the rectangle selection, we clear the current overlayFrame and
* redraw the current rectangle based on the orgin coordinate and * redraw the current rectangle based on the origin coordinate and
* the current mouse coordiinate in sprite. * the current mouse coordinate in sprite.
* @override * @override
*/ */
ns.RectangleSelect.prototype.onDragSelect_ = function (col, row, frame, overlay) { ns.RectangleSelect.prototype.onDragSelect_ = function (col, row, frame, overlay) {

View File

@ -18,13 +18,13 @@
/** /**
* For the shape select tool, you just need to click one time to create a selection. * For the shape select tool, you just need to click one time to create a selection.
* So we jsut need to implement onSelectStart_ (no need for onSelect_ & onSelectEnd_) * So we just need to implement onSelectStart_ (no need for onSelect_ & onSelectEnd_)
* @override * @override
*/ */
ns.ShapeSelect.prototype.onSelectStart_ = function (col, row, frame, overlay) { ns.ShapeSelect.prototype.onSelectStart_ = function (col, row, frame, overlay) {
if (this.hasSelection) { if (this.hasSelection) {
this.hasSelection = false; this.hasSelection = false;
this.commitSelection(overlay); this.commitSelection();
} else { } else {
this.hasSelection = true; this.hasSelection = true;
// From the pixel clicked, get shape using an algorithm similar to the paintbucket one: // From the pixel clicked, get shape using an algorithm similar to the paintbucket one:

View File

@ -23,7 +23,15 @@
isIntegrationTest : function () { isIntegrationTest : function () {
return window.location.href.indexOf('integration-test') !== -1; return window.location.href.indexOf('integration-test') !== -1;
} },
isDebug : function () {
return window.location.href.indexOf('debug') !== -1;
},
isHttps : function () {
return window.location.href.indexOf('https://') === 0;
},
}; };
})(); })();

View File

@ -5,6 +5,7 @@
GRID_COLOR : 'GRID_COLOR', GRID_COLOR : 'GRID_COLOR',
GRID_ENABLED : 'GRID_ENABLED', GRID_ENABLED : 'GRID_ENABLED',
GRID_WIDTH : 'GRID_WIDTH', GRID_WIDTH : 'GRID_WIDTH',
GRID_SPACING : 'GRID_SPACING',
MAX_FPS : 'MAX_FPS', MAX_FPS : 'MAX_FPS',
DEFAULT_SIZE : 'DEFAULT_SIZE', DEFAULT_SIZE : 'DEFAULT_SIZE',
CANVAS_BACKGROUND : 'CANVAS_BACKGROUND', CANVAS_BACKGROUND : 'CANVAS_BACKGROUND',
@ -17,6 +18,7 @@
LAYER_OPACITY : 'LAYER_OPACITY', LAYER_OPACITY : 'LAYER_OPACITY',
EXPORT_SCALE: 'EXPORT_SCALE', EXPORT_SCALE: 'EXPORT_SCALE',
EXPORT_TAB: 'EXPORT_TAB', EXPORT_TAB: 'EXPORT_TAB',
EXPORT_GIF_REPEAT: 'EXPORT_GIF_REPEAT',
PEN_SIZE : 'PEN_SIZE', PEN_SIZE : 'PEN_SIZE',
RESIZE_SETTINGS: 'RESIZE_SETTINGS', RESIZE_SETTINGS: 'RESIZE_SETTINGS',
COLOR_FORMAT: 'COLOR_FORMAT', COLOR_FORMAT: 'COLOR_FORMAT',
@ -26,6 +28,7 @@
'GRID_COLOR' : Constants.TRANSPARENT_COLOR, 'GRID_COLOR' : Constants.TRANSPARENT_COLOR,
'GRID_ENABLED' : false, 'GRID_ENABLED' : false,
'GRID_WIDTH' : 1, 'GRID_WIDTH' : 1,
'GRID_SPACING' : 1,
'MAX_FPS' : 24, 'MAX_FPS' : 24,
'DEFAULT_SIZE' : { 'DEFAULT_SIZE' : {
width : Constants.DEFAULT.WIDTH, width : Constants.DEFAULT.WIDTH,
@ -41,6 +44,7 @@
'LAYER_PREVIEW' : true, 'LAYER_PREVIEW' : true,
'EXPORT_SCALE' : 1, 'EXPORT_SCALE' : 1,
'EXPORT_TAB' : 'gif', 'EXPORT_TAB' : 'gif',
'EXPORT_GIF_REPEAT' : true,
'PEN_SIZE' : 1, 'PEN_SIZE' : 1,
'RESIZE_SETTINGS': { 'RESIZE_SETTINGS': {
maintainRatio : true, maintainRatio : true,
@ -122,20 +126,4 @@
} }
} }
}; };
// Migration script for version 11 to version 12. Initialize the GRID_ENABLED pref from
// the current GRID_WIDTH and update the stored grid width to 1 if it was set to 0.
// SHOULD BE REMOVED FOR RELEASE 13.
ns.UserSettings.migrate_to_v0_12 = function () {
var storedGridEnabled = ns.UserSettings.readFromLocalStorage_('GRID_ENABLED');
if (typeof storedGridEnabled === 'undefined' || storedGridEnabled === null) {
var gridWidth = ns.UserSettings.get('GRID_WIDTH');
ns.UserSettings.writeToLocalStorage_('GRID_ENABLED', gridWidth > 0);
}
var storedGridWidth = ns.UserSettings.readFromLocalStorage_('GRID_WIDTH');
if (storedGridWidth === 0) {
ns.UserSettings.writeToLocalStorage_('GRID_WIDTH', 1);
}
};
})(); })();

View File

@ -36,6 +36,7 @@
var descriptor = new pskl.model.piskel.Descriptor(name, description); var descriptor = new pskl.model.piskel.Descriptor(name, description);
this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height, piskelData.fps, descriptor); this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height, piskelData.fps, descriptor);
this.hiddenFrames = piskelData.hiddenFrames || [];
this.layersToLoad_ = piskelData.layers.length; this.layersToLoad_ = piskelData.layers.length;
piskelData.layers.forEach(this.deserializeLayer.bind(this)); piskelData.layers.forEach(this.deserializeLayer.bind(this));
@ -73,7 +74,9 @@
image.src = chunk.base64PNG; image.src = chunk.base64PNG;
return deferred.promise; return deferred.promise;
})).then(function () { })).then(function () {
frames.forEach(layer.addFrame.bind(layer)); frames.forEach(function (frame) {
layer.addFrame(frame);
});
this.layers_[index] = layer; this.layers_[index] = layer;
this.onLayerLoaded_(); this.onLayerLoaded_();
}.bind(this)).catch(function (error) { }.bind(this)).catch(function (error) {
@ -90,6 +93,7 @@
this.layers_.forEach(function (layer) { this.layers_.forEach(function (layer) {
this.piskel_.addLayer(layer); this.piskel_.addLayer(layer);
}.bind(this)); }.bind(this));
this.piskel_.hiddenFrames = this.hiddenFrames;
this.callback_(this.piskel_); this.callback_(this.piskel_);
} }
}; };

View File

@ -21,6 +21,7 @@
var serializedLayers = piskel.getLayers().map(function (l) { var serializedLayers = piskel.getLayers().map(function (l) {
return pskl.utils.serialization.Serializer.serializeLayer(l); return pskl.utils.serialization.Serializer.serializeLayer(l);
}); });
return JSON.stringify({ return JSON.stringify({
modelVersion : Constants.MODEL_VERSION, modelVersion : Constants.MODEL_VERSION,
piskel : { piskel : {
@ -29,7 +30,8 @@
fps : pskl.app.piskelController.getFPS(), fps : pskl.app.piskelController.getFPS(),
height : piskel.getHeight(), height : piskel.getHeight(),
width : piskel.getWidth(), width : piskel.getWidth(),
layers : serializedLayers layers : serializedLayers,
hiddenFrames : piskel.hiddenFrames,
} }
}); });
}, },

View File

@ -36,23 +36,36 @@
// Layers meta // Layers meta
var layerCount = arr16[6]; var layerCount = arr16[6];
// Layers meta
var serializedHiddenFramesLength = arr16[7];
var currentIndex = 8;
/********/ /********/
/* DATA */ /* DATA */
/********/ /********/
// Descriptor name // Descriptor name
var descriptorName = ''; var descriptorName = '';
for (i = 0; i < descriptorNameLength; i++) { for (i = 0; i < descriptorNameLength; i++) {
descriptorName += String.fromCharCode(arr16[7 + i]); descriptorName += String.fromCharCode(arr16[currentIndex + i]);
} }
currentIndex += descriptorNameLength;
// Descriptor description // Descriptor description
var descriptorDescription = ''; var descriptorDescription = '';
for (i = 0; i < descriptorDescriptionLength; i++) { for (i = 0; i < descriptorDescriptionLength; i++) {
descriptorDescription = String.fromCharCode(arr16[7 + descriptorNameLength + i]); descriptorDescription = String.fromCharCode(arr16[8 + descriptorNameLength + i]);
} }
currentIndex += descriptorDescriptionLength;
// Hidden frames
var serializedHiddenFrames = '';
for (i = 0; i < serializedHiddenFramesLength; i++) {
serializedHiddenFrames = String.fromCharCode(arr16[8 + descriptorNameLength + i]);
}
var hiddenFrames = serializedHiddenFrames.split('-');
currentIndex += serializedHiddenFramesLength;
// Layers // Layers
var layerStartIndex = 7 + descriptorNameLength + descriptorDescriptionLength;
var layers = []; var layers = [];
var layer; var layer;
for (i = 0; i < layerCount; i++) { for (i = 0; i < layerCount; i++) {
@ -60,27 +73,27 @@
var frames = []; var frames = [];
// Meta // Meta
var layerNameLength = arr16[layerStartIndex]; var layerNameLength = arr16[currentIndex];
var opacity = arr16[layerStartIndex + 1] / 65535; var opacity = arr16[currentIndex + 1] / 65535;
var frameCount = arr16[layerStartIndex + 2]; var frameCount = arr16[currentIndex + 2];
var dataUriLengthFirstHalf = arr16[layerStartIndex + 3]; var dataUriLengthFirstHalf = arr16[currentIndex + 3];
var dataUriLengthSecondHalf = arr16[layerStartIndex + 4]; var dataUriLengthSecondHalf = arr16[currentIndex + 4];
var dataUriLength = (dataUriLengthSecondHalf >>> 0) | (dataUriLengthFirstHalf << 16 >>> 0); var dataUriLength = (dataUriLengthSecondHalf >>> 0) | (dataUriLengthFirstHalf << 16 >>> 0);
// Name // Name
var layerName = ''; var layerName = '';
for (j = 0; j < layerNameLength; j++) { for (j = 0; j < layerNameLength; j++) {
layerName += String.fromCharCode(arr16[layerStartIndex + 5 + j]); layerName += String.fromCharCode(arr16[currentIndex + 5 + j]);
} }
// Data URI // Data URI
var dataUri = ''; var dataUri = '';
for (j = 0; j < dataUriLength; j++) { for (j = 0; j < dataUriLength; j++) {
dataUri += String.fromCharCode(arr8[(layerStartIndex + 5 + layerNameLength) * 2 + j]); dataUri += String.fromCharCode(arr8[(currentIndex + 5 + layerNameLength) * 2 + j]);
} }
dataUri = 'data:image/png;base64,' + dataUri; dataUri = 'data:image/png;base64,' + dataUri;
layerStartIndex += Math.ceil(5 + layerNameLength + (dataUriLength / 2)); currentIndex += Math.ceil(5 + layerNameLength + (dataUriLength / 2));
layer.name = layerName; layer.name = layerName;
layer.opacity = opacity; layer.opacity = opacity;
@ -91,6 +104,7 @@
var descriptor = new pskl.model.piskel.Descriptor(descriptorName, descriptorDescription); var descriptor = new pskl.model.piskel.Descriptor(descriptorName, descriptorDescription);
var piskel = new pskl.model.Piskel(width, height, fps, descriptor); var piskel = new pskl.model.Piskel(width, height, fps, descriptor);
piskel.hiddenFrames = hiddenFrames;
var loadedLayers = 0; var loadedLayers = 0;
var loadLayerImage = function(layer, cb) { var loadLayerImage = function(layer, cb) {

View File

@ -101,7 +101,15 @@
framesData.push({uri: dataUri, length: dataUriLength}); framesData.push({uri: dataUri, length: dataUriLength});
} }
var bytes = ns.ArrayBufferSerializer.calculateRequiredBytes(piskel, framesData); var frames = pskl.app.piskelController.getLayerAt(0).getFrames();
var hiddenFrames = piskel.hiddenFrames;
var serializedHiddenFrames = hiddenFrames.join('-');
var bytes = ns.ArrayBufferSerializer.calculateRequiredBytes(
piskel,
framesData,
serializedHiddenFrames
);
var buffer = new ArrayBuffer(bytes); var buffer = new ArrayBuffer(bytes);
var arr8 = new Uint8Array(buffer); var arr8 = new Uint8Array(buffer);
@ -130,21 +138,33 @@
// Layers meta // Layers meta
arr16[6] = piskel.getLayers().length; arr16[6] = piskel.getLayers().length;
// Frames meta
arr16[7] = serializedHiddenFrames.length;
var currentIndex = 8;
/********/ /********/
/* DATA */ /* DATA */
/********/ /********/
// Descriptor name // Descriptor name
for (i = 0; i < descriptorNameLength; i++) { for (i = 0; i < descriptorNameLength; i++) {
arr16[7 + i] = descriptorName.charCodeAt(i); arr16[currentIndex + i] = descriptorName.charCodeAt(i);
} }
currentIndex = currentIndex + descriptorNameLength;
// Descriptor description // Descriptor description
for (i = 0; i < descriptorDescriptionLength; i++) { for (i = 0; i < descriptorDescriptionLength; i++) {
arr16[7 + descriptorNameLength + i] = descriptorDescription.charCodeAt(i); arr16[currentIndex + i] = descriptorDescription.charCodeAt(i);
} }
currentIndex = currentIndex + descriptorDescriptionLength;
// Hidden frames
for (i = 0; i < serializedHiddenFrames.length; i++) {
arr16[currentIndex + i] = serializedHiddenFrames.charCodeAt(i);
}
currentIndex = currentIndex + serializedHiddenFrames.length;
// Layers // Layers
var layerStartIndex = 7 + descriptorNameLength + descriptorDescriptionLength;
for (i = 0, layers = piskel.getLayers(); i < layers.length; i++) { for (i = 0, layers = piskel.getLayers(); i < layers.length; i++) {
var layer = layers[i]; var layer = layers[i];
var frames = layer.getFrames(); var frames = layer.getFrames();
@ -158,23 +178,23 @@
dataUriLength = framesData[i].length; dataUriLength = framesData[i].length;
// Meta // Meta
arr16[layerStartIndex] = layerNameLength; arr16[currentIndex] = layerNameLength;
arr16[layerStartIndex + 1] = Math.floor(opacity * 65535); arr16[currentIndex + 1] = Math.floor(opacity * 65535);
arr16[layerStartIndex + 2] = frameCount; arr16[currentIndex + 2] = frameCount;
arr16[layerStartIndex + 3] = ((dataUriLength & 0xffff0000) >> 16) >>> 0; // Upper 16 arr16[currentIndex + 3] = ((dataUriLength & 0xffff0000) >> 16) >>> 0; // Upper 16
arr16[layerStartIndex + 4] = ((dataUriLength & 0x0000ffff)) >>> 0; // Lower 16 arr16[currentIndex + 4] = ((dataUriLength & 0x0000ffff)) >>> 0; // Lower 16
// Name // Name
for (j = 0; j < layerNameLength; j++) { for (j = 0; j < layerNameLength; j++) {
arr16[layerStartIndex + 5 + j] = layerName.charCodeAt(j); arr16[currentIndex + 5 + j] = layerName.charCodeAt(j);
} }
// Data URI // Data URI
for (j = 0; j < dataUriLength; j++) { for (j = 0; j < dataUriLength; j++) {
arr8[(layerStartIndex + 5 + layerNameLength) * 2 + j] = dataUri.charCodeAt(j); arr8[(currentIndex + 5 + layerNameLength) * 2 + j] = dataUri.charCodeAt(j);
} }
layerStartIndex += Math.ceil(5 + layerNameLength + (dataUriLength / 2)); currentIndex += Math.ceil(5 + layerNameLength + (dataUriLength / 2));
} }
return buffer; return buffer;

View File

@ -139,7 +139,8 @@
var colorElement = drop.item.get(0); var colorElement = drop.item.get(0);
var oldIndex = parseInt(colorElement.dataset.paletteIndex, 10); var oldIndex = parseInt(colorElement.dataset.paletteIndex, 10);
var newIndex = $('.create-palette-color').index(drop.item); var colors = document.querySelectorAll('.create-palette-color');
var newIndex = Array.prototype.indexOf.call(colors, colorElement);
this.palette.move(oldIndex, newIndex); this.palette.move(oldIndex, newIndex);
this.selectedIndex = newIndex; this.selectedIndex = newIndex;

View File

@ -27,10 +27,9 @@
pskl.utils.Dom.removeClass('labeled', this.container); pskl.utils.Dom.removeClass('labeled', this.container);
pskl.utils.Dom.removeClass('selected', this.container); pskl.utils.Dom.removeClass('selected', this.container);
var selectedOption; var selectedOption;
if (size <= 4) {
selectedOption = this.container.querySelector('[data-size="' + size + '"]'); selectedOption = this.container.querySelector('[data-size="' + size + '"]');
} else { if (!selectedOption) {
selectedOption = this.container.querySelector('[data-size="4"]'); selectedOption = this.container.querySelector('[data-size]:last-child');
selectedOption.classList.add('labeled'); selectedOption.classList.add('labeled');
selectedOption.setAttribute('real-size', size); selectedOption.setAttribute('real-size', size);
} }

View File

@ -68,12 +68,21 @@
}; };
loadScript('piskel-script-list.js', 'loadNextScript()'); loadScript('piskel-script-list.js', 'loadNextScript()');
var styles;
window.loadStyles = function () { window.loadStyles = function () {
var styles = window.pskl_exports.styles; styles = window.pskl_exports.styles;
for (var i = 0 ; i < styles.length ; i++) { for (var i = 0 ; i < styles.length ; i++) {
loadStyle(styles[i]); loadStyle(styles[i]);
} }
}; };
window.reloadStyles = function () {
for (var i = 0 ; i < styles.length ; i++) {
document.querySelector('link[href="' + styles[i] + '"]').remove();
loadStyle(styles[i]);
}
};
loadScript('piskel-style-list.js', 'loadStyles()'); loadScript('piskel-style-list.js', 'loadStyles()');
} else { } else {
var script; var script;

View File

@ -79,6 +79,11 @@
"js/model/Palette.js", "js/model/Palette.js",
"js/model/Piskel.js", "js/model/Piskel.js",
// Database (IndexedDB)
"js/database/BackupDatabase.js",
"js/database/PiskelDatabase.js",
"js/database/migrate/MigrateLocalStorageToIndexedDb.js",
// Selection // Selection
"js/selection/SelectionManager.js", "js/selection/SelectionManager.js",
"js/selection/BaseSelection.js", "js/selection/BaseSelection.js",
@ -108,6 +113,7 @@
"js/controller/HeaderController.js", "js/controller/HeaderController.js",
"js/controller/LayersListController.js", "js/controller/LayersListController.js",
"js/controller/preview/PopupPreviewController.js", "js/controller/preview/PopupPreviewController.js",
"js/controller/preview/PreviewActionsController.js",
"js/controller/preview/PreviewController.js", "js/controller/preview/PreviewController.js",
"js/controller/MinimapController.js", "js/controller/MinimapController.js",
"js/controller/ToolController.js", "js/controller/ToolController.js",
@ -144,6 +150,9 @@
"js/controller/dialogs/CreatePaletteController.js", "js/controller/dialogs/CreatePaletteController.js",
"js/controller/dialogs/BrowseLocalController.js", "js/controller/dialogs/BrowseLocalController.js",
"js/controller/dialogs/CheatsheetController.js", "js/controller/dialogs/CheatsheetController.js",
"js/controller/dialogs/backups/steps/SelectSession.js",
"js/controller/dialogs/backups/steps/SessionDetails.js",
"js/controller/dialogs/backups/BrowseBackups.js",
"js/controller/dialogs/importwizard/steps/AbstractImportStep.js", "js/controller/dialogs/importwizard/steps/AbstractImportStep.js",
"js/controller/dialogs/importwizard/steps/AdjustSize.js", "js/controller/dialogs/importwizard/steps/AdjustSize.js",
"js/controller/dialogs/importwizard/steps/ImageImport.js", "js/controller/dialogs/importwizard/steps/ImageImport.js",
@ -170,6 +179,7 @@
// Services // Services
"js/service/storage/StorageService.js", "js/service/storage/StorageService.js",
"js/service/storage/FileDownloadStorageService.js", "js/service/storage/FileDownloadStorageService.js",
"js/service/storage/IndexedDbStorageService.js",
"js/service/storage/LocalStorageService.js", "js/service/storage/LocalStorageService.js",
"js/service/storage/GalleryStorageService.js", "js/service/storage/GalleryStorageService.js",
"js/service/storage/DesktopStorageService.js", "js/service/storage/DesktopStorageService.js",
@ -195,15 +205,13 @@
"js/service/keyboard/ShortcutService.js", "js/service/keyboard/ShortcutService.js",
"js/service/ImportService.js", "js/service/ImportService.js",
"js/service/ImageUploadService.js", "js/service/ImageUploadService.js",
"js/service/ClipboardService.js",
"js/service/CurrentColorsService.js", "js/service/CurrentColorsService.js",
"js/service/FileDropperService.js", "js/service/FileDropperService.js",
"js/service/SelectedColorsService.js", "js/service/SelectedColorsService.js",
"js/service/MouseStateService.js", "js/service/MouseStateService.js",
"js/service/performance/PerformanceReport.js", "js/service/performance/PerformanceReport.js",
"js/service/performance/PerformanceReportService.js", "js/service/performance/PerformanceReportService.js",
"js/service/storage/LocalStorageService.js",
"js/service/storage/GalleryStorageService.js",
"js/service/storage/DesktopStorageService.js",
// Tools // Tools
"js/tools/ToolsHelper.js", "js/tools/ToolsHelper.js",

View File

@ -18,6 +18,7 @@
"css/icons.css", "css/icons.css",
"css/color-picker-slider.css", "css/color-picker-slider.css",
"css/dialogs.css", "css/dialogs.css",
"css/dialogs-browse-backups.css",
"css/dialogs-browse-local.css", "css/dialogs-browse-local.css",
"css/dialogs-cheatsheet.css", "css/dialogs-cheatsheet.css",
"css/dialogs-create-palette.css", "css/dialogs-create-palette.css",

View File

@ -0,0 +1,70 @@
<div style="display:none">
<script type="text/template" id="data-uri-export-partial">
<style>
html, body {
margin: 0;
}
body {
background: #444;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: monospace;
}
#image-wrapper {
flex-grow: 1;
flex-shrink: 0;
display: flex;
flex-direction: column;
justify-content: center;
}
img {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABlBMVEXf39////8zI3BgAAAAHklEQVR4AWNghAIGCMDgjwgFCDDSw2M0PSCD0fQAACRcAgF4ciGUAAAAAElFTkSuQmCC') repeat;
}
#bottom-wrapper {
width: 600px;
height: 300px;
margin-top: 20px;
position: relative;
flex-shrink: 0;
}
textarea {
position: absolute;
bottom: 0;
width: 100%;
height: 100%;
padding: 10px;
border-style: none;
background: black;
color: gold;
font-family: monospace;
}
span {
position: absolute;
bottom: 10px;
left: 10px;
font-size: 12px;
padding: 5px;
color: white;
background-color: #444;
}
</style>
<body>
<div id="image-wrapper">
<img src="{{src}}" alt="Exported image as data-uri">
</div>
<div id="bottom-wrapper">
<textarea spellcheck="false" onclick="this.select()">{{src}}</textarea>
<span>Data-uri for the exported framesheet</span>
</div>
</body>
</script>
</div>

Some files were not shown because too many files have changed in this diff Show More