Cleanup of piskel.js

Partial cleanup of piskel:
  new events and constant
  move palette and color picker in ToolSelector
  create Notifcation.js and move user message in it
  create LocalStorageService and move LS stuff in it
This commit is contained in:
Vince 2012-09-03 01:24:55 +02:00
parent 9758aa62d9
commit d7044dc44d
11 changed files with 301 additions and 145 deletions

View file

@ -265,6 +265,7 @@ ul, li {
left: 40%;
background-color: #F9EDBE;
padding: 7px 22px;
padding-right: 42px;
border-top-left-radius: 7px;
border-top-right-radius: 7px;
font-family: Arial Black, Gadget, sans-serif;
@ -276,6 +277,20 @@ ul, li {
z-index: 10000;
}
.user-message .close {
position: absolute;
top: 3px;
right: 6px;
color: gray;
font-weight: bold;
cursor: pointer;
font-size: 18px;
}
.user-message .close:hover {
color: black;
}
/* Force apparition of scrollbars on leopard */
::-webkit-scrollbar {
-webkit-appearance: none;

View file

@ -43,7 +43,7 @@
<div class="color-tool">
<input id="color-picker" class="color {hash:true}" type="text" value=""/>
<ul id="palette" class="palette" onclick="piskel.onPaletteClick(event)">
<ul id="palette" class="palette">
<span class="palette-color transparent-color" data-color="TRANSPARENT" title="Transparent"></span>
</ul>
</div>
@ -75,7 +75,9 @@
<script src="js/lib/jsColor_1_4_0/jscolor.js"></script>
<!-- Application libraries-->
<script src="js/frameSheetModel.js"></script>
<script src="js/FrameSheetModel.js"></script>
<script src="js/LocalStorageService.js"></script>
<script src="js/Notification.js"></script>
<script src="js/drawingtools/BaseTool.js"></script>
<script src="js/drawingtools/SimplePen.js"></script>
<script src="js/drawingtools/Eraser.js"></script>

View file

@ -1,3 +1,5 @@
var Constants = {
TRANSPARENT_COLOR : "TRANSPARENT"
DEFAULT_PEN_COLOR : "#000000",
TRANSPARENT_COLOR : "TRANSPARENT",
PISKEL_SERVICE_URL : "http://2.piskel-app.appspot.com"
};

View file

@ -1,6 +1,26 @@
Events = {
TOOL_SELECTED : "TOOL_SELECTED",
COLOR_SELECTED: "COLOR_SELECTED",
COLOR_USED: "COLOR_USED",
/**
* When this event is emitted, a request is sent to the localstorage
* Service to save the current framesheet. The storage service
* may not immediately store data (internal throttling of requests).
*/
LOCALSTORAGE_REQUEST: "LOCALSTORAGE_REQUEST",
CANVAS_RIGHT_CLICKED: "CANVAS_RIGHT_CLICKED",
CANVAS_RIGHT_CLICK_RELEASED: "CANVAS_RIGHT_CLICK_RELEASED"
CANVAS_RIGHT_CLICK_RELEASED: "CANVAS_RIGHT_CLICK_RELEASED",
/**
* Event to requset a refresh of the display.
* A bit overkill but, it's just workaround in our current drawing system.
* TODO: Remove or rework when redraw system is refactored.
*/
REFRESH: "REFRESH",
SHOW_NOTIFICATION: "SHOW_NOTIFICATION",
HIDE_NOTIFICATION: "HIDE_NOTIFICATION"
};

95
js/LocalStorageService.js Normal file
View file

@ -0,0 +1,95 @@
/*
* @provide pskl.LocalStrageService
*
* @require Constants
* @require Events
*/
$.namespace("pskl");
pskl.LocalStorageService = (function() {
var frameSheet_;
/**
* @private
*/
var localStorageThrottler_ = null;
/**
* @private
*/
var persistToLocalStorageRequest_ = function() {
// Persist to localStorage when drawing. We throttle localStorage accesses
// for high frequency drawing (eg mousemove).
if(localStorageThrottler_ != null) {
window.clearTimeout(localStorageThrottler_);
}
localStorageThrottler_ = window.setTimeout(function() {
persistToLocalStorage_();
localStorageThrottler_ = null;
}, 1000);
};
/**
* @private
*/
var persistToLocalStorage_ = function() {
console.log('[LocalStorage service]: Snapshot stored')
window.localStorage['snapShot'] = frameSheet_.serialize();
};
/**
* @private
*/
var restoreFromLocalStorage_ = function() {
frameSheet_.deserialize(window.localStorage['snapShot']);
// Model updated, redraw everything:
$.publish(Events.REFRESH);
};
/**
* @private
*/
var cleanLocalStorage_ = function() {
console.log('[LocalStorage service]: Snapshot removed')
delete window.localStorage['snapShot'];
};
return {
init: function(frameSheet) {
if(frameSheet == undefined) {
throw "Bad LocalStorageService initialization: <undefined frameSheet>"
}
frameSheet_ = frameSheet;
$.subscribe(Events.LOCALSTORAGE_REQUEST, persistToLocalStorageRequest_);
},
// TODO(vincz): Find a good place to put this UI rendering, a service should not render UI.
displayRestoreNotification: function() {
if(window.localStorage && window.localStorage['snapShot']) {
var reloadLink = "<a href='#' class='localstorage-restore onclick='piskel.restoreFromLocalStorage()'>reload</a>";
var discardLink = "<a href='#' class='localstorage-discard' onclick='piskel.cleanLocalStorage()'>discard</a>";
var content = "Non saved version found. " + reloadLink + " or " + discardLink;
$.publish(Events.SHOW_NOTIFICATION, [{
"content": content,
"behavior": function(rootNode) {
rootNode = $(rootNode);
rootNode.click(function(evt) {
var target = $(evt.target);
if(target.hasClass("localstorage-restore")) {
restoreFromLocalStorage_();
}
else if (target.hasClass("localstorage-discard")) {
cleanLocalStorage_();
}
$.publish(Events.HIDE_NOTIFICATION);
});
}
}]);
}
}
};
})();

39
js/Notification.js Normal file
View file

@ -0,0 +1,39 @@
/*
* @provide pskl.NotificationService
*
*/
$.namespace("pskl");
pskl.NotificationService = (function() {
/**
* @private
*/
var displayMessage_ = function (evt, messageInfo) {
var message = document.createElement('div');
message.id = "user-message";
message.className = "user-message";
message.innerHTML = messageInfo.content;
message.innerHTML = message.innerHTML + "<div title='Close message' class='close'>x</div>";
document.body.appendChild(message);
$(message).find(".close").click(removeMessage_);
if(messageInfo.behavior) messageInfo.behavior(message);
};
/**
* @private
*/
var removeMessage_ = function (evt) {
var message = $("#user-message");
if (message.length) {
message.remove();
}
};
return {
init: function() {
$.subscribe(Events.SHOW_NOTIFICATION, displayMessage_);
$.subscribe(Events.HIDE_NOTIFICATION, removeMessage_);
}
};
})();

View file

@ -5,9 +5,12 @@
* @require Events
* @require pskl.drawingtools
*/
$.namespace("pskl");
pskl.ToolSelector = (function() {
var paletteColors = [];
var toolInstances = {
"simplePen" : new pskl.drawingtools.SimplePen(),
"eraser" : new pskl.drawingtools.Eraser(),
@ -19,7 +22,6 @@ pskl.ToolSelector = (function() {
var previousSelectedTool = toolInstances.simplePen;
var selectTool_ = function(tool) {
var maincontainer = $("body");
var previousSelectedToolClass = maincontainer.data("selected-tool-class");
if(previousSelectedToolClass) {
@ -46,6 +48,9 @@ pskl.ToolSelector = (function() {
$.publish(Events.TOOL_SELECTED, [tool]);
};
/**
* @private
*/
var onToolIconClicked_ = function(evt) {
var target = $(evt.target);
var clickedTool = target.closest(".tool-icon");
@ -63,8 +68,76 @@ pskl.ToolSelector = (function() {
}
};
/**
* @private
*/
var onPickerChange_ = function(evt) {
var inputPicker = $(evt.target);
$.publish(Events.COLOR_SELECTED, [inputPicker.val()]);
};
/**
* @private
*/
var addColorToPalette_ = function (color) {
if (paletteColors.indexOf(color) == -1) {
var paletteEl = $("#palette");
var colorEl = document.createElement("li");
colorEl.className = "palette-color";
colorEl.setAttribute("data-color", color);
colorEl.setAttribute("title", color);
colorEl.style.background = color;
paletteEl[0].appendChild(colorEl);
paletteColors.push(color);
}
},
/**
* @private
*/
onPaletteColorClick_ = function (event) {
var selectedColor = $(event.target).data("color");
var colorPicker = $('#color-picker');
if (selectedColor == Constants.TRANSPARENT_COLOR) {
// We can set the current palette color to transparent.
// You can then combine this transparent color with an advanced
// tool for customized deletions.
// Eg: bucket + transparent: Delete a colored area
// Stroke + transparent: hollow out the equivalent of a stroke
// 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[0].color.fromString("#fff");
colorPicker.val("TRANSPARENT");
} else {
colorPicker[0].color.fromString(selectedColor);
}
$.publish(Events.COLOR_SELECTED, [selectedColor])
};
return {
init: function() {
// Initialize tool:
// Set SimplePen as default selected tool:
selectTool_(toolInstances.simplePen);
// Activate listener on tool panel:
$("#tools-container").click(onToolIconClicked_);
// Initialize colorpicker:
var colorPicker = $('#color-picker');
colorPicker.val(Constants.DEFAULT_PEN_COLOR);
colorPicker.change(onPickerChange_);
// Initialize palette:
$("#palette").click(onPaletteColorClick_);
$.subscribe(Events.COLOR_USED, function(evt, color) {
addColorToPalette_(color);
});
// Special right click handlers (select the eraser tool)
$.subscribe(Events.CANVAS_RIGHT_CLICKED, function() {
previousSelectedTool = currentSelectedTool;
currentSelectedTool = toolInstances.eraser;
@ -75,12 +148,9 @@ pskl.ToolSelector = (function() {
currentSelectedTool = previousSelectedTool;
$.publish(Events.TOOL_SELECTED, [currentSelectedTool]);
});
// Set SimplePen as default selected tool:
selectTool_(toolInstances.simplePen);
// Activate listener on tool panel:
$("#tools-container").click(onToolIconClicked_);
}
};
})()
})();

View file

@ -21,8 +21,8 @@
context.clearRect(col * dpi, row * dpi, dpi, dpi);
}
else {
// TODO(vincz): Remove this global access to piskel when Palette component is created.
piskel.addColorToPalette(color);
// TODO(vincz): Found a better design to update the palette, it's called too frequently.
$.publish(Events.COLOR_USED, [color]);
context.fillStyle = color;
context.fillRect(col * dpi, row * dpi, dpi, dpi);
}

View file

@ -1,5 +1,7 @@
var FrameSheetModel = (function() {
$.namespace("pskl");
pskl.FrameSheetModel = (function() {
var inst;
var frames = [];

View file

@ -7,7 +7,7 @@
var o = $({});
$.subscribe = function() {
console.log("SUBSCRIBE: " + arguments[0]);
//console.log("SUBSCRIBE: " + arguments[0]);
o.on.apply(o, arguments);
};
@ -16,7 +16,7 @@
};
$.publish = function() {
console.log("PUBLISH: " + arguments[0]);
//console.log("PUBLISH: " + arguments[0]);
o.trigger.apply(o, arguments);
};

View file

@ -5,11 +5,11 @@
$.namespace("pskl");
(function () {
var frameSheet,
// Constants:
DEFAULT_PEN_COLOR = '#000000',
PISKEL_SERVICE_URL = 'http://2.piskel-app.appspot.com',
/**
* FrameSheetModel instance.
*/
var frameSheet,
// Temporary zoom implementation to easily get bigger canvases to
// see how good perform critical algorithms on big canvas.
@ -33,15 +33,13 @@ $.namespace("pskl");
drawingAreaContainer,
drawingAreaCanvas,
previewCanvas,
paletteEl,
// States:
isClicked = false,
isRightClicked = false,
activeFrameIndex = -1,
animIndex = 0,
penColor = DEFAULT_PEN_COLOR,
paletteColors = [],
penColor = Constants.DEFAULT_PEN_COLOR,
currentFrame = null;
currentToolBehavior = null,
previousMousemoveTime = 0,
@ -53,25 +51,29 @@ $.namespace("pskl");
} else {
return "#" + color;
}
},
// setTimeout/setInterval references:
localStorageThrottler = null
;
};
/**
* Main application controller
*/
var piskel = {
init : function () {
frameSheet = FrameSheetModel.getInstance(framePixelWidth, framePixelHeight);
frameSheet = pskl.FrameSheetModel.getInstance(framePixelWidth, framePixelHeight);
frameSheet.addEmptyFrame();
this.setActiveFrame(0);
pskl.NotificationService.init();
pskl.LocalStorageService.init(frameSheet);
// TODO: Add comments
var frameId = this.getFrameIdFromUrl();
if (frameId) {
this.displayMessage("Loading animation with id : [" + frameId + "]");
$.publish(Events.SHOW_NOTIFICATION, [{"content": "Loading animation with id : [" + frameId + "]"}]);
this.loadFramesheetFromService(frameId);
} else {
this.finishInit();
pskl.LocalStorageService.displayRestoreNotification();
}
},
@ -82,15 +84,22 @@ $.namespace("pskl");
currentToolBehavior = toolBehavior;
});
this.initPalette();
$.subscribe(Events.COLOR_SELECTED, function(evt, color) {
console.log("Color selected: ", color);
penColor = color;
});
$.subscribe(Events.REFRESH, function() {
piskel.setActiveFrameAndRedraw(0);
});
// TODO: Move this into their service or behavior files:
this.initDrawingArea();
this.initPreviewSlideshow();
this.initAnimationPreview();
this.initColorPicker();
this.initLocalStorageBackup();
pskl.ToolSelector.init();
this.startAnimation();
pskl.ToolSelector.init();
},
getFrameIdFromUrl : function() {
@ -102,73 +111,23 @@ $.namespace("pskl");
loadFramesheetFromService : function (frameId) {
var xhr = new XMLHttpRequest();
xhr.open('GET', PISKEL_SERVICE_URL + '/get?l=' + frameId, true);
xhr.open('GET', Constants.PISKEL_SERVICE_URL + '/get?l=' + frameId, true);
xhr.responseType = 'text';
xhr.onload = function(e) {
frameSheet.deserialize(this.responseText);
piskel.removeMessage();
$.publish(Events.HIDE_NOTIFICATION);
piskel.finishInit();
};
xhr.onerror = function () {
piskel.removeMessage();
$.publish(Events.HIDE_NOTIFICATION);
piskel.finishInit();
};
xhr.send();
},
initLocalStorageBackup: function() {
if(window.localStorage && window.localStorage['snapShot']) {
var reloadLink = "<a href='#' onclick='piskel.restoreFromLocalStorage()'>reload</a>";
var discardLink = "<a href='#' onclick='piskel.cleanLocalStorage()'>discard</a>";
this.displayMessage("Non saved version found. " + reloadLink + " or " + discardLink);
}
},
displayMessage : function (content) {
var message = document.createElement('div');
message.id = "user-message";
message.className = "user-message";
message.innerHTML = content;
message.onclick = this.removeMessage;
document.body.appendChild(message);
},
removeMessage : function () {
var message = $("#user-message");
if (message.length) {
message.remove();
}
},
persistToLocalStorageRequest: function() {
// Persist to localStorage when drawing. We throttle localStorage accesses
// for high frequency drawing (eg mousemove).
if(localStorageThrottler != null) {
window.clearTimeout(localStorageThrottler);
}
localStorageThrottler = window.setTimeout(function() {
piskel.persistToLocalStorage();
localStorageThrottler = null;
}, 1000);
},
persistToLocalStorage: function() {
console.log('persited')
window.localStorage['snapShot'] = frameSheet.serialize();
},
restoreFromLocalStorage: function() {
frameSheet.deserialize(window.localStorage['snapShot']);
this.setActiveFrameAndRedraw(0);
},
cleanLocalStorage: function() {
delete window.localStorage['snapShot'];
},
setActiveFrame: function(index) {
activeFrameIndex = index;
currentFrame = frameSheet.getFrameByIndex(activeFrameIndex)
@ -194,33 +153,6 @@ $.namespace("pskl");
return activeFrameIndex;
},
initColorPicker: function() {
this.colorPicker = $('#color-picker');
this.colorPicker.val(DEFAULT_PEN_COLOR);
this.colorPicker.change(this.onPickerChange.bind(this));
},
onPickerChange : function(evt) {
var inputPicker = $(evt.target);
penColor = _normalizeColor(inputPicker.val());
},
initPalette : function (color) {
paletteEl = $('#palette')[0];
},
addColorToPalette : function (color) {
if (color && color != Constants.TRANSPARENT_COLOR && paletteColors.indexOf(color) == -1) {
var colorEl = document.createElement("li");
colorEl.className = "palette-color";
colorEl.setAttribute("data-color", color);
colorEl.setAttribute("title", color);
colorEl.style.background = color;
paletteEl.appendChild(colorEl);
paletteColors.push(color);
}
},
initDrawingArea : function() {
drawingAreaContainer = $('#drawing-canvas-container')[0];
@ -394,8 +326,8 @@ $.namespace("pskl");
penColor,
drawingAreaCanvas,
drawingCanvasDpi);
piskel.persistToLocalStorageRequest();
$.publish(Events.LOCALSTORAGE_REQUEST);
},
onCanvasMousemove : function (event) {
@ -417,7 +349,7 @@ $.namespace("pskl");
// TODO(vincz): Find a way to move that to the model instead of being at the interaction level.
// Eg when drawing, it may make sense to have it here. However for a non drawing tool,
// you don't need to draw anything when mousemoving and you request useless localStorage.
piskel.persistToLocalStorageRequest();
$.publish(Events.LOCALSTORAGE_REQUEST);
}
previousMousemoveTime = currentTime;
}
@ -477,28 +409,6 @@ $.namespace("pskl");
return false;
},
onPaletteClick : function (event) {
var color = $(event.target).data("color");
var colorPicker = $('#color-picker');
if (color == "TRANSPARENT") {
// We can set the current palette color to transparent.
// You can then combine this transparent color with an advanced
// tool for customized deletions.
// Eg: bucket + transparent: Delete a colored area
// Stroke + transparent: hollow out the equivalent of a stroke
penColor = Constants.TRANSPARENT_COLOR;
// 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[0].color.fromString("#fff");
colorPicker.val("TRANSPARENT");
} else if (null !== color) {
colorPicker[0].color.fromString(color);
penColor = color;
}
},
getRelativeCoordinates : function (x, y) {
var canvasRect = drawingAreaCanvas.getBoundingClientRect();
return {
@ -518,11 +428,12 @@ $.namespace("pskl");
// TODO(julz): Create package ?
storeSheet : function (event) {
// TODO Refactor using jquery ?
var xhr = new XMLHttpRequest();
var formData = new FormData();
formData.append('framesheet_content', frameSheet.serialize());
formData.append('fps_speed', $('#preview-fps').val());
xhr.open('POST', PISKEL_SERVICE_URL + "/store", true);
xhr.open('POST', Constants.PISKEL_SERVICE_URL + "/store", true);
xhr.onload = function(e) {
if (this.status == 200) {
var baseUrl = window.location.href.replace(window.location.search, "");
@ -541,4 +452,4 @@ $.namespace("pskl");
window.piskel = piskel;
piskel.init();
})(function(id){return document.getElementById(id)});
})();