95 Commits

Author SHA1 Message Date
a69b44eaec Return promise results from PiskelDB and IndexedDBStorageService 2017-10-17 02:11:08 +02:00
3e779a651f Show error message if BackupDatabase promise rejected 2017-10-17 02:09:28 +02:00
ab9bbce1ed Add templates for backup database errors 2017-10-17 02:08:48 +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
7c37372b13 release: bump version to v0.12.0 2017-06-23 21:01:47 +02:00
b21ea30fa8 Issue #658 - Support shift+UP/RIGHT/DOWN/LEFT to move the viewport 2017-06-10 23:12:11 +02:00
c2dbddcf9f Issue #636 - rename all application-settings things to preferences-settings
The name is not ideal, but it's better to have a MiscPreferencesController than a MainApplicationController for this kind of very simple panels.
2017-06-10 11:20:23 +02:00
09ce6ff88f Issue #636 - remove unused code 2017-06-10 11:20:23 +02:00
2c4a8efb44 Issue #636 - add integration test for main settings panel 2017-06-10 11:20:23 +02:00
02a25d3f84 Fix eslint violations 2017-06-10 11:20:23 +02:00
d159de2e65 Update gitignore for vscode 2017-06-10 11:20:23 +02:00
726a8f74c1 Switch from jscs+jshint to eslint 2017-06-10 11:20:23 +02:00
89a65ab032 add test-export-gif-scale test to integration test suite 2017-06-10 11:20:23 +02:00
d8ec58b42c Remove reference to seamless in tooltip 2017-06-10 11:20:23 +02:00
1168870ee0 Issue #636 - Allow users to change the grid color 2017-06-10 11:20:23 +02:00
d3a37c74e9 Issue #636 - rename seamless* to tile* 2017-06-10 11:20:23 +02:00
5d2ca7e70c Issue #636 - create sizepicker widget and use it in grid settings 2017-06-10 11:20:23 +02:00
2976fd09ea Issue #636 - create Tabs widget and use it application settings panel 2017-06-10 11:20:23 +02:00
317fda83c3 add integration test for tiny-palettes 2017-06-04 22:29:54 +02:00
94160d8fc4 Transform palette color title to uppercase 2017-06-04 22:29:54 +02:00
b977a146e9 Issue #663 - display 10 colors per row in palette for palettes with > 10 colors 2017-06-04 22:29:54 +02:00
aea4e4d6a6 Fix #704 - increase height of offline apps window to 700px 2017-06-03 10:23:40 +02:00
5456ea973a Fix #690 - remove references to github/juliandescottes in README 2017-06-03 09:38:23 +02:00
a299d9aed0 Fix #697 - update confirmation message when closing Piskel with unsaved changes 2017-06-03 00:49:52 +02:00
cc2fc48107 Fix #271: add new piskel button for desktop application 2017-06-03 00:15:08 +02:00
799c9fbf5a Feature #541 crop based on the current selection 2017-06-01 19:46:34 +02:00
a9e22535d6 feature #541: add crop transform tool 2017-06-01 19:46:34 +02:00
4b4cbe47c8 support expanding the transform toolbox 2017-06-01 19:46:34 +02:00
e7d07c5353 Fix #699 - add SPACE to keycode translator 2017-05-27 09:05:05 +02:00
cf3383722a check meta key when recording drawing test (OSX) 2017-05-20 20:03:42 +02:00
6566ca07a5 add new layer just before the current layer 2017-05-20 20:03:42 +02:00
9fafa8b7a7 use SHIFT meta when clicking up down layer to move to top/bottom 2017-05-20 20:03:42 +02:00
4a9f7cc74b add grunt task to run drawing tests 2017-05-20 20:03:42 +02:00
319060beb6 add drawing test for move layer top/bottom 2017-05-20 20:03:42 +02:00
729c9f4732 Updating README.md to resolve license dating
Changed 2016 to 2017 to resolve any license dating issues.
2017-05-20 19:53:55 +02:00
cd560012e1 Adds comments to Stroke tool for drawing lines. 2017-05-17 23:57:30 +02:00
e819503cc5 Adjusts lineTool ends to square shape. 2017-05-17 23:57:30 +02:00
6512c3dcc0 Improves performance of line tool for large pen sizes. 2017-05-17 23:57:30 +02:00
3535dfb25e replace references to github.com/juliandescottes for github.com/piskelapp 2017-05-14 19:18:19 +02:00
a69554f6c9 release: bump version to 0.12.0-SNAPSHOT 2017-05-14 19:16:58 +02:00
1040cb4d8c release: bump version to 0.11.1 2017-05-14 19:04:33 +02:00
d18b85df16 Fix template bug for IE11 2017-05-14 18:55:51 +02:00
76563bfc41 release: bump version to v0.12.0-SNAPSHOT 2017-05-14 17:43:35 +02:00
135 changed files with 6601 additions and 926 deletions

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
# Exclude libs.
**/lib/**/*.js

6
.gitignore vendored
View File

@ -1,4 +1,4 @@
# mac artefacts # mac artifacts
*.DS_Store *.DS_Store
# nodejs local installs # nodejs local installs
@ -15,10 +15,12 @@ cache
# netbeans project folder # netbeans project folder
nbproject nbproject
# vscode workspace folder
.vscode
# git stackdumps # git stackdumps
*.stackdump *.stackdump
# diffs # diffs
diff.txt diff.txt

75
.jscsrc
View File

@ -1,75 +0,0 @@
{
"requireCurlyBraces": [
"if",
"else",
"for",
"while",
"do",
"try",
"catch"
],
"requireOperatorBeforeLineBreak": true,
"requireCamelCaseOrUpperCaseIdentifiers": true,
"maximumLineLength": {
"value": 80,
"allExcept": ["comments", "regex"]
},
"validateIndentation": 2,
"validateQuoteMarks": "'",
"disallowMultipleLineStrings": true,
"disallowMixedSpacesAndTabs": true,
"disallowTrailingWhitespace": true,
"disallowSpaceAfterPrefixUnaryOperators": true,
"disallowMultipleVarDecl": true,
"disallowKeywordsOnNewLine": ["else"],
"requireSpaceAfterKeywords": [
"if",
"else",
"for",
"while",
"do",
"switch",
"return",
"try",
"catch"
],
"requireSpaceBeforeBinaryOperators": [
"=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
"&=", "|=", "^=", "+=",
"+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
"|", "^", "&&", "||", "===", "==", ">=",
"<=", "<", ">", "!=", "!=="
],
"requireSpaceAfterBinaryOperators": true,
"requireSpacesInConditionalExpression": true,
"requireSpaceBeforeBlockStatements": true,
"requireSpacesInForStatement": true,
"requireLineFeedAtFileEnd": true,
"requireSpacesInFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"disallowSpacesInAnonymousFunctionExpression": {
"beforeOpeningRoundBrace": false
},
"disallowSpacesInsideObjectBrackets": "all",
"disallowSpacesInsideArrayBrackets": "all",
"disallowSpacesInsideParentheses": true,
"disallowMultipleLineBreaks": true,
"disallowNewlineBeforeBlockStatements": true,
"disallowKeywords": ["with"],
"disallowSpacesInFunctionExpression": null,
"disallowSpacesInFunctionDeclaration": null,
"disallowSpacesInCallExpression": true,
"disallowSpaceAfterObjectKeys": false,
"requireSpaceBeforeObjectValues": true,
"requireCapitalizedConstructors": true,
"requireDotNotation": true,
"requireSemicolons": true,
"validateParameterSeparator": ", ",
"jsDoc": null
}

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

@ -83,33 +83,12 @@ module.exports = function(grunt) {
css : ['src/css/**/*.css'] css : ['src/css/**/*.css']
}, },
jscs : { eslint: {
options : {
"config": ".jscsrc",
"maximumLineLength": 120,
"requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties",
"validateQuoteMarks": { "mark": "'", "escape": true },
"disallowMultipleVarDecl": "exceptUndefined",
"disallowSpacesInAnonymousFunctionExpression": null
},
js : [ 'src/js/**/*.js' , '!src/js/**/lib/**/*.js' ]
},
jshint: {
options: {
undef : true,
latedef : true,
browser : true,
trailing : true,
curly : true,
globals : {'$':true, 'jQuery' : true, 'pskl':true, 'Events':true, 'Constants':true, 'console' : true, 'module':true, 'require':true, 'Q':true, 'Promise': true}
},
files: [ files: [
// Includes // Includes
'Gruntfile.js',
'package.json',
'src/js/**/*.js', 'src/js/**/*.js',
// Excludes // Exludes
// TODO: remove this (for now we still get warnings from the lib folder)
'!src/js/**/lib/**/*.js' '!src/js/**/lib/**/*.js'
] ]
}, },
@ -238,20 +217,6 @@ module.exports = function(grunt) {
src: ['dest/tmp/css/piskel-style-packaged' + version + '.css'], src: ['dest/tmp/css/piskel-style-packaged' + version + '.css'],
dest: 'dest/prod/css/piskel-style-packaged' + version + '.css' dest: 'dest/prod/css/piskel-style-packaged' + version + '.css'
}] }]
},
// remove the fake header from the desktop build
desktop: {
options: {
patterns: [{
match: /<!--standalone-start-->(?:.|[\r\n])*<!--standalone-end-->/,
replacement: "",
description : "Remove everything between standalone-start & standalone-end"
}
]
},
files: [
{src: ['dest/prod/index.html'], dest: 'dest/prod/index.html'}
]
} }
}, },
@ -351,11 +316,13 @@ module.exports = function(grunt) {
// TEST TASKS // TEST TASKS
// Run linting // Run linting
grunt.registerTask('lint', ['jscs:js', 'leadingIndent:css', 'jshint']); grunt.registerTask('lint', ['eslint', 'leadingIndent:css']);
// Run unit-tests // Run unit-tests
grunt.registerTask('unit-test', ['karma']); grunt.registerTask('unit-test', ['karma']);
// Run integration tests // Run integration tests
grunt.registerTask('integration-test', ['build-dev', 'connect:test', 'casperjs:integration']); grunt.registerTask('integration-test', ['build-dev', 'connect:test', 'casperjs:integration']);
// Run drawing tests
grunt.registerTask('drawing-test', ['build-dev', 'connect:test', 'casperjs:drawing']);
// Run linting, unit tests, drawing tests and integration tests // Run linting, unit tests, drawing tests and integration tests
grunt.registerTask('test', ['lint', 'unit-test', 'build-dev', 'connect:test', 'casperjs:drawing', 'casperjs:integration']); grunt.registerTask('test', ['lint', 'unit-test', 'build-dev', 'connect:test', 'casperjs:drawing', 'casperjs:integration']);
@ -370,8 +337,8 @@ module.exports = function(grunt) {
grunt.registerTask('merge-statics', ['concat:js', 'concat:css', 'uglify']); grunt.registerTask('merge-statics', ['concat:js', 'concat:css', 'uglify']);
grunt.registerTask('build', ['clean:prod', 'sprite', 'merge-statics', 'build-index.html', 'replace:mainPartial', 'replace:css', 'copy:prod']); grunt.registerTask('build', ['clean:prod', 'sprite', 'merge-statics', 'build-index.html', 'replace:mainPartial', 'replace:css', 'copy:prod']);
grunt.registerTask('build-dev', ['clean:dev', 'sprite', 'build-index.html', 'copy:dev']); grunt.registerTask('build-dev', ['clean:dev', 'sprite', 'build-index.html', 'copy:dev']);
grunt.registerTask('desktop', ['clean:desktop', 'default', 'replace:desktop', 'nwjs:windows']); grunt.registerTask('desktop', ['clean:desktop', 'default', 'nwjs:windows']);
grunt.registerTask('desktop-mac', ['clean:desktop', 'default', 'replace:desktop', 'nwjs:macos']); grunt.registerTask('desktop-mac', ['clean:desktop', 'default', 'nwjs:macos']);
grunt.registerTask('desktop-mac-old', ['clean:desktop', 'default', 'replace:desktop', 'nwjs:macos_old']); grunt.registerTask('desktop-mac-old', ['clean:desktop', 'default', 'replace:desktop', 'nwjs:macos_old']);
// SERVER TASKS // SERVER TASKS

View File

@ -1,50 +1,21 @@
Piskel Piskel
====== ======
[![Travis Status](https://api.travis-ci.org/juliandescottes/piskel.png?branch=master)](https://travis-ci.org/juliandescottes/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)](http://gruntjs.com/)
A simple web-based tool for Spriting and 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)**.
![Piskel editor screenshot](https://screenletstore.appspot.com/img/8f03e768-ac59-11e3-b2a1-7f5a1b97c420.jpeg "Piskel editor screenshot") <img
src="https://screenletstore.appspot.com/img/95aaa0f0-37a4-11e7-a652-7b8128ce3e3b.png"
title="Piskel editor screenshot"
width="500">
You can try the standalone editor at **http://juliandescottes.github.io/piskel** or see it integrated in **http://piskelapp.com**. ## About Piskel
Piskel is mainly developped by : ### Built with
* **[@juliandescottes](https://github.com/juliandescottes)** The Piskel editor is purely built in **JavaScript, HTML and CSS**.
* **[@grosbouddha](https://github.com/grosbouddha)**
## What's the point ?
You can use Piskel to do two things :
* **spriting** : create retro-style sprites for games
![Megaman spritesheet](http://piskel-imgstore-a.appspot.com/img/c8081287-ac58-11e3-bd8c-b3c4036c0eee.png "Megaman spritesheet")
* **pixelart** : create crazy/pretty pixelart animations for fun !
![Rabbit jumping](http://piskel-imgstore-a.appspot.com/img/947f2dab-ac58-11e3-949a-b3c4036c0eee.gif "Rabit jumping")
Integrated in **[piskelapp.com](http://piskelapp.com)**, you can share everything you work on with others as easily as you share a link.
## Requirements
Piskel supports the following browsers :
* **Chrome** (latest)
* **Firefox** (latest)
* **Internet Explorer** 11+
... and a fairly recent computer.
We don't plan/want/could be forced into supporting older IEs. For Opera and Safari, we've never tested them but the gap shouldn't be huge.
## Offline version
Offline builds are available. More details in the [dedicated wiki page](https://github.com/juliandescottes/piskel/wiki/Desktop-applications).
## Built with
The Piskel editor is purely built in **JavaScript, HTML and CSS**. It uses Canvas extensively for displaying all them pretty sprites.
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
@ -59,17 +30,32 @@ As well as some **icons** from the [Noun Project](http://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)
### Browser Support
Piskel supports the following browsers:
* **Chrome** (latest)
* **Firefox** (latest)
* **Edge** (latest)
* **Internet Explorer** 11
### Mobile/Tablets
There is no support for mobile.
### Offline builds
Offline builds are available. More details in the [dedicated wiki page](https://github.com/piskelapp/piskel/wiki/Desktop-applications).
## Contributing ? ## Contributing ?
Help is always welcome ! Help is always welcome !
* **Issues** : Found a problem when using the application, want to request a feature, [open an issue](https://github.com/juliandescottes/piskel/issues). * **Issues** : Found a problem when using the application, want to request a feature, [open an issue](https://github.com/piskelapp/piskel/issues).
* **Participate** : Have a look at the [wiki](https://github.com/juliandescottes/piskel/wiki) to set up the development environment * **Development** : Have a look at the [wiki](https://github.com/piskelapp/piskel/wiki) to set up the development environment
## License ## License
Copyright 2016 Julian Descottes Copyright 2017 Julian Descottes
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -83,7 +69,3 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
## Mobile/Tablets
There is no support for mobile for now.

View File

@ -17,7 +17,16 @@ 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.unlink(previousPartialPath, onDeletePreviousPartial); 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);
}
})
} }
function onDeletePreviousPartial(err) { function onDeletePreviousPartial(err) {

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,16 +1,16 @@
{ {
"name": "piskel", "name": "piskel",
"version": "0.11.0", "version": "0.12.1",
"description": "Pixel art editor", "description": "Pixel art editor",
"author": "Julian Descottes <julian.descottes@gmail.com>", "author": "Julian Descottes <julian.descottes@gmail.com>",
"contributors": [ "contributors": [
"Vincent Renaudin" "Vincent Renaudin"
], ],
"homepage": "http://github.com/juliandescottes/piskel", "homepage": "http://github.com/piskelapp/piskel",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "http://github.com/juliandescottes/piskel.git" "url": "http://github.com/piskelapp/piskel.git"
}, },
"files": [ "files": [
"dest/prod", "dest/prod",
@ -39,6 +39,7 @@
"grunt-contrib-jshint": "1.1.0", "grunt-contrib-jshint": "1.1.0",
"grunt-contrib-uglify": "2.3.0", "grunt-contrib-uglify": "2.3.0",
"grunt-contrib-watch": "1.0.0", "grunt-contrib-watch": "1.0.0",
"grunt-eslint": "^19.0.0",
"grunt-include-replace": "4.0.1", "grunt-include-replace": "4.0.1",
"grunt-jscs": "2.8.0", "grunt-jscs": "2.8.0",
"grunt-karma": "1.0.0", "grunt-karma": "1.0.0",
@ -62,6 +63,6 @@
"icon": "dest/prod/logo.png", "icon": "dest/prod/logo.png",
"toolbar": false, "toolbar": false,
"width": 1000, "width": 1000,
"height": 500 "height": 700
} }
} }

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

@ -1,11 +1,9 @@
/*******************************/ /********************************/
/* Application Setting panel */ /* Preferences Settings panel */
/*******************************/ /********************************/
.application-settings-form {
height: 100%;
}
.settings-section-application { .settings-section-preferences {
height: 100%;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
margin: 0 20px; margin: 0 20px;
@ -45,14 +43,11 @@
vertical-align: middle; vertical-align: middle;
} }
.layer-opacity-input { .layer-opacity-input,
.tile-mask-opacity-input {
width: 100px; width: 100px;
} }
.seamless-opacity-input {
width: 75px;
}
.settings-opacity-text { .settings-opacity-text {
height: 31px; height: 31px;
display: inline-block; display: inline-block;
@ -64,17 +59,62 @@
text-align: center; text-align: center;
} }
.settings-item-grid-size,
.settings-item-grid-color {
display: flex;
align-items: center;
}
.settings-item-grid-size > label,
.settings-item-grid-color > label {
width: 65px;
flex-shrink: 0;
}
.settings-item-grid-size .size-picker-option {
border-color: #888;
}
.settings-item-grid-size .size-picker-option:not(.selected):hover {
border-color: white;
}
.grid-width-select, .grid-width-select,
.color-format-select { .color-format-select {
margin: 5px 5px 0 5px; margin: 5px 5px 0 5px;
} }
.settings-section-application > .settings-title {
.grid-colors-list {
overflow: hidden;
padding: 0 5px;
}
.grid-colors-item {
float: left;
width: 20px;
height: 20px;
cursor: pointer;
border: 2px solid #888;
margin-right: 2px;
margin-bottom: 2px;
box-sizing: border-box;
}
.grid-colors-item:hover {
border-color: white;
}
.grid-colors-item.selected {
border-color: gold;
}
.settings-section-preferences > .settings-title {
/* Override the default 10px margin bottom for this panel */ /* Override the default 10px margin bottom for this panel */
margin-bottom: 15px; margin-bottom: 15px;
} }
.settings-section-application .button-primary { .settings-section-preferences .button-primary {
margin-top: 10px; margin-top: 10px;
} }

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%;
@ -100,45 +121,6 @@
line-height: 23px; line-height: 23px;
} }
.export-tabs {
overflow: hidden;
position: relative;
}
.export-tabs:after {
content: "";
display: block;
position: absolute;
bottom: 0;
width: 100%;
height: 1px;
z-index: 0;
background-color: var(--highlight-color);
}
.export-tab {
float: left;
cursor: pointer;
padding: 5px;
border: 1px solid transparent;
border-radius: 2px 2px 0 0;
/* Make sure the tab and its border are positioned above the :after element; */
position: relative;
z-index: 1;
}
.export-tab.selected,
.export-tab:hover {
color: var(--highlight-color);
}
.export-tab.selected {
border-color: var(--highlight-color);
border-bottom-color: #444;
border-style: solid;
border-width: 1px;
}
.export-panel-header { .export-panel-header {
padding: 10px 5px 0px; padding: 10px 5px 0px;
} }

View File

@ -55,6 +55,10 @@ body {
margin-left: 0; margin-left: 0;
} }
.hidden {
display: none;
}
/** /**
* TOOLTIPS * TOOLTIPS
*/ */

View File

@ -76,6 +76,11 @@
position: relative; position: relative;
} }
.palettes-list-colors.tiny > .palettes-list-color {
width: calc((100% - 35px) / 10);
height: 16px;
}
.palettes-list-color div { .palettes-list-color div {
height: 100%; height: 100%;
} }
@ -121,7 +126,8 @@
* Color index for the 9 first colors * Color index for the 9 first colors
*/ */
.palettes-list-color:nth-child(-n+10):after { :not(.tiny) > .palettes-list-color:nth-child(-n+10):after {
content: attr(data-color-index);
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
@ -136,39 +142,3 @@
padding: 2px 3px 2px 3px; padding: 2px 3px 2px 3px;
border-radius: 0 0 0 2px; border-radius: 0 0 0 2px;
} }
.palettes-list-color:nth-child(1):after {
content: "1";
}
.palettes-list-color:nth-child(2):after {
content: "2";
}
.palettes-list-color:nth-child(3):after {
content: "3";
}
.palettes-list-color:nth-child(4):after {
content: "4";
}
.palettes-list-color:nth-child(5):after {
content: "5";
}
.palettes-list-color:nth-child(6):after {
content: "6";
}
.palettes-list-color:nth-child(7):after {
content: "7";
}
.palettes-list-color:nth-child(8):after {
content: "8";
}
.palettes-list-color:nth-child(9):after {
content: "9";
}

View File

@ -3,5 +3,38 @@
} }
.transformations-container .tool-icon { .transformations-container .tool-icon {
float:left; margin: 0 0 5px 0;
} }
.transformations-container .tools-wrapper {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
height: 46px;
/* Override the float:left set on tools-wrapper in layout.css; */
float: initial;
}
.transformations-container.show-more .tools-wrapper {
height: auto;
/* Compensate the 5px bottom-margin coming from the tool-icon */
margin-bottom: -5px;
}
.transformations-show-more-link {
position: absolute;
color: #999;
right: 10px;
font-weight: normal;
cursor: pointer;
transition: 0.2s linear;
}
.transformations-show-more-link:hover {
color: white;
}
.show-more .transformations-show-more-link {
color: gold;
}

View File

@ -1,9 +1,13 @@
.pen-size-container { /***********************/
/* SIZE PICKER WIDGET */
/***********************/
.size-picker-container {
overflow: hidden; overflow: hidden;
padding: 5px 5px; padding: 5px 5px;
} }
.pen-size-option { .size-picker-option {
float: left; float: left;
box-sizing: border-box; box-sizing: border-box;
width: 20px; width: 20px;
@ -15,20 +19,20 @@
cursor: pointer; cursor: pointer;
} }
.pen-size-option[data-size='1'] { .size-picker-option[data-size='1'] {
padding: 5px; padding: 5px;
} }
.pen-size-option[data-size='2'] { .size-picker-option[data-size='2'] {
padding: 4px; padding: 4px;
} }
.pen-size-option[data-size='3'] { .size-picker-option[data-size='3'] {
padding: 3px; padding: 3px;
} }
.pen-size-option[data-size='4'] { .size-picker-option[data-size='4'] {
padding: 2px; padding: 2px;
} }
.pen-size-option:before { .size-picker-option:before {
content: ''; content: '';
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -39,19 +43,19 @@
font-size: 90%; font-size: 90%;
} }
.pen-size-option:hover { .size-picker-option:hover {
border-color: #888; border-color: #888;
} }
.pen-size-option.selected:before { .size-picker-option.selected:before {
background-color: var(--highlight-color); background-color: var(--highlight-color);
} }
.pen-size-option.selected { .size-picker-option.selected {
border-color: var(--highlight-color); border-color: var(--highlight-color);
} }
.pen-size-option.labeled:before { .size-picker-option.labeled:before {
content: attr(real-pen-size); content: attr(real-size);
color: black; color: black;
} }

42
src/css/widgets-tabs.css Normal file
View File

@ -0,0 +1,42 @@
/*****************/
/* TABS WIDGET */
/*****************/
.tab-list {
overflow: hidden;
position: relative;
}
.tab-list:after {
content: "";
display: block;
position: absolute;
bottom: 0;
width: 100%;
height: 1px;
z-index: 0;
background-color: var(--highlight-color);
}
.tab-item {
float: left;
cursor: pointer;
padding: 5px;
border: 1px solid transparent;
border-radius: 2px 2px 0 0;
/* Make sure the tab and its border are positioned above the :after element; */
position: relative;
z-index: 1;
}
.tab-item.selected,
.tab-item:hover {
color: var(--highlight-color);
}
.tab-item.selected {
border-color: var(--highlight-color);
border-bottom-color: #444;
border-style: solid;
border-width: 1px;
}

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: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

View File

@ -22,9 +22,7 @@
color:white;"> color:white;">
<span style="top:45%">Loading Piskel ...</span> <span style="top:45%">Loading Piskel ...</span>
</div> </div>
<!--standalone-start-->
@@include('templates/debug-header.html', {}) @@include('templates/debug-header.html', {})
<!--standalone-end-->
<!-- the comment below indicates the beginning of markup reused by the editor integrated in piskelapp.com --> <!-- the comment below indicates the beginning of markup reused by the editor integrated in piskelapp.com -->
<!-- do not delete, do not move :) --> <!-- do not delete, do not move :) -->
@ -69,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"
@ -77,15 +76,19 @@
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', {})
<!-- settings-panel partials --> <!-- settings-panel partials -->
@@include('templates/settings/application.html', {}) @@include('templates/settings/preferences.html', {})
@@include('templates/settings/preferences/grid.html', {})
@@include('templates/settings/preferences/misc.html', {})
@@include('templates/settings/preferences/tile.html', {})
@@include('templates/settings/resize.html', {}) @@include('templates/settings/resize.html', {})
@@include('templates/settings/save.html', {}) @@include('templates/settings/save.html', {})
@@include('templates/settings/import.html', {}) @@include('templates/settings/import.html', {})

99
src/js/.eslintrc Normal file
View File

@ -0,0 +1,99 @@
{
"env": {
"es6": true,
"browser": true
},
"globals": {
"$": true,
"jQuery": true,
"pskl": true,
"Events": true,
"Constants": true,
"console": true,
"module": true,
"require": true,
"Q": true,
"Promise": true
},
"rules": {
"no-undef": 2,
"no-use-before-define": [
2,
{
"functions": false
}
],
"curly": [
2,
"all"
],
"operator-linebreak": [
2,
"after"
],
"camelcase": [
2,
{
"properties": "never"
}
],
"max-len": [
2,
120
],
"indent": [
2,
2,
{
"SwitchCase": 1
}
],
"quotes": [
2,
"single"
],
"no-multi-str": 2,
"no-mixed-spaces-and-tabs": 2,
"no-trailing-spaces": 2,
"space-unary-ops": [
2,
{
"nonwords": false,
"overrides": {}
}
],
"one-var": [
2,
"never"
],
"brace-style": [
2,
"1tbs",
{
"allowSingleLine": true
}
],
"keyword-spacing": [
2,
{}
],
"space-infix-ops": 2,
"space-before-blocks": [
2,
"always"
],
"eol-last": 2,
"space-in-parens": [
2,
"never"
],
"no-multiple-empty-lines": 2,
"no-with": 2,
"no-spaced-func": 2,
"dot-notation": 2,
"semi": [
2,
"always"
]
}
}

View File

@ -12,7 +12,7 @@ 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,
@ -58,8 +58,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,11 +10,17 @@
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();
@ -111,6 +117,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();
@ -165,6 +174,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();
@ -192,6 +204,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

@ -64,6 +64,10 @@
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.RESET_ZOOM, this.resetZoom_.bind(this)); pskl.app.shortcutService.registerShortcut(shortcuts.MISC.RESET_ZOOM, this.resetZoom_.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.INCREASE_ZOOM, this.updateZoom_.bind(this, 1)); pskl.app.shortcutService.registerShortcut(shortcuts.MISC.INCREASE_ZOOM, this.updateZoom_.bind(this, 1));
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.DECREASE_ZOOM, this.updateZoom_.bind(this, -1)); pskl.app.shortcutService.registerShortcut(shortcuts.MISC.DECREASE_ZOOM, this.updateZoom_.bind(this, -1));
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.OFFSET_UP, this.updateOffset_.bind(this, 'up'));
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.OFFSET_RIGHT, this.updateOffset_.bind(this, 'right'));
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.OFFSET_DOWN, this.updateOffset_.bind(this, 'down'));
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.OFFSET_LEFT, this.updateOffset_.bind(this, 'left'));
window.setTimeout(function () { window.setTimeout(function () {
this.afterWindowResize_(); this.afterWindowResize_();
@ -262,6 +266,29 @@
this.updateZoom_(modifier, coords); this.updateZoom_(modifier, coords);
}; };
/**
* Update the current viewport offset of 1 pixel in the provided direction.
* Direction can be one of 'up', 'right', 'down', 'left'.
* Callback for the OFFSET_${DIR} shortcuts.
*/
ns.DrawingController.prototype.updateOffset_ = function (direction) {
var off = this.getOffset();
if (direction === 'up') {
off.y -= 1;
} else if (direction === 'right') {
off.x += 1;
} else if (direction === 'down') {
off.y += 1;
} else if (direction === 'left') {
off.x -= 1;
}
this.setOffset(
off.x,
off.y
);
};
/** /**
* Update the current zoom level by a given multiplier. * Update the current zoom level by a given multiplier.
* *

View File

@ -17,7 +17,7 @@
this.rootEl.addEventListener('click', this.onClick_.bind(this)); this.rootEl.addEventListener('click', this.onClick_.bind(this));
this.toggleLayerPreviewEl.addEventListener('click', this.toggleLayerPreview_.bind(this)); this.toggleLayerPreviewEl.addEventListener('click', this.toggleLayerPreview_.bind(this));
this.initCreateLayerButton_(); this.createButtonTooltips_();
this.initToggleLayerPreview_(); this.initToggleLayerPreview_();
this.renderLayerList_(); this.renderLayerList_();
@ -46,12 +46,24 @@
} }
}; };
ns.LayersListController.prototype.initCreateLayerButton_ = function () { ns.LayersListController.prototype.createButtonTooltips_ = function () {
var tooltip = pskl.utils.TooltipFormatter.format('Create a layer', null, [ var addTooltip = pskl.utils.TooltipFormatter.format('Create a layer', null, [
{key : 'shift', description : 'Clone current layer'} {key : 'shift', description : 'Clone current layer'}
]); ]);
var addButton = this.rootEl.querySelector('[data-action="add"]'); var addButton = this.rootEl.querySelector('[data-action="add"]');
addButton.setAttribute('title', tooltip); addButton.setAttribute('title', addTooltip);
var moveDownTooltip = pskl.utils.TooltipFormatter.format('Move layer down', null, [
{key : 'shift', description : 'Move to the bottom'}
]);
var moveDownButton = this.rootEl.querySelector('[data-action="down"]');
moveDownButton.setAttribute('title', moveDownTooltip);
var moveUpTooltip = pskl.utils.TooltipFormatter.format('Move layer up', null, [
{key : 'shift', description : 'Move to the top'}
]);
var moveUpButton = this.rootEl.querySelector('[data-action="up"]');
moveUpButton.setAttribute('title', moveUpTooltip);
}; };
ns.LayersListController.prototype.initToggleLayerPreview_ = function () { ns.LayersListController.prototype.initToggleLayerPreview_ = function () {
@ -157,9 +169,9 @@
ns.LayersListController.prototype.onButtonClick_ = function (button, evt) { ns.LayersListController.prototype.onButtonClick_ = function (button, evt) {
var action = button.getAttribute('data-action'); var action = button.getAttribute('data-action');
if (action == 'up') { if (action == 'up') {
this.piskelController.moveLayerUp(); this.piskelController.moveLayerUp(evt.shiftKey);
} else if (action == 'down') { } else if (action == 'down') {
this.piskelController.moveLayerDown(); this.piskelController.moveLayerDown(evt.shiftKey);
} else if (action == 'add') { } else if (action == 'add') {
if (evt.shiftKey) { if (evt.shiftKey) {
this.piskelController.duplicateCurrentLayer(); this.piskelController.duplicateCurrentLayer();

View File

@ -51,12 +51,17 @@
}; };
ns.PalettesListController.prototype.fillColorListContainer = function () { ns.PalettesListController.prototype.fillColorListContainer = function () {
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 pskl.utils.Template.replace(this.paletteColorTemplate_, {color : color, index : index}); return !!color;
}).map(function (color, index) {
return pskl.utils.Template.replace(this.paletteColorTemplate_, {
color : color,
index : index + 1,
title : color.toUpperCase()
});
}.bind(this)).join(''); }.bind(this)).join('');
this.colorListContainer_.innerHTML = html; this.colorListContainer_.innerHTML = html;
@ -64,6 +69,10 @@
} else { } else {
this.colorListContainer_.innerHTML = pskl.utils.Template.get('palettes-list-no-colors-partial'); this.colorListContainer_.innerHTML = pskl.utils.Template.get('palettes-list-no-colors-partial');
} }
// If we have more than 10 colors, use tiny mode, where 10 colors will fit on the same
// line.
this.colorListContainer_.classList.toggle('tiny', colors.length > 10);
}; };
ns.PalettesListController.prototype.selectPalette = function (paletteId) { ns.PalettesListController.prototype.selectPalette = function (paletteId) {
@ -101,7 +110,7 @@
var currentIndex = 0; var currentIndex = 0;
var selectedColor = document.querySelector('.' + PRIMARY_COLOR_CLASSNAME); var selectedColor = document.querySelector('.' + PRIMARY_COLOR_CLASSNAME);
if (selectedColor) { if (selectedColor) {
currentIndex = parseInt(selectedColor.dataset.colorIndex, 10); currentIndex = parseInt(selectedColor.dataset.colorIndex, 10) - 1;
} }
return currentIndex; return currentIndex;
}; };

View File

@ -1,23 +1,19 @@
(function () { (function () {
var ns = $.namespace('pskl.controller'); var ns = $.namespace('pskl.controller');
ns.PenSizeController = function () {}; ns.PenSizeController = function () {
this.sizePicker = new pskl.widgets.SizePicker(this.onSizePickerChanged_.bind(this));
};
ns.PenSizeController.prototype.init = function () { ns.PenSizeController.prototype.init = function () {
this.container = document.querySelector('.pen-size-container'); this.sizePicker.init(document.querySelector('.pen-size-container'));
pskl.utils.Event.addEventListener(this.container, 'click', this.onPenSizeOptionClick_, this);
$.subscribe(Events.PEN_SIZE_CHANGED, this.onPenSizeChanged_.bind(this)); $.subscribe(Events.PEN_SIZE_CHANGED, this.onPenSizeChanged_.bind(this));
this.updateSelectedOption_(); this.updateSelectedOption_();
}; };
ns.PenSizeController.prototype.onPenSizeOptionClick_ = function (e) { ns.PenSizeController.prototype.onSizePickerChanged_ = function (size) {
var size = e.target.dataset.size; pskl.app.penSizeService.setPenSize(size);
if (!isNaN(size)) {
size = parseInt(size, 10);
pskl.app.penSizeService.setPenSize(size);
}
}; };
ns.PenSizeController.prototype.onPenSizeChanged_ = function (e) { ns.PenSizeController.prototype.onPenSizeChanged_ = function (e) {
@ -25,19 +21,7 @@
}; };
ns.PenSizeController.prototype.updateSelectedOption_ = function () { ns.PenSizeController.prototype.updateSelectedOption_ = function () {
pskl.utils.Dom.removeClass('labeled', this.container);
pskl.utils.Dom.removeClass('selected', this.container);
var size = pskl.app.penSizeService.getPenSize(); var size = pskl.app.penSizeService.getPenSize();
var selectedOption; this.sizePicker.setSize(size);
if (size <= 4) {
selectedOption = this.container.querySelector('[data-size="' + size + '"]');
} else {
selectedOption = this.container.querySelector('[data-size="4"]');
selectedOption.classList.add('labeled');
selectedOption.setAttribute('real-pen-size', size);
}
if (selectedOption) {
selectedOption.classList.add('selected');
}
}; };
})(); })();

View File

@ -1,22 +1,30 @@
(function () { (function () {
var ns = $.namespace('pskl.controller'); var ns = $.namespace('pskl.controller');
var SHOW_MORE_CLASS = 'show-more';
ns.TransformationsController = function () { ns.TransformationsController = function () {
this.tools = [ this.tools = [
new pskl.tools.transform.Flip(), new pskl.tools.transform.Flip(),
new pskl.tools.transform.Rotate(), new pskl.tools.transform.Rotate(),
new pskl.tools.transform.Clone(), new pskl.tools.transform.Clone(),
new pskl.tools.transform.Center() new pskl.tools.transform.Center(),
new pskl.tools.transform.Crop(),
]; ];
this.toolIconBuilder = new pskl.tools.ToolIconBuilder(); this.toolIconBuilder = new pskl.tools.ToolIconBuilder();
}; };
ns.TransformationsController.prototype.init = function () { ns.TransformationsController.prototype.init = function () {
var container = document.querySelector('.transformations-container'); this.container = document.querySelector('.transformations-container');
this.toolsContainer = container.querySelector('.tools-wrapper'); this.container.addEventListener('click', this.onTransformationClick_.bind(this));
container.addEventListener('click', this.onTransformationClick_.bind(this));
this.showMoreLink = this.container.querySelector('.transformations-show-more-link');
this.showMoreLink.addEventListener('click', this.toggleShowMoreTools_.bind(this));
this.createToolsDom_(); this.createToolsDom_();
this.updateShowMoreLink_();
$.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
}; };
ns.TransformationsController.prototype.applyTool = function (toolId, evt) { ns.TransformationsController.prototype.applyTool = function (toolId, evt) {
@ -30,13 +38,35 @@
ns.TransformationsController.prototype.onTransformationClick_ = function (evt) { ns.TransformationsController.prototype.onTransformationClick_ = function (evt) {
var toolId = evt.target.dataset.toolId; var toolId = evt.target.dataset.toolId;
this.applyTool(toolId, evt); if (toolId) {
this.applyTool(toolId, evt);
}
};
ns.TransformationsController.prototype.toggleShowMoreTools_ = function (evt) {
var showMore = pskl.UserSettings.get(pskl.UserSettings.TRANSFORM_SHOW_MORE);
pskl.UserSettings.set(pskl.UserSettings.TRANSFORM_SHOW_MORE, !showMore);
};
ns.TransformationsController.prototype.onUserSettingsChange_ = function (evt, settingName) {
if (settingName == pskl.UserSettings.TRANSFORM_SHOW_MORE) {
this.updateShowMoreLink_();
}
};
ns.TransformationsController.prototype.updateShowMoreLink_ = function () {
var showMoreEnabled = pskl.UserSettings.get(pskl.UserSettings.TRANSFORM_SHOW_MORE);
this.container.classList.toggle(SHOW_MORE_CLASS, showMoreEnabled);
// Hide the link in case there are 4 or less tools available.
this.showMoreLink.classList.toggle('hidden', this.tools.length < 5);
}; };
ns.TransformationsController.prototype.createToolsDom_ = function() { ns.TransformationsController.prototype.createToolsDom_ = function() {
var html = this.tools.reduce(function (p, tool) { var html = this.tools.reduce(function (p, tool) {
return p + this.toolIconBuilder.createIcon(tool, 'left'); return p + this.toolIconBuilder.createIcon(tool, 'left');
}.bind(this), ''); }.bind(this), '');
this.toolsContainer.innerHTML = html; var toolsContainer = this.container.querySelector('.tools-wrapper');
toolsContainer.innerHTML = html;
}; };
})(); })();

View File

@ -10,7 +10,7 @@
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 = $('.local-piskel-list');
this.prevSessionContainer = $('.previous-session'); this.prevSessionContainer = $('.previous-session');
@ -36,24 +36,24 @@
}; };
ns.BrowseLocalController.prototype.fillLocalPiskelsList_ = function () { ns.BrowseLocalController.prototype.fillLocalPiskelsList_ = function () {
var html = ''; this.service_.getKeys().then(function (keys) {
var keys = this.service_.getKeys(); var html = '';
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;} return 0;
return 0;
});
keys.forEach((function (key) {
var date = pskl.utils.DateUtils.format(key.date, '{{Y}}/{{M}}/{{D}} {{H}}:{{m}}');
html += pskl.utils.Template.replace(this.localStorageItemTemplate_, {
name : key.name,
date : date
}); });
}).bind(this));
var tableBody_ = this.piskelList.get(0).tBodies[0]; keys.forEach((function (key) {
tableBody_.innerHTML = html; var date = pskl.utils.DateUtils.format(key.date, '{{Y}}/{{M}}/{{D}} {{H}}:{{m}}');
html += pskl.utils.Template.replace(this.localStorageItemTemplate_, {
name : key.name,
date : date
});
}).bind(this));
var tableBody_ = this.piskelList.get(0).tBodies[0];
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') {
this.wizard.goTo('SELECT_MODE'); 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');
}
} 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

@ -12,11 +12,15 @@
/** /**
* Set the current piskel. Will reset the selected frame and layer unless specified * Set the current piskel. Will reset the selected frame and layer unless specified
* @param {Object} piskel * @param {Object} piskel
* @param {Boolean} preserveState if true, keep the selected frame and layer * @param {Object} options:
* preserveState {Boolean} if true, keep the selected frame and layer
* noSnapshot {Boolean} if true, do not save a snapshot in the piskel
* history for this call to setPiskel
*/ */
ns.PiskelController.prototype.setPiskel = function (piskel, preserveState) { ns.PiskelController.prototype.setPiskel = function (piskel, options) {
this.piskel = piskel; this.piskel = piskel;
if (!preserveState) { options = options || {};
if (!options.preserveState) {
this.currentLayerIndex = 0; this.currentLayerIndex = 0;
this.currentFrameIndex = 0; this.currentFrameIndex = 0;
} }
@ -233,8 +237,9 @@
ns.PiskelController.prototype.duplicateCurrentLayer = function () { ns.PiskelController.prototype.duplicateCurrentLayer = function () {
var layer = this.getCurrentLayer(); var layer = this.getCurrentLayer();
var clone = pskl.utils.LayerUtils.clone(layer); var clone = pskl.utils.LayerUtils.clone(layer);
this.piskel.addLayer(clone); var currentLayerIndex = this.getCurrentLayerIndex();
this.setCurrentLayerIndex(this.piskel.getLayers().length - 1); this.piskel.addLayerAt(clone, currentLayerIndex + 1);
this.setCurrentLayerIndex(currentLayerIndex + 1);
}; };
ns.PiskelController.prototype.createLayer = function (name) { ns.PiskelController.prototype.createLayer = function (name) {
@ -246,9 +251,9 @@
for (var i = 0 ; i < this.getFrameCount() ; i++) { for (var i = 0 ; i < this.getFrameCount() ; i++) {
layer.addFrame(this.createEmptyFrame_()); layer.addFrame(this.createEmptyFrame_());
} }
this.piskel.addLayer(layer); var currentLayerIndex = this.getCurrentLayerIndex();
this.setCurrentLayerIndex(this.piskel.getLayers().length - 1); this.piskel.addLayerAt(layer, currentLayerIndex + 1);
this.setCurrentLayerIndex(currentLayerIndex + 1);
} else { } else {
throw 'Layer name should be unique'; throw 'Layer name should be unique';
} }
@ -258,15 +263,15 @@
return this.piskel.getLayersByName(name).length > 0; return this.piskel.getLayersByName(name).length > 0;
}; };
ns.PiskelController.prototype.moveLayerUp = function () { ns.PiskelController.prototype.moveLayerUp = function (toTop) {
var layer = this.getCurrentLayer(); var layer = this.getCurrentLayer();
this.piskel.moveLayerUp(layer); this.piskel.moveLayerUp(layer, toTop);
this.selectLayer(layer); this.selectLayer(layer);
}; };
ns.PiskelController.prototype.moveLayerDown = function () { ns.PiskelController.prototype.moveLayerDown = function (toBottom) {
var layer = this.getCurrentLayer(); var layer = this.getCurrentLayer();
this.piskel.moveLayerDown(layer); this.piskel.moveLayerDown(layer, toBottom);
this.selectLayer(layer); this.selectLayer(layer);
}; };
@ -292,4 +297,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

@ -49,14 +49,25 @@
return this.piskelController; return this.piskelController;
}; };
ns.PublicPiskelController.prototype.setPiskel = function (piskel, preserveState) { /**
this.piskelController.setPiskel(piskel, preserveState); * Set the current piskel. Will reset the selected frame and layer unless specified
* @param {Object} piskel
* @param {Object} options:
* preserveState {Boolean} if true, keep the selected frame and layer
* noSnapshot {Boolean} if true, do not save a snapshot in the piskel
* history for this call to setPiskel
*/
ns.PublicPiskelController.prototype.setPiskel = function (piskel, options) {
this.piskelController.setPiskel(piskel, options);
$.publish(Events.FRAME_SIZE_CHANGED); $.publish(Events.FRAME_SIZE_CHANGED);
$.publish(Events.PISKEL_RESET); $.publish(Events.PISKEL_RESET);
$.publish(Events.PISKEL_SAVE_STATE, {
type : pskl.service.HistoryService.SNAPSHOT if (!options || !options.noSnapshot) {
}); $.publish(Events.PISKEL_SAVE_STATE, {
type : pskl.service.HistoryService.SNAPSHOT
});
}
}; };
ns.PublicPiskelController.prototype.resetWrap_ = function (methodName) { ns.PublicPiskelController.prototype.resetWrap_ = function (methodName) {

View File

@ -102,7 +102,7 @@
this.disablePreviewSizeWidget_('No other option available'); this.disablePreviewSizeWidget_('No other option available');
validSizes = ['original']; validSizes = ['original'];
} else if (seamlessModeEnabled) { } else if (seamlessModeEnabled) {
this.disablePreviewSizeWidget_('Disabled in seamless mode'); this.disablePreviewSizeWidget_('Disabled in tile mode');
validSizes = ['original']; validSizes = ['original'];
} else { } else {
this.enablePreviewSizeWidget_(); this.enablePreviewSizeWidget_();
@ -309,7 +309,8 @@
var isSeamless = pskl.UserSettings.get(pskl.UserSettings.SEAMLESS_MODE); var isSeamless = pskl.UserSettings.get(pskl.UserSettings.SEAMLESS_MODE);
this.renderer.setRepeated(isSeamless); this.renderer.setRepeated(isSeamless);
var height, width; var width;
var height;
if (isSeamless) { if (isSeamless) {
height = PREVIEW_SIZE; height = PREVIEW_SIZE;

View File

@ -1,144 +0,0 @@
(function () {
var ns = $.namespace('pskl.controller.settings');
ns.ApplicationSettingsController = function () {};
pskl.utils.inherit(ns.ApplicationSettingsController, pskl.controller.settings.AbstractSettingController);
ns.ApplicationSettingsController.prototype.init = function() {
this.backgroundContainer = document.querySelector('.background-picker-wrapper');
this.addEventListener(this.backgroundContainer, 'click', this.onBackgroundClick_);
// Highlight selected background :
var background = pskl.UserSettings.get(pskl.UserSettings.CANVAS_BACKGROUND);
var selectedBackground = this.backgroundContainer.querySelector('[data-background=' + background + ']');
if (selectedBackground) {
selectedBackground.classList.add('selected');
}
// Grid display and size
var gridWidth = pskl.UserSettings.get(pskl.UserSettings.GRID_WIDTH);
var gridSelect = document.querySelector('.grid-width-select');
var selectedOption = gridSelect.querySelector('option[value="' + gridWidth + '"]');
if (selectedOption) {
selectedOption.setAttribute('selected', 'selected');
}
this.addEventListener(gridSelect, 'change', this.onGridWidthChange_);
// Seamless mode
var seamlessMode = pskl.UserSettings.get(pskl.UserSettings.SEAMLESS_MODE);
var seamlessModeCheckbox = document.querySelector('.seamless-mode-checkbox');
if (seamlessMode) {
seamlessModeCheckbox.setAttribute('checked', seamlessMode);
}
this.addEventListener(seamlessModeCheckbox, 'change', this.onSeamlessModeChange_);
// Max FPS
var maxFpsInput = document.querySelector('.max-fps-input');
maxFpsInput.value = pskl.UserSettings.get(pskl.UserSettings.MAX_FPS);
this.addEventListener(maxFpsInput, 'change', this.onMaxFpsChange_);
// Color format
var colorFormat = pskl.UserSettings.get(pskl.UserSettings.COLOR_FORMAT);
var colorFormatSelect = document.querySelector('.color-format-select');
var selectedColorFormatOption = colorFormatSelect.querySelector('option[value="' + colorFormat + '"]');
if (selectedColorFormatOption) {
selectedColorFormatOption.setAttribute('selected', 'selected');
}
this.addEventListener(colorFormatSelect, 'change', this.onColorFormatChange_);
// Layer preview opacity
var layerOpacityInput = document.querySelector('.layer-opacity-input');
layerOpacityInput.value = pskl.UserSettings.get(pskl.UserSettings.LAYER_OPACITY);
this.addEventListener(layerOpacityInput, 'change', this.onLayerOpacityChange_);
this.addEventListener(layerOpacityInput, 'input', this.onLayerOpacityChange_);
this.updateLayerOpacityText_(layerOpacityInput.value);
// Seamless mask opacity
var seamlessOpacityInput = document.querySelector('.seamless-opacity-input');
seamlessOpacityInput.value = pskl.UserSettings.get(pskl.UserSettings.SEAMLESS_OPACITY);
this.addEventListener(seamlessOpacityInput, 'change', this.onSeamlessOpacityChange_);
this.addEventListener(seamlessOpacityInput, 'input', this.onSeamlessOpacityChange_);
this.updateSeamlessOpacityText_(seamlessOpacityInput.value);
// Form
this.applicationSettingsForm = document.querySelector('[name="application-settings-form"]');
this.addEventListener(this.applicationSettingsForm, 'submit', this.onFormSubmit_);
};
ns.ApplicationSettingsController.prototype.onGridWidthChange_ = function (evt) {
var width = parseInt(evt.target.value, 10);
pskl.UserSettings.set(pskl.UserSettings.GRID_WIDTH, width);
};
ns.ApplicationSettingsController.prototype.onColorFormatChange_ = function (evt) {
pskl.UserSettings.set(pskl.UserSettings.COLOR_FORMAT, evt.target.value);
};
ns.ApplicationSettingsController.prototype.onSeamlessModeChange_ = function (evt) {
pskl.UserSettings.set(pskl.UserSettings.SEAMLESS_MODE, evt.currentTarget.checked);
};
ns.ApplicationSettingsController.prototype.onBackgroundClick_ = function (evt) {
var target = evt.target;
var background = target.dataset.background;
if (background) {
pskl.UserSettings.set(pskl.UserSettings.CANVAS_BACKGROUND, background);
var selected = this.backgroundContainer.querySelector('.selected');
if (selected) {
selected.classList.remove('selected');
}
target.classList.add('selected');
}
};
ns.ApplicationSettingsController.prototype.onMaxFpsChange_ = function (evt) {
var target = evt.target;
var fps = parseInt(target.value, 10);
if (fps && !isNaN(fps)) {
pskl.UserSettings.set(pskl.UserSettings.MAX_FPS, fps);
} else {
target.value = pskl.UserSettings.get(pskl.UserSettings.MAX_FPS);
}
};
ns.ApplicationSettingsController.prototype.onLayerOpacityChange_ = function (evt) {
var target = evt.target;
var opacity = parseFloat(target.value);
if (!isNaN(opacity)) {
pskl.UserSettings.set(pskl.UserSettings.LAYER_OPACITY, opacity);
pskl.UserSettings.set(pskl.UserSettings.LAYER_PREVIEW, opacity !== 0);
this.updateLayerOpacityText_(opacity);
} else {
target.value = pskl.UserSettings.get(pskl.UserSettings.LAYER_OPACITY);
}
};
ns.ApplicationSettingsController.prototype.onSeamlessOpacityChange_ = function (evt) {
var target = evt.target;
var opacity = parseFloat(target.value);
if (!isNaN(opacity)) {
pskl.UserSettings.set(pskl.UserSettings.SEAMLESS_OPACITY, opacity);
this.updateSeamlessOpacityText_(opacity);
} else {
target.value = pskl.UserSettings.get(pskl.UserSettings.SEAMLESS_OPACITY);
}
};
ns.ApplicationSettingsController.prototype.updateLayerOpacityText_ = function (opacity) {
var layerOpacityText = document.querySelector('.layer-opacity-text');
layerOpacityText.innerHTML = opacity;
};
ns.ApplicationSettingsController.prototype.updateSeamlessOpacityText_ = function (opacity) {
var seamlessOpacityText = document.querySelector('.seamless-opacity-text');
seamlessOpacityText.innerHTML = opacity;
};
ns.ApplicationSettingsController.prototype.onFormSubmit_ = function (evt) {
evt.preventDefault();
$.publish(Events.CLOSE_SETTINGS_DRAWER);
};
})();

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

@ -0,0 +1,35 @@
(function () {
var ns = $.namespace('pskl.controller.settings');
var tabs = {
'misc' : {
template : 'templates/settings/preferences/misc.html',
controller : ns.preferences.MiscPreferencesController
},
'grid' : {
template : 'templates/settings/preferences/grid.html',
controller : ns.preferences.GridPreferencesController
},
'tile' : {
template : 'templates/settings/preferences/tile.html',
controller : ns.preferences.TilePreferencesController
}
};
ns.PreferencesController = function () {
this.tabsWidget = new pskl.widgets.Tabs(tabs, this, pskl.UserSettings.PREFERENCES_TAB);
};
pskl.utils.inherit(ns.PreferencesController, pskl.controller.settings.AbstractSettingController);
ns.PreferencesController.prototype.init = function() {
var container = document.querySelector('.settings-section-preferences');
this.tabsWidget.init(container);
};
ns.PreferencesController.prototype.destroy = function () {
this.tabsWidget.destroy();
this.superclass.destroy.call(this);
};
})();

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

@ -3,8 +3,8 @@
var settings = { var settings = {
'user' : { 'user' : {
template : 'templates/settings/application.html', template : 'templates/settings/preferences.html',
controller : ns.ApplicationSettingsController controller : ns.PreferencesController
}, },
'resize' : { 'resize' : {
template : 'templates/settings/resize.html', template : 'templates/settings/resize.html',

View File

@ -22,6 +22,7 @@
ns.ExportController = function (piskelController) { ns.ExportController = function (piskelController) {
this.piskelController = piskelController; this.piskelController = piskelController;
this.tabsWidget = new pskl.widgets.Tabs(tabs, this, pskl.UserSettings.EXPORT_TAB);
this.onSizeInputChange_ = this.onSizeInputChange_.bind(this); this.onSizeInputChange_ = this.onSizeInputChange_.bind(this);
}; };
@ -47,47 +48,16 @@
this.onSizeInputChange_(); this.onSizeInputChange_();
// Initialize tabs and panel // Initialize tabs and panel
this.exportPanel = document.querySelector('.export-panel'); var container = document.querySelector('.settings-section-export');
this.exportTabs = document.querySelector('.export-tabs'); this.tabsWidget.init(container);
this.addEventListener(this.exportTabs, 'click', this.onTabsClicked_);
var tab = pskl.UserSettings.get(pskl.UserSettings.EXPORT_TAB);
this.selectTab(tab);
}; };
ns.ExportController.prototype.destroy = function () { ns.ExportController.prototype.destroy = function () {
this.sizeInputWidget.destroy(); this.sizeInputWidget.destroy();
this.currentController.destroy(); this.tabsWidget.destroy();
this.superclass.destroy.call(this); this.superclass.destroy.call(this);
}; };
ns.ExportController.prototype.selectTab = function (tabId) {
if (!tabs[tabId] || this.currentTab == tabId) {
return;
}
if (this.currentController) {
this.currentController.destroy();
}
this.exportPanel.innerHTML = pskl.utils.Template.get(tabs[tabId].template);
this.currentController = new tabs[tabId].controller(this.piskelController, this);
this.currentController.init();
this.currentTab = tabId;
pskl.UserSettings.set(pskl.UserSettings.EXPORT_TAB, tabId);
var selectedTab = this.exportTabs.querySelector('.selected');
if (selectedTab) {
selectedTab.classList.remove('selected');
}
this.exportTabs.querySelector('[data-tab-id="' + tabId + '"]').classList.add('selected');
};
ns.ExportController.prototype.onTabsClicked_ = function (e) {
var tabId = pskl.utils.Dom.getData(e.target, 'tabId');
this.selectTab(tabId);
};
ns.ExportController.prototype.onScaleChange_ = function () { ns.ExportController.prototype.onScaleChange_ = function () {
var value = parseFloat(this.scaleInput.value); var value = parseFloat(this.scaleInput.value);
if (!isNaN(value)) { if (!isNaN(value)) {

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 () {
@ -85,7 +93,8 @@
var isTransparent = layers.some(function (l) {return l.isTransparent();}); var isTransparent = layers.some(function (l) {return l.isTransparent();});
var preserveColors = !isTransparent && currentColors.length < MAX_GIF_COLORS; var preserveColors = !isTransparent && currentColors.length < MAX_GIF_COLORS;
var transparentColor, transparent; var transparentColor;
var transparent;
// transparency only supported if preserveColors is true, see Issue #357 // transparency only supported if preserveColors is true, see Issue #357
if (preserveColors) { if (preserveColors) {
transparentColor = this.getTransparentColor(currentColors); transparentColor = this.getTransparentColor(currentColors);
@ -104,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
}); });
@ -148,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

@ -190,7 +190,7 @@
var json = { var json = {
'frames': frames, 'frames': frames,
'meta': { 'meta': {
'app': 'https://github.com/juliandescottes/piskel/', 'app': 'https://github.com/piskelapp/piskel/',
'version': '1.0', 'version': '1.0',
'image': name + '.png', 'image': name + '.png',
'format': 'RGBA8888', 'format': 'RGBA8888',
@ -207,6 +207,14 @@
}; };
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);
}; };
})(); })();

View File

@ -0,0 +1,90 @@
(function () {
var ns = $.namespace('pskl.controller.settings.preferences');
var colorsMap = {
'transparent': Constants.TRANSPARENT_COLOR,
'white': '#FFF1E8',
'light-gray': '#C2C3C7',
'dark-gray': '#5F574F',
'black': '#000000',
'blue': '#29ADFF',
'dark-blue': '#1D2B53',
'green': '#00E436',
'dark-green': '#008751',
'peach': '#FFCCAA',
'pink': '#FF77A8',
'yellow': '#FFEC27',
'orange': '#FFA300',
'red': '#FF004D',
};
ns.GridPreferencesController = function (piskelController, preferencesController) {
this.piskelController = piskelController;
this.preferencesController = preferencesController;
this.sizePicker = new pskl.widgets.SizePicker(this.onSizePickerChanged_.bind(this));
};
pskl.utils.inherit(ns.GridPreferencesController, pskl.controller.settings.AbstractSettingController);
ns.GridPreferencesController.prototype.init = function () {
// Grid enabled
var isEnabled = pskl.UserSettings.get(pskl.UserSettings.GRID_ENABLED);
var enableGridCheckbox = document.querySelector('.enable-grid-checkbox');
if (isEnabled) {
enableGridCheckbox.setAttribute('checked', 'true');
}
this.addEventListener(enableGridCheckbox, 'change', this.onEnableGridChange_);
// Grid size
var gridWidth = pskl.UserSettings.get(pskl.UserSettings.GRID_WIDTH);
this.sizePicker.init(document.querySelector('.grid-size-container'));
this.sizePicker.setSize(gridWidth);
// Grid color
var colorListItemTemplate = pskl.utils.Template.get('color-list-item-template');
var gridColor = pskl.UserSettings.get(pskl.UserSettings.GRID_COLOR);
var gridColorSelect = document.querySelector('#grid-color');
var markup = '';
Object.keys(colorsMap).forEach(function (key, index) {
var background = colorsMap[key];
if (key === 'transparent') {
background = 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZ' +
'F8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)';
}
markup += pskl.utils.Template.replace(colorListItemTemplate, {
color: colorsMap[key],
title: key,
background: background,
':selected': gridColor === colorsMap[key]
});
});
this.gridColorList = document.querySelector('.grid-colors-list');
this.gridColorList.innerHTML = markup;
this.addEventListener(this.gridColorList, 'click', this.onGridColorClicked_.bind(this));
};
ns.GridPreferencesController.prototype.destroy = function () {
this.sizePicker.destroy();
this.superclass.destroy.call(this);
};
ns.GridPreferencesController.prototype.onSizePickerChanged_ = function (size) {
pskl.UserSettings.set(pskl.UserSettings.GRID_WIDTH, size);
};
ns.GridPreferencesController.prototype.onEnableGridChange_ = function (evt) {
pskl.UserSettings.set(pskl.UserSettings.GRID_ENABLED, evt.currentTarget.checked);
};
ns.GridPreferencesController.prototype.onGridColorClicked_ = function (evt) {
var color = evt.target.dataset.color;
if (color) {
pskl.UserSettings.set(pskl.UserSettings.GRID_COLOR, color);
this.gridColorList.querySelector('.selected').classList.remove('selected');
evt.target.classList.add('selected');
}
};
})();

View File

@ -0,0 +1,88 @@
(function () {
var ns = $.namespace('pskl.controller.settings.preferences');
ns.MiscPreferencesController = function (piskelController, preferencesController) {
this.piskelController = piskelController;
this.preferencesController = preferencesController;
};
pskl.utils.inherit(ns.MiscPreferencesController, pskl.controller.settings.AbstractSettingController);
ns.MiscPreferencesController.prototype.init = function () {
this.backgroundContainer = document.querySelector('.background-picker-wrapper');
this.addEventListener(this.backgroundContainer, 'click', this.onBackgroundClick_);
// Highlight selected background :
var background = pskl.UserSettings.get(pskl.UserSettings.CANVAS_BACKGROUND);
var selectedBackground = this.backgroundContainer.querySelector('[data-background=' + background + ']');
if (selectedBackground) {
selectedBackground.classList.add('selected');
}
// Max FPS
var maxFpsInput = document.querySelector('.max-fps-input');
maxFpsInput.value = pskl.UserSettings.get(pskl.UserSettings.MAX_FPS);
this.addEventListener(maxFpsInput, 'change', this.onMaxFpsChange_);
// Color format
var colorFormat = pskl.UserSettings.get(pskl.UserSettings.COLOR_FORMAT);
var colorFormatSelect = document.querySelector('.color-format-select');
var selectedColorFormatOption = colorFormatSelect.querySelector('option[value="' + colorFormat + '"]');
if (selectedColorFormatOption) {
selectedColorFormatOption.setAttribute('selected', 'selected');
}
this.addEventListener(colorFormatSelect, 'change', this.onColorFormatChange_);
// Layer preview opacity
var layerOpacityInput = document.querySelector('.layer-opacity-input');
layerOpacityInput.value = pskl.UserSettings.get(pskl.UserSettings.LAYER_OPACITY);
this.addEventListener(layerOpacityInput, 'change', this.onLayerOpacityChange_);
this.addEventListener(layerOpacityInput, 'input', this.onLayerOpacityChange_);
this.updateLayerOpacityText_(layerOpacityInput.value);
};
ns.MiscPreferencesController.prototype.onBackgroundClick_ = function (evt) {
var target = evt.target;
var background = target.dataset.background;
if (background) {
pskl.UserSettings.set(pskl.UserSettings.CANVAS_BACKGROUND, background);
var selected = this.backgroundContainer.querySelector('.selected');
if (selected) {
selected.classList.remove('selected');
}
target.classList.add('selected');
}
};
ns.MiscPreferencesController.prototype.onColorFormatChange_ = function (evt) {
pskl.UserSettings.set(pskl.UserSettings.COLOR_FORMAT, evt.target.value);
};
ns.MiscPreferencesController.prototype.onMaxFpsChange_ = function (evt) {
var target = evt.target;
var fps = parseInt(target.value, 10);
if (fps && !isNaN(fps)) {
pskl.UserSettings.set(pskl.UserSettings.MAX_FPS, fps);
} else {
target.value = pskl.UserSettings.get(pskl.UserSettings.MAX_FPS);
}
};
ns.MiscPreferencesController.prototype.onLayerOpacityChange_ = function (evt) {
var target = evt.target;
var opacity = parseFloat(target.value);
if (!isNaN(opacity)) {
pskl.UserSettings.set(pskl.UserSettings.LAYER_OPACITY, opacity);
pskl.UserSettings.set(pskl.UserSettings.LAYER_PREVIEW, opacity !== 0);
this.updateLayerOpacityText_(opacity);
} else {
target.value = pskl.UserSettings.get(pskl.UserSettings.LAYER_OPACITY);
}
};
ns.MiscPreferencesController.prototype.updateLayerOpacityText_ = function (opacity) {
var layerOpacityText = document.querySelector('.layer-opacity-text');
layerOpacityText.innerHTML = (opacity * 1).toFixed(2);
};
})();

View File

@ -0,0 +1,47 @@
(function () {
var ns = $.namespace('pskl.controller.settings.preferences');
ns.TilePreferencesController = function (piskelController, preferencesController) {
this.piskelController = piskelController;
this.preferencesController = preferencesController;
};
pskl.utils.inherit(ns.TilePreferencesController, pskl.controller.settings.AbstractSettingController);
ns.TilePreferencesController.prototype.init = function () {
// Tile mode
var tileMode = pskl.UserSettings.get(pskl.UserSettings.SEAMLESS_MODE);
var tileModeCheckbox = document.querySelector('.tile-mode-checkbox');
if (tileMode) {
tileModeCheckbox.setAttribute('checked', tileMode);
}
this.addEventListener(tileModeCheckbox, 'change', this.onTileModeChange_);
// Tile mask opacity
var tileMaskOpacityInput = document.querySelector('.tile-mask-opacity-input');
tileMaskOpacityInput.value = pskl.UserSettings.get(pskl.UserSettings.SEAMLESS_OPACITY);
this.addEventListener(tileMaskOpacityInput, 'change', this.onTileMaskOpacityChange_);
this.addEventListener(tileMaskOpacityInput, 'input', this.onTileMaskOpacityChange_);
this.updateTileMaskOpacityText_(tileMaskOpacityInput.value);
};
ns.TilePreferencesController.prototype.onTileModeChange_ = function (evt) {
pskl.UserSettings.set(pskl.UserSettings.SEAMLESS_MODE, evt.currentTarget.checked);
};
ns.TilePreferencesController.prototype.onTileMaskOpacityChange_ = function (evt) {
var target = evt.target;
var opacity = parseFloat(target.value);
if (!isNaN(opacity)) {
pskl.UserSettings.set(pskl.UserSettings.SEAMLESS_OPACITY, opacity);
this.updateTileMaskOpacityText_(opacity);
} else {
target.value = pskl.UserSettings.get(pskl.UserSettings.SEAMLESS_OPACITY);
}
};
ns.TilePreferencesController.prototype.updateTileMaskOpacityText_ = function (opacity) {
var seamlessOpacityText = document.querySelector('.tile-mask-opacity-text');
seamlessOpacityText.innerHTML = (opacity * 1).toFixed(2);
};
})();

View File

@ -69,7 +69,9 @@
resizeContent: this.resizeContentCheckbox.checked resizeContent: this.resizeContentCheckbox.checked
}); });
pskl.app.piskelController.setPiskel(piskel, true); pskl.app.piskelController.setPiskel(piskel, {
preserveState: true
});
$.publish(Events.CLOSE_SETTINGS_DRAWER); $.publish(Events.CLOSE_SETTINGS_DRAWER);
}; };

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 databse 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) {
pskl.app.penSizeService.setPenSize(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);
}; };
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') {
@ -85,7 +89,7 @@
which : domEvent.which, which : domEvent.which,
shiftKey : domEvent.shiftKey, shiftKey : domEvent.shiftKey,
altKey : domEvent.altKey, altKey : domEvent.altKey,
ctrlKey : domEvent.ctrlKey, ctrlKey : domEvent.ctrlKey || domEvent.metaKey,
target : { target : {
nodeName : domEvent.target.nodeName nodeName : domEvent.target.nodeName
} }
@ -136,12 +140,27 @@
} }
}; };
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 = {};
recordEvent.type = 'instrumented-event'; recordEvent.type = 'instrumented-event';
recordEvent.methodName = methodName; recordEvent.methodName = methodName;
recordEvent.args = Array.prototype.slice.call(args, 0); recordEvent.args = Array.prototype.slice.call(args, 0);
if (methodName === 'setPiskel' && args[1].noSnapshot) {
// Skip recording calls to setPiskel that don't trigger a save.
return;
}
this.events.push(recordEvent); this.events.push(recordEvent);
} }
}; };

View File

@ -16,7 +16,6 @@
this.descriptor = descriptor; this.descriptor = descriptor;
this.savePath = null; this.savePath = null;
this.fps = fps; this.fps = fps;
} else { } else {
throw 'Missing arguments in Piskel constructor : ' + Array.prototype.join.call(arguments, ','); throw 'Missing arguments in Piskel constructor : ' + Array.prototype.join.call(arguments, ',');
} }
@ -82,20 +81,28 @@
this.layers.splice(index, 0, layer); this.layers.splice(index, 0, layer);
}; };
ns.Piskel.prototype.moveLayerUp = function (layer) { ns.Piskel.prototype.moveLayerUp = function (layer, toTop) {
var index = this.layers.indexOf(layer); var index = this.layers.indexOf(layer);
if (index > -1 && index < this.layers.length - 1) { var toIndex = toTop ? this.layers.length - 1 : index + 1;
this.layers[index] = this.layers[index + 1]; this.moveLayer_(index, toIndex);
this.layers[index + 1] = layer;
}
}; };
ns.Piskel.prototype.moveLayerDown = function (layer) { ns.Piskel.prototype.moveLayerDown = function (layer, toBottom) {
var index = this.layers.indexOf(layer); var index = this.layers.indexOf(layer);
if (index > 0) { var toIndex = toBottom ? 0 : index - 1;
this.layers[index] = this.layers[index - 1]; this.moveLayer_(index, toIndex);
this.layers[index - 1] = layer; };
/**
* Move the layer at the provided index to the provided toIndex.
*/
ns.Piskel.prototype.moveLayer_ = function (fromIndex, toIndex) {
if (fromIndex == -1 || toIndex == -1 || fromIndex == toIndex) {
return;
} }
toIndex = pskl.utils.Math.minmax(toIndex, 0, this.layers.length - 1);
var layer = this.layers.splice(fromIndex, 1)[0];
this.layers.splice(toIndex, 0, layer);
}; };
ns.Piskel.prototype.removeLayer = function (layer) { ns.Piskel.prototype.removeLayer = function (layer) {

View File

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

View File

@ -55,7 +55,7 @@
this.displayCanvas = null; this.displayCanvas = null;
this.setDisplaySize(renderingOptions.width, renderingOptions.height); this.setDisplaySize(renderingOptions.width, renderingOptions.height);
this.setGridWidth(pskl.UserSettings.get(pskl.UserSettings.GRID_WIDTH)); this.setGridWidth(this.getUserGridWidth_());
$.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this)); $.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
}; };
@ -138,6 +138,10 @@
this.offset.y = y; this.offset.y = y;
}; };
ns.FrameRenderer.prototype.getGridColor = function () {
return pskl.UserSettings.get(pskl.UserSettings.GRID_COLOR);
};
ns.FrameRenderer.prototype.setGridWidth = function (value) { ns.FrameRenderer.prototype.setGridWidth = function (value) {
this.gridWidth_ = value; this.gridWidth_ = value;
}; };
@ -180,11 +184,18 @@
}; };
ns.FrameRenderer.prototype.onUserSettingsChange_ = function (evt, settingName, settingValue) { ns.FrameRenderer.prototype.onUserSettingsChange_ = function (evt, settingName, settingValue) {
if (settingName == pskl.UserSettings.GRID_WIDTH) { var settings = pskl.UserSettings;
this.setGridWidth(settingValue); if (settingName == settings.GRID_WIDTH || settingName == settings.GRID_ENABLED) {
this.setGridWidth(this.getUserGridWidth_());
} }
}; };
ns.FrameRenderer.prototype.getUserGridWidth_ = function () {
var gridEnabled = pskl.UserSettings.get(pskl.UserSettings.GRID_ENABLED);
var width = pskl.UserSettings.get(pskl.UserSettings.GRID_WIDTH);
return gridEnabled ? width : 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.
@ -258,7 +269,7 @@
var isZoomedOut = translateX > 0 || translateY > 0; var isZoomedOut = translateX > 0 || translateY > 0;
// Draw the background / zoomed-out color only if needed. Otherwise the clearRect // Draw the background / zoomed-out color only if needed. Otherwise the clearRect
// happening after that will clear "out of bounds" and seems to be doing nothing // happening after that will clear "out of bounds" and seems to be doing nothing
// on some chromebooks (cf https://github.com/juliandescottes/piskel/issues/651) // on some chromebooks (cf https://github.com/piskelapp/piskel/issues/651)
if (isZoomedOut) { if (isZoomedOut) {
// Draw background // Draw background
displayContext.fillStyle = Constants.ZOOMED_OUT_BACKGROUND_COLOR; displayContext.fillStyle = Constants.ZOOMED_OUT_BACKGROUND_COLOR;
@ -288,15 +299,25 @@
// Draw grid. // Draw grid.
var gridWidth = this.computeGridWidthForDisplay_(); var gridWidth = this.computeGridWidthForDisplay_();
if (gridWidth > 0) { if (gridWidth > 0) {
var gridColor = this.getGridColor();
// Scale out before drawing the grid. // Scale out before drawing the grid.
displayContext.scale(1 / z, 1 / z); displayContext.scale(1 / z, 1 / z);
// Clear vertical lines.
for (var i = 1 ; i < frame.getWidth() ; i++) { var drawOrClear;
displayContext.clearRect((i * z) - (gridWidth / 2), 0, gridWidth, h * z); if (gridColor === Constants.TRANSPARENT_COLOR) {
drawOrClear = displayContext.clearRect.bind(displayContext);
} else {
displayContext.fillStyle = gridColor;
drawOrClear = displayContext.fillRect.bind(displayContext);
} }
// Clear horizontal lines.
// Draw or clear vertical lines.
for (var i = 1 ; i < frame.getWidth() ; i++) {
drawOrClear((i * z) - (gridWidth / 2), 0, gridWidth, h * z);
}
// Draw or clear horizontal lines.
for (var j = 1 ; j < frame.getHeight() ; j++) { for (var j = 1 ; j < frame.getHeight() ; j++) {
displayContext.clearRect(0, (j * z) - (gridWidth / 2), w * z, gridWidth); drawOrClear(0, (j * z) - (gridWidth / 2), w * z, gridWidth);
} }
} }

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

@ -17,14 +17,11 @@
$.subscribe(Events.SELECTION_CREATED, $.proxy(this.onSelectionCreated_, this)); $.subscribe(Events.SELECTION_CREATED, $.proxy(this.onSelectionCreated_, this));
$.subscribe(Events.SELECTION_DISMISSED, $.proxy(this.onSelectionDismissed_, this)); $.subscribe(Events.SELECTION_DISMISSED, $.proxy(this.onSelectionDismissed_, this));
$.subscribe(Events.SELECTION_MOVE_REQUEST, $.proxy(this.onSelectionMoved_, this)); $.subscribe(Events.SELECTION_MOVE_REQUEST, $.proxy(this.onSelectionMoved_, 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));
@ -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());
this.erase(); if (domEvent) {
domEvent.clipboardData.setData('text/plain', this.currentSelection.stringify());
domEvent.preventDefault();
}
if (event.type === Events.CLIPBOARD_CUT) {
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++) {
return; 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;
}
} }
} 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'); window.setInterval(this.backup.bind(this), BACKUP_INTERVAL);
if (previousPiskel && previousInfo) { }.bind(this));
this.savePiskel_('prev', previousPiskel, previousInfo); };
}
window.setInterval(this.backup.bind(this), BACKUP_INTERVAL); // 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) {
this.lastHash = hash; return Q.resolve();
var serializedPiskel = pskl.utils.serialization.Serializer.serialize(piskel);
this.savePiskel_('next', serializedPiskel, JSON.stringify(info));
} }
};
ns.BackupService.prototype.getPreviousPiskelInfo = function () { // Update the hash
var previousInfo = window.localStorage.getItem('bkp.prev.info'); // TODO: should only be done after a successful save.
if (previousInfo) { this.lastHash = hash;
return JSON.parse(previousInfo);
}
};
ns.BackupService.prototype.load = function() { // Prepare the backup snapshot.
var previousPiskel = window.localStorage.getItem('bkp.prev.piskel'); var descriptor = piskel.getDescriptor();
previousPiskel = JSON.parse(previousPiskel); 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)
};
pskl.utils.serialization.Deserializer.deserialize(previousPiskel, function (piskel) { return this.getSnapshotsBySessionId(pskl.app.sessionId).then(function (snapshots) {
pskl.app.piskelController.setPiskel(piskel); var latest = snapshots[0];
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;
}
// Prepare an array containing all the ids of the sessions to be deleted.
var sessionIdsToDelete = sessions.sort(function (s1, s2) {
return s1.startDate - s2.startDate;
}).map(function (s) {
return s.id;
}).slice(0, sessions.length - MAX_SESSIONS);
// Delete all the extra sessions.
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,9 +30,11 @@
}; };
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 Piskel seems to have unsaved changes'; var confirmationMessage = 'Your current sprite has unsaved changes. Are you sure you want to quit?';
evt = evt || window.event; evt = evt || window.event;
if (evt) { if (evt) {

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

@ -3,6 +3,7 @@
8 : 'back', 8 : 'back',
13 : 'enter', 13 : 'enter',
27 : 'esc', 27 : 'esc',
32 : 'space',
37 : 'left', 37 : 'left',
38 : 'up', 38 : 'up',
39 : 'right', 39 : 'right',

View File

@ -61,7 +61,11 @@
ONION_SKIN : createShortcut('onion-skin', 'Toggle onion skin', 'alt+O'), ONION_SKIN : createShortcut('onion-skin', 'Toggle onion skin', 'alt+O'),
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'),
OFFSET_UP : createShortcut('move-up', 'Move viewport up', 'shift+up'),
OFFSET_RIGHT : createShortcut('move-right', 'Move viewport right', 'shift+right'),
OFFSET_DOWN : createShortcut('move-down', 'Move viewport down', 'shift+down'),
OFFSET_LEFT : createShortcut('move-left', 'Move viewport left', 'shift+left'),
}, },
STORAGE : { STORAGE : {
@ -80,6 +84,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

@ -35,7 +35,11 @@
var evenY = (coords.y0 + coords.y1) % 2; var evenY = (coords.y0 + coords.y1) % 2;
var rX = coords.x1 - xC; var rX = coords.x1 - xC;
var rY = coords.y1 - yC; var rY = coords.y1 - yC;
var x, y, angle, r;
var x;
var y;
var angle;
var r;
if (penSize == 1) { if (penSize == 1) {
for (x = coords.x0 ; x <= xC ; x++) { for (x = coords.x0 ; x <= xC ; x++) {

View File

@ -97,8 +97,31 @@
linePixels = pskl.PixelUtils.getLinePixels(col, this.startCol, row, this.startRow); linePixels = pskl.PixelUtils.getLinePixels(col, this.startCol, row, this.startRow);
} }
pskl.PixelUtils.resizePixels(linePixels, penSize).forEach(function (point) { //draw the square ends of the line
targetFrame.setPixel(point[0], point[1], color); pskl.PixelUtils.resizePixel(linePixels[0].col, linePixels[0].row, penSize)
.forEach(function (point) {targetFrame.setPixel(point[0], point[1], color);});
pskl.PixelUtils.resizePixel(linePixels[linePixels.length - 1].col, linePixels[linePixels.length - 1].row, penSize)
.forEach(function (point) {targetFrame.setPixel(point[0], point[1],color);});
//for each step along the line, draw an x centered on that pixel of size penSize
linePixels.forEach(function (point) {
for (var i = 0; i < penSize; i++) {
targetFrame.setPixel(
point.col - Math.floor(penSize / 2) + i, point.row - Math.floor(penSize / 2) + i, color
);
targetFrame.setPixel(
point.col - Math.floor(penSize / 2) + i, point.row + Math.ceil(penSize / 2) - i - 1, color
);
//draw an additional x directly next to the first to prevent unwanted dithering
if (i !== 0) {
targetFrame.setPixel(
point.col - Math.floor(penSize / 2) + i, point.row - Math.floor(penSize / 2) + i - 1, color
);
targetFrame.setPixel(
point.col - Math.floor(penSize / 2) + i, point.row + Math.ceil(penSize / 2) - i, color
);
}
}
}); });
}; };

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

@ -26,6 +26,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);
@ -52,7 +54,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);
@ -111,16 +113,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,7 +24,7 @@
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

@ -0,0 +1,127 @@
(function () {
var ns = $.namespace('pskl.tools.transform');
ns.Crop = function () {
this.toolId = 'tool-crop';
this.helpText = 'Crop the sprite';
this.tooltipDescriptors = [
{
description : 'Crop to fit the content or the selection. ' +
'Applies to all frames and layers!'
}
];
};
// This transform tool is the only one that adapts to the current selection and can't
// rely on the default AbstractTransformTool behavior.
pskl.utils.inherit(ns.Crop, pskl.tools.Tool);
ns.Crop.prototype.applyTransformation = function (evt) {
var frames = this.getFrames_();
var boundaries;
if (pskl.app.selectionManager.currentSelection) {
// If we have a selection, we will compute the boundaries of the selection instead
// of looping on the frames.
boundaries = this.getBoundariesForSelection_();
} else {
boundaries = pskl.tools.transform.TransformUtils.getBoundaries(frames);
}
var applied = this.applyTool_(frames, boundaries);
if (applied) {
this.raiseSaveStateEvent({
boundaries : boundaries
});
}
};
ns.Crop.prototype.replay = function (frame, replayData) {
var frames = this.getFrames_();
this.applyTool_(frames, replayData.boundaries);
};
ns.Crop.prototype.applyTool_ = function (frames, boundaries) {
if (boundaries.minx >= boundaries.maxx) {
return false;
}
var currentPiskel = pskl.app.piskelController.getPiskel();
var width = 1 + boundaries.maxx - boundaries.minx;
var height = 1 + boundaries.maxy - boundaries.miny;
if (width === currentPiskel.getWidth() && height === currentPiskel.getHeight()) {
// Do not perform an unnecessary resize if it's a noop.
return false;
}
frames.forEach(function (frame) {
pskl.tools.transform.TransformUtils.moveFramePixels(frame, -boundaries.minx, -boundaries.miny);
});
var piskel = pskl.utils.ResizeUtils.resizePiskel(currentPiskel, {
width : 1 + boundaries.maxx - boundaries.minx,
height : 1 + boundaries.maxy - boundaries.miny,
origin: 'TOP-LEFT',
resizeContent: false
});
// Clear the current selection.
$.publish(Events.SELECTION_DISMISSED);
// Replace the current piskel with the resized version.
pskl.app.piskelController.setPiskel(piskel, {
preserveState: true,
// Saving is already handled by recording the transform tool action, no need for
// an expensive snapshot.
noSnapshot: true
});
return true;
};
/**
* Retrieve the list of frames for the current piskel in a single flat array.
*/
ns.Crop.prototype.getFrames_ = function () {
var currentPiskel = pskl.app.piskelController.getPiskel();
// Get all frames in a single array.
var frames = currentPiskel.getLayers().map(function (l) {
return l.getFrames();
}).reduce(function (p, n) {
return p.concat(n);
});
return frames;
};
/**
* Retrieve a boundaries object {minx, maxx, miny, maxy} for the current selection.
*/
ns.Crop.prototype.getBoundariesForSelection_ = function () {
var selectionManager = pskl.app.selectionManager;
var pixels = selectionManager.currentSelection.pixels;
// Fetch the first frame to perform out-of-bound checks.
var currentPiskel = pskl.app.piskelController.getPiskel();
var exampleFrame = currentPiskel.getLayerAt(0).getFrameAt(0);
// Anything different from Constants.TRANSPARENT_COLOR toInt().
var FAKE_COLOR = 1;
// Create a fake frame reimplementing the forEachPixel API.
var selectionFrame = {
forEachPixel : function (callback) {
for (var i = 0; i < pixels.length ; i++) {
var pixel = pixels[i];
// Selections might contain out of bound pixels, filter those out.
if (exampleFrame.containsPixel(pixel.col, pixel.row)) {
callback(FAKE_COLOR, pixel.col, pixel.row);
}
}
}
};
return pskl.tools.transform.TransformUtils.getBoundaries([selectionFrame]);
};
})();

View File

@ -64,32 +64,34 @@
return frame; return frame;
}, },
center : function(frame) { getBoundaries : function(frames) {
// Figure out the boundary var minx = +Infinity;
var minx = frame.width; var miny = +Infinity;
var miny = frame.height;
var maxx = 0; var maxx = 0;
var maxy = 0; var maxy = 0;
var transparentColorInt = pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR); var transparentColorInt = pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR);
frame.forEachPixel(function (color, x, y) {
if (color !== transparentColorInt) { frames.forEach(function (frame) {
minx = Math.min(minx, x); frame.forEachPixel(function (color, x, y) {
maxx = Math.max(maxx, x); if (color !== transparentColorInt) {
miny = Math.min(miny, y); minx = Math.min(minx, x);
maxy = Math.max(maxy, y); maxx = Math.max(maxx, x);
} miny = Math.min(miny, y);
maxy = Math.max(maxy, y);
}
});
}); });
// Calculate how much to move the pixels return {
var bw = (maxx - minx + 1) / 2; minx: minx,
var bh = (maxy - miny + 1) / 2; maxx: maxx,
var fw = frame.width / 2; miny: miny,
var fh = frame.height / 2; maxy: maxy,
};
},
var dx = Math.floor(fw - bw - minx); moveFramePixels : function (frame, dx, dy) {
var dy = Math.floor(fh - bh - miny);
// Actually move the pixels
var clone = frame.clone(); var clone = frame.clone();
frame.forEachPixel(function(color, x, y) { frame.forEachPixel(function(color, x, y) {
var _x = x; var _x = x;
@ -104,7 +106,24 @@
frame.setPixel(_x, _y, Constants.TRANSPARENT_COLOR); frame.setPixel(_x, _y, Constants.TRANSPARENT_COLOR);
} }
}); });
},
center : function(frame) {
// Figure out the boundary
var boundaries = ns.TransformUtils.getBoundaries([frame]);
// Calculate how much to move the pixels
var bw = (boundaries.maxx - boundaries.minx + 1) / 2;
var bh = (boundaries.maxy - boundaries.miny + 1) / 2;
var fw = frame.width / 2;
var fh = frame.height / 2;
var dx = Math.floor(fw - bw - boundaries.minx);
var dy = Math.floor(fh - bh - boundaries.miny);
// Actually move the pixels
ns.TransformUtils.moveFramePixels(frame, dx, dy);
return frame; return frame;
} }
}; };

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

@ -21,7 +21,8 @@
* every X milliseconds, where X is the provided interval. * every X milliseconds, where X is the provided interval.
*/ */
throttle : function (fn, interval) { throttle : function (fn, interval) {
var last, timer; var last;
var timer;
return function () { return function () {
var now = Date.now(); var now = Date.now();
if (last && now < last + interval) { if (last && now < last + interval) {

View File

@ -182,7 +182,7 @@
var loopCount = 0; var loopCount = 0;
var cellCount = frame.getWidth() * frame.getHeight(); var cellCount = frame.getWidth() * frame.getHeight();
while (queue.length > 0) { while (queue.length > 0) {
loopCount ++; loopCount++;
var currentItem = queue.pop(); var currentItem = queue.pop();

View File

@ -28,7 +28,10 @@
var dummyEl = ns.Template._getDummyEl(); var dummyEl = ns.Template._getDummyEl();
dummyEl.innerHTML = html; dummyEl.innerHTML = html;
var element = dummyEl.children[0]; var element = dummyEl.children[0];
dummyEl.innerHTML = '';
if (!pskl.utils.UserAgent.isIE11) {
dummyEl.innerHTML = '';
}
return element; return element;
}, },
@ -78,22 +81,24 @@
dummyEl.textContent = string; dummyEl.textContent = string;
var sanitizedString = dummyEl.innerHTML; var sanitizedString = dummyEl.innerHTML;
dummyEl.innerHTML = ''; if (!pskl.utils.UserAgent.isIE11) {
dummyEl.innerHTML = '';
}
return sanitizedString; return sanitizedString;
}, },
_getDummyEl : pskl.utils.UserAgent.isIE11 ? _getDummyEl : pskl.utils.UserAgent.isIE11 ?
// IE11 specific implementation // IE11 specific implementation
function () { function () {
return document.createElement('div'); return document.createElement('div');
} } :
// Normal, sane browsers implementation. // Normal, sane browsers implementation.
: function () { function () {
if (!ns.Template._dummyEl) { if (!ns.Template._dummyEl) {
ns.Template._dummyEl = document.createElement('div'); ns.Template._dummyEl = document.createElement('div');
}
return ns.Template._dummyEl;
} }
return ns.Template._dummyEl;
}
}; };
})(); })();

View File

@ -2,6 +2,8 @@
var ns = $.namespace('pskl'); var ns = $.namespace('pskl');
ns.UserSettings = { ns.UserSettings = {
GRID_COLOR : 'GRID_COLOR',
GRID_ENABLED : 'GRID_ENABLED',
GRID_WIDTH : 'GRID_WIDTH', GRID_WIDTH : 'GRID_WIDTH',
MAX_FPS : 'MAX_FPS', MAX_FPS : 'MAX_FPS',
DEFAULT_SIZE : 'DEFAULT_SIZE', DEFAULT_SIZE : 'DEFAULT_SIZE',
@ -15,11 +17,16 @@
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',
TRANSFORM_SHOW_MORE: 'TRANSFORM_SHOW_MORE',
PREFERENCES_TAB: 'PREFERENCES_TAB',
KEY_TO_DEFAULT_VALUE_MAP_ : { KEY_TO_DEFAULT_VALUE_MAP_ : {
'GRID_WIDTH' : 0, 'GRID_COLOR' : Constants.TRANSPARENT_COLOR,
'GRID_ENABLED' : false,
'GRID_WIDTH' : 1,
'MAX_FPS' : 24, 'MAX_FPS' : 24,
'DEFAULT_SIZE' : { 'DEFAULT_SIZE' : {
width : Constants.DEFAULT.WIDTH, width : Constants.DEFAULT.WIDTH,
@ -35,6 +42,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,
@ -42,6 +50,8 @@
origin : 'TOPLEFT' origin : 'TOPLEFT'
}, },
COLOR_FORMAT: 'hex', COLOR_FORMAT: 'hex',
TRANSFORM_SHOW_MORE: false,
PREFERENCES_TAB: 'misc',
}, },
/** /**
@ -77,7 +87,7 @@
/** /**
* @private * @private
*/ */
readFromLocalStorage_ : function(key) { readFromLocalStorage_ : function (key) {
var value = window.localStorage[key]; var value = window.localStorage[key];
if (typeof value != 'undefined') { if (typeof value != 'undefined') {
value = JSON.parse(value); value = JSON.parse(value);
@ -88,7 +98,7 @@
/** /**
* @private * @private
*/ */
writeToLocalStorage_ : function(key, value) { writeToLocalStorage_ : function (key, value) {
// TODO(grosbouddha): Catch storage exception here. // TODO(grosbouddha): Catch storage exception here.
window.localStorage[key] = JSON.stringify(value); window.localStorage[key] = JSON.stringify(value);
}, },
@ -103,7 +113,7 @@
/** /**
* @private * @private
*/ */
checkKeyValidity_ : function(key) { checkKeyValidity_ : function (key) {
if (key.indexOf(pskl.service.keyboard.Shortcut.USER_SETTINGS_PREFIX) === 0) { if (key.indexOf(pskl.service.keyboard.Shortcut.USER_SETTINGS_PREFIX) === 0) {
return true; return true;
} }
@ -114,4 +124,20 @@
} }
} }
}; };
// 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

@ -227,7 +227,8 @@
var dimension = slider.dataset.dimension; var dimension = slider.dataset.dimension;
var model = slider.dataset.model; var model = slider.dataset.model;
var start, end; var start;
var end;
var isHueSlider = dimension === 'h'; var isHueSlider = dimension === 'h';
if (!isHueSlider) { if (!isHueSlider) {
var colors = this.getSliderBackgroundColors_(model, dimension); var colors = this.getSliderBackgroundColors_(model, dimension);

View File

@ -0,0 +1,50 @@
(function () {
var ns = $.namespace('pskl.widgets');
ns.SizePicker = function (onChange) {
this.onChange = onChange;
};
ns.SizePicker.prototype.init = function (container) {
this.container = container;
pskl.utils.Event.addEventListener(this.container, 'click', this.onSizeOptionClick_, this);
};
ns.SizePicker.prototype.destroy = function () {
pskl.utils.Event.removeAllEventListeners(this);
};
ns.SizePicker.prototype.getSize = function () {
var selectedOption = this.container.querySelector('.selected');
return selectedOption ? selectedOption.dataset.size : null;
};
ns.SizePicker.prototype.setSize = function (size) {
if (this.getSize() === size) {
return;
}
pskl.utils.Dom.removeClass('labeled', this.container);
pskl.utils.Dom.removeClass('selected', this.container);
var selectedOption;
if (size <= 4) {
selectedOption = this.container.querySelector('[data-size="' + size + '"]');
} else {
selectedOption = this.container.querySelector('[data-size="4"]');
selectedOption.classList.add('labeled');
selectedOption.setAttribute('real-size', size);
}
if (selectedOption) {
selectedOption.classList.add('selected');
}
};
ns.SizePicker.prototype.onSizeOptionClick_ = function (e) {
var size = e.target.dataset.size;
if (!isNaN(size)) {
size = parseInt(size, 10);
this.onChange(size);
this.setSize(size);
}
};
})();

57
src/js/widgets/Tabs.js Normal file
View File

@ -0,0 +1,57 @@
(function () {
var ns = $.namespace('pskl.widgets');
ns.Tabs = function (tabs, parentController, settingsName) {
this.tabs = tabs;
this.parentController = parentController;
this.settingsName = settingsName;
this.currentTab = null;
this.currentController = null;
};
ns.Tabs.prototype.init = function (container) {
this.tabListEl = container.querySelector('.tab-list');
this.tabContentlEl = container.querySelector('.tab-content');
pskl.utils.Event.addEventListener(this.tabListEl, 'click', this.onTabsClicked_, this);
var tab = pskl.UserSettings.get(this.settingsName);
if (tab) {
this.selectTab(tab);
}
};
ns.Tabs.prototype.destroy = function () {
if (this.currentController) {
this.currentController.destroy();
}
pskl.utils.Event.removeAllEventListeners(this);
};
ns.Tabs.prototype.selectTab = function (tabId) {
if (!this.tabs[tabId] || this.currentTab == tabId) {
return;
}
if (this.currentController) {
this.currentController.destroy();
}
this.tabContentlEl.innerHTML = pskl.utils.Template.get(this.tabs[tabId].template);
this.currentController = new this.tabs[tabId].controller(pskl.app.piskelController, this.parentController);
this.currentController.init();
this.currentTab = tabId;
pskl.UserSettings.set(this.settingsName, tabId);
var selectedTab = this.tabListEl.querySelector('.selected');
if (selectedTab) {
selectedTab.classList.remove('selected');
}
this.tabListEl.querySelector('[data-tab-id="' + tabId + '"]').classList.add('selected');
};
ns.Tabs.prototype.onTabsClicked_ = function (e) {
var tabId = pskl.utils.Dom.getData(e.target, 'tabId');
this.selectTab(tabId);
};
})();

View File

@ -2,7 +2,9 @@
var ns = $.namespace('pskl.worker.imageprocessor'); var ns = $.namespace('pskl.worker.imageprocessor');
ns.ImageProcessorWorker = function () { ns.ImageProcessorWorker = function () {
var currentStep, currentProgress, currentTotal; var currentStep;
var currentProgress;
var currentTotal;
var initStepCounter_ = function (total) { var initStepCounter_ = function (total) {
currentStep = 0; currentStep = 0;

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",
@ -122,7 +127,10 @@
// Settings sub-controllers // Settings sub-controllers
"js/controller/settings/AbstractSettingController.js", "js/controller/settings/AbstractSettingController.js",
"js/controller/settings/ApplicationSettingsController.js", "js/controller/settings/preferences/GridPreferencesController.js",
"js/controller/settings/preferences/MiscPreferencesController.js",
"js/controller/settings/preferences/TilePreferencesController.js",
"js/controller/settings/PreferencesController.js",
"js/controller/settings/exportimage/GifExportController.js", "js/controller/settings/exportimage/GifExportController.js",
"js/controller/settings/exportimage/PngExportController.js", "js/controller/settings/exportimage/PngExportController.js",
"js/controller/settings/exportimage/ZipExportController.js", "js/controller/settings/exportimage/ZipExportController.js",
@ -141,6 +149,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",
@ -159,12 +170,15 @@
"js/widgets/FramePicker.js", "js/widgets/FramePicker.js",
"js/widgets/HslRgbColorPicker.js", "js/widgets/HslRgbColorPicker.js",
"js/widgets/SizeInput.js", "js/widgets/SizeInput.js",
"js/widgets/SizePicker.js",
"js/widgets/SynchronizedInputs.js", "js/widgets/SynchronizedInputs.js",
"js/widgets/Tabs.js",
"js/widgets/Wizard.js", "js/widgets/Wizard.js",
// 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",
@ -190,15 +204,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",
@ -226,6 +238,7 @@
"js/tools/transform/AbstractTransformTool.js", "js/tools/transform/AbstractTransformTool.js",
"js/tools/transform/Center.js", "js/tools/transform/Center.js",
"js/tools/transform/Clone.js", "js/tools/transform/Clone.js",
"js/tools/transform/Crop.js",
"js/tools/transform/Flip.js", "js/tools/transform/Flip.js",
"js/tools/transform/Rotate.js", "js/tools/transform/Rotate.js",
"js/tools/transform/TransformUtils.js", "js/tools/transform/TransformUtils.js",

View File

@ -15,10 +15,10 @@
"css/settings-resize.css", "css/settings-resize.css",
"css/settings-save.css", "css/settings-save.css",
"css/tools.css", "css/tools.css",
"css/pensize.css",
"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",
@ -39,5 +39,7 @@
"css/minimap.css", "css/minimap.css",
"css/widgets-anchor.css", "css/widgets-anchor.css",
"css/widgets-frame-picker.css", "css/widgets-frame-picker.css",
"css/widgets-size-picker.css",
"css/widgets-tabs.css",
"css/widgets-wizard.css" "css/widgets-wizard.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>

View File

@ -4,16 +4,48 @@
} }
.fake-piskelapp-header { .fake-piskelapp-header {
position: relative;
text-align: center; text-align: center;
height: 40px; height: 40px;
line-height: 40px; line-height: 40px;
font-size: 24px; font-size: 24px;
background: black; background: black;
color: gold; color: gold;
z-index: 100;
} }
.piskel-name.piskel-name-saving { .piskel-name.piskel-name-saving {
color: red; color: red;
} }
.new-piskel-desktop {
position: absolute;
display: flex;
top: 7px;
right: 7px;
height: 26px;
width: 26px;
border-radius: 3px;
font-size: 22px;
color: black;
background: gold;
text-decoration: none;
align-items: center;
justify-content: center;
}
</style> </style>
<div class="fake-piskelapp-header"><span class="piskel-name"></span></div> <div class="fake-piskelapp-header">
<a
href="index.html"
target="_blank"
class="new-piskel-desktop button button-primary"
title="Create a new sprite"
rel="tooltip"
data-placement="left"
>+</a>
<span class="piskel-name"></span>
</div>

View File

@ -0,0 +1,83 @@
<script type="text/template" id="templates/dialogs/browse-backups.html">
<div class="dialog-wrapper">
<h3 class="dialog-head">
Browse backups
<span class="dialog-close">X</span>
</h3>
<div class="dialog-content backups-wizard-container"></div>
</div>
</script>
<script type="text/template" id="backups-select-session">
<div class="backups-step-container">
<div class="backups-step-content">
<div class="browse-backups-disclaimer">
<div class="backups-icon icon-common-backup-white">&nbsp;</div>
<div class="browse-backups-disclaimer-content">
<!-- Keep in sync with MAX_SESSIONS in BackupService.js -->
If you forgot to save your work or if Piskel crashed, try to restore one of the automatically backed up sessions below (up to 10 sessions).
<br/>
<br/>
Backups may be erased without notice, so try to save your work to a file or to your gallery as soon as you can.
</div>
</div>
<div class="session-list">
</div>
</div>
</div>
</script>
<script type="text/template" id="session-list-empty">
<div class="centered-message session-list-empty">No session found ...</div>
</script>
<script type="text/template" id="session-list-error">
<div class="centered-message session-list-error">Could not load backup sessions, something went wrong.</div>
</script>
<script type="text/template" id="session-list-item">
<div class="session-item">
<div class="session-details">
<span class="session-details-title">{{name}} {{description}}</span>
<span class="session-details-info">Session recorded {{date}}</span>
<span class="session-details-info">{{count}} saved</span>
</div>
<div class="session-actions">
<button class="button" data-session-id="{{id}}" data-action="delete">Delete</button>
<button class="button button-primary" data-session-id="{{id}}" data-action="view">View</button>
</div>
</div>
</script>
<script type="text/template" id="backups-session-details">
<div class="backups-step-container">
<div class="backups-step-content">
<div class="snapshot-list"></div>
</div>
<div class="backups-step-actions">
<button class="button back-button">back</button>
</div>
</div>
</script>
<script type="text/template" id="snapshot-list-empty">
<div class="centered-message snapshot-list-empty">No snapshot found ...</div>
</script>
<script type="text/template" id="snapshot-list-error">
<div class="centered-message snapshot-list-error">Could not load snapshots, something went wrong.</div>
</script>
<script type="text/template" id="snapshot-list-item">
<div class="snapshot-item" data-snapshot-id={{id}}>
<div class="snapshot-preview lowcont-dark-picker-background"></div>
<div class="snapshot-details">
<span class="snapshot-details-title">{{name}} {{description}}</span>
<span class="snapshot-details-info">Snapshot recorded {{date}}</span>
<span class="snapshot-details-info">{{frames}}, size {{resolution}}, {{fps}}fps</span>
</div>
<div class="snapshot-actions">
<button class="button button-primary" data-action="load" data-snapshot-id="{{id}}">Load</button>
</div>
</div>
</script>

View File

@ -67,11 +67,11 @@
<div class="import-mode"> <div class="import-mode">
<div class="import-mode-title">How do you want to import the new content?</div> <div class="import-mode-title">How do you want to import the new content?</div>
<div class="import-mode-section"> <div class="import-mode-section">
<span class="import-mode-section-description">Combine the imported content and your sprite.</span> <span class="import-mode-section-description">Combine with your sprite</span>
<button class="import-mode-merge-button button-primary button">Merge</button> <button class="import-mode-merge-button button-primary button">Combine</button>
</div> </div>
<div class="import-mode-section"> <div class="import-mode-section">
<span class="import-mode-section-description">Replace your current sprite by the imported content.</span> <span class="import-mode-section-description">Replace your sprite</span>
<button class="import-mode-replace-button button-primary button">Replace</button> <button class="import-mode-replace-button button-primary button">Replace</button>
</div> </div>
</div> </div>
@ -131,9 +131,6 @@
The imported image is bigger than the current sprite. The imported image is bigger than the current sprite.
</div> </div>
<div class="import-resize-section"> <div class="import-resize-section">
<div class="import-resize-option-label">
How do you want to proceed?
</div>
<label class="import-resize-option"> <label class="import-resize-option">
<input type="radio" name="resize-mode" id="resize-option-expand" value="expand" {{expandChecked}}/> <input type="radio" name="resize-mode" id="resize-option-expand" value="expand" {{expandChecked}}/>
<span>Expand canvas to {{newSize}}</span> <span>Expand canvas to {{newSize}}</span>
@ -162,19 +159,19 @@
</div> </div>
<div class="import-step-content"> <div class="import-step-content">
<div>Select a frame in your current sprite:</div> <div>Select the insertion frame:</div>
<div class="insert-frame-preview"></div> <div class="insert-frame-preview"></div>
<div class="insert-mode-container"> <div class="insert-mode-container">
<div class="insert-mode-option-label"> <div class="insert-mode-option-label">
How should the imported frames be inserted: Insert imported frames:
</div> </div>
<label class="insert-mode-option"> <label class="insert-mode-option">
<input type="radio" name="insert-mode" id="insert-mode-add" value="add" checked="checked"/> <input type="radio" name="insert-mode" id="insert-mode-add" value="add" checked="checked"/>
<span>Add new frames</span> <span>as new frames</span>
</label> </label>
<label class="insert-mode-option"> <label class="insert-mode-option">
<input type="radio" name="insert-mode" id="insert-mode-insert" value="insert"/> <input type="radio" name="insert-mode" id="insert-mode-insert" value="insert"/>
<span>Insert in existing frames</span> <span>in existing frames</span>
</label> </label>
</div> </div>
<div class="import-step-buttons"> <div class="import-step-buttons">
@ -184,13 +181,3 @@
</div> </div>
</div> </div>
</script> </script>
<script type="text/template" id="import-invalid-file">
<div class="import-step-container">
<div>THIS IS AN INVALID FILEZ</div>
<div class="import-step-buttons">
<button class="import-back-button button">back</button>
<button class="import-next-button button button-primary">next</button>
</div>
</div>
</script>

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