Compare commits

...

241 Commits

Author SHA1 Message Date
Julian Descottes 21b8bdd0f3 [chore] update version number to 15 2 snapshot 2021-07-02 15:12:00 +02:00
Julian Descottes 881d2f1100 Fix linting errors 2021-07-02 15:09:39 +02:00
Julian Descottes 117d094df9 [chore] Update all dependencies 2021-07-02 15:09:39 +02:00
Julian Descottes ae8b2ade39 release 0.15.1 2021-07-02 15:09:39 +02:00
Julian Descottes 6943dc3ebe Fix deserialization of fps-less sprites 2021-07-02 15:09:39 +02:00
Dominik K cb41a72b5b
Fix #894: Replaced help text for flip &rotated tool-flip icon (#899)
* Fix #894: Replaced help text for flip &rotated tool-flip icon

* Undo changes to icons.
2020-02-03 06:44:38 +01:00
Todor Imreorov 17b1712b82 use grunt play instead of grunt serve 2020-02-02 12:17:44 +01:00
blurymind 24b28504a1 add npm run formt command 2020-02-02 12:17:44 +01:00
blurymind 3e926ff093 update start command 2020-02-02 12:17:44 +01:00
blurymind b9d4c5c2a6 add build and serve commands 2020-02-02 12:17:44 +01:00
blurymind c13acba6b1 update outdated/broken node-webkit start script 2020-02-02 12:17:44 +01:00
blurymind cf92045489 add grunt-cli to devDependencies 2020-02-02 12:17:44 +01:00
juliandescottes 025c34d07c release: bump version to 0.16-SNAPSHOT 2018-11-25 18:10:55 +01:00
juliandescottes eb14e489a7 Add some release documentation 2018-11-25 18:10:55 +01:00
juliandescottes ef945ef8a6 bump version de 0.15 2018-11-25 18:10:55 +01:00
Julian Descottes c5c8184ae3 Fix size picker display for pen size > 4 2018-11-24 21:03:09 +01:00
Julian Descottes 66452de1e5 Fix hidden frames bug when adding or removing frames 2018-11-24 20:48:47 +01:00
Jordan Watkins fa62ae511e Fix to avoid double extension when using the dest argument 2018-10-25 18:30:44 +02:00
Jordan Watkins f106909150 Replace backticks with single quotes 2018-10-25 18:30:44 +02:00
Jordan Watkins b54efbde21 Add piskel-cli for exporting from .piskel files via command line 2018-10-25 18:30:44 +02:00
Jordan Watkins 5ebf83badf Add inline image option for PixiJS Movie export 2018-10-18 09:03:55 +02:00
Alex 3804afbeb5 Enabled inline editing of layer names 2018-10-18 08:14:23 +02:00
Julian Descottes 0a64d62b84 Add comments for hiddenFrames feature 2018-10-07 14:19:01 +02:00
Julian Descottes af9095b934 Move hidden frames info to Piskel model 2018-10-07 14:19:01 +02:00
janczer 65f4fd0f27 Refactoring. Create function to get current frame 2018-10-05 23:41:55 +02:00
janczer 5e364e984c Add to frame size information about number of current frame and how much frames exists 2018-10-05 23:41:55 +02:00
janczer 5a0b6b90e4 Fit typos 2018-09-25 08:35:46 +02:00
juliandescottes 717e8dd52a Add keyboard shortcut to toggle grid (alt+G) 2018-09-19 23:24:52 +02:00
juliandescottes bd0478fb35 Extract PreviewActionsController from PreviewController 2018-09-19 23:24:52 +02:00
juliandescottes 2bbbfd1219 Add integration test for grid icon in animated preview 2018-09-19 23:24:52 +02:00
juliandescottes da4156912e Remove migration script for 0.12 to 0.13 2018-09-19 23:24:52 +02:00
juliandescottes 99c060f4a7 Cleanup local storage before integration tests 2018-09-19 23:24:52 +02:00
juliandescottes 52e49a1ed6 Add toggle grid icon to minimap 2018-09-19 23:24:52 +02:00
Grzegorz 6acfa2256c Fix in serialization 2018-09-17 22:19:14 +02:00
Grzegorz 89629d2939 Implemented with history management. 2018-09-17 22:19:14 +02:00
Grzegorz e5d89104a1 Added frame toggling for preview 2018-09-17 22:19:14 +02:00
Zakar Handricken 6e84f0a2b5 Set color of Alpha icon based on opacity value. 2018-09-17 22:17:19 +02:00
Zakar Handricken ba0e86dc7e Place opacity value in layer item title. 2018-09-17 22:17:19 +02:00
juliandescottes 27497313ff Issue #803 - Add integration test for single png export 2018-09-09 08:47:00 +02:00
juliandescottes 8662ab65ad Issue #803 - Reuse downloadCanvas_ to download single frame canvas 2018-09-09 08:47:00 +02:00
juliandescottes 594d748146 Issue #803 - Move single frame export to PNG tab and support scale 2018-09-09 08:47:00 +02:00
Joanne Hong 379c61a11d Fix grid bleeding through from other layers by only enabling support for
grid rendering for current layer.
2018-08-26 10:34:25 +02:00
Maxwell Paul Brickner 230a38bf07 Updated urls to use https
Updated urls to use https instead of http when possible to avoid redirects.
2018-05-23 07:12:10 +09:00
Julian Descottes 0e5d74e1d8 Issue #772 - flash colorpicker cursor when using middle click or alt+click 2018-04-08 17:59:01 +02:00
Julian Descottes f462cd3b70 Remove jquery from app.js 2018-04-08 17:39:15 +02:00
Julian Descottes 45d2245346 Remove jquery from BrowseLocalController.js 2018-04-08 17:39:15 +02:00
Julian Descottes e5e85f67c2 Remove jquery from ColorsList.js 2018-04-08 17:39:15 +02:00
Julian Descottes 4836251bd3 Remove deprecated comment in FramesListController.js 2018-04-08 17:39:15 +02:00
Julian Descottes e3182504dd Remove jquery from FramesListController.js 2018-04-08 17:39:15 +02:00
Julian Descottes 34a88b79a0 Remove jquery from MinimapController.js 2018-04-08 17:39:15 +02:00
Julian Descottes 7b8978da7a Remove jquery from LayersListController.js 2018-04-08 17:39:15 +02:00
Julian Descottes 1c3b359b5c Remove unused constant from LayersListController.js 2018-04-08 17:39:15 +02:00
Julian Descottes 5c4cbbbba1 Remove jquery from NotificationController.js 2018-04-08 17:39:15 +02:00
Julian Descottes b9cc2eb4f8 Remove jquery from PaletteController.js 2018-04-08 17:39:15 +02:00
Julian Descottes ac13cf13c1 Remove jquery from PalettesListController.js 2018-04-08 17:39:15 +02:00
Julian Descottes 32600f9e66 Remove jquery from LayersRenderer.js 2018-04-08 17:39:15 +02:00
Julian Descottes 39eb0d3143 Remove jquery from ProgressBarController.js 2018-04-08 17:39:15 +02:00
Julian Descottes 6e53bce2cf Remove jquery from SelectionManager.js 2018-04-08 17:39:15 +02:00
Julian Descottes 090acee11a Remove jquery from PreviewController.js 2018-04-08 17:39:15 +02:00
Julian Descottes a1bb5f3b97 Remove jquery from DrawingController.js 2018-04-08 17:39:15 +02:00
Julian Descottes f69e502639 Remove jquery from FrameRenderer.js 2018-04-08 17:39:15 +02:00
Julian Descottes 027cd2e2a6 Remove jquery from BackgroundImageFrameRenderer.js 2018-04-08 17:39:15 +02:00
Julian Descottes 2726af2d81 Remove jquery from ShortcutService.js 2018-04-08 17:39:15 +02:00
Julian Descottes 22cec2e8c7 Remove jquery from BaseSelect.js 2018-04-08 17:39:15 +02:00
Julian Descottes 57ee8e4365 Remove jquery from FrameRenderer.js 2018-04-08 17:39:15 +02:00
Julian Descottes a9cdf98281 Remove jquery from ToolController.js 2018-04-08 17:39:15 +02:00
Julian Descottes 0a9e7dcffb Update the no-colors message for palettes to be more readable 2018-04-08 17:03:07 +02:00
Julian Descottes fe3f6996f1 release: update version to 0.15.0-SNAPSHOT 2018-03-11 23:48:06 +01:00
Julian Descottes 9d32a8c3aa release: bump to version 0.14.0 2018-03-04 13:04:20 +01:00
Alex 64cd724139 reverted checkbox class and added method to controller 2018-03-03 16:45:27 +01:00
Alex dae08107e2 Added new checkbox to zip export html template and use layer name as prefix to file names (in ZipExportController) if checkbox is selected. 2018-03-03 16:45:27 +01:00
juliandescottes 779761628e Grid spacing: css nits, remove unused variable, string case fix 2018-02-18 13:29:37 +01:00
Craig Gilchrist 3e450c3d77 Changes requested in PR 2018-02-18 13:11:08 +01:00
Craig Gilchrist 5c7070b01b Add a Grid Spacing option to the GridSettings
Fixes #774
2018-02-18 13:11:08 +01:00
Guillaume Martigny 7c215ebcbe Remove use of grunt-open in favor of the option in grunt-contrib-connect 2018-02-07 18:52:03 +01:00
Carl Smith bcee24609f Changed cursor coordinates color from gold to --highlight-color. 2017-11-16 21:20:49 +01:00
Carl Smith f420c6f1fd Made cursor coordinates gold (and whitespace more consistent). 2017-11-16 21:20:49 +01:00
Alex K 76429dfd86 noloop gif should be -1
repeat	0	repeat count, -1 = no repeat, 0 = forever
https://github.com/jnordberg/gif.js
2017-11-10 10:13:08 +01:00
Jordan Watkins 4ebf43fda4 Display drawing zoom 2017-10-23 11:39:38 +02:00
juliandescottes 2f2b4cd9ba release: bump to version 0.13.0 2017-10-22 16:40:09 +02:00
juliandescottes dfb049bbf0 release: bump version to 0.13.0-RC2 2017-10-22 16:40:09 +02:00
juliandescottes 8c54108a9b Remove html autocomplete fromt size input fields 2017-10-22 16:40:09 +02:00
juliandescottes 3dd72f9781 release: fix minor ui issues for checkbox containers 2017-10-22 16:40:09 +02:00
juliandescottes 80001eab0e release: bump version to 0.13.0-RC1 2017-10-22 16:40:09 +02:00
Julian Descottes 5ecf351e0f Return promise results from PiskelDB and IndexedDBStorageService 2017-10-21 23:48:51 +02:00
Julian Descottes 407b432227 Show error message if BackupDatabase promise rejected 2017-10-21 23:48:51 +02:00
Julian Descottes 69cc27557e Add templates for backup database errors 2017-10-21 23:48:51 +02:00
juliandescottes cfd3773a2b Issue #751 - add repeat checkbox to GIF export panel 2017-10-08 19:46:43 +02:00
juliandescottes 0eface45f1 Issue #750 - drawing tests: always initialize penSize before starting test 2017-10-08 19:12:04 +02:00
juliandescottes 87893bb4ac Issue #750 - Fix mirror pen with even pensizes 2017-10-08 19:12:04 +02:00
juliandescottes 77d26bffa9 Issue #727 - add integration test for simple import flow 2017-10-08 18:19:52 +02:00
juliandescottes 652027bd3f Issue #727 - update integration tests to wait for color service update 2017-10-08 18:19:52 +02:00
Julian Descottes bf4cc3302a Issue #727 - skip import steps if current piskel is empty 2017-10-08 18:19:52 +02:00
juliandescottes 95c8df1224 Issue #727 - simplify import: resize and insertion steps 2017-10-08 18:19:52 +02:00
juliandescottes 7445357368 Issue #727 - simplify import mode text 2017-10-08 18:19:52 +02:00
juliandescottes a2369cac0c Issue #727 - remove border around meta info in import wizard 2017-10-08 18:19:52 +02:00
Julian Descottes 51538dff48 Make piskel performance warning less scary 2017-09-24 18:06:37 +02:00
Julian Descottes da739e78da Issue #743 - bump color palette cap to 256 2017-09-24 17:39:03 +02:00
Julian Descottes dd8217e21b Issue #744 - show notification when exporting to GIF can not preserve colors 2017-09-24 17:37:49 +02:00
Julian Descottes d502d3416b Issue #745 - Add https support 2017-09-24 17:37:14 +02:00
juliandescottes d1156954ca Issue #729 - implement custom PNG export viewer instead of opening window to data-uri 2017-09-24 17:36:02 +02:00
juliandescottes dc5209628c fix selectionmanager unit test 2017-09-06 23:05:17 +02:00
juliandescottes 8568663949 Move clipboard events to dedicated service and fix tests 2017-09-06 23:05:17 +02:00
juliandescottes fd3d828067 remove unused selection copy cut paste events 2017-09-06 23:05:17 +02:00
juliandescottes e1797b2008 Fix SelectionManagerTest by using a clipboard event mock 2017-09-06 23:05:17 +02:00
juliandescottes 0a43f6bbec Fix copy to website script to work if main-partial is missing. 2017-09-06 23:05:17 +02:00
juliandescottes b9423bc831 Issue #645: Support clipboard to paste images 2017-09-06 23:05:17 +02:00
juliandescottes 5e6280301d Issue #736 - cleanup selection tool state on SELECTION_DISMISSED event 2017-09-06 00:39:35 +02:00
juliandescottes 5671eb4782 Delete all extra backup sessions if MAX is reached 2017-08-06 22:56:43 +02:00
juliandescottes 35788b54ba update travis yml to upgrade node and stop downloading casper 2017-08-03 00:44:53 +02:00
juliandescottes 629ecf83b4 add comments for values synced between JS and CSS 2017-08-03 00:21:08 +02:00
juliandescottes c037b07693 rename mergeData to backupsData in browse backups wizard 2017-08-03 00:21:08 +02:00
juliandescottes c31b7a351c update piskel mock in BackupServiceTest 2017-08-03 00:21:08 +02:00
juliandescottes 7de03f1e73 show snpashot previews in the browse backups dialog 2017-08-03 00:21:08 +02:00
juliandescottes eab21e0839 Show confirmation message when loading snapshot backup 2017-08-03 00:21:08 +02:00
juliandescottes 2b3bd02479 improve styling of snapshot list in browse backups dialog 2017-08-03 00:21:08 +02:00
juliandescottes 4e86fa1570 dev-environment: add ctrl+alt+R shortcut to reload styles 2017-08-03 00:21:08 +02:00
juliandescottes 170a7e4731 skip backups for current session in browse backups dialog 2017-08-03 00:21:08 +02:00
juliandescottes 6b7f04b63e browse backups dialog: add styling for empty session list 2017-08-03 00:21:08 +02:00
juliandescottes da2e9f99e4 cleanup: remove title on backup session element 2017-08-03 00:21:08 +02:00
juliandescottes 530a949e54 add icon for backup dialog 2017-08-03 00:21:08 +02:00
Julian Descottes 4377c9e601 add disclaimer in the browse backups dialog 2017-08-03 00:21:08 +02:00
Julian Descottes e0bbb88d47 confirm backup session delete, add animation 2017-08-03 00:21:08 +02:00
Julian Descottes 9ff2ecbb45 improve styling for browse-backups dialog 2017-08-03 00:21:08 +02:00
juliandescottes 8beba2088b remove useless console.log 2017-08-03 00:21:08 +02:00
juliandescottes ee45cdcc45 add a browse backups dialog 2017-08-03 00:21:08 +02:00
juliandescottes 30ea7fa079 fix migration script for localstorage to indexeddb 2017-08-03 00:21:08 +02:00
Julian Descottes e9b39a5c61 add unit test for PiskelDatabase 2017-08-03 00:21:08 +02:00
Julian Descottes d0a32b18c5 add unit test for backup database 2017-08-03 00:21:08 +02:00
Julian Descottes 372ad1f513 add unit test for BackupService 2017-08-03 00:21:08 +02:00
Julian Descottes c6e106fe2d add a limit to the number of sessions backed up 2017-08-03 00:21:08 +02:00
Julian Descottes f9570ea3c5 Issue #640 - extract database code to dedicated package 2017-08-03 00:21:08 +02:00
Julian Descottes f9cb631acb Issue #640 - migrate backup service to indexeddb 2017-08-03 00:21:08 +02:00
Julian Descottes ed749a747f Issue #640 - migrate local browser save to indexeddb 2017-08-03 00:21:08 +02:00
Julian Descottes 30ecd41452 Issue #640 - remove duplicated entries in piskel-script-list 2017-08-03 00:21:08 +02:00
Julian Descottes 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
juliandescottes 183133496e Fix #718 - when dropping image, only use import wizard for big images 2017-08-01 01:06:09 +02:00
Julian Descottes 8a2c0191f9 release: bump version to 0.12.1 2017-07-18 08:06:54 +02:00
Julian Descottes a096dcabfd Fix #717: filter invalid colors 2017-07-18 08:05:48 +02:00
Julian Descottes 96d326ef12 release: bump version to 0.13.0-SNAPSHOT 2017-06-23 21:01:47 +02:00
Julian Descottes 7c37372b13 release: bump version to v0.12.0 2017-06-23 21:01:47 +02:00
Julian Descottes b21ea30fa8 Issue #658 - Support shift+UP/RIGHT/DOWN/LEFT to move the viewport 2017-06-10 23:12:11 +02:00
Julian Descottes 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
Julian Descottes 09ce6ff88f Issue #636 - remove unused code 2017-06-10 11:20:23 +02:00
Julian Descottes 2c4a8efb44 Issue #636 - add integration test for main settings panel 2017-06-10 11:20:23 +02:00
Julian Descottes 02a25d3f84 Fix eslint violations 2017-06-10 11:20:23 +02:00
Julian Descottes d159de2e65 Update gitignore for vscode 2017-06-10 11:20:23 +02:00
Julian Descottes 726a8f74c1 Switch from jscs+jshint to eslint 2017-06-10 11:20:23 +02:00
Julian Descottes 89a65ab032 add test-export-gif-scale test to integration test suite 2017-06-10 11:20:23 +02:00
Julian Descottes d8ec58b42c Remove reference to seamless in tooltip 2017-06-10 11:20:23 +02:00
Julian Descottes 1168870ee0 Issue #636 - Allow users to change the grid color 2017-06-10 11:20:23 +02:00
Julian Descottes d3a37c74e9 Issue #636 - rename seamless* to tile* 2017-06-10 11:20:23 +02:00
Julian Descottes 5d2ca7e70c Issue #636 - create sizepicker widget and use it in grid settings 2017-06-10 11:20:23 +02:00
Julian Descottes 2976fd09ea Issue #636 - create Tabs widget and use it application settings panel 2017-06-10 11:20:23 +02:00
Julian Descottes 317fda83c3 add integration test for tiny-palettes 2017-06-04 22:29:54 +02:00
Julian Descottes 94160d8fc4 Transform palette color title to uppercase 2017-06-04 22:29:54 +02:00
Julian Descottes b977a146e9 Issue #663 - display 10 colors per row in palette for palettes with > 10 colors 2017-06-04 22:29:54 +02:00
Julian Descottes aea4e4d6a6 Fix #704 - increase height of offline apps window to 700px 2017-06-03 10:23:40 +02:00
Julian Descottes 5456ea973a Fix #690 - remove references to github/juliandescottes in README 2017-06-03 09:38:23 +02:00
Julian Descottes a299d9aed0 Fix #697 - update confirmation message when closing Piskel with unsaved changes 2017-06-03 00:49:52 +02:00
Julian Descottes cc2fc48107 Fix #271: add new piskel button for desktop application 2017-06-03 00:15:08 +02:00
Julian Descottes 799c9fbf5a Feature #541 crop based on the current selection 2017-06-01 19:46:34 +02:00
Julian Descottes a9e22535d6 feature #541: add crop transform tool 2017-06-01 19:46:34 +02:00
Julian Descottes 4b4cbe47c8 support expanding the transform toolbox 2017-06-01 19:46:34 +02:00
Julian Descottes e7d07c5353 Fix #699 - add SPACE to keycode translator 2017-05-27 09:05:05 +02:00
Julian Descottes cf3383722a check meta key when recording drawing test (OSX) 2017-05-20 20:03:42 +02:00
Julian Descottes 6566ca07a5 add new layer just before the current layer 2017-05-20 20:03:42 +02:00
Julian Descottes 9fafa8b7a7 use SHIFT meta when clicking up down layer to move to top/bottom 2017-05-20 20:03:42 +02:00
Julian Descottes 4a9f7cc74b add grunt task to run drawing tests 2017-05-20 20:03:42 +02:00
Julian Descottes 319060beb6 add drawing test for move layer top/bottom 2017-05-20 20:03:42 +02:00
ItsPugle 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
Smie cd560012e1 Adds comments to Stroke tool for drawing lines. 2017-05-17 23:57:30 +02:00
Smie e819503cc5 Adjusts lineTool ends to square shape. 2017-05-17 23:57:30 +02:00
Smie 6512c3dcc0 Improves performance of line tool for large pen sizes. 2017-05-17 23:57:30 +02:00
juliandescottes 3535dfb25e replace references to github.com/juliandescottes for github.com/piskelapp 2017-05-14 19:18:19 +02:00
juliandescottes a69554f6c9 release: bump version to 0.12.0-SNAPSHOT 2017-05-14 19:16:58 +02:00
juliandescottes 1040cb4d8c release: bump version to 0.11.1 2017-05-14 19:04:33 +02:00
juliandescottes d18b85df16 Fix template bug for IE11 2017-05-14 18:55:51 +02:00
juliandescottes 76563bfc41 release: bump version to v0.12.0-SNAPSHOT 2017-05-14 17:43:35 +02:00
juliandescottes 2ba95667b2 release: bump version to v0.11.0 2017-05-14 17:39:33 +02:00
juliandescottes 3949051ba7 release: add release script to run grunt+copy to piskel-website 2017-05-14 17:39:33 +02:00
juliandescottes df6780a3e7 store version in window.pskl and display it in settings 2017-05-14 17:39:33 +02:00
juliandescottes b768a22b1c Fix #689 - add unit test for v1 deserializer 2017-05-14 17:39:33 +02:00
juliandescottes 8f4f9d9b0a Fix #689 - add unit test for v0 deserializer 2017-05-14 17:39:33 +02:00
juliandescottes 14e969a3bb Fix typos and errors in existing DeserializationTest 2017-05-14 17:39:33 +02:00
juliandescottes 0ef69bae58 Fix #689 - Constants.DEFAULTS->DEFAULT in deserializers v0/v1 2017-05-14 17:39:33 +02:00
juliandescottes b40c1c4744 reduce button size 2017-05-13 01:36:04 +02:00
juliandescottes 261259b38f remove unused styles from dialogs-import.css 2017-05-13 01:36:04 +02:00
juliandescottes 799bf76a86 update integration tests after selectMode step changes 2017-05-13 01:36:04 +02:00
juliandescottes 42f329980c remove cancel button from import steps, add big select mode buttons 2017-05-13 01:36:04 +02:00
juliandescottes 80a1edf027 add unit test for piskel MergeUtils 2017-05-13 01:36:04 +02:00
juliandescottes 92934502e8 fix merge of smaller piskel into bigger piskel 2017-05-13 01:36:04 +02:00
juliandescottes 4d07a4eb90 select the first imported layer after a merge 2017-05-13 01:36:04 +02:00
juliandescottes 41ef0cd460 increase casper load timeout 2017-05-13 01:36:04 +02:00
juliandescottes d77889d265 add integration test chaining two import flows 2017-05-13 01:36:04 +02:00
juliandescottes 4f15b14b58 IE11 fixes 2017-05-13 01:36:04 +02:00
juliandescottes 00d4a1614b protect frame-picker destroy if frame picker init() was not called 2017-05-13 01:36:04 +02:00
juliandescottes a72b0c69e5 initialize insert-location frame-picker on show() 2017-05-13 01:36:04 +02:00
juliandescottes c6733cdad4 add integration test for import merge wizard 2017-05-13 01:36:04 +02:00
juliandescottes 30274ad1f8 replace Promise by Q.defer in ImageImport step 2017-05-13 01:36:04 +02:00
juliandescottes b7fe8c1a5e use image name as piskel name for image import 2017-05-13 01:36:04 +02:00
juliandescottes a68dccfce0 persist preview UI during import wizard 2017-05-13 01:36:04 +02:00
juliandescottes 78bbc71e6c Implement Import and Merge wizard dialog 2017-05-13 01:36:04 +02:00
juliandescottes b5a8eb9f96 Feature #683 - add drawing test for duplicate layer 2017-05-13 01:11:45 +02:00
juliandescottes af3d0fa48b Feature #683: duplicate layer when SHIFT+CLICK on create layer button 2017-05-13 01:11:45 +02:00
juliandescottes e6a65c0db4 Fix #687 - use normal flavor for nwjs apps 2017-05-13 01:03:14 +02:00
juliandescottes f2b733c91e Fix #684 - import GIF as spritesheet failed 2017-05-11 01:47:15 +02:00
Julian Descottes 4506fdbdf2 add new grunt target to build desktop apps for older osx 2017-05-09 09:45:08 +02:00
juliandescottes 5d6f354443 Fix #652 - fix worker ObjectURL creation 2017-05-08 01:17:29 +02:00
juliandescottes a6d3aff9f1 releae: update version to 0.11.0-SNAPSHOT 2017-05-08 00:17:00 +02:00
juliandescottes a707170fa2 release: update version to v0.10.1 2017-05-08 00:06:14 +02:00
juliandescottes 8c815f320d copy version file to piskel website 2017-05-08 00:06:14 +02:00
juliandescottes f74b081fad avoid sanitization for cheatsheet keys 2017-05-08 00:06:14 +02:00
juliandescottes b8fed60fa6 Fix #651 - stop drawing the zoomedout background if unnecessary 2017-05-06 18:47:01 +02:00
Julian Descottes d841ad10f6 Merge pull request #677 from juliandescottes/dependencies
Dependencies
2017-05-06 18:42:55 +02:00
juliandescottes f6e16e7b4b chore: update grunt-spritesmith to 6.4.0 2017-05-06 18:31:49 +02:00
juliandescottes 6fb8a6859c chore: update karma-phantomjs-launcher to 1.0.4 2017-05-06 18:30:08 +02:00
juliandescottes 866553b2ca chore: update grunt-contrib-clean & grunt-contrib-uglify 2017-05-06 17:45:42 +02:00
juliandescottes 5aad87471e chore: update jasmine-chore to 2.6.1 2017-05-06 17:42:19 +02:00
juliandescottes 942aacbb94 chore: update fs-extra to 3.0.1 2017-05-06 17:35:40 +02:00
juliandescottes 88da91bde3 chore: remove karma-chrome-launcher (unused) 2017-05-06 17:32:07 +02:00
juliandescottes ce94dbeeeb chore: update karma to 1.7.0 2017-05-06 16:54:11 +02:00
juliandescottes a8c85b8a29 Issue #654 : stop using jquery to create the FramesList markup 2017-04-01 12:48:11 +02:00
Julian Descottes 89199f2d6a Merge pull request #644 from juliandescottes/alt-to-pick-color
select color using alt+click (fixes #623)
2017-03-02 01:52:04 +01:00
juliandescottes 98768b2e5b select color using alt+click (fixes #623) 2017-03-02 01:39:28 +01:00
Julian Descottes 62b1b8baf0 Merge pull request #642 from juliandescottes/sanitize-strings
sanitize strings coming from user inputs
2017-02-23 21:01:05 +01:00
Julian Descottes 11a063de12 sanitize strings coming from user inputs 2017-02-23 19:37:29 +01:00
Julian Descottes 6f4413f353 Merge pull request #638 from juliandescottes/nw-warning
add confirmation message when closing desktop app with unsaved changes
2017-02-19 15:14:33 +01:00
Julian Descottes 974450837e add confirmation message when closing desktop app with unsaved changes 2017-02-19 13:19:09 +01:00
juliandescottes c6c64af2fd Use Q deferred instead of native promise (IE11) 2017-02-11 16:57:56 +01:00
juliandescottes c68b82339c remove classList toggle with 2nd argument for IE11 2017-02-11 16:51:27 +01:00
Julian Descottes 099ff80155 Merge pull request #634 from zoeesilcock/628-fix-arrows-in-cheatsheet
Use standard HTML entities for the arrows displayed on the keyboard shortcut list
2017-02-05 12:41:30 +01:00
Zoee Silcock f30e16386d Use standard HTML entities for the arrows displayed on the keyboard shortcut list 2017-02-05 11:47:45 +01:00
juliandescottes 08b97cb6f0 select previous layer after deleting layer 2017-02-04 14:32:23 +01:00
juliandescottes b6fa769ba1 temporary fix for layerlist scroll 2017-02-04 14:09:27 +01:00
juliandescottes 47b09b10c5 Merge branch 'master' of https://github.com/juliandescottes/piskel 2017-02-04 13:49:20 +01:00
juliandescottes f039a89572 disable layerlist smoothscrolling 2017-02-04 13:43:49 +01:00
Julian Descottes e74329f04e Merge pull request #629 from GMartigny/master
Add CodeClimate
2017-02-02 19:30:40 +01:00
Guillaume Martigny 8959b201e9 Excludes lib from linting 2017-02-01 16:17:38 +01:00
Guillaume Martigny f5491dc557 Irregular whitespace 2017-02-01 16:15:02 +01:00
Guillaume Martigny 9ce3d44cc6 Add config file for CodeClimate 2017-02-01 15:51:13 +01:00
232 changed files with 11157 additions and 2404 deletions

25
.codeclimate.yml Normal file
View File

@ -0,0 +1,25 @@
engines:
csslint:
enabled: true
duplication:
enabled: true
config:
languages:
- javascript
eslint:
enabled: true
checks:
wrap-iife:
enabled: false
fixme:
enabled: true
ratings:
paths:
- "**.css"
- "**.js"
exclude_paths:
- .github/
- bin/
- misc/
- src/js/lib/
- test/

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
# nodejs local installs
@ -15,10 +15,12 @@ cache
# netbeans project folder
nbproject
# vscode workspace folder
.vscode
# git stackdumps
*.stackdump
# diffs
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
node_js:
- "4.1"
- "7.4.0"
before_install:
- npm update -g npm
- 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:
- phantomjs --version
- casperjs --version

View File

@ -12,6 +12,7 @@ module.exports = function(grunt) {
// create a version based on the build timestamp
var dateFormat = require('dateformat');
var version = '-' + dateFormat(new Date(), "yyyy-mm-dd-hh-MM");
var releaseVersion = require('./package.json').version;
/**
* Helper to prefix all strings in provided array with the provided path
@ -46,16 +47,13 @@ module.exports = function(grunt) {
var integrationTestPaths = require('./test/casperjs/integration/IntegrationSuite.js').tests;
var integrationTests = prefixPaths(integrationTestPaths, "test/casperjs/integration/");
var getConnectConfig = function (base, port, host) {
if (typeof base === 'string') {
base = [base];
}
var getConnectConfig = function (base, port, host, open) {
return {
options: {
port: port,
hostname : host,
base: base
base: base,
open: open
}
};
};
@ -82,35 +80,17 @@ module.exports = function(grunt) {
css : ['src/css/**/*.css']
},
jscs : {
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}
},
eslint: {
files: [
// Includes
'Gruntfile.js',
'package.json',
'src/js/**/*.js',
// Excludes
// Exludes
// TODO: remove this (for now we still get warnings from the lib folder)
'!src/js/**/lib/**/*.js'
]
],
options: {
fix: grunt.option('fix') // this will get params from the flags
}
},
/**
@ -118,18 +98,9 @@ module.exports = function(grunt) {
*/
connect: {
prod: getConnectConfig('dest/prod', PORT.PROD, hostname),
test: getConnectConfig(['dest/dev', 'test'], PORT.TEST, hostname),
dev: getConnectConfig(['dest/dev', 'test'], PORT.DEV, hostname)
},
open : {
prod : {
path : 'http://' + hostname + ':' + PORT.PROD + '/'
},
dev : {
path : 'http://' + hostname + ':' + PORT.DEV + '/?debug'
}
prod: getConnectConfig('dest/prod', PORT.PROD, hostname, true),
test: getConnectConfig(['dest/dev', 'test'], PORT.TEST, hostname, false),
dev: getConnectConfig(['dest/dev', 'test'], PORT.DEV, hostname, 'http://' + hostname + ':' + PORT.DEV + '/?debug')
},
watch: {
@ -194,7 +165,8 @@ module.exports = function(grunt) {
dest: 'dest/tmp/index.html',
options : {
globals : {
'version' : version
'version' : version,
'releaseVersion' : releaseVersion
}
}
}
@ -236,20 +208,6 @@ module.exports = function(grunt) {
src: ['dest/tmp/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'}
]
}
},
@ -319,7 +277,8 @@ module.exports = function(grunt) {
build_dir: './dest/desktop/', // destination folder of releases.
win: true,
linux32: true,
linux64: true
linux64: true,
flavor: "normal",
},
src: ['./dest/prod/**/*', "./package.json", "!./dest/desktop/"]
},
@ -328,7 +287,18 @@ module.exports = function(grunt) {
downloadUrl: 'https://dl.nwjs.io/',
osx64: true,
version : "0.19.4",
build_dir: './dest/desktop/'
build_dir: './dest/desktop/',
flavor: "normal",
},
src: ['./dest/prod/**/*', "./package.json", "!./dest/desktop/"]
},
macos_old : {
options: {
downloadUrl: 'https://dl.nwjs.io/',
osx64: true,
version : "0.12.3",
build_dir: './dest/desktop/old',
flavor: "normal",
},
src: ['./dest/prod/**/*', "./package.json", "!./dest/desktop/"]
}
@ -337,11 +307,13 @@ module.exports = function(grunt) {
// TEST TASKS
// Run linting
grunt.registerTask('lint', ['jscs:js', 'leadingIndent:css', 'jshint']);
grunt.registerTask('lint', ['eslint', 'leadingIndent:css']);
// Run unit-tests
grunt.registerTask('unit-test', ['karma']);
// Run integration tests
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
grunt.registerTask('test', ['lint', 'unit-test', 'build-dev', 'connect:test', 'casperjs:drawing', 'casperjs:integration']);
@ -356,14 +328,15 @@ module.exports = function(grunt) {
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-dev', ['clean:dev', 'sprite', 'build-index.html', 'copy:dev']);
grunt.registerTask('desktop', ['clean:desktop', 'default', 'replace:desktop', 'nwjs:windows']);
grunt.registerTask('desktop-mac', ['clean:desktop', 'default', 'replace:desktop', 'nwjs:macos']);
grunt.registerTask('desktop', ['clean:desktop', 'default', 'nwjs:windows']);
grunt.registerTask('desktop-mac', ['clean:desktop', 'default', 'nwjs:macos']);
grunt.registerTask('desktop-mac-old', ['clean:desktop', 'default', 'replace:desktop', 'nwjs:macos_old']);
// SERVER TASKS
// Start webserver and watch for changes
grunt.registerTask('serve', ['build', 'connect:prod', 'open:prod', 'watch:prod']);
grunt.registerTask('serve', ['build', 'connect:prod', 'watch:prod']);
// Start webserver on src folder, in debug mode
grunt.registerTask('play', ['build-dev', 'connect:dev', 'open:dev', 'watch:dev']);
grunt.registerTask('play', ['build-dev', 'connect:dev', 'watch:dev']);
// ALIASES, kept for backward compatibility
grunt.registerTask('serve-debug', ['play']);

View File

@ -1,75 +1,61 @@
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)](https://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](https://www.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)**
* **[@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.
The Piskel editor is purely built in **JavaScript, HTML and CSS**.
We also use the following **libraries** :
* [spectrum](https://github.com/bgrins/spectrum) : awesome standalone colorpicker
* [gifjs](http://jnordberg.github.io/gif.js/) : generate animated GIFs in javascript, using webworkers
* [gifjs](https://jnordberg.github.io/gif.js/) : generate animated GIFs in javascript, using webworkers
* [supergif](https://github.com/buzzfeed/libgif-js) : modified version of SuperGif to parse and import GIFs
* [jszip](https://github.com/Stuk/jszip) : create, read and edit .zip files with Javascript
* [canvas-toBlob](https://github.com/eligrey/canvas-toBlob.js/) : shim for canvas toBlob
* [jquery](http://jquery.com/) : used sporadically in the application
* [bootstrap-tooltip](http://getbootstrap.com/javascript/#tooltips) : nice tooltips
* [jquery](https://jquery.com/) : used sporadically in the application
* [bootstrap-tooltip](https://getbootstrap.com/javascript/#tooltips) : nice tooltips
As well as some **icons** from the [Noun Project](http://thenounproject.com/) :
As well as some **icons** from the [Noun Project](https://thenounproject.com/) :
* Folder by Simple Icons from The Noun Project
* (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 ?
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).
* **Participate** : Have a look at the [wiki](https://github.com/juliandescottes/piskel/wiki) to set up the development environment
* **Issues** : Found a problem when using the application, want to request a feature, [open an issue](https://github.com/piskelapp/piskel/issues).
* **Development** : Have a look at the [wiki](https://github.com/piskelapp/piskel/wiki) to set up the development environment
## License
Copyright 2016 Julian Descottes
Copyright 2017 Julian Descottes
Licensed under the Apache License, Version 2.0 (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
limitations under the License.
## Mobile/Tablets
There is no support for mobile for now.

32
RELEASE.md Normal file
View File

@ -0,0 +1,32 @@
# Release instructions
- retrieve source code and dependencies
- clone piskel `git clone https://github.com/piskelapp/piskel`
- clone piskel website `git clone https://github.com/piskelapp/piskel-website`
- download the appengine SDK for python at [this url](https://cloud.google.com/appengine/docs/standard/python/download). It seems recent versions don't work for me on Windows. [GoogleAppEngine-1.9.67.msi](https://storage.cloud.google.com/appengine-sdks/featured/GoogleAppEngine-1.9.67.msi?_ga=2.6838962.-1257793585.1543157659) looks ok...
- prepare the release
- in `piskel-website` clone, checkout the `master` branch, not the `release`. The release branch is similar to the master branch, except it doesn't ignore the statics files coming from the `piskel` project. The idea is that we can easily checkout the release branch to do a new release, without having to rebuild piskel, in case nothing changed in piskel itself. We will switch to the release branch when we perform the final release.
- in `piskel` clone, create a new branch for the release `git checkout -b vX.Y`
- build and copy piskel to piskel-website (assumes the clones are in the same folder) `grunt && node bin/copy-to-piskel-website.js`
- local test
- start piskel-website in the Google app engine launcher, test manually the update
- if any issue is detected create a commit to fix it (on the `vX.Y` branch) and push it.
- beta test
- check the version in app.yaml is set to 'beta'. For historical reasons, alpha is the "production" version, while beta is the ... beta version. Don't ask.
- in the google app engine launcher, deploy this version.
- go to https://beta-dot-piskel-app.appspot.com and test (while this is not production frontend, it shares the same database, so be careful with what you create and delete)
- if any issue is detected create a commit to fix it (on `master`) and push it.
- check caches...
- If any icon was modified or any script of piskel-website was modified, there's no nice way to clean the cache on each release... so we just put a time stamp in the requests. eg `background-image: url(../img/icons@2x.png?20181125);` or `<script type="text/javascript" src="/static/js/piskel-animated-preview.js?20181124"></script>`. Erk.
- finalize the release
- in `piskel` update version in package.json. Remove the "-SNAPSHOT" at the end and commit the change, with a commit message such as `release: bump version to vX.Y`.
- in `piskel-website` checkout the `release` branch. Rebase it on master (that's pretty bad, we should probably cherry pick all the changes between the two branches...)
- once again build and copy piskel to piskel-website
- in `piskel-website` run `git status` to check that there are a bunch of changes to commit. (test locally again if you are paranoid)
- in `piskel-website` commit the current changes with a commit message such as `update statics for release vX.Y`
- Check that the version in app.yaml is now set to alpha. Deploy. Pray.
- post release
- in piskel-website push the release branch to github
- in piskel add a last commit to update the version in package.json to update the version to `vX+1.0-SNAPSHOT`
- create a tag on github

View File

@ -6,6 +6,8 @@ const fse = require('fs-extra');
const PISKEL_PATH = path.resolve(__dirname, '..');
const PISKELAPP_PATH = path.resolve(__dirname, '../../piskel-website');
var pjson = require('../package.json');
// Callbacks sorted by call sequence.
function onCopy(err) {
if (err) {
@ -15,7 +17,16 @@ function onCopy(err) {
console.log('Copied static files to piskel-website...');
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) {
@ -52,6 +63,17 @@ function onDeleteTempPartial(err) {
}
console.log('Temporary main partial deleted...');
fs.writeFile(path.resolve(PISKELAPP_PATH, "static/editor/VERSION"), pjson.version, onVersionFileCreated);
}
function onVersionFileCreated(err) {
if (err) {
console.error('Failed to create temporary main partial...');
return console.error(err);
}
console.log('Version file created...');
console.log('Finished!');
}

64
cli/README.md Normal file
View File

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

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

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

93
cli/index.js Normal file
View File

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

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

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

View File

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

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -1,60 +1,67 @@
{
"name": "piskel",
"version": "0.10.0",
"version": "0.15.2-SNAPSHOT",
"description": "Pixel art editor",
"author": "Julian Descottes <julian.descottes@gmail.com>",
"contributors": [
"Vincent Renaudin"
],
"homepage": "http://github.com/juliandescottes/piskel",
"homepage": "http://github.com/piskelapp/piskel",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "http://github.com/juliandescottes/piskel.git"
"url": "http://github.com/piskelapp/piskel.git"
},
"files": [
"dest/prod",
"misc/scripts/piskel-root"
],
"bin": {
"piskel-root": "./misc/scripts/piskel-root"
"piskel-root": "./misc/scripts/piskel-root",
"piskel-cli": "./cli/index.js"
},
"main": "./dest/prod/index.html",
"scripts": {
"test": "grunt test",
"start": "nodewebkit",
"dev": "grunt play",
"start": "grunt build && nw",
"preversion": "grunt test build",
"postversion": "git push && git push --tags && npm publish"
"postversion": "git push && git push --tags && npm publish",
"release": "grunt && node ./bin/copy-to-piskel-website",
"build": "grunt desktop",
"build:mac": "grunt desktop-mac",
"format": "grunt eslint --fix"
},
"devDependencies": {
"dateformat": "2.0.0",
"fs-extra": "1.0.0",
"grunt": "^0.4.5",
"dateformat": "4.5.1",
"fs-extra": "10.0.0",
"grunt": "1.4.1",
"grunt-casperjs": "^2.2.1",
"grunt-contrib-clean": "1.0.0",
"grunt-cli": "^1.4.3",
"grunt-contrib-clean": "2.0.0",
"grunt-contrib-concat": "1.0.1",
"grunt-contrib-connect": "1.0.2",
"grunt-contrib-connect": "3.0.0",
"grunt-contrib-copy": "1.0.0",
"grunt-contrib-jshint": "1.1.0",
"grunt-contrib-uglify": "1.0.1",
"grunt-contrib-watch": "1.0.0",
"grunt-include-replace": "4.0.1",
"grunt-jscs": "2.8.0",
"grunt-karma": "1.0.0",
"grunt-contrib-jshint": "3.0.0",
"grunt-contrib-uglify": "5.0.1",
"grunt-contrib-watch": "^1.1.0",
"grunt-eslint": "23.0.0",
"grunt-include-replace": "5.0.0",
"grunt-jscs": "3.0.1",
"grunt-karma": "4.0.2",
"grunt-leading-indent": "0.2.0",
"grunt-nw-builder": "3.1.0",
"grunt-open": "0.2.3",
"grunt-replace": "1.0.1",
"grunt-spritesmith": "6.3.0",
"jasmine-core": "2.1.0",
"karma": "1.3.0",
"karma-chrome-launcher": "1.0.1",
"karma-jasmine": "1.0.2",
"karma-phantomjs-launcher": "0.2.3",
"load-grunt-tasks": "3.5.0",
"grunt-replace": "2.0.2",
"grunt-spritesmith": "^6.9.0",
"jasmine-core": "3.8.0",
"karma": "6.3.4",
"karma-jasmine": "4.0.1",
"karma-phantomjs-launcher": "1.0.4",
"load-grunt-tasks": "5.1.0",
"nw": "0.54.0",
"phantomjs": "2.1.7",
"phantomjs-polyfill-object-assign": "0.0.2",
"promise-polyfill": "6.0.2",
"promise-polyfill": "8.2.0",
"rmdir": "1.2.0"
},
"window": {
@ -62,6 +69,9 @@
"icon": "dest/prod/logo.png",
"toolbar": false,
"width": 1000,
"height": 500
"height": 700
},
"dependencies": {
"minimist": "^1.2.5"
}
}

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

@ -3,6 +3,19 @@
/* Browse local piskels panel */
/************************************************************************************************/
#dialog-container.browse-local {
width: 700px;
height: 500px;
top : 50%;
left : 50%;
position : absolute;
margin-left: -350px;
}
.show #dialog-container.browse-local {
margin-top: -250px;
}
.local-piskel-list {
width: 100%;
}

View File

@ -1,101 +0,0 @@
/************************************************************************************************/
/* Import dialog */
/************************************************************************************************/
#dialog-container.import-image {
width: 550px;
height: 360px;
top : 50%;
left : 50%;
position : absolute;
margin-left: -250px;
}
.show #dialog-container.import-image {
margin-top: -150px;
}
.import-subsection {
margin-left: 25px;
}
.import-section:not(.import-subsection) > .dialog-section-title {
width: 50px;
}
.import-section-preview-title {
position: absolute;
margin-left: 50%;
margin-top: -28px;
}
.import-section-preview {
position: absolute;
display: inline-block;
border: 1px dashed #999;
border-radius: 3px;
margin-left: 50%;
}
.import-section-preview img {
max-width: 220px;
max-height: 220px;
display: block;
}
.import-section-preview.no-border {
border-color: transparent;
}
.import-section-preview canvas {
position: absolute;
left: 0;
top: 0;
}
.dialog-section-title {
display : inline-block;
width: 80px;
}
.dialog-section-radio {
margin-top: 15px;
vertical-align: sub;
}
.import-size-field:nth-of-type(2) {
margin-left: 5px;
}
.import-image-file-name {
display: inline-block;
overflow: hidden;
width: 200px;
vertical-align: middle;
word-break : break-all;
white-space: nowrap;
text-overflow: ellipsis;
font-style: italic;
font-weight: normal;
text-shadow: none;
color: var(--highlight-color);
}
[name=smooth-resize-checkbox] {
margin : 0 8px;
}
.dialog-import-body {
padding: 10px 20px;
font-size:1.3em
}
.import-button {
font-size: 1em;
height: 28px;
padding: 0px 10px;
margin-top: 15px;
}

288
src/css/dialogs-import.css vendored Normal file
View File

@ -0,0 +1,288 @@
#dialog-container.import {
width: 500px;
height: 350px;
top : 50%;
left : 50%;
position : absolute;
margin-left: -250px;
margin-top: -175px;
}
.import .dialog-content {
font-size: 1.3em;
}
.import-step-container {
display: flex;
width: 100%;
height: 100%;
box-sizing: border-box;
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.5);
background: #444;
}
.import-step-content {
padding: 10px;
}
.import-step-buttons {
position: absolute;
bottom: 10px;
right: 10px;
text-align: right;
}
.import-first-step .import-back-button {
display: none;
}
/**
* IMAGE IMPORT STEP
*/
.import-image-container {
padding: 10px;
}
.import-image-loading {
opacity: 0.3;
pointer-events: none;
}
.import-image-loading::after {
content: 'loading image';
position: absolute;
width: 100%;
top: 0;
left: 0;
margin-top: 110px;
text-align: center;
font-size: 3em;
color: white;
text-shadow: 0 0 60px black;
}
.import-subsection {
margin-left: 25px;
}
.import-section:not(.import-subsection) > .dialog-section-title {
width: 50px;
}
.import-section-preview-title {
position: absolute;
margin-left: 50%;
margin-top: -28px;
}
.import-section-preview {
position: absolute;
display: inline-block;
border: 1px dashed #999;
border-radius: 3px;
margin-left: 50%;
top: 10px;
width: 220px;
height: 220px;
display: flex;
align-items: center;
justify-content: center;
}
.import-section-preview img {
max-width: 100%;
max-height: 100%;
display: block;
}
.import-section-preview canvas {
position: absolute;
}
.dialog-section-title {
display : inline-block;
width: 80px;
}
.dialog-section-radio {
margin-top: 15px;
vertical-align: sub;
}
.import-size-field:nth-of-type(2) {
margin-left: 5px;
}
.import-image-file-name {
display: inline-block;
overflow: hidden;
width: 200px;
vertical-align: middle;
word-break : break-all;
white-space: nowrap;
text-overflow: ellipsis;
font-style: italic;
font-weight: normal;
text-shadow: none;
color: var(--highlight-color);
}
[name=smooth-resize-checkbox] {
margin : 0 8px;
}
.dialog-import-body {
padding: 10px 20px;
font-size:1.3em
}
/**
* SELECT MODE
*/
.import-info {
display: flex;
flex-direction: column;
height: 100%;
max-width: 178px;
box-sizing: border-box;
padding: 10px;
border-right: 3px solid gold;
}
.import-preview canvas {
max-width: 100%;
max-height: 100%;
}
.import-meta {
margin-top: 10px;
box-sizing: border-box;
/*center meta information horizontally*/
display: flex;
flex-direction: column;
justify-content: center;
}
.import-meta > div {
height: 22px;
display: flex;
margin-bottom: 5px;
}
.import-meta-value,
.import-meta-label {
padding: 2px 4px;
}
.import-meta-label {
border-radius: 2px 0 0 2px;
color: var(--highlight-color);
}
.import-meta-value {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.import-missing {
text-align: center;
line-height: 70px;
font-style: italic;
color: #aaa;
}
.import-mode-title {
margin-bottom: 10px
}
.import-mode-section {
display: flex;
border: 3px solid #666;
border-radius: 3px;
padding: 10px;
margin-bottom: 10px;
align-items: center;
}
.import-mode-section .button {
width: 75px;
height: 30px;
font-size: 14px;
margin-left: 10px;
flex-grow: 0;
flex-shrink: 0;
}
.import-mode-section-description {
flex-grow: 1;
}
.import-resize-option,
.insert-mode-option {
display: flex;
align-items: center;
margin: 5px 0;
}
.import-resize-option :checked + span,
.insert-mode-option :checked + span {
color: var(--highlight-color);
}
.import-resize-option input,
.insert-mode-option input {
margin: 5px;
}
/**
* ADJUST SIZE
*/
.import-resize-anchor-info {
margin-bottom: 10px;
}
.import-resize-section {
margin-bottom: 10px;
}
.import-resize-anchor {
margin-top: 20px;
}
.import-resize-option :checked + span {
color: var(--highlight-color);
}
.import-resize-warning {
color: var(--highlight-color);
}
/**
* INSERT LOCATION
*/
.insert-mode-container {
margin-top: 20px;
margin-bottom: 10px;
}
.insert-frame-preview {
margin: 10px 0;
}
.insert-frame-preview .frame-picker-wrapper {
height: 120px;
}

View File

@ -48,19 +48,6 @@
margin-top: 0;
}
#dialog-container.browse-local {
width: 700px;
height: 500px;
top : 50%;
left : 50%;
position : absolute;
margin-left: -350px;
}
.show #dialog-container.browse-local {
margin-top: -250px;
}
.dialog-wrapper {
height: 100%;
position : relative;

View File

@ -54,6 +54,9 @@
}
.add-frame-action {
display: flex;
align-items: center;
margin-top: 8px;
padding: 6px 0;
overflow: hidden;
@ -71,6 +74,7 @@
.add-frame-action-icon {
margin: 3px;
float: left;
flex-shrink: 0;
}
.add-frame-action .label {
@ -131,6 +135,11 @@
cursor: default;
}
.preview-tile .tile-overlay.tile-count.toggled {
background-color: gold;
color: black;
}
.preview-tile .tile-overlay.delete-frame-action {
top: 0;
right: 0;

View File

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

View File

@ -5,6 +5,7 @@ html, body {
cursor : default;
font-family: Arial;
font-size: 11px;
line-height: 1.1;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;

View File

@ -1,6 +1,14 @@
/*******************************/
/* Application Setting panel */
/*******************************/
/********************************/
/* Preferences Settings panel */
/********************************/
.settings-section-preferences {
height: 100%;
position: relative;
overflow: hidden;
margin: 0 20px;
height: 100%;
}
.background-picker-wrapper {
display: inline-block;
@ -35,14 +43,11 @@
vertical-align: middle;
}
.layer-opacity-input {
.layer-opacity-input,
.tile-mask-opacity-input {
width: 100px;
}
.seamless-opacity-input {
width: 75px;
}
.settings-opacity-text {
height: 31px;
display: inline-block;
@ -54,16 +59,102 @@
text-align: center;
}
.settings-item-grid-size,
.settings-item-grid-spacing,
.settings-item-grid-color {
display: flex;
align-items: center;
}
.settings-item-grid-size > label,
.settings-item-grid-spacing > label,
.settings-item-grid-color > label {
width: 65px;
flex-shrink: 0;
}
.grid-spacing-container .size-picker-option[data-size='1'] {
padding: 7px;
}
.grid-spacing-container .size-picker-option[data-size='2'] {
padding: 6px;
}
.grid-spacing-container .size-picker-option[data-size='4'] {
padding: 5px;
}
.grid-spacing-container .size-picker-option[data-size='8'] {
padding: 4px;
}
.grid-spacing-container .size-picker-option[data-size='16'] {
padding: 3px;
}
.grid-spacing-container .size-picker-option[data-size='32'] {
padding: 2px;
}
.grid-spacing-container .size-picker-option[data-size='64'] {
padding: 1px;
}
.settings-item-grid-size .size-picker-option,
.settings-item-grid-spacing .size-picker-option {
border-color: #888;
}
.settings-item-grid-size .size-picker-option:not(.selected):hover,
.settings-item-grid-spacing .size-picker-option:not(.selected):hover {
border-color: white;
}
.grid-width-select,
.color-format-select {
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 */
margin-bottom: 15px;
}
.settings-section-application .button-primary {
.settings-section-preferences .button-primary {
margin-top: 10px;
}
.settings-version-info {
position: absolute;
bottom: 10px;
right: 10px;
color: #888;
}
/* Override default link styles for the release notes link
It doesn't need to popout too much */
.settings-version,
.settings-version:hover {
color: white !important;
text-decoration: none !important;
}

View File

@ -43,8 +43,29 @@
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{
content: "Upload ongoing ...";
content: "Upload in progress...";
position: absolute;
display: block;
height: 100%;
@ -100,45 +121,6 @@
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 {
padding: 10px 5px 0px;
}
@ -162,7 +144,8 @@
overflow: hidden;
}
.export-panel-gif .button {
.export-panel-gif .button,
.export-panel-png .button {
margin-right: 5px;
width: 75px;
flex-shrink: 0;
@ -172,11 +155,6 @@
width: 50px;
}
.png-export-dimension-info,
.png-export-datauri-info {
margin-left: 5px;
}
#png-export-columns,
#png-export-rows {
/* Override default textfield padding-right to keep the number spinners

View File

@ -5,103 +5,8 @@
width: 25%;
}
/*****************/
/* ANCHOR WIDGET */
/*****************/
.resize-origin-container {
overflow: hidden;
.resize-anchor-container {
position: relative;
width: 70px;
margin-top: 5px;
display: inline-block;
}
.transition .resize-origin-option,
.transition .resize-origin-option:before {
transition: background-color 0.2s, border-color 0.2s;
}
.resize-origin-option {
float: left;
position: relative;
box-sizing: border-box;
margin: 0 1px 1px 0;
width: 20px;
height: 20px;
background : #888;
font-size: 8px;
text-align: center;
cursor: pointer;
}
.disabled .resize-origin-option {
cursor: default;
background : #555;
border-color: #555 !important;
}
.resize-origin-option:hover {
border : 3px solid white;
}
.resize-origin-option.selected {
border : 3px solid var(--highlight-color);
}
.resize-origin-option:before {
content: '';
position: absolute;
display: block;
top: 50%;
left: 50%;
margin: -2px;
}
.resize-origin-option.selected:before {
content: '';
width: 4px;
height: 4px;
background: var(--highlight-color);
}
.disabled .resize-origin-option.selected:before {
background: #555;
}
.disabled .resize-origin-option[data-neighbor]:before {
border-color: #555 !important;
}
.resize-origin-option[data-neighbor]:before {
width: 0;
height: 0;
border-width: 4px;
border-style: solid;
border-color: transparent;
}
.resize-origin-option[data-neighbor="bottom"]:before {
border-top-color: var(--highlight-color);
margin-left: -4px;
}
.resize-origin-option[data-neighbor="left"]:before {
border-right-color: var(--highlight-color);
margin-top: -4px;
margin-left: -6px;
}
.resize-origin-option[data-neighbor="top"]:before {
border-bottom-color: var(--highlight-color);
margin-top: -6px;
margin-left: -4px;
}
.resize-origin-option[data-neighbor="right"]:before {
border-left-color: var(--highlight-color);
margin-top: -4px;
}

View File

@ -1,113 +0,0 @@
/*
Icon classes can be used entirely standalone. They are named after their original file names.
```html
<!-- `display: block` sprite -->
<div class="icon-home"></div>
<!-- `display: inline-block` sprite -->
<img class="icon-home" />
```
*/
.icon-cloud_export {
background-image: url(../img/spritesheet.png);
background-position: -512px -276px;
width: 50px;
height: 47px;
}
.icon-dragndrop {
background-image: url(../img/spritesheet.png);
background-position: -564px -173px;
width: 48px;
height: 48px;
}
.icon-duplicate {
background-image: url(../img/spritesheet.png);
background-position: -512px -369px;
width: 16px;
height: 16px;
}
.icon-export {
background-image: url(../img/spritesheet.png);
background-position: -564px -225px;
width: 43px;
height: 42px;
}
.icon-favicon {
background-image: url(../img/spritesheet.png);
background-position: -582px -151px;
width: 16px;
height: 16px;
}
.icon-gallery {
background-image: url(../img/spritesheet.png);
background-position: -512px -225px;
width: 52px;
height: 51px;
}
.icon-garbage {
background-image: url(../img/spritesheet.png);
background-position: 0px 0px;
width: 512px;
height: 512px;
}
.icon-gear {
background-image: url(../img/spritesheet.png);
background-position: -563px -323px;
width: 38px;
height: 37px;
}
.icon-import-icon {
background-image: url(../img/spritesheet.png);
background-position: -576px -69px;
width: 28px;
height: 36px;
}
.icon-keyboard {
background-image: url(../img/spritesheet.png);
background-position: -512px -133px;
width: 70px;
height: 40px;
}
.icon-local-storage-icon {
background-image: url(../img/spritesheet.png);
background-position: -512px 0px;
width: 100px;
height: 69px;
}
.icon-merge-icon {
background-image: url(../img/spritesheet.png);
background-position: -512px -69px;
width: 64px;
height: 64px;
}
.icon-plus {
background-image: url(../img/spritesheet.png);
background-position: -512px -323px;
width: 51px;
height: 46px;
}
.icon-popup-preview-arrow-gold {
background-image: url(../img/spritesheet.png);
background-position: -576px -105px;
width: 24px;
height: 18px;
}
.icon-popup-preview-arrow-white {
background-image: url(../img/spritesheet.png);
background-position: -582px -133px;
width: 24px;
height: 18px;
}
.icon-resize-icon {
background-image: url(../img/spritesheet.png);
background-position: -512px -173px;
width: 52px;
height: 52px;
}
.icon-save {
background-image: url(../img/spritesheet.png);
background-position: -562px -276px;
width: 43px;
height: 42px;
}

View File

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

View File

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

View File

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

View File

@ -76,6 +76,11 @@
position: relative;
}
.palettes-list-colors.tiny > .palettes-list-color {
width: calc((100% - 35px) / 10);
height: 16px;
}
.palettes-list-color div {
height: 100%;
}
@ -89,7 +94,7 @@
line-height: 35px;
width: 100%;
color: gray;
font-size: 0.7em;
font-size: 0.8em;
font-style: italic;
text-align: center
}
@ -121,7 +126,8 @@
* 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;
top: 0;
right: 0;
@ -136,39 +142,3 @@
padding: 2px 3px 2px 3px;
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 {
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

@ -0,0 +1,98 @@
/*****************/
/* ANCHOR WIDGET */
/*****************/
.anchor-wrapper {
overflow: hidden;
width: 70px;
}
/* When transitioning between enabled and disabled states */
.transition .anchor-option,
.transition .anchor-option:before {
transition: background-color 0.2s, border-color 0.2s;
}
.anchor-option {
float: left;
position: relative;
box-sizing: border-box;
margin: 0 1px 1px 0;
width: 20px;
height: 20px;
background : #888;
font-size: 8px;
text-align: center;
cursor: pointer;
}
.disabled .anchor-option {
cursor: default;
background : #555;
border-color: #555 !important;
}
.anchor-option:hover {
border : 3px solid white;
}
.anchor-option.selected {
border : 3px solid var(--highlight-color);
}
.anchor-option:before {
content: '';
position: absolute;
display: block;
top: 50%;
left: 50%;
margin: -2px;
}
.anchor-option.selected:before {
content: '';
width: 4px;
height: 4px;
background: var(--highlight-color);
}
.disabled .anchor-option.selected:before {
background: #555;
}
.disabled .anchor-option[data-neighbor]:before {
border-color: #555 !important;
}
.anchor-option[data-neighbor]:before {
width: 0;
height: 0;
border-width: 4px;
border-style: solid;
border-color: transparent;
}
.anchor-option[data-neighbor="bottom"]:before {
border-top-color: var(--highlight-color);
margin-left: -4px;
}
.anchor-option[data-neighbor="left"]:before {
border-right-color: var(--highlight-color);
margin-top: -4px;
margin-left: -6px;
}
.anchor-option[data-neighbor="top"]:before {
border-bottom-color: var(--highlight-color);
margin-top: -6px;
margin-left: -4px;
}
.anchor-option[data-neighbor="right"]:before {
border-left-color: var(--highlight-color);
margin-top: -4px;
}

View File

@ -0,0 +1,55 @@
/***********************/
/* FRAME PICKER WIDGET */
/***********************/
.frame-picker-wrapper {
width: 150px;
height: 150px;
border: 3px solid #666;
border-radius: 3px;
}
.frame-viewer {
width: 100%;
height: calc(100% - 25px);
display: flex;
align-items: center;
justify-content: center;
}
.frame-viewer > canvas,
.frame-viewer > img {
max-width: 100%;
max-height: 100%;
}
.frame-nav {
display: flex;
width: 100%;
height: 24px;
border-top: 1px solid #666;
}
.frame-nav .button {
flex-shrink: 0;
border-radius: 0;
height: 24px;
background-color: #3f3f3f;
}
.frame-nav .button[disabled],
.frame-nav .button[disabled]:hover {
background-color: #aaa;
}
.frame-nav .button + .button {
border-left: 1px solid #333;
}
.frame-nav-input {
min-width: 1px;
border-style: none;
height: 24px;
text-align: center;
}

View File

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

View File

@ -0,0 +1,32 @@
.wizard-wrapper {
z-index: 1;
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.wizard-step {
z-index: -1;
margin-left: calc(100% + 5px);
position: absolute;
}
.current-step {
z-index: 1;
margin-left: 0;
}
.current-step-in,
.current-step-out {
z-index: 10;
transition: margin-left 200ms;
}
.current-step-in {
margin-left: 0;
}
.current-step-out {
margin-left: 100%;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

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;">
<span style="top:45%">Loading Piskel ...</span>
</div>
<!--standalone-start-->
@@include('templates/debug-header.html', {})
<!--standalone-end-->
<!-- the comment below indicates the beginning of markup reused by the editor integrated in piskelapp.com -->
<!-- do not delete, do not move :) -->
@ -69,6 +67,7 @@
</div>
@@include('templates/misc-templates.html', {})
@@include('templates/data-uri-export.html', {})
@@include('templates/popup-preview.html', {})
<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>
<!-- dialogs partials -->
@@include('templates/dialogs/create-palette.html', {})
@@include('templates/dialogs/import-image.html', {})
@@include('templates/dialogs/browse-backups.html', {})
@@include('templates/dialogs/browse-local.html', {})
@@include('templates/dialogs/cheatsheet.html', {})
@@include('templates/dialogs/create-palette.html', {})
@@include('templates/dialogs/import.html', {})
@@include('templates/dialogs/performance-info.html', {})
@@include('templates/dialogs/unsupported-browser.html', {})
<!-- 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/save.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,13 +12,15 @@ var Constants = {
MAX_HEIGHT : 1024,
MAX_WIDTH : 1024,
MAX_PALETTE_COLORS : 100,
MAX_PALETTE_COLORS : 256,
// allow current colors service to get up to 256 colors.
// GIF generation is different if the color count goes over 256.
MAX_WORKER_COLORS : 256,
PREVIEW_FILM_SIZE : 96,
ANIMATED_PREVIEW_WIDTH : 200,
// Keep in sync with padding-left: 10px in layout.css
RIGHT_COLUMN_PADDING_LEFT : 10,
DEFAULT_PEN_COLOR : '#000000',
TRANSPARENT_COLOR : 'rgba(0, 0, 0, 0)',
@ -58,8 +60,11 @@ var Constants = {
// The datastore limit is 1 MiB, which we roughly approximate to 1 million characters.
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
APPENGINE_SAVE_URL : 'save',
IMAGE_SERVICE_UPLOAD_URL : 'http://piskel-imgstore-b.appspot.com/__/upload',
IMAGE_SERVICE_GET_URL : 'http://piskel-imgstore-b.appspot.com/img/'
IMAGE_SERVICE_UPLOAD_URL : '{{protocol}}://piskel-imgstore-b.appspot.com/__/upload',
IMAGE_SERVICE_GET_URL : '{{protocol}}://piskel-imgstore-b.appspot.com/img/'
};

View File

@ -15,7 +15,7 @@ var Events = {
DRAG_START : 'DRAG_START',
DRAG_END : 'DRAG_END',
DIALOG_DISPLAY : 'DIALOG_DISPLAY',
DIALOG_SHOW : 'DIALOG_SHOW',
DIALOG_HIDE : 'DIALOG_HIDE',
PALETTE_LIST_UPDATED : 'PALETTE_LIST_UPDATED',
@ -64,9 +64,10 @@ var Events = {
SELECTION_CREATED: 'SELECTION_CREATED',
SELECTION_MOVE_REQUEST: 'SELECTION_MOVE_REQUEST',
SELECTION_DISMISSED: 'SELECTION_DISMISSED',
SELECTION_COPY: 'SELECTION_COPY',
SELECTION_CUT: 'SELECTION_CUT',
SELECTION_PASTE: 'SELECTION_PASTE',
CLIPBOARD_COPY: 'CLIPBOARD_COPY',
CLIPBOARD_CUT: 'CLIPBOARD_CUT',
CLIPBOARD_PASTE: 'CLIPBOARD_PASTE',
SHOW_NOTIFICATION: 'SHOW_NOTIFICATION',
HIDE_NOTIFICATION: 'HIDE_NOTIFICATION',

View File

@ -15,6 +15,9 @@
*/
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.init();
@ -61,24 +64,24 @@
this.drawingController = new pskl.controller.DrawingController(
this.piskelController,
$('#drawing-canvas-container'));
document.querySelector('#drawing-canvas-container'));
this.drawingController.init();
this.previewController = new pskl.controller.preview.PreviewController(
this.piskelController,
$('#animated-preview-canvas-container'));
document.querySelector('#animated-preview-canvas-container'));
this.previewController.init();
this.minimapController = new pskl.controller.MinimapController(
this.piskelController,
this.previewController,
this.drawingController,
$('.minimap-container'));
document.querySelector('.minimap-container'));
this.minimapController.init();
this.framesListController = new pskl.controller.FramesListController(
this.piskelController,
$('#preview-list'));
document.querySelector('#preview-list-wrapper'));
this.framesListController.init();
this.layersListController = new pskl.controller.LayersListController(this.piskelController);
@ -111,6 +114,9 @@
this.canvasBackgroundController = new pskl.controller.CanvasBackgroundController();
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.init();
@ -165,6 +171,9 @@
this.currentColorsService);
this.performanceReportService.init();
this.clipboardService = new pskl.service.ClipboardService(this.piskelController);
this.clipboardService.init();
this.drawingLoop = new pskl.rendering.DrawingLoop();
this.drawingLoop.addCallback(this.render, this);
this.drawingLoop.start();
@ -187,11 +196,16 @@
gui.Window.get().menu = mb;
}
if (pskl.utils.UserAgent.isUnsupported()) {
$.publish(Events.DIALOG_DISPLAY, {
if (!pskl.utils.Environment.isIntegrationTest() && pskl.utils.UserAgent.isUnsupported()) {
$.publish(Events.DIALOG_SHOW, {
dialogId : 'unsupported-browser'
});
}
if (pskl.utils.Environment.isDebug()) {
pskl.app.shortcutService.registerShortcut(pskl.service.keyboard.Shortcuts.DEBUG.RELOAD_STYLES,
window.reloadStyles);
}
},
loadPiskel_ : function (piskelData) {

View File

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

View File

@ -22,7 +22,7 @@
var cfg = {
'zoom': this.calculateZoom_(),
'supportGridRendering' : true,
'supportGridRendering' : false,
'height' : this.getContainerHeight_(),
'width' : this.getContainerWidth_(),
'xOffset' : 0,
@ -30,9 +30,10 @@
};
this.overlayRenderer = new pskl.rendering.frame.CachedFrameRenderer(this.container, cfg, ['canvas-overlay']);
this.renderer = new pskl.rendering.frame.CachedFrameRenderer(this.container, cfg, ['drawing-canvas']);
this.onionSkinRenderer = pskl.rendering.OnionSkinRenderer.createInContainer(this.container, cfg, piskelController);
this.layersRenderer = new pskl.rendering.layer.LayersRenderer(this.container, cfg, piskelController);
cfg.supportGridRendering = true;
this.renderer = new pskl.rendering.frame.CachedFrameRenderer(this.container, cfg, ['drawing-canvas']);
this.compositeRenderer = new pskl.rendering.CompositeRenderer();
this.compositeRenderer
@ -50,12 +51,12 @@
ns.DrawingController.prototype.init = function () {
this.initMouseBehavior();
$.subscribe(Events.TOOL_SELECTED, $.proxy(function(evt, toolBehavior) {
$.subscribe(Events.TOOL_SELECTED, (function(evt, toolBehavior) {
this.currentToolBehavior = toolBehavior;
this.overlayFrame.clear();
}, this));
}).bind(this));
$(window).resize($.proxy(this.startResizeTimer_, this));
window.addEventListener('resize', this.startResizeTimer_.bind(this));
$.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
$.subscribe(Events.FRAME_SIZE_CHANGED, this.onFrameSizeChange_.bind(this));
@ -64,6 +65,10 @@
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.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 () {
this.afterWindowResize_();
@ -72,13 +77,12 @@
};
ns.DrawingController.prototype.initMouseBehavior = function() {
var body = $('body');
this.container.mousedown($.proxy(this.onMousedown_, this));
this.container.addEventListener('mousedown', this.onMousedown_.bind(this));
if (pskl.utils.UserAgent.isChrome || pskl.utils.UserAgent.isIE11) {
this.container.on('mousewheel', $.proxy(this.onMousewheel_, this));
this.container.addEventListener('mousewheel', this.onMousewheel_.bind(this));
} else {
this.container.on('wheel', $.proxy(this.onMousewheel_, this));
this.container.addEventListener('wheel', this.onMousewheel_.bind(this));
}
window.addEventListener('mouseup', this.onMouseup_.bind(this));
@ -89,22 +93,20 @@
window.addEventListener('touchend', this.onTouchend_.bind(this));
// Deactivate right click:
body.contextmenu(this.onCanvasContextMenu_);
document.body.addEventListener('contextmenu', this.onCanvasContextMenu_.bind(this));
};
ns.DrawingController.prototype.startResizeTimer_ = function () {
if (this.resizeTimer) {
window.clearInterval(this.resizeTimer);
}
this.resizeTimer = window.setTimeout($.proxy(this.afterWindowResize_, this), 200);
this.resizeTimer = window.setTimeout(this.afterWindowResize_.bind(this), 200);
};
ns.DrawingController.prototype.afterWindowResize_ = function () {
var initialWidth = this.compositeRenderer.getDisplaySize().width;
this.compositeRenderer.setDisplaySize(this.getContainerWidth_(), this.getContainerHeight_());
this.centerColumnWrapperHorizontally_();
var ratio = this.compositeRenderer.getDisplaySize().width / initialWidth;
var newZoom = ratio * this.compositeRenderer.getZoom();
this.compositeRenderer.setZoom(newZoom);
@ -129,7 +131,6 @@
ns.DrawingController.prototype.onFrameSizeChange_ = function () {
this.compositeRenderer.setDisplaySize(this.getContainerWidth_(), this.getContainerHeight_());
this.centerColumnWrapperHorizontally_();
this.compositeRenderer.setZoom(this.calculateZoom_());
this.compositeRenderer.setOffset(0, 0);
$.publish(Events.ZOOM_CHANGED);
@ -163,6 +164,9 @@
if (event.button === Constants.MIDDLE_BUTTON) {
this.dragHandler.startDrag(event.clientX, event.clientY);
} else if (event.altKey && !this.currentToolBehavior.supportsAlt()) {
this.currentToolBehavior.hideHighlightedPixel(this.overlayFrame);
this.isPickingColor = true;
} else {
this.currentToolBehavior.hideHighlightedPixel(this.overlayFrame);
$.publish(Events.TOOL_PRESSED);
@ -210,6 +214,8 @@
if (this.isClicked) {
if (pskl.app.mouseStateService.isMiddleButtonPressed()) {
this.dragHandler.updateDrag(x, y);
} else if (this.isPickingColor) {
// Nothing to do on mousemove when picking a color with ALT+click.
} else {
$.publish(Events.MOUSE_EVENT, [event, this]);
this.currentToolBehavior.moveToolAt(
@ -232,8 +238,7 @@
$.publish(Events.CURSOR_MOVED, [coords.x, coords.y]);
};
ns.DrawingController.prototype.onMousewheel_ = function (jQueryEvent) {
var evt = jQueryEvent.originalEvent;
ns.DrawingController.prototype.onMousewheel_ = function (evt) {
// Ratio between wheelDeltaY (mousewheel event) and deltaY (wheel event) is -40
var delta;
if (pskl.utils.UserAgent.isIE11) {
@ -257,6 +262,29 @@
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.
*
@ -294,39 +322,75 @@
* @private
*/
ns.DrawingController.prototype.onMouseup_ = function (event) {
var frame = this.piskelController.getCurrentFrame();
if (!this.isClicked) {
return;
}
var coords = this.getSpriteCoordinates(event.clientX, event.clientY);
if (event.changedTouches && event.changedTouches[0]) {
coords = this.getSpriteCoordinates(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
}
if (this.isClicked) {
// A mouse button was clicked on the drawing canvas before this mouseup event,
// the user was probably drawing on the canvas.
// Note: The mousemove movement (and the mouseup) may end up outside
// of the drawing canvas.
this.isClicked = false;
// A mouse button was clicked on the drawing canvas before this mouseup event,
// the user was probably drawing on the canvas.
// Note: The mousemove movement (and the mouseup) may end up outside
// of the drawing canvas.
if (pskl.app.mouseStateService.isMiddleButtonPressed()) {
if (this.dragHandler.isDragging()) {
this.dragHandler.stopDrag();
} else if (frame.containsPixel(coords.x, coords.y)) {
var color = pskl.utils.intToColor(frame.getPixel(coords.x, coords.y));
$.publish(Events.SELECT_PRIMARY_COLOR, [color]);
}
} else {
this.currentToolBehavior.releaseToolAt(
coords.x,
coords.y,
this.piskelController.getCurrentFrame(),
this.overlayFrame,
event
);
this.isClicked = false;
$.publish(Events.TOOL_RELEASED);
}
$.publish(Events.MOUSE_EVENT, [event, this]);
var isMiddleButton = pskl.app.mouseStateService.isMiddleButtonPressed();
var isMiddleClick = isMiddleButton && !this.dragHandler.isDragging();
var isMiddleDrag = isMiddleButton && this.dragHandler.isDragging();
if (this.isPickingColor || isMiddleClick) {
// Picking color after ALT+click or middle mouse button click.
this.pickColorAt_(coords);
this.isPickingColor = false;
// Flash the cursor to briefly show the colorpicker cursor.
this.flashColorPicker_();
} else if (isMiddleDrag) {
// Stop the drag handler after a middle button drag action.
this.dragHandler.stopDrag();
} else {
// Regular tool click, release the current tool.
this.currentToolBehavior.releaseToolAt(
coords.x,
coords.y,
this.piskelController.getCurrentFrame(),
this.overlayFrame,
event
);
$.publish(Events.TOOL_RELEASED);
}
$.publish(Events.MOUSE_EVENT, [event, this]);
};
/**
* Send a COLOR selection event for the color contained at the provided coordinates.
* No-op if the coordinate is outside of the drawing canvas.
* @param {Object} coords {x: Number, y: Number}
*/
ns.DrawingController.prototype.pickColorAt_ = function (coords) {
var frame = this.piskelController.getCurrentFrame();
if (!frame.containsPixel(coords.x, coords.y)) {
return;
}
var color = pskl.utils.intToColor(frame.getPixel(coords.x, coords.y));
var isRightButton = pskl.app.mouseStateService.isRightButtonPressed();
var evt = isRightButton ? Events.SELECT_SECONDARY_COLOR : Events.SELECT_PRIMARY_COLOR;
$.publish(evt, [color]);
};
ns.DrawingController.prototype.flashColorPicker_ = function () {
document.body.classList.add('tool-colorpicker');
document.body.classList.remove(this.currentToolBehavior.toolId);
window.clearTimeout(this.flashColorPickerTimer);
this.flashColorPickerTimer = window.setTimeout(function () {
document.body.classList.remove('tool-colorpicker');
document.body.classList.add(this.currentToolBehavior.toolId);
}.bind(this), 200);
};
/**
@ -347,7 +411,8 @@
* @private
*/
ns.DrawingController.prototype.onCanvasContextMenu_ = function (event) {
if ($(event.target).closest('#drawing-canvas-container').length) {
// closest() not really available everywhere yet, just skip if missing.
if (event.target.closest && event.target.closest('#drawing-canvas-container')) {
// Deactivate right click on drawing canvas only.
event.preventDefault();
event.stopPropagation();
@ -385,17 +450,21 @@
};
ns.DrawingController.prototype.getAvailableHeight_ = function () {
return $('#main-wrapper').height();
return document.querySelector('#main-wrapper').getBoundingClientRect().height;
};
ns.DrawingController.prototype.getSelectorWidth_ = function (selector) {
return document.querySelector(selector).getBoundingClientRect().width;
};
ns.DrawingController.prototype.getAvailableWidth_ = function () {
var leftSectionWidth = $('.left-column').outerWidth(true);
var rightSectionWidth = $('.right-column').outerWidth(true);
var toolsContainerWidth = $('#tool-section').outerWidth(true);
var settingsContainerWidth = $('#application-action-section').outerWidth(true);
var leftSectionWidth = this.getSelectorWidth_('.left-column');
var rightSectionWidth = this.getSelectorWidth_('.right-column');
var toolsContainerWidth = this.getSelectorWidth_('#tool-section');
var settingsContainerWidth = this.getSelectorWidth_('#application-action-section');
var usedWidth = leftSectionWidth + rightSectionWidth + toolsContainerWidth + settingsContainerWidth;
var availableWidth = $('#main-wrapper').width() - usedWidth;
var availableWidth = this.getSelectorWidth_('#main-wrapper') - usedWidth;
var comfortMargin = 10;
return availableWidth - comfortMargin;
@ -409,17 +478,6 @@
return this.getAvailableWidth_();
};
/**
* @private
*/
ns.DrawingController.prototype.centerColumnWrapperHorizontally_ = function() {
var containerHeight = this.getContainerHeight_();
var verticalGapInPixel = Math.floor(($('#main-wrapper').height() - containerHeight) / 2);
$('#column-wrapper').css({
'top': verticalGapInPixel + 'px'
});
};
ns.DrawingController.prototype.getRenderer = function () {
return this.compositeRenderer;
};

View File

@ -5,12 +5,14 @@
SELECT : 'select',
CLONE : 'clone',
DELETE : 'delete',
NEW_FRAME : 'newframe'
NEW_FRAME : 'newframe',
TOGGLE: 'toggle'
};
ns.FramesListController = function (piskelController, container) {
this.piskelController = piskelController;
this.container = container;
this.previewList = container.querySelector('#preview-list');
this.refreshZoom_();
this.redrawFlag = true;
@ -31,8 +33,9 @@
$.subscribe(Events.PISKEL_RESET, this.refreshZoom_.bind(this));
$('#preview-list-scroller').scroll(this.updateScrollerOverflows.bind(this));
this.container.get(0).addEventListener('click', this.onContainerClick_.bind(this));
this.previewListScroller = document.querySelector('#preview-list-scroller');
this.previewListScroller.addEventListener('scroll', this.updateScrollerOverflows.bind(this));
this.container.addEventListener('click', this.onContainerClick_.bind(this));
this.updateScrollerOverflows();
};
@ -64,11 +67,11 @@
};
ns.FramesListController.prototype.updateScrollerOverflows = function () {
var scroller = $('#preview-list-scroller');
var scrollerHeight = scroller.height();
var scrollTop = scroller.scrollTop();
var scrollerContentHeight = $('#preview-list').height();
var treshold = $('.top-overflow').height();
var scroller = this.previewListScroller;
var scrollerHeight = scroller.offsetHeight;
var scrollTop = scroller.scrollTop;
var scrollerContentHeight = this.previewList.offsetHeight;
var treshold = this.container.querySelector('.top-overflow').offsetHeight;
var overflowTop = false;
var overflowBottom = false;
@ -81,9 +84,8 @@
overflowBottom = true;
}
}
var wrapper = $('#preview-list-wrapper');
wrapper.toggleClass('top-overflow-visible', overflowTop);
wrapper.toggleClass('bottom-overflow-visible', overflowBottom);
this.container.classList.toggle('top-overflow-visible', overflowTop);
this.container.classList.toggle('bottom-overflow-visible', overflowBottom);
};
ns.FramesListController.prototype.onContainerClick_ = function (event) {
@ -97,12 +99,12 @@
if (action === ACTION.CLONE) {
this.piskelController.duplicateFrameAt(index);
var clonedTile = this.createPreviewTile_(index + 1);
this.container.get(0).insertBefore(clonedTile, this.tiles[index].nextSibling);
this.previewList.insertBefore(clonedTile, this.tiles[index].nextSibling);
this.tiles.splice(index, 0, clonedTile);
this.updateScrollerOverflows();
} else if (action === ACTION.DELETE) {
this.piskelController.removeFrameAt(index);
this.container.get(0).removeChild(this.tiles[index]);
this.previewList.removeChild(this.tiles[index]);
this.tiles.splice(index, 1);
this.updateScrollerOverflows();
} else if (action === ACTION.SELECT && !this.justDropped) {
@ -111,8 +113,10 @@
this.piskelController.addFrame();
var newtile = this.createPreviewTile_(this.tiles.length);
this.tiles.push(newtile);
this.container.get(0).insertBefore(newtile, this.addFrameTile);
this.previewList.insertBefore(newtile, this.addFrameTile);
this.updateScrollerOverflows();
} else if (action == ACTION.TOGGLE) {
this.piskelController.toggleFrameVisibilityAt(index);
}
this.flagForRedraw_();
@ -126,10 +130,18 @@
// Remove selected class
this.tiles[i].classList.remove('selected');
// Remove toggle class
this.tiles[i].querySelector('.tile-count').classList.remove('toggled');
// Update tile numbers
this.tiles[i].setAttribute('data-tile-number', i);
this.tiles[i].querySelector('.tile-count').innerHTML = (i + 1);
// Update visibility
if (this.piskelController.hasVisibleFrameAt(i)) {
this.tiles[i].querySelector('.tile-count').classList.add('toggled');
}
// Check if any tile is updated
var hash = this.piskelController.getCurrentLayer().getFrameAt(i).getHash();
if (this.tiles[i].getAttribute('data-tile-hash') !== hash) {
@ -147,7 +159,7 @@
}
// Hide/Show buttons if needed
var buttons = this.container.get(0).querySelectorAll('.delete-frame-action, .dnd-action');
var buttons = this.container.querySelectorAll('.delete-frame-action, .dnd-action');
var display = (this.piskelController.getFrameCount() > 1) ? 'block' : 'none';
for (i = 0, length = buttons.length; i < length; i++) {
buttons[i].style.display = display;
@ -158,15 +170,19 @@
};
ns.FramesListController.prototype.createPreviews_ = function () {
this.container.html('');
this.previewList.innerHTML = '';
// Manually remove tooltips since mouseout events were shortcut by the DOM refresh:
$('.tooltip').remove();
var tooltips = document.querySelectorAll('.tooltip');
Array.prototype.forEach.call(tooltips, function (tooltip) {
tooltip.parentNode.removeChild(tooltip);
});
var frameCount = this.piskelController.getFrameCount();
for (var i = 0 ; i < frameCount ; i++) {
var tile = this.createPreviewTile_(i);
this.container.append(tile);
this.previewList.appendChild(tile);
this.tiles[i] = tile;
}
// Append 'new empty frame' button
@ -176,7 +192,7 @@
newFrameButton.setAttribute('data-tile-action', ACTION.NEW_FRAME);
newFrameButton.innerHTML = '<div class="add-frame-action-icon icon-frame-plus-white">' +
'</div><div class="label">Add new frame</div>';
this.container.append(newFrameButton);
this.previewList.appendChild(newFrameButton);
this.addFrameTile = newFrameButton;
this.updateScrollerOverflows();
@ -186,24 +202,25 @@
* @private
*/
ns.FramesListController.prototype.initDragndropBehavior_ = function () {
$('#preview-list').sortable({
$(this.previewList).sortable({
placeholder: 'preview-tile preview-tile-drop-proxy',
update: $.proxy(this.onUpdate_, this),
stop: $.proxy(this.onSortableStop_, this),
update: this.onUpdate_.bind(this),
stop: this.onSortableStop_.bind(this),
items: '.preview-tile',
axis: 'y',
tolerance: 'pointer'
});
$('#preview-list').disableSelection();
$(this.previewList).disableSelection();
};
/**
* @private
*/
ns.FramesListController.prototype.onUpdate_ = function (event, ui) {
var originFrameId = parseInt(ui.item.data('tile-number'), 10);
var targetInsertionId = $('.preview-tile').index(ui.item);
var movedItem = ui.item.get(0);
var originFrameId = parseInt(movedItem.dataset.tileNumber, 10);
var tiles = document.querySelectorAll('.preview-tile');
var targetInsertionId = Array.prototype.indexOf.call(tiles, movedItem);
this.piskelController.moveFrame(originFrameId, targetInsertionId);
this.piskelController.setCurrentFrameIndex(targetInsertionId);
@ -219,14 +236,13 @@
ns.FramesListController.prototype.onSortableStop_ = function (event, ui) {
this.justDropped = true;
this.resizeTimer = window.setTimeout($.proxy(function() {
this.resizeTimer = window.setTimeout((function() {
this.justDropped = false;
}, this), 200);
}).bind(this), 200);
};
/**
* @private
* TODO(vincz): clean this giant rendering function & remove listeners.
*/
ns.FramesListController.prototype.createPreviewTile_ = function(tileNumber) {
var currentFrame = this.piskelController.getCurrentLayer().getFrameAt(tileNumber);
@ -285,8 +301,12 @@
previewTileRoot.appendChild(dndHandle);
// Add tile count
var tileCount = document.createElement('div');
tileCount.className = 'tile-overlay tile-count';
var tileCount = document.createElement('button');
tileCount.setAttribute('rel', 'tooltip');
tileCount.setAttribute('title', 'Toggle for preview');
tileCount.setAttribute('data-tile-number', tileNumber);
tileCount.setAttribute('data-tile-action', ACTION.TOGGLE);
tileCount.className = 'tile-overlay tile-count toggle-frame-action';
tileCount.innerHTML = tileNumber + 1;
previewTileRoot.appendChild(tileCount);

View File

@ -31,7 +31,7 @@
}
if (this.piskelName_) {
this.piskelName_.innerHTML = name;
this.piskelName_.textContent = name;
}
} catch (e) {
console.warn('Could not update header : ' + e.message);

View File

@ -1,15 +1,18 @@
(function () {
var ns = $.namespace('pskl.controller');
var TOGGLE_LAYER_SHORTCUT = 'alt+L';
ns.LayersListController = function (piskelController) {
this.piskelController = piskelController;
this.layerPreviewShortcut = pskl.service.keyboard.Shortcuts.MISC.LAYER_PREVIEW ;
this.layerPreviewShortcut = pskl.service.keyboard.Shortcuts.MISC.LAYER_PREVIEW;
this.startRenamingCurrentLayer_ = this.startRenamingCurrentLayer_.bind(this);
this.onRenameInput_ = this.onRenameInput_.bind(this);
};
ns.LayersListController.prototype.init = function () {
this.isRenaming = false;
this.layerItemTemplate_ = pskl.utils.Template.get('layer-item-template');
this.layerNameInputTemplate_ = pskl.utils.Template.get('layer-name-input-template');
this.rootEl = document.querySelector('.layers-list-container');
this.layersListEl = document.querySelector('.layers-list');
this.toggleLayerPreviewEl = document.querySelector('.layers-toggle-preview');
@ -17,28 +20,55 @@
this.rootEl.addEventListener('click', this.onClick_.bind(this));
this.toggleLayerPreviewEl.addEventListener('click', this.toggleLayerPreview_.bind(this));
this.createButtonTooltips_();
this.initToggleLayerPreview_();
this.renderLayerList_();
this.updateToggleLayerPreview_();
$.subscribe(Events.PISKEL_RESET, this.renderLayerList_.bind(this));
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this));
$.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
};
ns.LayersListController.prototype.renderLayerList_ = function () {
// Backup scroll before refresh.
var scrollTop = this.layersListEl.scrollTop;
this.layersListEl.innerHTML = '';
var layers = this.piskelController.getLayers();
layers.forEach(this.addLayerItem.bind(this));
this.updateButtonStatus_();
// Restore scroll
this.layersListEl.scrollTop = scrollTop;
// Ensure the currently the selected layer is visible.
var currentLayerEl = this.layersListEl.querySelector('.current-layer-item');
if (currentLayerEl) {
currentLayerEl.scrollIntoView({behavior: 'smooth'});
currentLayerEl.scrollIntoViewIfNeeded(false);
}
};
ns.LayersListController.prototype.createButtonTooltips_ = function () {
var addTooltip = pskl.utils.TooltipFormatter.format('Create a layer', null, [
{key : 'shift', description : 'Clone current layer'}
]);
var addButton = this.rootEl.querySelector('[data-action="add"]');
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 () {
var descriptors = [{description : 'Opacity defined in PREFERENCES'}];
var helpText = 'Preview all layers';
@ -50,7 +80,6 @@
ns.LayersListController.prototype.updateButtonStatus_ = function () {
var layers = this.piskelController.getLayers();
var currentLayer = this.piskelController.getCurrentLayer();
var index = this.piskelController.getCurrentLayerIndex();
var isLast = index === 0;
@ -91,19 +120,46 @@
ns.LayersListController.prototype.addLayerItem = function (layer, index) {
var isSelected = this.piskelController.getCurrentLayer() === layer;
var isRenaming = isSelected && this.isRenaming;
var layerItemHtml = pskl.utils.Template.replace(this.layerItemTemplate_, {
'layername' : layer.getName(),
'layerindex' : index,
'isselected:current-layer-item' : isSelected,
'opacity': layer.getOpacity()
'opacity' : layer.getOpacity()
});
var layerItem = pskl.utils.Template.createFromHTML(layerItemHtml);
this.layersListEl.insertBefore(layerItem, this.layersListEl.firstChild);
if (layerItem.offsetWidth < layerItem.scrollWidth) {
$(layerItem).find('.layer-name')
.addClass('overflowing-name')
.attr('title', layer.getName())
.tooltip();
var layerNameEl = layerItem.querySelector('.layer-name');
layerNameEl.classList.add('overflowing-name');
layerNameEl.setAttribute('title', layer.getName());
layerNameEl.setAttribute('rel', 'tooltip');
}
if (isSelected) {
layerItem.removeEventListener('dblclick', this.startRenamingCurrentLayer_);
layerItem.addEventListener('dblclick', this.startRenamingCurrentLayer_);
}
if (isRenaming) {
var layerNameInputHtml = pskl.utils.Template.replace(this.layerNameInputTemplate_, {
'layername' : layer.getName()
});
var layerNameInput = pskl.utils.Template.createFromHTML(layerNameInputHtml);
var layerNameEl = layerItem.querySelector('.layer-name');
layerItem.replaceChild(layerNameInput, layerNameEl);
layerNameInput.removeEventListener('blur', this.onRenameInput_);
layerNameInput.removeEventListener('keydown', this.onRenameInput_);
layerNameInput.addEventListener('blur', this.onRenameInput_);
layerNameInput.addEventListener('keydown', this.onRenameInput_);
layerNameInput.focus();
layerNameInput.select();
}
var opacity = layer.getOpacity();
if (opacity == 1) {
layerItem.querySelector('.layer-item-opacity').style.color = '#ffd700';
} else if (opacity == 0) {
layerItem.querySelector('.layer-item-opacity').style.color = '#969696';
} else {
layerItem.querySelector('.layer-item-opacity').style.color = '#ffffff';
}
};
@ -111,10 +167,15 @@
var el = evt.target || evt.srcElement;
var index;
if (el.classList.contains('button')) {
this.onButtonClick_(el);
this.onButtonClick_(el, evt);
} else if (el.classList.contains('layer-name')) {
var currentIndex = this.piskelController.getCurrentLayerIndex();
index = pskl.utils.Dom.getData(el, 'layerIndex');
this.piskelController.setCurrentLayerIndex(parseInt(index, 10));
if (index != currentIndex) {
var currentItem = el.parentElement.parentElement.querySelector('.current-layer-item');
currentItem.removeEventListener('dblclick', this.startRenamingCurrentLayer_);
this.piskelController.setCurrentLayerIndex(parseInt(index, 10));
}
} else if (el.classList.contains('layer-item-opacity')) {
index = pskl.utils.Dom.getData(el, 'layerIndex');
var layer = this.piskelController.getLayerAt(parseInt(index, 10));
@ -123,36 +184,55 @@
}
};
ns.LayersListController.prototype.renameCurrentLayer_ = function () {
var layer = this.piskelController.getCurrentLayer();
var name = window.prompt('Please enter the layer name', layer.getName());
if (name) {
var index = this.piskelController.getCurrentLayerIndex();
this.piskelController.renameLayerAt(index, name);
this.renderLayerList_();
ns.LayersListController.prototype.startRenamingCurrentLayer_ = function () {
this.isRenaming = true;
this.renderLayerList_();
};
ns.LayersListController.prototype.onRenameInput_ = function (evt) {
var el = evt.target || evt.srcElement;
if (evt.key === 'Enter') {
this.finishRenamingCurrentLayer_(el, el.value);
} else if (!evt.key || evt.key === 'Escape') {
this.finishRenamingCurrentLayer_(el);
}
};
ns.LayersListController.prototype.finishRenamingCurrentLayer_ = function (input, newName) {
if (newName) {
var index = this.piskelController.getCurrentLayerIndex();
this.piskelController.renameLayerAt(index, newName);
}
input.removeEventListener('blur', this.onRenameInput_);
input.removeEventListener('keydown', this.onRenameInput_);
this.isRenaming = false;
this.renderLayerList_();
};
ns.LayersListController.prototype.mergeDownCurrentLayer_ = function () {
var index = this.piskelController.getCurrentLayerIndex();
this.piskelController.mergeDownLayerAt(index);
this.renderLayerList_();
};
ns.LayersListController.prototype.onButtonClick_ = function (button) {
ns.LayersListController.prototype.onButtonClick_ = function (button, evt) {
var action = button.getAttribute('data-action');
if (action == 'up') {
this.piskelController.moveLayerUp();
this.piskelController.moveLayerUp(evt.shiftKey);
} else if (action == 'down') {
this.piskelController.moveLayerDown();
this.piskelController.moveLayerDown(evt.shiftKey);
} else if (action == 'add') {
this.piskelController.createLayer();
if (evt.shiftKey) {
this.piskelController.duplicateCurrentLayer();
} else {
this.piskelController.createLayer();
}
} else if (action == 'delete') {
this.piskelController.removeCurrentLayer();
} else if (action == 'merge') {
this.mergeDownCurrentLayer_();
} else if (action == 'edit') {
this.renameCurrentLayer_();
this.startRenamingCurrentLayer_();
}
};

View File

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

View File

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

View File

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

View File

@ -29,7 +29,7 @@
$.subscribe(Events.CURRENT_COLORS_UPDATED, this.fillColorListContainer.bind(this));
$.subscribe(Events.PRIMARY_COLOR_SELECTED, this.highlightSelectedColors.bind(this));
$.subscribe(Events.SECONDARY_COLOR_SELECTED, this.highlightSelectedColors.bind(this));
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this));
$.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
var shortcuts = pskl.service.keyboard.Shortcuts;
pskl.app.shortcutService.registerShortcut(shortcuts.COLOR.PREVIOUS_COLOR, this.selectPreviousColor_.bind(this));
@ -51,12 +51,17 @@
};
ns.PalettesListController.prototype.fillColorListContainer = function () {
var colors = this.getSelectedPaletteColors_();
if (colors.length > 0) {
var html = colors.map(function (color, index) {
return pskl.utils.Template.replace(this.paletteColorTemplate_, {color : color, index : index});
var html = colors.filter(function (color) {
return !!color;
}).map(function (color, index) {
return pskl.utils.Template.replace(this.paletteColorTemplate_, {
color : color,
index : index + 1,
title : color.toUpperCase()
});
}.bind(this)).join('');
this.colorListContainer_.innerHTML = html;
@ -64,6 +69,10 @@
} else {
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) {
@ -100,8 +109,8 @@
ns.PalettesListController.prototype.getCurrentColorIndex_ = function () {
var currentIndex = 0;
var selectedColor = document.querySelector('.' + PRIMARY_COLOR_CLASSNAME);
if (selectedColor) {
currentIndex = parseInt(selectedColor.dataset.colorIndex, 10);
if (selectedColor) {
currentIndex = parseInt(selectedColor.dataset.colorIndex, 10) - 1;
}
return currentIndex;
};
@ -139,14 +148,14 @@
};
ns.PalettesListController.prototype.onCreatePaletteClick_ = function (evt) {
$.publish(Events.DIALOG_DISPLAY, {
$.publish(Events.DIALOG_SHOW, {
dialogId : 'create-palette'
});
};
ns.PalettesListController.prototype.onEditPaletteClick_ = function (evt) {
var paletteId = this.colorPaletteSelect_.value;
$.publish(Events.DIALOG_DISPLAY, {
$.publish(Events.DIALOG_SHOW, {
dialogId : 'create-palette',
initArgs : paletteId
});

View File

@ -1,23 +1,19 @@
(function () {
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 () {
this.container = document.querySelector('.pen-size-container');
pskl.utils.Event.addEventListener(this.container, 'click', this.onPenSizeOptionClick_, this);
this.sizePicker.init(document.querySelector('.pen-size-container'));
$.subscribe(Events.PEN_SIZE_CHANGED, this.onPenSizeChanged_.bind(this));
this.updateSelectedOption_();
};
ns.PenSizeController.prototype.onPenSizeOptionClick_ = function (e) {
var size = e.target.dataset.size;
if (!isNaN(size)) {
size = parseInt(size, 10);
pskl.app.penSizeService.setPenSize(size);
}
ns.PenSizeController.prototype.onSizePickerChanged_ = function (size) {
pskl.app.penSizeService.setPenSize(size);
};
ns.PenSizeController.prototype.onPenSizeChanged_ = function (e) {
@ -25,19 +21,7 @@
};
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 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-pen-size', size);
}
if (selectedOption) {
selectedOption.classList.add('selected');
}
this.sizePicker.setSize(size);
};
})();

View File

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

View File

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

View File

@ -1,22 +1,30 @@
(function () {
var ns = $.namespace('pskl.controller');
var SHOW_MORE_CLASS = 'show-more';
ns.TransformationsController = function () {
this.tools = [
new pskl.tools.transform.Flip(),
new pskl.tools.transform.Rotate(),
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();
};
ns.TransformationsController.prototype.init = function () {
var container = document.querySelector('.transformations-container');
this.toolsContainer = container.querySelector('.tools-wrapper');
container.addEventListener('click', this.onTransformationClick_.bind(this));
this.container = document.querySelector('.transformations-container');
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.updateShowMoreLink_();
$.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
};
ns.TransformationsController.prototype.applyTool = function (toolId, evt) {
@ -30,13 +38,35 @@
ns.TransformationsController.prototype.onTransformationClick_ = function (evt) {
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() {
var html = this.tools.reduce(function (p, tool) {
return p + this.toolIconBuilder.createIcon(tool, 'left');
}.bind(this), '');
this.toolsContainer.innerHTML = html;
var toolsContainer = this.container.querySelector('.tools-wrapper');
toolsContainer.innerHTML = html;
};
})();

View File

@ -9,7 +9,7 @@
// This method is not attached to the prototype because we want to trigger it
// from markup generated for a notification message.
ns.UserWarningController.showPerformanceInfoDialog = function () {
$.publish(Events.DIALOG_DISPLAY, {
$.publish(Events.DIALOG_SHOW, {
dialogId: 'performance-info'
});
};
@ -38,7 +38,11 @@
var isWarningDisplayed = this.performanceLinkEl.classList.contains('visible');
// Show/hide the performance warning link depending on the received report.
this.performanceLinkEl.classList.toggle('visible', shouldDisplayWarning);
if (shouldDisplayWarning) {
this.performanceLinkEl.classList.add('visible');
} else {
this.performanceLinkEl.classList.remove('visible');
}
// Show a notification message if the new report indicates a performance issue
// and we were not displaying a warning before.

View File

@ -4,14 +4,19 @@
ns.AbstractDialogController = function () {};
ns.AbstractDialogController.prototype.init = function () {
this.closeButton = document.querySelector('.dialog-close');
this.closeButton.addEventListener('click', this.closeDialog.bind(this));
var closeButton = document.querySelector('.dialog-close');
this.addEventListener(closeButton, 'click', this.closeDialog);
};
ns.AbstractDialogController.prototype.destroy = function () {};
ns.AbstractDialogController.prototype.addEventListener = function (el, type, cb) {
pskl.utils.Event.addEventListener(el, type, cb, this);
};
ns.AbstractDialogController.prototype.destroy = function () {
pskl.utils.Event.removeAllEventListeners(this);
};
ns.AbstractDialogController.prototype.closeDialog = function () {
this.destroy();
$.publish(Events.DIALOG_HIDE);
};

View File

@ -1,8 +1,7 @@
(function () {
var ns = $.namespace('pskl.controller.dialogs');
ns.BrowseLocalController = function (piskelController) {
};
ns.BrowseLocalController = function (piskelController) {};
pskl.utils.inherit(ns.BrowseLocalController, ns.AbstractDialogController);
@ -11,13 +10,12 @@
this.localStorageItemTemplate_ = pskl.utils.Template.get('local-storage-item-template');
this.service_ = pskl.app.localStorageService;
this.piskelList = $('.local-piskel-list');
this.prevSessionContainer = $('.previous-session');
this.service_ = pskl.app.indexedDbStorageService;
this.piskelList = document.querySelector('.local-piskel-list');
this.fillLocalPiskelsList_();
this.piskelList.click(this.onPiskelsListClick_.bind(this));
this.piskelList.addEventListener('click', this.onPiskelsListClick_.bind(this));
};
ns.BrowseLocalController.prototype.onPiskelsListClick_ = function (evt) {
@ -37,21 +35,24 @@
};
ns.BrowseLocalController.prototype.fillLocalPiskelsList_ = function () {
var html = '';
var keys = this.service_.getKeys();
this.service_.getKeys().then(function (keys) {
var html = '';
keys.sort(function (k1, k2) {
if (k1.date < k2.date) {return 1;}
if (k1.date > k2.date) {return -1;}
return 0;
});
keys.sort(function (k1, k2) {
if (k1.date < k2.date) {return 1;}
if (k1.date > k2.date) {return -1;}
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));
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];
tableBody_.innerHTML = html;
var tableBody_ = this.piskelList.tBodies[0];
tableBody_.innerHTML = html;
}.bind(this));
};
})();

View File

@ -13,11 +13,12 @@
this.cheatsheetEl = document.getElementById('cheatsheetContainer');
this.eventTrapInput = document.getElementById('cheatsheetEventTrap');
pskl.utils.Event.addEventListener('.cheatsheet-restore-defaults', 'click', this.onRestoreDefaultsClick_, this);
pskl.utils.Event.addEventListener(this.cheatsheetEl, 'click', this.onCheatsheetClick_, this);
pskl.utils.Event.addEventListener(this.eventTrapInput, 'keydown', this.onEventTrapKeydown_, this);
this.addEventListener('.cheatsheet-restore-defaults', 'click', this.onRestoreDefaultsClick_);
this.addEventListener(this.cheatsheetEl, 'click', this.onCheatsheetClick_);
this.addEventListener(this.eventTrapInput, 'keydown', this.onEventTrapKeydown_);
$.subscribe(Events.SHORTCUTS_CHANGED, this.onShortcutsChanged_.bind(this));
this.onShortcutsChanged_ = this.onShortcutsChanged_.bind(this);
$.subscribe(Events.SHORTCUTS_CHANGED, this.onShortcutsChanged_);
this.initMarkup_();
document.querySelector('.cheatsheet-helptext').setAttribute('title', this.getHelptextTitle_());
@ -25,8 +26,11 @@
ns.CheatsheetController.prototype.destroy = function () {
this.eventTrapInput.blur();
pskl.utils.Event.removeAllEventListeners();
$.unsubscribe(Events.SHORTCUTS_CHANGED, this.onShortcutsChanged_);
this.cheatsheetEl = null;
this.superclass.destroy.call(this);
};
ns.CheatsheetController.prototype.onRestoreDefaultsClick_ = function () {
@ -140,7 +144,8 @@
title : title,
icon : descriptor.iconClass,
description : description,
key : this.formatKey_(shortcut.getDisplayKey()),
// Avoid sanitization
'!key!' : this.formatKey_(shortcut.getDisplayKey()),
className : shortcutClasses.join(' ')
});
@ -152,10 +157,10 @@
key = key.replace('ctrl', 'cmd');
key = key.replace('alt', 'option');
}
key = key.replace(/left/i, '&#65513;');
key = key.replace(/up/i, '&#65514;');
key = key.replace(/right/i, '&#65515;');
key = key.replace(/down/i, '&#65516;');
key = key.replace(/left/i, '&larr;');
key = key.replace(/up/i, '&uarr;');
key = key.replace(/right/i, '&rarr;');
key = key.replace(/down/i, '&darr;');
key = key.replace(/>/g, '&gt;');
key = key.replace(/</g, '&lt;');
// add spaces around '+' delimiters

View File

@ -19,12 +19,12 @@
var downloadButton = document.querySelector('.create-palette-download-button');
var importFileButton = document.querySelector('.create-palette-import-button');
this.nameInput.addEventListener('input', this.onNameInputChange_.bind(this));
this.hiddenFileInput.addEventListener('change', this.onFileInputChange_.bind(this));
this.addEventListener(this.nameInput, 'input', this.onNameInputChange_);
this.addEventListener(this.hiddenFileInput, 'change', this.onFileInputChange_);
buttonsContainer.addEventListener('click', this.onButtonClick_.bind(this));
downloadButton.addEventListener('click', this.onDownloadButtonClick_.bind(this));
importFileButton.addEventListener('click', this.onImportFileButtonClick_.bind(this));
this.addEventListener(buttonsContainer, 'click', this.onButtonClick_);
this.addEventListener(downloadButton, 'click', this.onDownloadButtonClick_);
this.addEventListener(importFileButton, 'click', this.onImportFileButtonClick_);
var colorsListContainer = document.querySelector('.colors-container');
this.colorsListWidget = new pskl.widgets.ColorsList(colorsListContainer);
@ -66,7 +66,10 @@
ns.CreatePaletteController.prototype.destroy = function () {
this.colorsListWidget.destroy();
this.superclass.destroy.call(this);
this.nameInput = null;
this.hiddenFileInput = null;
};
ns.CreatePaletteController.prototype.onButtonClick_ = function (evt) {

View File

@ -14,9 +14,9 @@
template : 'templates/dialogs/browse-local.html',
controller : ns.BrowseLocalController
},
'import-image' : {
template : 'templates/dialogs/import-image.html',
controller : ns.ImportImageController
'import' : {
template : 'templates/dialogs/import.html',
controller : ns.importwizard.ImportWizard
},
'performance-info' : {
template : 'templates/dialogs/performance-info.html',
@ -25,6 +25,10 @@
'unsupported-browser' : {
template : 'templates/dialogs/unsupported-browser.html',
controller : ns.UnsupportedBrowserController
},
'browse-backups' : {
template : 'templates/dialogs/browse-backups.html',
controller : ns.backups.BrowseBackups
}
};
@ -38,7 +42,7 @@
this.dialogContainer_ = document.getElementById('dialog-container');
this.dialogWrapper_ = document.getElementById('dialog-container-wrapper');
$.subscribe(Events.DIALOG_DISPLAY, this.onDialogDisplayEvent_.bind(this));
$.subscribe(Events.DIALOG_SHOW, this.onDialogDisplayEvent_.bind(this));
$.subscribe(Events.DIALOG_HIDE, this.hideDialog.bind(this));
var createPaletteShortcut = pskl.service.keyboard.Shortcuts.COLOR.CREATE_PALETTE;
@ -51,7 +55,6 @@
// adding the .animated class here instead of in the markup to avoid an animation during app startup
this.dialogWrapper_.classList.add('animated');
pskl.utils.Event.addEventListener(this.dialogWrapper_, 'click', this.onWrapperClicked_, this);
};
ns.DialogsController.prototype.onCreatePaletteShortcut_ = function () {

View File

@ -1,261 +0,0 @@
(function () {
var ns = $.namespace('pskl.controller.dialogs');
ns.ImportImageController = function (piskelController) {
this.importedImage_ = null;
this.file_ = null;
};
pskl.utils.inherit(ns.ImportImageController, ns.AbstractDialogController);
ns.ImportImageController.prototype.init = function (file) {
this.superclass.init.call(this);
this.file_ = file;
this.importPreview = $('.import-section-preview');
this.fileNameContainer = $('.import-image-file-name');
this.importType = $('[name=import-type]');
this.resizeWidth = $('[name=resize-width]');
this.resizeHeight = $('[name=resize-height]');
this.smoothResize = $('[name=smooth-resize-checkbox]');
this.frameSizeX = $('[name=frame-size-x]');
this.frameSizeY = $('[name=frame-size-y]');
this.frameOffsetX = $('[name=frame-offset-x]');
this.frameOffsetY = $('[name=frame-offset-y]');
this.importType.change(this.onImportTypeChange_.bind(this));
this.resizeWidth.keyup(this.onResizeInputKeyUp_.bind(this, 'width'));
this.resizeHeight.keyup(this.onResizeInputKeyUp_.bind(this, 'height'));
this.frameSizeX.keyup(this.onFrameInputKeyUp_.bind(this, 'frameSizeX'));
this.frameSizeY.keyup(this.onFrameInputKeyUp_.bind(this, 'frameSizeY'));
this.frameOffsetX.keyup(this.onFrameInputKeyUp_.bind(this, 'frameOffsetX'));
this.frameOffsetY.keyup(this.onFrameInputKeyUp_.bind(this, 'frameOffsetY'));
this.importImageForm = $('[name=import-image-form]');
this.importImageForm.submit(this.onImportFormSubmit_.bind(this));
pskl.utils.FileUtils.readImageFile(this.file_, this.onImageLoaded_.bind(this));
};
ns.ImportImageController.prototype.onImportTypeChange_ = function (evt) {
if (this.getImportType_() === 'single') {
// Using single image, so remove the frame grid
this.hideFrameGrid_();
} else {
// Using spritesheet import, so draw the frame grid in the preview
var x = this.sanitizeInputValue_(this.frameOffsetX, 0);
var y = this.sanitizeInputValue_(this.frameOffsetY, 0);
var w = this.sanitizeInputValue_(this.frameSizeX, 1);
var h = this.sanitizeInputValue_(this.frameSizeY, 1);
this.drawFrameGrid_(x, y, w, h);
}
};
ns.ImportImageController.prototype.onImportFormSubmit_ = function (evt) {
evt.originalEvent.preventDefault();
this.importImageToPiskel_();
};
ns.ImportImageController.prototype.onResizeInputKeyUp_ = function (from, evt) {
if (this.importedImage_) {
this.synchronizeResizeFields_(evt.target.value, from);
}
};
ns.ImportImageController.prototype.onFrameInputKeyUp_ = function (from, evt) {
if (this.importedImage_) {
this.synchronizeFrameFields_(evt.target.value, from);
}
};
ns.ImportImageController.prototype.synchronizeResizeFields_ = function (value, from) {
value = parseInt(value, 10);
if (isNaN(value)) {
value = 0;
}
var height = this.importedImage_.height;
var width = this.importedImage_.width;
// Select single image import type since the user changed a value here
this.importType.filter('[value="single"]').attr('checked', 'checked');
if (from === 'width') {
this.resizeHeight.val(Math.round(value * height / width));
} else {
this.resizeWidth.val(Math.round(value * width / height));
}
};
ns.ImportImageController.prototype.synchronizeFrameFields_ = function (value, from) {
value = parseInt(value, 10);
if (isNaN(value)) {
value = 0;
}
// Parse the frame input values
var frameSizeX = this.sanitizeInputValue_(this.frameSizeX, 1);
var frameSizeY = this.sanitizeInputValue_(this.frameSizeY, 1);
var frameOffsetX = this.sanitizeInputValue_(this.frameOffsetX, 0);
var frameOffsetY = this.sanitizeInputValue_(this.frameOffsetY, 0);
// Select spritesheet import type since the user changed a value here
this.importType.filter('[value="sheet"]').attr('checked', 'checked');
// Draw the grid
this.drawFrameGrid_(frameOffsetX, frameOffsetY, frameSizeX, frameSizeY);
};
ns.ImportImageController.prototype.sanitizeInputValue_ = function(input, minValue) {
var value = parseInt(input.val(), 10);
if (value <= minValue || isNaN(value)) {
input.val(minValue);
value = minValue;
}
return value;
};
ns.ImportImageController.prototype.getImportType_ = function () {
return this.importType.filter(':checked').val();
};
ns.ImportImageController.prototype.onImageLoaded_ = function (image) {
this.importedImage_ = image;
var w = this.importedImage_.width;
var h = this.importedImage_.height;
// FIXME : We remove the onload callback here because JsGif will insert
// the image again and we want to avoid retriggering the image onload
this.importedImage_.onload = function () {};
var fileName = this.extractFileNameFromPath_(this.file_.name);
this.fileNameContainer.html(fileName);
this.fileNameContainer.attr('title', fileName);
this.resizeWidth.val(w);
this.resizeHeight.val(h);
this.frameSizeX.val(w);
this.frameSizeY.val(h);
this.frameOffsetX.val(0);
this.frameOffsetY.val(0);
this.importPreview.width('auto');
this.importPreview.height('auto');
this.importPreview.html('');
this.importPreview.append(this.createImagePreview_());
};
ns.ImportImageController.prototype.createImagePreview_ = function () {
var image = document.createElement('IMG');
image.src = this.importedImage_.src;
return image;
};
ns.ImportImageController.prototype.extractFileNameFromPath_ = function (path) {
var parts = [];
if (path.indexOf('/') !== -1) {
parts = path.split('/');
} else if (path.indexOf('\\') !== -1) {
parts = path.split('\\');
} else {
parts = [path];
}
return parts[parts.length - 1];
};
ns.ImportImageController.prototype.importImageToPiskel_ = function () {
if (this.importedImage_) {
if (window.confirm('You are about to create a new Piskel, unsaved changes will be lost.')) {
pskl.app.importService.newPiskelFromImage(
this.importedImage_,
{
importType: this.getImportType_(),
frameSizeX: this.getImportType_() === 'single' ?
this.resizeWidth.val() : this.sanitizeInputValue_(this.frameSizeX, 1),
frameSizeY: this.getImportType_() === 'single' ?
this.resizeHeight.val() : this.sanitizeInputValue_(this.frameSizeY, 1),
frameOffsetX: this.sanitizeInputValue_(this.frameOffsetX, 0),
frameOffsetY: this.sanitizeInputValue_(this.frameOffsetY, 0),
smoothing: !!this.smoothResize.prop('checked')
},
this.closeDialog.bind(this)
);
}
}
};
ns.ImportImageController.prototype.drawFrameGrid_ = function (frameX, frameY, frameW, frameH) {
if (!this.importedImage_) {
return;
}
// Grab the sizes of the source and preview images
var width = this.importedImage_.width;
var height = this.importedImage_.height;
var previewWidth = this.importPreview.width();
var previewHeight = this.importPreview.height();
var canvasWrapper = this.importPreview.children('canvas');
var canvas = canvasWrapper.get(0);
if (!canvasWrapper.length) {
// Create a new canvas for the grid
canvas = pskl.utils.CanvasUtils.createCanvas(
previewWidth + 1,
previewHeight + 1);
this.importPreview.append(canvas);
canvasWrapper = $(canvas);
}
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
// Calculate the number of whole frames
var countX = Math.floor((width - frameX) / frameW);
var countY = Math.floor((height - frameY) / frameH);
if (countX > 0 && countY > 0) {
var scaleX = previewWidth / width;
var scaleY = previewHeight / height;
var maxWidth = countX * frameW + frameX;
var maxHeight = countY * frameH + frameY;
// Draw the vertical lines
for (var x = frameX + 0.5; x < maxWidth + 1 && x < width + 1; x += frameW) {
context.moveTo(x * scaleX, frameY * scaleY);
context.lineTo(x * scaleX, maxHeight * scaleY);
}
// Draw the horizontal lines
for (var y = frameY + 0.5; y < maxHeight + 1 && y < height + 1; y += frameH) {
context.moveTo(frameX * scaleX, y * scaleY);
context.lineTo(maxWidth * scaleX, y * scaleY);
}
// Set the line style to dashed
context.lineWidth = 1;
context.setLineDash([2, 1]);
context.strokeStyle = '#000000';
context.stroke();
// Show the canvas
canvasWrapper.show();
this.importPreview.addClass('no-border');
} else {
this.hideFrameGrid_();
}
};
ns.ImportImageController.prototype.hideFrameGrid_ = function() {
this.importPreview.children('canvas').hide();
this.importPreview.removeClass('no-border');
};
})();

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

@ -0,0 +1,202 @@
(function () {
var ns = $.namespace('pskl.controller.dialogs.importwizard');
var stepDefinitions = {
'IMAGE_IMPORT' : {
controller : ns.steps.ImageImport,
template : 'import-image-import'
},
'ADJUST_SIZE' : {
controller : ns.steps.AdjustSize,
template : 'import-adjust-size'
},
'INSERT_LOCATION' : {
controller : ns.steps.InsertLocation,
template : 'import-insert-location'
},
'SELECT_MODE' : {
controller : ns.steps.SelectMode,
template : 'import-select-mode'
}
};
ns.ImportWizard = function (piskelController, args) {
this.piskelController = piskelController;
// Merge data object used by steps to communicate and share their
// results.
this.mergeData = {
rawFiles : [],
mergePiskel: null,
origin: null,
resize: null,
insertIndex: null,
insertMode: null
};
};
pskl.utils.inherit(ns.ImportWizard, pskl.controller.dialogs.AbstractDialogController);
ns.ImportWizard.prototype.init = function (initArgs) {
this.superclass.init.call(this);
// Prepare mergeData object and wizard steps.
this.mergeData.rawFiles = initArgs.rawFiles;
this.steps = this.createSteps_();
// Start wizard widget.
var wizardContainer = document.querySelector('.import-wizard-container');
this.wizard = new pskl.widgets.Wizard(this.steps, wizardContainer);
this.wizard.init();
if (this.hasSingleImage_()) {
this.wizard.goTo('IMAGE_IMPORT');
} else if (this.hasSinglePiskelFile_()) {
// If a piskel file was provided we can directly go to
pskl.utils.PiskelFileUtils.loadFromFile(this.mergeData.rawFiles[0],
// onSuccess
function (piskel) {
this.mergeData.mergePiskel = piskel;
this.wizard.goTo('SELECT_MODE');
}.bind(this),
// onError
function (reason) {
this.closeDialog();
$.publish(Events.PISKEL_FILE_IMPORT_FAILED, [reason]);
}.bind(this)
);
} else {
console.error('Unsupported import. Only single piskel or single image are supported at the moment.');
this.closeDialog();
}
};
ns.ImportWizard.prototype.back = function () {
this.wizard.back();
this.wizard.getCurrentStep().instance.onShow();
};
ns.ImportWizard.prototype.next = function () {
var step = this.wizard.getCurrentStep();
if (step.name === 'IMAGE_IMPORT') {
if (this.piskelController.isEmpty()) {
// If the current sprite is empty finalize immediately and replace the current sprite.
this.mergeData.importMode = ns.steps.SelectMode.MODES.REPLACE;
this.finalizeImport_();
} else {
this.wizard.goTo('SELECT_MODE');
}
} else if (step.name === 'SELECT_MODE') {
if (this.mergeData.importMode === ns.steps.SelectMode.MODES.REPLACE) {
this.finalizeImport_();
} else if (this.hasSameSize_()) {
this.wizard.goTo('INSERT_LOCATION');
} else {
this.wizard.goTo('ADJUST_SIZE');
}
} else if (step.name === 'ADJUST_SIZE') {
this.wizard.goTo('INSERT_LOCATION');
} else if (step.name === 'INSERT_LOCATION') {
this.finalizeImport_();
}
};
ns.ImportWizard.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.ImportWizard.prototype.createSteps_ = function () {
// The IMAGE_IMPORT step is used only if there is a single image file
// being imported.
var hasSingleImage = this.hasSingleImage_();
var steps = {};
Object.keys(stepDefinitions).forEach(function (stepName) {
if (stepName === 'IMAGE_IMPORT' && !hasSingleImage) {
return;
}
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));
if (hasSingleImage) {
steps.IMAGE_IMPORT.el.classList.add('import-first-step');
} else {
steps.SELECT_MODE.el.classList.add('import-first-step');
}
return steps;
};
ns.ImportWizard.prototype.finalizeImport_ = function () {
var piskel = this.mergeData.mergePiskel;
var mode = this.mergeData.importMode;
if (mode === ns.steps.SelectMode.MODES.REPLACE) {
// Replace the current piskel and close the dialog.
if (window.confirm(Constants.CONFIRM_OVERWRITE)) {
this.piskelController.setPiskel(piskel);
this.closeDialog();
}
} else if (mode === ns.steps.SelectMode.MODES.MERGE) {
var merge = pskl.utils.MergeUtils.merge(this.piskelController.getPiskel(), piskel, {
insertIndex: this.mergeData.insertIndex,
insertMode: this.mergeData.insertMode,
origin: this.mergeData.origin,
resize: this.mergeData.resize
});
this.piskelController.setPiskel(merge);
// Set the first imported layer as selected.
var importedLayers = piskel.getLayers().length;
var currentLayers = this.piskelController.getLayers().length;
this.piskelController.setCurrentLayerIndex(currentLayers - importedLayers);
this.closeDialog();
}
};
ns.ImportWizard.prototype.hasSameSize_ = function () {
var piskel = this.mergeData.mergePiskel;
if (!piskel) {
return false;
}
return piskel.width === this.piskelController.getWidth() &&
piskel.height === this.piskelController.getHeight();
};
ns.ImportWizard.prototype.hasSingleImage_ = function () {
if (this.mergeData.rawFiles.length !== 1) {
return false;
}
var file = this.mergeData.rawFiles[0];
return file.type.indexOf('image') === 0;
};
ns.ImportWizard.prototype.hasSinglePiskelFile_ = function () {
if (this.mergeData.rawFiles.length !== 1) {
return false;
}
var file = this.mergeData.rawFiles[0];
return (/\.piskel$/).test(file.name);
};
})();

View File

@ -0,0 +1,65 @@
(function () {
var ns = $.namespace('pskl.controller.dialogs.importwizard.steps');
ns.AbstractImportStep = function (piskelController, importController, container) {
this.piskelController = piskelController;
this.container = container;
this.importController = importController;
this.mergeData = this.importController.mergeData;
};
ns.AbstractImportStep.prototype.init = function () {
this.nextButton = this.container.querySelector('.import-next-button');
this.backButton = this.container.querySelector('.import-back-button');
this.addEventListener(this.nextButton, 'click', this.onNextClick);
this.addEventListener(this.backButton, 'click', this.onBackClick);
};
ns.AbstractImportStep.prototype.addEventListener = function (el, type, cb) {
pskl.utils.Event.addEventListener(el, type, cb, this);
};
ns.AbstractImportStep.prototype.destroy = function () {
if (this.framePickerWidget) {
this.framePickerWidget.destroy();
}
pskl.utils.Event.removeAllEventListeners(this);
};
ns.AbstractImportStep.prototype.onNextClick = function () {
this.importController.next(this);
};
ns.AbstractImportStep.prototype.onBackClick = function () {
this.importController.back(this);
};
ns.AbstractImportStep.prototype.onShow = function () {
var mergePiskel = this.mergeData.mergePiskel;
if (!mergePiskel) {
return;
}
if (!this.framePickerWidget) {
var framePickerContainer = this.container.querySelector('.import-preview');
this.framePickerWidget = new pskl.widgets.FramePicker(mergePiskel, framePickerContainer);
this.framePickerWidget.init();
} else if (this.framePickerWidget.piskel != mergePiskel) {
// If the piskel displayed by the frame picker is different from the previous one,
// refresh the widget.
this.framePickerWidget.piskel = mergePiskel;
this.framePickerWidget.setFrameIndex(1);
}
var metaHtml = pskl.utils.Template.getAndReplace('import-meta-content', {
name : mergePiskel.getDescriptor().name,
dimensions : pskl.utils.StringUtils.formatSize(mergePiskel.getWidth(), mergePiskel.getHeight()),
frames : mergePiskel.getFrameCount(),
layers : mergePiskel.getLayers().length
});
this.container.querySelector('.import-meta').innerHTML = metaHtml;
};
})();

View File

@ -0,0 +1,109 @@
(function () {
var ns = $.namespace('pskl.controller.dialogs.importwizard.steps');
ns.AdjustSize = function (piskelController, importController, container) {
this.superclass.constructor.apply(this, arguments);
};
ns.AdjustSize.OPTIONS = {
KEEP: 'keep',
EXPAND: 'expand'
};
pskl.utils.inherit(ns.AdjustSize, ns.AbstractImportStep);
ns.AdjustSize.prototype.init = function () {
this.superclass.init.call(this);
// Create anchor widget
var anchorContainer = this.container.querySelector('.import-resize-anchor-container');
this.anchorWidget = new pskl.widgets.AnchorWidget(anchorContainer, this.onAnchorChange_.bind(this));
this.anchorWidget.setOrigin('TOPLEFT');
this.resizeInfoContainer = this.container.querySelector('.import-resize-info');
this.addEventListener(this.resizeInfoContainer, 'change', this.onResizeOptionChange_);
// By default, set the mode to expand to avoid losing any image content.
this.mergeData.resize = ns.AdjustSize.OPTIONS.EXPAND;
};
ns.AdjustSize.prototype.destroy = function () {
this.anchorWidget.destroy();
this.superclass.destroy.call(this);
};
ns.AdjustSize.prototype.onShow = function () {
this.refresh_();
this.superclass.onShow.call(this);
};
ns.AdjustSize.prototype.refresh_ = function () {
var isBigger = this.isImportedPiskelBigger_();
var keep = this.mergeData.resize === ns.AdjustSize.OPTIONS.KEEP;
// Refresh resize partial
var size = this.formatPiskelSize_(this.piskelController.getPiskel());
var newSize = this.formatPiskelSize_(this.mergeData.mergePiskel);
var markup;
if (isBigger) {
markup = pskl.utils.Template.getAndReplace('import-resize-bigger-partial', {
size : size,
newSize : newSize,
keepChecked : keep ? 'checked="checked"' : '',
expandChecked : keep ? '' : 'checked="checked"'
});
} else {
markup = pskl.utils.Template.getAndReplace('import-resize-smaller-partial', {
size : size,
newSize : newSize
});
}
this.resizeInfoContainer.innerHTML = markup;
// Update anchor widget
if (this.mergeData.origin) {
this.anchorWidget.setOrigin(this.mergeData.origin);
}
// Update anchor widget info
var anchorInfo = this.container.querySelector('.import-resize-anchor-info');
if (isBigger && keep) {
anchorInfo.innerHTML = [
'<div class="import-resize-warning">',
' Imported content will be cropped!',
'</div>',
'Select crop anchor:'
].join('');
} else if (isBigger) {
anchorInfo.innerHTML = 'Select resize anchor:';
} else {
anchorInfo.innerHTML = 'Select position anchor:';
}
};
ns.AdjustSize.prototype.onAnchorChange_ = function (origin) {
this.mergeData.origin = origin;
};
ns.AdjustSize.prototype.onResizeOptionChange_ = function () {
var value = this.resizeInfoContainer.querySelector(':checked').value;
if (this.mergeData.resize != value) {
this.mergeData.resize = value;
this.refresh_();
}
};
ns.AdjustSize.prototype.isImportedPiskelBigger_ = function () {
var piskel = this.mergeData.mergePiskel;
if (!piskel) {
return false;
}
return piskel.width > this.piskelController.getWidth() ||
piskel.height > this.piskelController.getHeight();
};
ns.AdjustSize.prototype.formatPiskelSize_ = function (piskel) {
return pskl.utils.StringUtils.formatSize(piskel.width, piskel.height);
};
})();

View File

@ -0,0 +1,282 @@
(function () {
var ns = $.namespace('pskl.controller.dialogs.importwizard.steps');
ns.ImageImport = function (piskelController, importController, container) {
this.superclass.constructor.apply(this, arguments);
this.importedImage_ = null;
this.file_ = null;
};
pskl.utils.inherit(ns.ImageImport, ns.AbstractImportStep);
ns.ImageImport.prototype.init = function (file) {
this.superclass.init.call(this);
// This step is only used if rawFiles contains a single image.
this.file_ = this.mergeData.rawFiles[0];
this.importPreview = this.container.querySelector('.import-section-preview');
this.fileNameContainer = this.container.querySelector('.import-image-file-name');
this.singleImportType = this.container.querySelector('[name=import-type][value=single]');
this.sheetImportType = this.container.querySelector('[name=import-type][value=sheet]');
this.resizeWidth = this.container.querySelector('[name=resize-width]');
this.resizeHeight = this.container.querySelector('[name=resize-height]');
this.smoothResize = this.container.querySelector('[name=smooth-resize-checkbox]');
this.frameSizeX = this.container.querySelector('[name=frame-size-x]');
this.frameSizeY = this.container.querySelector('[name=frame-size-y]');
this.frameOffsetX = this.container.querySelector('[name=frame-offset-x]');
this.frameOffsetY = this.container.querySelector('[name=frame-offset-y]');
this.addEventListener(this.singleImportType, 'change', this.onImportTypeChange_);
this.addEventListener(this.sheetImportType, 'change', this.onImportTypeChange_);
this.addEventListener(this.resizeWidth, 'keyup', this.onResizeInputKeyUp_);
this.addEventListener(this.resizeHeight, 'keyup', this.onResizeInputKeyUp_);
this.addEventListener(this.frameSizeX, 'keyup', this.onFrameInputKeyUp_);
this.addEventListener(this.frameSizeY, 'keyup', this.onFrameInputKeyUp_);
this.addEventListener(this.frameOffsetX, 'keyup', this.onFrameInputKeyUp_);
this.addEventListener(this.frameOffsetY, 'keyup', this.onFrameInputKeyUp_);
pskl.utils.FileUtils.readImageFile(this.file_, this.onImageLoaded_.bind(this));
if (this.piskelController.isEmpty()) {
this.nextButton.textContent = 'import';
}
};
ns.ImageImport.prototype.onNextClick = function () {
this.container.classList.add('import-image-loading');
this.createPiskelFromImage().then(function (piskel) {
this.mergeData.mergePiskel = piskel;
this.container.classList.remove('import-image-loading');
this.superclass.onNextClick.call(this);
}.bind(this)).catch(function (e) {
console.error(e);
});
};
ns.ImageImport.prototype.onShow = function () {
this.container.classList.remove('import-image-loading');
};
ns.ImageImport.prototype.createPiskelFromImage = function () {
var name = this.extractFileNameFromPath_(this.file_.name);
// Remove extension from filename.
name = name.replace(/\.[a-zA-Z]+$/, '');
var deferred = Q.defer();
pskl.app.importService.newPiskelFromImage(
this.importedImage_,
{
importType: this.getImportType_(),
frameSizeX: this.getImportType_() === 'single' ?
this.resizeWidth.value : this.sanitizeInputValue_(this.frameSizeX, 1),
frameSizeY: this.getImportType_() === 'single' ?
this.resizeHeight.value : this.sanitizeInputValue_(this.frameSizeY, 1),
frameOffsetX: this.sanitizeInputValue_(this.frameOffsetX, 0),
frameOffsetY: this.sanitizeInputValue_(this.frameOffsetY, 0),
smoothing: !!this.smoothResize.checked,
name: name
},
deferred.resolve
);
return deferred.promise;
};
ns.ImageImport.prototype.onImportTypeChange_ = function (evt) {
if (this.getImportType_() === 'single') {
// Using single image, so remove the frame grid
this.hideFrameGrid_();
} else {
// Using spritesheet import, so draw the frame grid in the preview
var x = this.sanitizeInputValue_(this.frameOffsetX, 0);
var y = this.sanitizeInputValue_(this.frameOffsetY, 0);
var w = this.sanitizeInputValue_(this.frameSizeX, 1);
var h = this.sanitizeInputValue_(this.frameSizeY, 1);
this.drawFrameGrid_(x, y, w, h);
}
};
ns.ImageImport.prototype.onResizeInputKeyUp_ = function (evt) {
var from = evt.target.getAttribute('name');
if (this.importedImage_) {
this.synchronizeResizeFields_(evt.target.value, from);
}
};
ns.ImageImport.prototype.onFrameInputKeyUp_ = function (evt) {
if (this.importedImage_) {
this.synchronizeFrameFields_(evt.target.value);
}
};
ns.ImageImport.prototype.synchronizeResizeFields_ = function (value, from) {
value = parseInt(value, 10);
if (isNaN(value)) {
value = 0;
}
var height = this.importedImage_.height;
var width = this.importedImage_.width;
// Select single image import type since the user changed a value here
this.singleImportType.checked = true;
if (from === 'resize-width') {
this.resizeHeight.value = Math.round(value * height / width);
} else {
this.resizeWidth.value = Math.round(value * width / height);
}
};
ns.ImageImport.prototype.synchronizeFrameFields_ = function (value) {
value = parseInt(value, 10);
if (isNaN(value)) {
value = 0;
}
// Parse the frame input values
var frameSizeX = this.sanitizeInputValue_(this.frameSizeX, 1);
var frameSizeY = this.sanitizeInputValue_(this.frameSizeY, 1);
var frameOffsetX = this.sanitizeInputValue_(this.frameOffsetX, 0);
var frameOffsetY = this.sanitizeInputValue_(this.frameOffsetY, 0);
// Select spritesheet import type since the user changed a value here
this.sheetImportType.checked = true;
// Draw the grid
this.drawFrameGrid_(frameOffsetX, frameOffsetY, frameSizeX, frameSizeY);
};
ns.ImageImport.prototype.sanitizeInputValue_ = function(input, minValue) {
var value = parseInt(input.value, 10);
if (value <= minValue || isNaN(value)) {
input.value = minValue;
value = minValue;
}
return value;
};
ns.ImageImport.prototype.getImportType_ = function () {
if (this.singleImportType.checked) {
return this.singleImportType.value;
} else if (this.sheetImportType.checked) {
return this.sheetImportType.value;
} else {
throw 'Could not find the currently selected import type';
}
};
ns.ImageImport.prototype.onImageLoaded_ = function (image) {
this.importedImage_ = image;
var w = this.importedImage_.width;
var h = this.importedImage_.height;
// FIXME : We remove the onload callback here because JsGif will insert
// the image again and we want to avoid retriggering the image onload
this.importedImage_.onload = function () {};
var fileName = this.extractFileNameFromPath_(this.file_.name);
this.fileNameContainer.textContent = fileName;
this.fileNameContainer.setAttribute('title', fileName);
this.resizeWidth.value = w;
this.resizeHeight.value = h;
this.frameSizeX.value = w;
this.frameSizeY.value = h;
this.frameOffsetX.value = 0;
this.frameOffsetY.value = 0;
this.importPreview.innerHTML = '';
this.importPreview.appendChild(this.createImagePreview_());
};
ns.ImageImport.prototype.createImagePreview_ = function () {
var image = document.createElement('IMG');
image.src = this.importedImage_.src;
return image;
};
ns.ImageImport.prototype.extractFileNameFromPath_ = function (path) {
var parts = [];
if (path.indexOf('/') !== -1) {
parts = path.split('/');
} else if (path.indexOf('\\') !== -1) {
parts = path.split('\\');
} else {
parts = [path];
}
return parts[parts.length - 1];
};
ns.ImageImport.prototype.drawFrameGrid_ = function (frameX, frameY, frameW, frameH) {
if (!this.importedImage_) {
return;
}
// Grab the sizes of the source and preview images
var width = this.importedImage_.width;
var height = this.importedImage_.height;
var image = this.importPreview.querySelector('img');
var previewWidth = image.offsetWidth;
var previewHeight = image.offsetHeight;
var canvas = this.importPreview.querySelector('canvas');
if (!canvas) {
// Create a new canvas for the grid
canvas = pskl.utils.CanvasUtils.createCanvas(
previewWidth + 1,
previewHeight + 1);
this.importPreview.appendChild(canvas);
}
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
// Calculate the number of whole frames
var countX = Math.floor((width - frameX) / frameW);
var countY = Math.floor((height - frameY) / frameH);
if (countX > 0 && countY > 0) {
var scaleX = previewWidth / width;
var scaleY = previewHeight / height;
var maxWidth = countX * frameW + frameX;
var maxHeight = countY * frameH + frameY;
// Draw the vertical lines
for (var x = frameX + 0.5; x < maxWidth + 1 && x < width + 1; x += frameW) {
context.moveTo(x * scaleX, frameY * scaleY);
context.lineTo(x * scaleX, maxHeight * scaleY);
}
// Draw the horizontal lines
for (var y = frameY + 0.5; y < maxHeight + 1 && y < height + 1; y += frameH) {
context.moveTo(frameX * scaleX, y * scaleY);
context.lineTo(maxWidth * scaleX, y * scaleY);
}
context.lineWidth = 1;
context.strokeStyle = 'gold';
context.stroke();
// Show the canvas
canvas.style.display = 'block';
} else {
this.hideFrameGrid_();
}
};
ns.ImageImport.prototype.hideFrameGrid_ = function() {
var canvas = this.importPreview.querySelector('canvas');
if (canvas) {
canvas.style.display = 'none';
}
};
})();

View File

@ -0,0 +1,59 @@
(function () {
var ns = $.namespace('pskl.controller.dialogs.importwizard.steps');
ns.InsertLocation = function () {
this.superclass.constructor.apply(this, arguments);
};
ns.InsertLocation.MODES = {
ADD : 'add',
INSERT : 'insert'
};
pskl.utils.inherit(ns.InsertLocation, ns.AbstractImportStep);
ns.InsertLocation.prototype.init = function () {
this.superclass.init.call(this);
this.framePreview = this.container.querySelector('.insert-frame-preview');
this.currentPiskelFramePickerWidget = new pskl.widgets.FramePicker(
this.piskelController.getPiskel(), this.framePreview);
this.insertModeContainer = this.container.querySelector('.insert-mode-container');
this.addEventListener(this.insertModeContainer, 'change', this.onInsertModeChange_);
this.mergeData.insertMode = ns.InsertLocation.MODES.ADD;
};
ns.InsertLocation.prototype.onInsertModeChange_ = function () {
var value = this.insertModeContainer.querySelector(':checked').value;
this.mergeData.insertMode = value;
if (this.mergeData.insertMode === ns.InsertLocation.MODES.ADD) {
this.currentPiskelFramePickerWidget.setFirstFrameIndex(0);
} else {
this.currentPiskelFramePickerWidget.setFirstFrameIndex(1);
}
};
ns.InsertLocation.prototype.onShow = function () {
// Initialize the frame picker on show, to be able to calculate correctly the
// container's offsetWidth and offsetHeight.
this.currentPiskelFramePickerWidget.init();
var currentFrameIndex = this.piskelController.getCurrentFrameIndex();
this.currentPiskelFramePickerWidget.setFrameIndex(currentFrameIndex + 1);
this.currentPiskelFramePickerWidget.setFirstFrameIndex(0);
this.superclass.onShow.call(this);
};
ns.InsertLocation.prototype.onNextClick = function () {
var insertIndex = this.currentPiskelFramePickerWidget.getFrameIndex();
this.mergeData.insertIndex = insertIndex;
this.superclass.onNextClick.call(this);
};
ns.InsertLocation.prototype.destroy = function () {
this.currentPiskelFramePickerWidget.destroy();
this.superclass.destroy.call(this);
};
})();

View File

@ -0,0 +1,42 @@
(function () {
var ns = $.namespace('pskl.controller.dialogs.importwizard.steps');
ns.SelectMode = function (piskelController, importController, container) {
this.superclass.constructor.apply(this, arguments);
};
ns.SelectMode.MODES = {
REPLACE : 'replace',
MERGE : 'merge'
};
pskl.utils.inherit(ns.SelectMode, ns.AbstractImportStep);
ns.SelectMode.prototype.init = function () {
this.superclass.init.call(this);
var replaceButton = this.container.querySelector('.import-mode-replace-button');
var mergeButton = this.container.querySelector('.import-mode-merge-button');
this.addEventListener(replaceButton, 'click', this.onReplaceButtonClick_);
this.addEventListener(mergeButton, 'click', this.onMergeButtonClick_);
};
ns.SelectMode.prototype.onShow = function () {
this.superclass.onShow.call(this);
};
ns.SelectMode.prototype.destroy = function () {
this.superclass.destroy.call(this);
};
ns.SelectMode.prototype.onReplaceButtonClick_ = function () {
this.mergeData.importMode = ns.SelectMode.MODES.REPLACE;
this.onNextClick();
};
ns.SelectMode.prototype.onMergeButtonClick_ = function () {
this.mergeData.importMode = ns.SelectMode.MODES.MERGE;
this.onNextClick();
};
})();

View File

@ -12,11 +12,15 @@
/**
* Set the current piskel. Will reset the selected frame and layer unless specified
* @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;
if (!preserveState) {
options = options || {};
if (!options.preserveState) {
this.currentLayerIndex = 0;
this.currentFrameIndex = 0;
}
@ -118,6 +122,17 @@
l.addFrameAt(this.createEmptyFrame_(), index);
}.bind(this));
this.onFrameAddedAt_(index);
};
ns.PiskelController.prototype.onFrameAddedAt_ = function (index) {
this.piskel.hiddenFrames = this.piskel.hiddenFrames.map(function (hiddenIndex) {
if (hiddenIndex >= index) {
return hiddenIndex + 1;
}
return hiddenIndex;
});
this.setCurrentFrameIndex(index);
};
@ -131,6 +146,15 @@
this.getLayers().forEach(function (l) {
l.removeFrameAt(index);
});
// Update the array of hidden frames since some hidden indexes might have shifted.
this.piskel.hiddenFrames = this.piskel.hiddenFrames.map(function (hiddenIndex) {
if (hiddenIndex > index) {
return hiddenIndex - 1;
}
return hiddenIndex;
});
// Current frame index is impacted if the removed frame was before the current frame
if (this.currentFrameIndex >= index && this.currentFrameIndex > 0) {
this.setCurrentFrameIndex(this.currentFrameIndex - 1);
@ -145,18 +169,68 @@
this.getLayers().forEach(function (l) {
l.duplicateFrameAt(index);
});
this.setCurrentFrameIndex(index + 1);
this.onFrameAddedAt_(index + 1);
};
/**
* Toggle frame visibility for the frame at the provided index.
* A visible frame will be included in the animated preview.
*/
ns.PiskelController.prototype.toggleFrameVisibilityAt = function (index) {
var hiddenFrames = this.piskel.hiddenFrames;
if (hiddenFrames.indexOf(index) === -1) {
hiddenFrames.push(index);
} else {
hiddenFrames = hiddenFrames.filter(function (i) {
return i !== index;
});
}
// Keep the hiddenFrames array sorted.
this.piskel.hiddenFrames = hiddenFrames.sort();
};
ns.PiskelController.prototype.moveFrame = function (fromIndex, toIndex) {
this.getLayers().forEach(function (l) {
l.moveFrame(fromIndex, toIndex);
});
// Update the array of hidden frames since some hidden indexes might have shifted.
this.piskel.hiddenFrames = this.piskel.hiddenFrames.map(function (index) {
if (index === fromIndex) {
return toIndex;
}
// All the frames between fromIndex and toIndex changed their index.
var isImpacted = index >= Math.min(fromIndex, toIndex) &&
index <= Math.max(fromIndex, toIndex);
if (isImpacted) {
if (fromIndex < toIndex) {
// If the frame moved to a higher index, all impacted frames had their index
// reduced by 1.
return index - 1;
} else {
// Otherwise, they had their index increased by 1.
return index + 1;
}
}
});
};
ns.PiskelController.prototype.hasVisibleFrameAt = function (index) {
return this.piskel.hiddenFrames.indexOf(index) === -1;
};
ns.PiskelController.prototype.getVisibleFrameIndexes = function () {
return this.getCurrentLayer().getFrames().map(function (frame, index) {
return index;
}).filter(function (index) {
return this.piskel.hiddenFrames.indexOf(index) === -1;
}.bind(this));
};
ns.PiskelController.prototype.getFrameCount = function () {
var layer = this.piskel.getLayerAt(0);
return layer.size();
return this.piskel.getFrameCount();
};
ns.PiskelController.prototype.setCurrentFrameIndex = function (index) {
@ -231,6 +305,14 @@
return name;
};
ns.PiskelController.prototype.duplicateCurrentLayer = function () {
var layer = this.getCurrentLayer();
var clone = pskl.utils.LayerUtils.clone(layer);
var currentLayerIndex = this.getCurrentLayerIndex();
this.piskel.addLayerAt(clone, currentLayerIndex + 1);
this.setCurrentLayerIndex(currentLayerIndex + 1);
};
ns.PiskelController.prototype.createLayer = function (name) {
if (!name) {
name = this.generateLayerName_();
@ -240,9 +322,9 @@
for (var i = 0 ; i < this.getFrameCount() ; i++) {
layer.addFrame(this.createEmptyFrame_());
}
this.piskel.addLayer(layer);
this.setCurrentLayerIndex(this.piskel.getLayers().length - 1);
var currentLayerIndex = this.getCurrentLayerIndex();
this.piskel.addLayerAt(layer, currentLayerIndex + 1);
this.setCurrentLayerIndex(currentLayerIndex + 1);
} else {
throw 'Layer name should be unique';
}
@ -252,15 +334,15 @@
return this.piskel.getLayersByName(name).length > 0;
};
ns.PiskelController.prototype.moveLayerUp = function () {
ns.PiskelController.prototype.moveLayerUp = function (toTop) {
var layer = this.getCurrentLayer();
this.piskel.moveLayerUp(layer);
this.piskel.moveLayerUp(layer, toTop);
this.selectLayer(layer);
};
ns.PiskelController.prototype.moveLayerDown = function () {
ns.PiskelController.prototype.moveLayerDown = function (toBottom) {
var layer = this.getCurrentLayer();
this.piskel.moveLayerDown(layer);
this.piskel.moveLayerDown(layer, toBottom);
this.selectLayer(layer);
};
@ -270,16 +352,28 @@
};
ns.PiskelController.prototype.removeLayerAt = function (index) {
if (this.getLayers().length > 1) {
var layer = this.getLayerAt(index);
if (layer) {
this.piskel.removeLayer(layer);
this.setCurrentLayerIndex(0);
}
if (!this.hasLayerAt(index)) {
return;
}
var layer = this.getLayerAt(index);
this.piskel.removeLayer(layer);
// Update the selected layer if needed.
if (this.getCurrentLayerIndex() === index) {
this.setCurrentLayerIndex(Math.max(0, index - 1));
}
};
ns.PiskelController.prototype.serialize = function () {
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

@ -31,11 +31,13 @@
this.saveWrap_('duplicateFrameAt', true);
this.saveWrap_('moveFrame', true);
this.saveWrap_('createLayer', true);
this.saveWrap_('duplicateCurrentLayer', true);
this.saveWrap_('mergeDownLayerAt', true);
this.saveWrap_('moveLayerUp', true);
this.saveWrap_('moveLayerDown', true);
this.saveWrap_('removeCurrentLayer', true);
this.saveWrap_('setLayerOpacityAt', true);
this.saveWrap_('toggleFrameVisibilityAt', true);
var shortcuts = pskl.service.keyboard.Shortcuts;
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.PREVIOUS_FRAME, this.selectPreviousFrame.bind(this));
@ -48,14 +50,25 @@
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.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) {

View File

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

View File

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

View File

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

View File

@ -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.addEventListener('.browse-local-button', 'click', this.onBrowseLocalClick_);
this.addEventListener('.browse-backups-button', 'click', this.onBrowseBackupsClick_);
this.addEventListener('.file-input-button', 'click', this.onFileInputClick_);
// different handlers, depending on the Environment
@ -23,24 +24,6 @@
this.addEventListener(this.hiddenOpenPiskelInput, 'change', this.onOpenPiskelChange_);
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 () {
@ -71,48 +54,54 @@
};
ns.ImportController.prototype.onBrowseLocalClick_ = function (evt) {
$.publish(Events.DIALOG_DISPLAY, {
$.publish(Events.DIALOG_SHOW, {
dialogId : 'browse-local'
});
this.closeDrawer_();
};
ns.ImportController.prototype.onBrowseBackupsClick_ = function (evt) {
$.publish(Events.DIALOG_SHOW, {
dialogId : 'browse-backups'
});
this.closeDrawer_();
};
ns.ImportController.prototype.openPiskelFile_ = function (file) {
if (this.isPiskel_(file)) {
pskl.utils.PiskelFileUtils.loadFromFile(file,
// onSuccess
function (piskel) {
pskl.app.piskelController.setPiskel(piskel);
},
// onError
function (reason) {
$.publish(Events.PISKEL_FILE_IMPORT_FAILED, [reason]);
});
$.publish(Events.DIALOG_SHOW, {
dialogId : 'import',
initArgs : {
rawFiles: [file]
}
});
this.closeDrawer_();
} else {
this.closeDrawer_();
console.error('The selected file is not a piskel file');
}
};
ns.ImportController.prototype.importPictureFromFile_ = function () {
var files = this.hiddenFileInput.files;
if (files.length == 1) {
var file = files[0];
if (this.isImage_(file)) {
$.publish(Events.DIALOG_DISPLAY, {
dialogId : 'import-image',
initArgs : file
});
this.closeDrawer_();
} else {
this.closeDrawer_();
console.error('File is not an image : ' + file.type);
}
// TODO : Simply filter and remove stuff
var areImages = Array.prototype.every.call(files, function (file) {
return file.type.indexOf('image') === 0;
});
if (areImages) {
$.publish(Events.DIALOG_SHOW, {
dialogId : 'import',
initArgs : {
rawFiles: files
}
});
this.closeDrawer_();
} else {
this.closeDrawer_();
console.error('Some files are not images');
}
};
ns.ImportController.prototype.isImage_ = function (file) {
return file.type.indexOf('image') === 0;
};
ns.ImportController.prototype.isPiskel_ = function (file) {
return (/\.piskel$/).test(file.name);
};

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.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.saveDesktopButton, 'click', this.saveToDesktop_);
this.safeAddEventListener_(this.saveDesktopAsNewButton, 'click', this.saveToDesktopAsNew_);
@ -62,7 +62,7 @@
ns.SaveController.prototype.insertSavePartials_ = function () {
this.getPartials_().forEach(function (partial) {
pskl.utils.Template.insert(this.saveForm, 'beforeend', partial);
this.saveForm.insertAdjacentHTML('beforeend', pskl.utils.Template.get(partial));
}.bind(this));
};
@ -99,7 +99,7 @@
if (pskl.app.isLoggedIn()) {
this.saveToGallery_();
} else {
this.saveToLocalStorage_();
this.saveToIndexedDb_();
}
};
@ -111,8 +111,8 @@
this.saveTo_('saveToGallery', false);
};
ns.SaveController.prototype.saveToLocalStorage_ = function () {
this.saveTo_('saveToLocalStorage', false);
ns.SaveController.prototype.saveToIndexedDb_ = function () {
this.saveTo_('saveToIndexedDb', false);
};
ns.SaveController.prototype.saveToDesktop_ = function () {

View File

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

View File

@ -22,6 +22,7 @@
ns.ExportController = function (piskelController) {
this.piskelController = piskelController;
this.tabsWidget = new pskl.widgets.Tabs(tabs, this, pskl.UserSettings.EXPORT_TAB);
this.onSizeInputChange_ = this.onSizeInputChange_.bind(this);
};
@ -47,47 +48,16 @@
this.onSizeInputChange_();
// Initialize tabs and panel
this.exportPanel = document.querySelector('.export-panel');
this.exportTabs = document.querySelector('.export-tabs');
this.addEventListener(this.exportTabs, 'click', this.onTabsClicked_);
var tab = pskl.UserSettings.get(pskl.UserSettings.EXPORT_TAB);
this.selectTab(tab);
var container = document.querySelector('.settings-section-export');
this.tabsWidget.init(container);
};
ns.ExportController.prototype.destroy = function () {
this.sizeInputWidget.destroy();
this.currentController.destroy();
this.tabsWidget.destroy();
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 () {
var value = parseFloat(this.scaleInput.value);
if (!isNaN(value)) {

View File

@ -14,14 +14,22 @@
pskl.utils.inherit(ns.GifExportController, pskl.controller.settings.AbstractSettingController);
ns.GifExportController.prototype.init = function () {
this.uploadStatusContainerEl = document.querySelector('.gif-upload-status');
this.previewContainerEl = document.querySelector('.gif-export-preview');
this.uploadButton = document.querySelector('.gif-upload-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.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 () {
@ -85,7 +93,8 @@
var isTransparent = layers.some(function (l) {return l.isTransparent();});
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
if (preserveColors) {
transparentColor = this.getTransparentColor(currentColors);
@ -104,6 +113,7 @@
width: width * zoom,
height: height * zoom,
preserveColors : preserveColors,
repeat: this.getRepeatSetting_() ? 0 : -1,
transparent : transparent
});
@ -148,6 +158,15 @@
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) {
if (imageUrl) {
var linkTpl = '<a class="highlight" href="{{link}}" target="_blank">{{shortLink}}</a>';

View File

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

View File

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

View File

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

View File

@ -0,0 +1,101 @@
(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));
this.spacingPicker = new pskl.widgets.SizePicker(this.onSpacingPickerChanged_.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 Spacing
var gridSpacing = pskl.UserSettings.get(pskl.UserSettings.GRID_SPACING);
this.spacingPicker.init(document.querySelector('.grid-spacing-container'));
this.spacingPicker.setSize(gridSpacing);
// 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.spacingPicker.destroy();
this.superclass.destroy.call(this);
};
ns.GridPreferencesController.prototype.onSizePickerChanged_ = function (size) {
pskl.UserSettings.set(pskl.UserSettings.GRID_WIDTH, size);
};
ns.GridPreferencesController.prototype.onSpacingPickerChanged_ = function (size) {
pskl.UserSettings.set(pskl.UserSettings.GRID_SPACING, size);
};
ns.GridPreferencesController.prototype.onEnableGridChange_ = function (evt) {
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

@ -6,8 +6,8 @@
this.container = document.querySelector('.resize-canvas');
var anchorWidgetContainer = this.container.querySelector('.resize-origin-container');
this.anchorWidget = new ns.AnchorWidget(anchorWidgetContainer);
var anchorWidgetContainer = this.container.querySelector('.resize-anchor-container');
this.anchorWidget = new pskl.widgets.AnchorWidget(anchorWidgetContainer);
this.defaultSizeController = new ns.DefaultSizeController(piskelController);
};
@ -28,7 +28,7 @@
});
var settings = pskl.UserSettings.get('RESIZE_SETTINGS');
var origin = ns.AnchorWidget.ORIGIN[settings.origin] || ns.AnchorWidget.ORIGIN.TOPLEFT;
var origin = pskl.widgets.AnchorWidget.ORIGIN[settings.origin] || 'TOPLEFT';
this.anchorWidget.setOrigin(origin);
if (settings.resizeContent) {
@ -61,27 +61,21 @@
ns.ResizeController.prototype.onResizeFormSubmit_ = function (evt) {
evt.preventDefault();
var resizedLayers = this.piskelController.getLayers().map(this.resizeLayer_.bind(this));
var currentPiskel = this.piskelController.getPiskel();
var fps = this.piskelController.getFPS();
var piskel = pskl.model.Piskel.fromLayers(resizedLayers, fps, currentPiskel.getDescriptor());
// propagate savepath to new Piskel
piskel.savePath = currentPiskel.savePath;
var piskel = pskl.utils.ResizeUtils.resizePiskel(currentPiskel, {
width : parseInt(this.widthInput.value, 10),
height : parseInt(this.heightInput.value, 10),
origin: this.anchorWidget.getOrigin(),
resizeContent: this.resizeContentCheckbox.checked
});
pskl.app.piskelController.setPiskel(piskel, true);
pskl.app.piskelController.setPiskel(piskel, {
preserveState: true
});
$.publish(Events.CLOSE_SETTINGS_DRAWER);
};
ns.ResizeController.prototype.resizeLayer_ = function (layer) {
var opacity = layer.getOpacity();
var resizedFrames = layer.getFrames().map(this.resizeFrame_.bind(this));
var resizedLayer = pskl.model.Layer.fromFrames(layer.getName(), resizedFrames);
resizedLayer.setOpacity(opacity);
return resizedLayer;
};
ns.ResizeController.prototype.onResizeContentChange_ = function (evt) {
var target = evt.target;
if (target.checked) {
@ -107,55 +101,4 @@
maintainRatio : !!this.maintainRatioCheckbox.checked
});
};
/***********************/
/* RESIZE LOGIC */
/***********************/
ns.ResizeController.prototype.resizeFrame_ = function (frame) {
var width = parseInt(this.widthInput.value, 10);
var height = parseInt(this.heightInput.value, 10);
if (this.resizeContentCheckbox.checked) {
return pskl.utils.FrameUtils.resize(frame, width, height, false);
} else {
var resizedFrame = new pskl.model.Frame(width, height);
frame.forEachPixel(function (color, x, y) {
var translated = this.translateCoordinates_(x, y, frame, resizedFrame);
if (resizedFrame.containsPixel(translated.x, translated.y)) {
resizedFrame.setPixel(translated.x, translated.y, color);
}
}.bind(this));
return resizedFrame;
}
};
ns.ResizeController.prototype.translateCoordinates_ = function (x, y, frame, resizedFrame) {
return {
x : this.translateX_(x, frame.width, resizedFrame.width),
y : this.translateY_(y, frame.height, resizedFrame.height)
};
};
ns.ResizeController.prototype.translateX_ = function (x, width, resizedWidth) {
var origin = this.anchorWidget.getOrigin();
if (origin.indexOf('LEFT') != -1) {
return x;
} else if (origin.indexOf('RIGHT') != -1) {
return x - (width - resizedWidth);
} else {
return x - Math.round((width - resizedWidth) / 2);
}
};
ns.ResizeController.prototype.translateY_ = function (y, height, resizedHeight) {
var origin = this.anchorWidget.getOrigin();
if (origin.indexOf('TOP') != -1) {
return y;
} else if (origin.indexOf('BOTTOM') != -1) {
return y - (height - resizedHeight);
} else {
return y - Math.round((height - resizedHeight) / 2);
}
};
})();

View File

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

View File

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

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