2012-09-07 02:18:59 +04:00
|
|
|
(function () {
|
2014-04-17 03:27:49 +04:00
|
|
|
var ns = $.namespace('pskl.service');
|
|
|
|
|
2014-08-12 09:11:23 +04:00
|
|
|
ns.HistoryService = function (piskelController, shortcutService, deserializer) {
|
2014-08-24 20:10:09 +04:00
|
|
|
this.piskelController = piskelController || pskl.app.piskelController;
|
|
|
|
this.shortcutService = shortcutService || pskl.app.shortcutService;
|
|
|
|
this.deserializer = deserializer || pskl.utils.serialization.Deserializer;
|
2014-08-12 02:30:57 +04:00
|
|
|
|
2014-04-17 03:27:49 +04:00
|
|
|
this.stateQueue = [];
|
|
|
|
this.currentIndex = -1;
|
2014-04-20 15:15:30 +04:00
|
|
|
this.lastLoadState = -1;
|
2012-09-11 01:26:12 +04:00
|
|
|
};
|
2012-09-07 02:18:59 +04:00
|
|
|
|
2015-02-28 01:54:18 +03:00
|
|
|
// Force to save a state as a SNAPSHOT
|
2014-04-23 01:57:30 +04:00
|
|
|
ns.HistoryService.SNAPSHOT = 'SNAPSHOT';
|
2015-02-28 01:54:18 +03:00
|
|
|
|
|
|
|
// Default save state
|
2014-04-23 01:57:30 +04:00
|
|
|
ns.HistoryService.REPLAY = 'REPLAY';
|
2015-02-28 01:54:18 +03:00
|
|
|
|
|
|
|
// Period (in number of state saved) between two snapshots
|
2014-08-12 09:11:23 +04:00
|
|
|
ns.HistoryService.SNAPSHOT_PERIOD = 50;
|
2015-02-28 01:54:18 +03:00
|
|
|
|
|
|
|
// Interval/buffer (in milliseconds) between two state load (ctrl+z/y spamming)
|
2014-08-12 09:11:23 +04:00
|
|
|
ns.HistoryService.LOAD_STATE_INTERVAL = 50;
|
2013-09-29 01:52:51 +04:00
|
|
|
|
2014-04-23 01:57:30 +04:00
|
|
|
ns.HistoryService.prototype.init = function () {
|
2014-08-12 02:30:57 +04:00
|
|
|
$.subscribe(Events.PISKEL_SAVE_STATE, this.onSaveStateEvent.bind(this));
|
2013-11-20 02:46:33 +04:00
|
|
|
|
2014-08-24 20:10:09 +04:00
|
|
|
this.shortcutService.addShortcut('ctrl+Z', this.undo.bind(this));
|
|
|
|
this.shortcutService.addShortcut('ctrl+Y', this.redo.bind(this));
|
2014-04-23 01:57:30 +04:00
|
|
|
|
|
|
|
this.saveState({
|
|
|
|
type : ns.HistoryService.SNAPSHOT
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2015-02-28 01:54:18 +03:00
|
|
|
ns.HistoryService.prototype.onSaveStateEvent = function (evt, action) {
|
|
|
|
this.saveState(action);
|
2013-08-10 14:11:16 +04:00
|
|
|
};
|
2012-09-07 02:18:59 +04:00
|
|
|
|
2015-02-28 01:54:18 +03:00
|
|
|
ns.HistoryService.prototype.saveState = function (action) {
|
2014-04-17 03:27:49 +04:00
|
|
|
this.stateQueue = this.stateQueue.slice(0, this.currentIndex + 1);
|
|
|
|
this.currentIndex = this.currentIndex + 1;
|
|
|
|
|
|
|
|
var state = {
|
2015-02-28 01:54:18 +03:00
|
|
|
action : action,
|
|
|
|
frameIndex : action.state ? action.state.frameIndex : this.piskelController.currentFrameIndex,
|
|
|
|
layerIndex : action.state ? action.state.layerIndex : this.piskelController.currentLayerIndex
|
2014-04-17 03:27:49 +04:00
|
|
|
};
|
|
|
|
|
2015-02-28 01:54:18 +03:00
|
|
|
var isSnapshot = action.type === ns.HistoryService.SNAPSHOT;
|
|
|
|
var isAtAutoSnapshotInterval = this.currentIndex % ns.HistoryService.SNAPSHOT_PERIOD === 0;
|
|
|
|
if (isSnapshot || isAtAutoSnapshotInterval) {
|
2014-08-24 20:10:09 +04:00
|
|
|
state.piskel = this.piskelController.serialize(true);
|
2014-04-17 03:27:49 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
this.stateQueue.push(state);
|
2015-02-28 01:54:18 +03:00
|
|
|
$.publish(Events.HISTORY_STATE_SAVED);
|
2012-09-11 01:26:12 +04:00
|
|
|
};
|
2012-09-07 02:18:59 +04:00
|
|
|
|
2012-09-16 15:10:05 +04:00
|
|
|
ns.HistoryService.prototype.undo = function () {
|
2014-04-20 15:15:30 +04:00
|
|
|
this.loadState(this.currentIndex - 1);
|
2012-09-11 01:26:12 +04:00
|
|
|
};
|
|
|
|
|
2012-09-16 15:10:05 +04:00
|
|
|
ns.HistoryService.prototype.redo = function () {
|
2014-04-20 15:15:30 +04:00
|
|
|
this.loadState(this.currentIndex + 1);
|
2014-04-17 03:27:49 +04:00
|
|
|
};
|
|
|
|
|
2014-04-23 01:57:30 +04:00
|
|
|
ns.HistoryService.prototype.isLoadStateAllowed_ = function (index) {
|
2014-08-12 09:11:23 +04:00
|
|
|
var timeOk = (Date.now() - this.lastLoadState) > ns.HistoryService.LOAD_STATE_INTERVAL;
|
2014-04-23 01:57:30 +04:00
|
|
|
var indexInRange = index >= 0 && index < this.stateQueue.length;
|
|
|
|
return timeOk && indexInRange;
|
|
|
|
};
|
|
|
|
|
|
|
|
ns.HistoryService.prototype.getPreviousSnapshotIndex_ = function (index) {
|
|
|
|
while (this.stateQueue[index] && !this.stateQueue[index].piskel) {
|
|
|
|
index = index - 1;
|
|
|
|
}
|
|
|
|
return index;
|
|
|
|
};
|
|
|
|
|
2014-04-17 03:27:49 +04:00
|
|
|
ns.HistoryService.prototype.loadState = function (index) {
|
2014-06-27 04:08:00 +04:00
|
|
|
try {
|
|
|
|
if (this.isLoadStateAllowed_(index)) {
|
|
|
|
this.lastLoadState = Date.now();
|
|
|
|
|
|
|
|
var snapshotIndex = this.getPreviousSnapshotIndex_(index);
|
|
|
|
if (snapshotIndex < 0) {
|
|
|
|
throw 'Could not find previous SNAPSHOT saved in history stateQueue';
|
|
|
|
}
|
|
|
|
var serializedPiskel = this.getSnapshotFromState_(snapshotIndex);
|
|
|
|
var onPiskelLoadedCb = this.onPiskelLoaded_.bind(this, index, snapshotIndex);
|
2014-08-24 20:10:09 +04:00
|
|
|
this.deserializer.deserialize(serializedPiskel, onPiskelLoadedCb);
|
2014-06-27 04:08:00 +04:00
|
|
|
}
|
2015-04-13 15:33:34 +03:00
|
|
|
} catch (error) {
|
|
|
|
console.error("[CRITICAL ERROR] : Unable to load a history state.");
|
|
|
|
this.logError_(error);
|
2014-07-13 19:59:53 +04:00
|
|
|
this.stateQueue = [];
|
|
|
|
this.currentIndex = -1;
|
2014-04-23 01:57:30 +04:00
|
|
|
}
|
|
|
|
};
|
2014-04-17 03:27:49 +04:00
|
|
|
|
2015-04-13 15:33:34 +03:00
|
|
|
ns.HistoryService.prototype.logError_ = function (error) {
|
|
|
|
if (typeof error === "string") {
|
|
|
|
console.error(error);
|
|
|
|
} else {
|
|
|
|
console.error(error.message);
|
|
|
|
console.error(error.stack);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-04-23 01:57:30 +04:00
|
|
|
ns.HistoryService.prototype.getSnapshotFromState_ = function (stateIndex) {
|
|
|
|
var state = this.stateQueue[stateIndex];
|
|
|
|
var piskelSnapshot = state.piskel;
|
2014-04-17 03:27:49 +04:00
|
|
|
|
2014-04-23 01:57:30 +04:00
|
|
|
// If the snapshot is stringified, parse it and backup the result for faster access next time
|
|
|
|
// FIXME : Memory consumption might go crazy if we keep unpacking big piskels indefinitely
|
|
|
|
// ==> should ensure I remove some of them :)
|
|
|
|
if (typeof piskelSnapshot === "string") {
|
|
|
|
piskelSnapshot = JSON.parse(piskelSnapshot);
|
|
|
|
state.piskel = piskelSnapshot;
|
2014-04-17 03:27:49 +04:00
|
|
|
}
|
2014-04-20 15:15:30 +04:00
|
|
|
|
2014-04-23 01:57:30 +04:00
|
|
|
return piskelSnapshot;
|
2014-04-20 15:15:30 +04:00
|
|
|
};
|
2014-04-17 03:27:49 +04:00
|
|
|
|
2014-05-05 00:58:36 +04:00
|
|
|
ns.HistoryService.prototype.onPiskelLoaded_ = function (index, snapshotIndex, piskel) {
|
|
|
|
var originalSize = this.getPiskelSize_();
|
2014-08-24 20:10:09 +04:00
|
|
|
piskel.setDescriptor(this.piskelController.piskel.getDescriptor());
|
2015-03-17 14:24:03 +03:00
|
|
|
// propagate save path to the new piskel instance
|
|
|
|
piskel.savePath = this.piskelController.piskel.savePath;
|
2014-08-24 20:10:09 +04:00
|
|
|
this.piskelController.setPiskel(piskel);
|
2014-04-17 03:27:49 +04:00
|
|
|
|
|
|
|
for (var i = snapshotIndex + 1 ; i <= index ; i++) {
|
|
|
|
var state = this.stateQueue[i];
|
2014-04-18 15:13:42 +04:00
|
|
|
this.setupState(state);
|
2014-04-17 03:27:49 +04:00
|
|
|
this.replayState(state);
|
|
|
|
}
|
|
|
|
|
2014-08-12 09:11:23 +04:00
|
|
|
// Should only do this when going backwards
|
2014-07-13 20:17:13 +04:00
|
|
|
var lastState = this.stateQueue[index+1];
|
2014-05-17 13:03:18 +04:00
|
|
|
if (lastState) {
|
|
|
|
this.setupState(lastState);
|
|
|
|
}
|
2014-07-13 19:59:53 +04:00
|
|
|
|
2014-04-20 15:15:30 +04:00
|
|
|
this.currentIndex = index;
|
2013-09-29 02:01:18 +04:00
|
|
|
$.publish(Events.PISKEL_RESET);
|
2015-02-28 01:54:18 +03:00
|
|
|
$.publish(Events.HISTORY_STATE_LOADED);
|
2014-05-05 00:58:36 +04:00
|
|
|
if (originalSize !== this.getPiskelSize_()) {
|
|
|
|
$.publish(Events.FRAME_SIZE_CHANGED);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
ns.HistoryService.prototype.getPiskelSize_ = function () {
|
2014-08-24 20:10:09 +04:00
|
|
|
return this.piskelController.getWidth() + 'x' + this.piskelController.getHeight();
|
2014-04-17 03:27:49 +04:00
|
|
|
};
|
|
|
|
|
2014-04-18 15:13:42 +04:00
|
|
|
ns.HistoryService.prototype.setupState = function (state) {
|
2014-08-24 20:10:09 +04:00
|
|
|
this.piskelController.setCurrentFrameIndex(state.frameIndex);
|
|
|
|
this.piskelController.setCurrentLayerIndex(state.layerIndex);
|
2014-04-18 15:13:42 +04:00
|
|
|
};
|
|
|
|
|
2014-04-17 03:27:49 +04:00
|
|
|
ns.HistoryService.prototype.replayState = function (state) {
|
2014-04-20 15:15:30 +04:00
|
|
|
var action = state.action;
|
|
|
|
var type = action.type;
|
2014-08-24 20:10:09 +04:00
|
|
|
var layer = this.piskelController.getLayerAt(state.layerIndex);
|
2014-04-20 15:15:30 +04:00
|
|
|
var frame = layer.getFrameAt(state.frameIndex);
|
|
|
|
action.scope.replay(frame, action.replay);
|
2012-09-11 01:26:12 +04:00
|
|
|
};
|
|
|
|
|
2012-09-07 02:18:59 +04:00
|
|
|
})();
|