21 Commits

Author SHA1 Message Date
5e46d8b7e2 docs(readme): add Greenkeeper badge 2017-05-06 14:44:55 +00:00
e15d7b2422 chore(package): update dependencies 2017-05-06 14:43:20 +00:00
a8c85b8a29 Issue #654 : stop using jquery to create the FramesList markup 2017-04-01 12:48:11 +02:00
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
98768b2e5b select color using alt+click (fixes #623) 2017-03-02 01:39:28 +01:00
62b1b8baf0 Merge pull request #642 from juliandescottes/sanitize-strings
sanitize strings coming from user inputs
2017-02-23 21:01:05 +01:00
11a063de12 sanitize strings coming from user inputs 2017-02-23 19:37:29 +01:00
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
974450837e add confirmation message when closing desktop app with unsaved changes 2017-02-19 13:19:09 +01:00
c6c64af2fd Use Q deferred instead of native promise (IE11) 2017-02-11 16:57:56 +01:00
c68b82339c remove classList toggle with 2nd argument for IE11 2017-02-11 16:51:27 +01:00
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
f30e16386d Use standard HTML entities for the arrows displayed on the keyboard shortcut list 2017-02-05 11:47:45 +01:00
08b97cb6f0 select previous layer after deleting layer 2017-02-04 14:32:23 +01:00
b6fa769ba1 temporary fix for layerlist scroll 2017-02-04 14:09:27 +01:00
47b09b10c5 Merge branch 'master' of https://github.com/juliandescottes/piskel 2017-02-04 13:49:20 +01:00
f039a89572 disable layerlist smoothscrolling 2017-02-04 13:43:49 +01:00
e74329f04e Merge pull request #629 from GMartigny/master
Add CodeClimate
2017-02-02 19:30:40 +01:00
8959b201e9 Excludes lib from linting 2017-02-01 16:17:38 +01:00
f5491dc557 Irregular whitespace 2017-02-01 16:15:02 +01:00
9ce3d44cc6 Add config file for CodeClimate 2017-02-01 15:51:13 +01:00
26 changed files with 280 additions and 113 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/

View File

@ -1,6 +1,8 @@
Piskel Piskel
====== ======
[![Greenkeeper badge](https://badges.greenkeeper.io/juliandescottes/piskel.svg)](https://greenkeeper.io/)
[![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/juliandescottes/piskel.png?branch=master)](https://travis-ci.org/juliandescottes/piskel) [![Built with Grunt](https://cdn.gruntjs.com/builtwith.png)](http://gruntjs.com/)
A simple web-based tool for Spriting and Pixel art. A simple web-based tool for Spriting and Pixel art.

View File

@ -28,30 +28,30 @@
}, },
"devDependencies": { "devDependencies": {
"dateformat": "2.0.0", "dateformat": "2.0.0",
"fs-extra": "1.0.0", "fs-extra": "3.0.1",
"grunt": "^0.4.5", "grunt": "^1.0.1",
"grunt-casperjs": "^2.2.1", "grunt-casperjs": "^2.2.1",
"grunt-contrib-clean": "1.0.0", "grunt-contrib-clean": "1.1.0",
"grunt-contrib-concat": "1.0.1", "grunt-contrib-concat": "1.0.1",
"grunt-contrib-connect": "1.0.2", "grunt-contrib-connect": "1.0.2",
"grunt-contrib-copy": "1.0.0", "grunt-contrib-copy": "1.0.0",
"grunt-contrib-jshint": "1.1.0", "grunt-contrib-jshint": "1.1.0",
"grunt-contrib-uglify": "1.0.1", "grunt-contrib-uglify": "2.3.0",
"grunt-contrib-watch": "1.0.0", "grunt-contrib-watch": "1.0.0",
"grunt-include-replace": "4.0.1", "grunt-include-replace": "5.0.0",
"grunt-jscs": "2.8.0", "grunt-jscs": "3.0.1",
"grunt-karma": "1.0.0", "grunt-karma": "2.0.0",
"grunt-leading-indent": "0.2.0", "grunt-leading-indent": "0.2.0",
"grunt-nw-builder": "3.1.0", "grunt-nw-builder": "3.1.0",
"grunt-open": "0.2.3", "grunt-open": "0.2.3",
"grunt-replace": "1.0.1", "grunt-replace": "1.0.1",
"grunt-spritesmith": "6.3.0", "grunt-spritesmith": "6.4.0",
"jasmine-core": "2.1.0", "jasmine-core": "2.6.1",
"karma": "1.3.0", "karma": "1.7.0",
"karma-chrome-launcher": "1.0.1", "karma-chrome-launcher": "2.1.1",
"karma-jasmine": "1.0.2", "karma-jasmine": "1.1.0",
"karma-phantomjs-launcher": "0.2.3", "karma-phantomjs-launcher": "1.0.4",
"load-grunt-tasks": "3.5.0", "load-grunt-tasks": "3.5.2",
"phantomjs": "2.1.7", "phantomjs": "2.1.7",
"phantomjs-polyfill-object-assign": "0.0.2", "phantomjs-polyfill-object-assign": "0.0.2",
"promise-polyfill": "6.0.2", "promise-polyfill": "6.0.2",

View File

@ -78,7 +78,7 @@
this.framesListController = new pskl.controller.FramesListController( this.framesListController = new pskl.controller.FramesListController(
this.piskelController, this.piskelController,
$('#preview-list')); $('#preview-list-wrapper').get(0));
this.framesListController.init(); this.framesListController.init();
this.layersListController = new pskl.controller.LayersListController(this.piskelController); this.layersListController = new pskl.controller.LayersListController(this.piskelController);

View File

@ -163,6 +163,9 @@
if (event.button === Constants.MIDDLE_BUTTON) { if (event.button === Constants.MIDDLE_BUTTON) {
this.dragHandler.startDrag(event.clientX, event.clientY); this.dragHandler.startDrag(event.clientX, event.clientY);
} else if (event.altKey && !this.currentToolBehavior.supportsAlt()) {
this.currentToolBehavior.hideHighlightedPixel(this.overlayFrame);
this.isPickingColor = true;
} else { } else {
this.currentToolBehavior.hideHighlightedPixel(this.overlayFrame); this.currentToolBehavior.hideHighlightedPixel(this.overlayFrame);
$.publish(Events.TOOL_PRESSED); $.publish(Events.TOOL_PRESSED);
@ -210,6 +213,8 @@
if (this.isClicked) { if (this.isClicked) {
if (pskl.app.mouseStateService.isMiddleButtonPressed()) { if (pskl.app.mouseStateService.isMiddleButtonPressed()) {
this.dragHandler.updateDrag(x, y); this.dragHandler.updateDrag(x, y);
} else if (this.isPickingColor) {
// Nothing to do on mousemove when picking a color with ALT+click.
} else { } else {
$.publish(Events.MOUSE_EVENT, [event, this]); $.publish(Events.MOUSE_EVENT, [event, this]);
this.currentToolBehavior.moveToolAt( this.currentToolBehavior.moveToolAt(
@ -294,39 +299,63 @@
* @private * @private
*/ */
ns.DrawingController.prototype.onMouseup_ = function (event) { ns.DrawingController.prototype.onMouseup_ = function (event) {
var frame = this.piskelController.getCurrentFrame(); if (!this.isClicked) {
return;
}
var coords = this.getSpriteCoordinates(event.clientX, event.clientY); var coords = this.getSpriteCoordinates(event.clientX, event.clientY);
if (event.changedTouches && event.changedTouches[0]) { if (event.changedTouches && event.changedTouches[0]) {
coords = this.getSpriteCoordinates(event.changedTouches[0].clientX, event.changedTouches[0].clientY); 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()) { this.isClicked = false;
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
);
$.publish(Events.TOOL_RELEASED); var isMiddleButton = pskl.app.mouseStateService.isMiddleButtonPressed();
} var isMiddleClick = isMiddleButton && !this.dragHandler.isDragging();
$.publish(Events.MOUSE_EVENT, [event, this]); 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;
} 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]);
}; };
/** /**

View File

@ -11,6 +11,7 @@
ns.FramesListController = function (piskelController, container) { ns.FramesListController = function (piskelController, container) {
this.piskelController = piskelController; this.piskelController = piskelController;
this.container = container; this.container = container;
this.previewList = container.querySelector('#preview-list');
this.refreshZoom_(); this.refreshZoom_();
this.redrawFlag = true; this.redrawFlag = true;
@ -31,8 +32,9 @@
$.subscribe(Events.PISKEL_RESET, this.refreshZoom_.bind(this)); $.subscribe(Events.PISKEL_RESET, this.refreshZoom_.bind(this));
$('#preview-list-scroller').scroll(this.updateScrollerOverflows.bind(this)); this.previewListScroller = document.querySelector('#preview-list-scroller');
this.container.get(0).addEventListener('click', this.onContainerClick_.bind(this)); this.previewListScroller.addEventListener('scroll', this.updateScrollerOverflows.bind(this));
this.container.addEventListener('click', this.onContainerClick_.bind(this));
this.updateScrollerOverflows(); this.updateScrollerOverflows();
}; };
@ -64,11 +66,11 @@
}; };
ns.FramesListController.prototype.updateScrollerOverflows = function () { ns.FramesListController.prototype.updateScrollerOverflows = function () {
var scroller = $('#preview-list-scroller'); var scroller = this.previewListScroller;
var scrollerHeight = scroller.height(); var scrollerHeight = scroller.offsetHeight;
var scrollTop = scroller.scrollTop(); var scrollTop = scroller.scrollTop;
var scrollerContentHeight = $('#preview-list').height(); var scrollerContentHeight = this.previewList.offsetHeight;
var treshold = $('.top-overflow').height(); var treshold = this.container.querySelector('.top-overflow').offsetHeight;
var overflowTop = false; var overflowTop = false;
var overflowBottom = false; var overflowBottom = false;
@ -81,9 +83,8 @@
overflowBottom = true; overflowBottom = true;
} }
} }
var wrapper = $('#preview-list-wrapper'); this.container.classList.toggle('top-overflow-visible', overflowTop);
wrapper.toggleClass('top-overflow-visible', overflowTop); this.container.classList.toggle('bottom-overflow-visible', overflowBottom);
wrapper.toggleClass('bottom-overflow-visible', overflowBottom);
}; };
ns.FramesListController.prototype.onContainerClick_ = function (event) { ns.FramesListController.prototype.onContainerClick_ = function (event) {
@ -97,12 +98,12 @@
if (action === ACTION.CLONE) { if (action === ACTION.CLONE) {
this.piskelController.duplicateFrameAt(index); this.piskelController.duplicateFrameAt(index);
var clonedTile = this.createPreviewTile_(index + 1); 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.tiles.splice(index, 0, clonedTile);
this.updateScrollerOverflows(); this.updateScrollerOverflows();
} else if (action === ACTION.DELETE) { } else if (action === ACTION.DELETE) {
this.piskelController.removeFrameAt(index); this.piskelController.removeFrameAt(index);
this.container.get(0).removeChild(this.tiles[index]); this.previewList.removeChild(this.tiles[index]);
this.tiles.splice(index, 1); this.tiles.splice(index, 1);
this.updateScrollerOverflows(); this.updateScrollerOverflows();
} else if (action === ACTION.SELECT && !this.justDropped) { } else if (action === ACTION.SELECT && !this.justDropped) {
@ -111,7 +112,7 @@
this.piskelController.addFrame(); this.piskelController.addFrame();
var newtile = this.createPreviewTile_(this.tiles.length); var newtile = this.createPreviewTile_(this.tiles.length);
this.tiles.push(newtile); this.tiles.push(newtile);
this.container.get(0).insertBefore(newtile, this.addFrameTile); this.previewList.insertBefore(newtile, this.addFrameTile);
this.updateScrollerOverflows(); this.updateScrollerOverflows();
} }
@ -147,7 +148,7 @@
} }
// Hide/Show buttons if needed // 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'; var display = (this.piskelController.getFrameCount() > 1) ? 'block' : 'none';
for (i = 0, length = buttons.length; i < length; i++) { for (i = 0, length = buttons.length; i < length; i++) {
buttons[i].style.display = display; buttons[i].style.display = display;
@ -158,7 +159,8 @@
}; };
ns.FramesListController.prototype.createPreviews_ = function () { ns.FramesListController.prototype.createPreviews_ = function () {
this.container.html(''); this.previewList.innerHTML = '';
// Manually remove tooltips since mouseout events were shortcut by the DOM refresh: // Manually remove tooltips since mouseout events were shortcut by the DOM refresh:
$('.tooltip').remove(); $('.tooltip').remove();
@ -166,7 +168,7 @@
for (var i = 0 ; i < frameCount ; i++) { for (var i = 0 ; i < frameCount ; i++) {
var tile = this.createPreviewTile_(i); var tile = this.createPreviewTile_(i);
this.container.append(tile); this.previewList.appendChild(tile);
this.tiles[i] = tile; this.tiles[i] = tile;
} }
// Append 'new empty frame' button // Append 'new empty frame' button
@ -176,7 +178,7 @@
newFrameButton.setAttribute('data-tile-action', ACTION.NEW_FRAME); newFrameButton.setAttribute('data-tile-action', ACTION.NEW_FRAME);
newFrameButton.innerHTML = '<div class="add-frame-action-icon icon-frame-plus-white">' + newFrameButton.innerHTML = '<div class="add-frame-action-icon icon-frame-plus-white">' +
'</div><div class="label">Add new frame</div>'; '</div><div class="label">Add new frame</div>';
this.container.append(newFrameButton); this.previewList.appendChild(newFrameButton);
this.addFrameTile = newFrameButton; this.addFrameTile = newFrameButton;
this.updateScrollerOverflows(); this.updateScrollerOverflows();
@ -186,8 +188,7 @@
* @private * @private
*/ */
ns.FramesListController.prototype.initDragndropBehavior_ = function () { ns.FramesListController.prototype.initDragndropBehavior_ = function () {
$(this.previewList).sortable({
$('#preview-list').sortable({
placeholder: 'preview-tile preview-tile-drop-proxy', placeholder: 'preview-tile preview-tile-drop-proxy',
update: $.proxy(this.onUpdate_, this), update: $.proxy(this.onUpdate_, this),
stop: $.proxy(this.onSortableStop_, this), stop: $.proxy(this.onSortableStop_, this),
@ -195,7 +196,7 @@
axis: 'y', axis: 'y',
tolerance: 'pointer' tolerance: 'pointer'
}); });
$('#preview-list').disableSelection(); $(this.previewList).disableSelection();
}; };
/** /**

View File

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

View File

@ -27,15 +27,21 @@
}; };
ns.LayersListController.prototype.renderLayerList_ = function () { ns.LayersListController.prototype.renderLayerList_ = function () {
// Backup scroll before refresh.
var scrollTop = this.layersListEl.scrollTop;
this.layersListEl.innerHTML = ''; this.layersListEl.innerHTML = '';
var layers = this.piskelController.getLayers(); var layers = this.piskelController.getLayers();
layers.forEach(this.addLayerItem.bind(this)); layers.forEach(this.addLayerItem.bind(this));
this.updateButtonStatus_(); this.updateButtonStatus_();
// Restore scroll
this.layersListEl.scrollTop = scrollTop;
// Ensure the currently the selected layer is visible. // Ensure the currently the selected layer is visible.
var currentLayerEl = this.layersListEl.querySelector('.current-layer-item'); var currentLayerEl = this.layersListEl.querySelector('.current-layer-item');
if (currentLayerEl) { if (currentLayerEl) {
currentLayerEl.scrollIntoView({behavior: 'smooth'}); currentLayerEl.scrollIntoViewIfNeeded(false);
} }
}; };

View File

@ -100,7 +100,7 @@
ns.PalettesListController.prototype.getCurrentColorIndex_ = function () { ns.PalettesListController.prototype.getCurrentColorIndex_ = function () {
var currentIndex = 0; var currentIndex = 0;
var selectedColor = document.querySelector('.' + PRIMARY_COLOR_CLASSNAME); var selectedColor = document.querySelector('.' + PRIMARY_COLOR_CLASSNAME);
if (selectedColor) { if (selectedColor) {
currentIndex = parseInt(selectedColor.dataset.colorIndex, 10); currentIndex = parseInt(selectedColor.dataset.colorIndex, 10);
} }
return currentIndex; return currentIndex;

View File

@ -38,7 +38,11 @@
var isWarningDisplayed = this.performanceLinkEl.classList.contains('visible'); var isWarningDisplayed = this.performanceLinkEl.classList.contains('visible');
// Show/hide the performance warning link depending on the received report. // 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 // Show a notification message if the new report indicates a performance issue
// and we were not displaying a warning before. // and we were not displaying a warning before.

View File

@ -48,7 +48,10 @@
keys.forEach((function (key) { keys.forEach((function (key) {
var date = pskl.utils.DateUtils.format(key.date, '{{Y}}/{{M}}/{{D}} {{H}}:{{m}}'); 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}); html += pskl.utils.Template.replace(this.localStorageItemTemplate_, {
name : key.name,
date : date
});
}).bind(this)); }).bind(this));
var tableBody_ = this.piskelList.get(0).tBodies[0]; var tableBody_ = this.piskelList.get(0).tBodies[0];

View File

@ -152,10 +152,10 @@
key = key.replace('ctrl', 'cmd'); key = key.replace('ctrl', 'cmd');
key = key.replace('alt', 'option'); key = key.replace('alt', 'option');
} }
key = key.replace(/left/i, '&#65513;'); key = key.replace(/left/i, '&larr;');
key = key.replace(/up/i, '&#65514;'); key = key.replace(/up/i, '&uarr;');
key = key.replace(/right/i, '&#65515;'); key = key.replace(/right/i, '&rarr;');
key = key.replace(/down/i, '&#65516;'); key = key.replace(/down/i, '&darr;');
key = key.replace(/>/g, '&gt;'); key = key.replace(/>/g, '&gt;');
key = key.replace(/</g, '&lt;'); key = key.replace(/</g, '&lt;');
// add spaces around '+' delimiters // add spaces around '+' delimiters

View File

@ -135,7 +135,7 @@
this.importedImage_.onload = function () {}; this.importedImage_.onload = function () {};
var fileName = this.extractFileNameFromPath_(this.file_.name); var fileName = this.extractFileNameFromPath_(this.file_.name);
this.fileNameContainer.html(fileName); this.fileNameContainer.text(fileName);
this.fileNameContainer.attr('title', fileName); this.fileNameContainer.attr('title', fileName);
this.resizeWidth.val(w); this.resizeWidth.val(w);

View File

@ -270,12 +270,16 @@
}; };
ns.PiskelController.prototype.removeLayerAt = function (index) { ns.PiskelController.prototype.removeLayerAt = function (index) {
if (this.getLayers().length > 1) { if (!this.hasLayerAt(index)) {
var layer = this.getLayerAt(index); return;
if (layer) { }
this.piskel.removeLayer(layer);
this.setCurrentLayerIndex(0); 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));
} }
}; };

View File

@ -124,7 +124,13 @@
if (this.previewSizes.hasOwnProperty(size)) { if (this.previewSizes.hasOwnProperty(size)) {
var previewSize = this.previewSizes[size]; var previewSize = this.previewSizes[size];
var isSizeEnabled = validSizes.indexOf(size) != -1; var isSizeEnabled = validSizes.indexOf(size) != -1;
previewSize.button.classList.toggle('preview-contextual-action-hidden', !isSizeEnabled);
// 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');
}
} }
} }
@ -174,7 +180,13 @@
ns.PreviewController.prototype.updateOnionSkinPreview_ = function () { ns.PreviewController.prototype.updateOnionSkinPreview_ = function () {
var enabledClassname = 'preview-toggle-onion-skin-enabled'; var enabledClassname = 'preview-toggle-onion-skin-enabled';
var isEnabled = pskl.UserSettings.get(pskl.UserSettings.ONION_SKIN); var isEnabled = pskl.UserSettings.get(pskl.UserSettings.ONION_SKIN);
this.toggleOnionSkinButton.classList.toggle(enabledClassname, isEnabled);
// classList.toggle is not available on IE11.
if (isEnabled) {
this.toggleOnionSkinButton.classList.add(enabledClassname);
} else {
this.toggleOnionSkinButton.classList.remove(enabledClassname);
}
}; };
ns.PreviewController.prototype.selectPreviewSizeButton_ = function () { ns.PreviewController.prototype.selectPreviewSizeButton_ = function () {

View File

@ -87,7 +87,7 @@
if (background) { if (background) {
pskl.UserSettings.set(pskl.UserSettings.CANVAS_BACKGROUND, background); pskl.UserSettings.set(pskl.UserSettings.CANVAS_BACKGROUND, background);
var selected = this.backgroundContainer.querySelector('.selected'); var selected = this.backgroundContainer.querySelector('.selected');
if (selected) { if (selected) {
selected.classList.remove('selected'); selected.classList.remove('selected');
} }
target.classList.add('selected'); target.classList.add('selected');

View File

@ -62,7 +62,7 @@
ns.SaveController.prototype.insertSavePartials_ = function () { ns.SaveController.prototype.insertSavePartials_ = function () {
this.getPartials_().forEach(function (partial) { this.getPartials_().forEach(function (partial) {
pskl.utils.Template.insert(this.saveForm, 'beforeend', partial); this.saveForm.insertAdjacentHTML('beforeend', pskl.utils.Template.get(partial));
}.bind(this)); }.bind(this));
}; };

View File

@ -0,0 +1,27 @@
if (!Element.prototype.scrollIntoViewIfNeeded) {
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
var parent = this.parentNode,
parentComputedStyle = window.getComputedStyle(parent, null),
parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
alignWithTop = overTop && !overBottom;
if ((overTop || overBottom) && centerIfNeeded) {
parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
}
if ((overLeft || overRight) && centerIfNeeded) {
parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
}
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
this.scrollIntoView(alignWithTop);
}
};
}

View File

@ -6,15 +6,38 @@
}; };
ns.BeforeUnloadService.prototype.init = function () { ns.BeforeUnloadService.prototype.init = function () {
if (pskl.utils.Environment.detectNodeWebkit()) {
// Add a dedicated listener to window 'close' event in nwjs environment.
var win = require('nw.gui').Window.get();
win.on('close', this.onNwWindowClose.bind(this, win));
}
window.addEventListener('beforeunload', this.onBeforeUnload.bind(this)); window.addEventListener('beforeunload', this.onBeforeUnload.bind(this));
}; };
/**
* In nw.js environment "onbeforeunload" is not triggered when closing the window.
* Polyfill the behavior here.
*/
ns.BeforeUnloadService.prototype.onNwWindowClose = function (win) {
var msg = this.onBeforeUnload();
if (msg) {
if (!window.confirm(msg)) {
return false;
}
}
win.close(true);
};
ns.BeforeUnloadService.prototype.onBeforeUnload = function (evt) { ns.BeforeUnloadService.prototype.onBeforeUnload = function (evt) {
pskl.app.backupService.backup(); pskl.app.backupService.backup();
if (pskl.app.savedStatusService.isDirty()) { if (pskl.app.savedStatusService.isDirty()) {
var confirmationMessage = 'Your Piskel seems to have unsaved changes'; var confirmationMessage = 'Your Piskel seems to have unsaved changes';
(evt || window.event).returnValue = confirmationMessage; evt = evt || window.event;
if (evt) {
evt.returnValue = confirmationMessage;
}
return confirmationMessage; return confirmationMessage;
} }
}; };

View File

@ -82,4 +82,12 @@
ns.BaseTool.prototype.releaseToolAt = function (col, row, frame, overlay, event) {}; ns.BaseTool.prototype.releaseToolAt = function (col, row, frame, overlay, event) {};
/**
* Does the tool support the ALT modifier. To be overridden by subclasses.
*
* @return {Boolean} true if the tool supports ALT.
*/
ns.BaseTool.prototype.supportsAlt = function () {
return false;
};
})(); })();

View File

@ -102,4 +102,8 @@
this.shiftFrame(replayData.colDiff, replayData.rowDiff, frame, frame.clone(), event); this.shiftFrame(replayData.colDiff, replayData.rowDiff, frame, frame.clone(), event);
}.bind(this)); }.bind(this));
}; };
ns.Move.prototype.supportsAlt = function() {
return true;
};
})(); })();

View File

@ -16,23 +16,12 @@
}, },
createFromHTML : function (html) { createFromHTML : function (html) {
var dummyEl = document.createElement('div'); var dummyEl = ns.Template._getDummyEl();
dummyEl.innerHTML = html; dummyEl.innerHTML = html;
return dummyEl.children[0]; var element = dummyEl.children[0];
}, dummyEl.innerHTML = '';
insert : function (parent, position, templateId, dict) { return element;
var html = pskl.utils.Template.getAndReplace(templateId, dict);
parent.insertAdjacentHTML(position, html);
},
getAndReplace : function (templateId, dict) {
var result = '';
var tpl = pskl.utils.Template.get(templateId);
if (tpl) {
result = pskl.utils.Template.replace(tpl, dict);
}
return result;
}, },
replace : function (template, dict) { replace : function (template, dict) {
@ -49,10 +38,38 @@
value = ''; value = '';
} }
} }
// Sanitize all values expect if the key is surrounded by `!`
if (!/^!.*!$/.test(key)) {
value = ns.Template.sanitize(value);
}
template = template.replace(new RegExp('\\{\\{' + key + '\\}\\}', 'g'), value); template = template.replace(new RegExp('\\{\\{' + key + '\\}\\}', 'g'), value);
} }
} }
return template; return template;
},
/**
* Sanitize the provided string to make it safer for using in templates.
*/
sanitize : function (string) {
var dummyEl = ns.Template._getDummyEl();
// Apply the unsafe string as text content and
dummyEl.textContent = string;
var sanitizedString = dummyEl.innerHTML;
dummyEl.innerHTML = '';
return sanitizedString;
},
_getDummyEl : function () {
if (!ns.Template._dummyEl) {
ns.Template._dummyEl = document.createElement('div');
}
return ns.Template._dummyEl;
} }
}; };
})(); })();

View File

@ -9,7 +9,8 @@
return pskl.utils.Template.replace(tpl, { return pskl.utils.Template.replace(tpl, {
helptext : helpText, helptext : helpText,
shortcut : shortcut, shortcut : shortcut,
descriptors : this.formatDescriptors_(descriptors) // Avoid sanitization for descriptors (markup)
'!descriptors!' : this.formatDescriptors_(descriptors)
}); });
}; };

View File

@ -56,22 +56,22 @@
// Prepare a frames array to store frame objects extracted from the chunks. // Prepare a frames array to store frame objects extracted from the chunks.
var frames = []; var frames = [];
Promise.all(chunks.map(function (chunk) { Q.all(chunks.map(function (chunk) {
// Create a promise for each chunk. // Create a promise for each chunk.
return new Promise(function (resolve, reject) { var deferred = Q.defer();
var image = new Image(); var image = new Image();
// Load the chunk image in an Image object. // Load the chunk image in an Image object.
image.onload = function () { image.onload = function () {
// extract the chunkFrames from the chunk image // extract the chunkFrames from the chunk image
var chunkFrames = pskl.utils.FrameUtils.createFramesFromChunk(image, chunk.layout); var chunkFrames = pskl.utils.FrameUtils.createFramesFromChunk(image, chunk.layout);
// add each image to the frames array, at the extracted index // add each image to the frames array, at the extracted index
chunkFrames.forEach(function (chunkFrame) { chunkFrames.forEach(function (chunkFrame) {
frames[chunkFrame.index] = chunkFrame.frame; frames[chunkFrame.index] = chunkFrame.frame;
}); });
resolve(); deferred.resolve();
}; };
image.src = chunk.base64PNG; image.src = chunk.base64PNG;
}); return deferred.promise;
})).then(function () { })).then(function () {
frames.forEach(layer.addFrame.bind(layer)); frames.forEach(layer.addFrame.bind(layer));
this.layers_[index] = layer; this.layers_[index] = layer;

View File

@ -54,6 +54,7 @@
// JSZip https://github.com/Stuk/jszip // JSZip https://github.com/Stuk/jszip
"js/lib/jszip/jszip.min.js", "js/lib/jszip/jszip.min.js",
"js/lib/scrollifneeded/scrollifneeded.js",
// Smoothscroll: https://github.com/iamdustan/smoothscroll // Smoothscroll: https://github.com/iamdustan/smoothscroll
"js/lib/smoothscroll/smoothscroll.js", "js/lib/smoothscroll/smoothscroll.js",

View File

@ -11,7 +11,7 @@
<script type="text/template" id="tooltip-container-template"> <script type="text/template" id="tooltip-container-template">
<div class='tooltip-container'> <div class='tooltip-container'>
<div>{{helptext}} <span class='tooltip-shortcut'>{{shortcut}}</span></div> <div>{{helptext}} <span class='tooltip-shortcut'>{{shortcut}}</span></div>
{{descriptors}} {{!descriptors!}}
</div> </div>
</script> </script>