diff --git a/.gitignore b/.gitignore
index 1bdbebc0..41c1deac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,5 +15,8 @@ npm-debug.log
# builds
build
+# marked as private
+*.private.*
+
# Closure compiler generated JS binary.
closure_compiled_binary.js
\ No newline at end of file
diff --git a/Gruntfile.js b/Gruntfile.js
index 2201f43e..01ea242a 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -65,7 +65,7 @@ module.exports = function(grunt) {
}
},
ghost : {
- 'default' : getGhostConfig(3000),
+ 'default' : getGhostConfig(5000),
local : getGhostConfig(50)
},
concat : {
diff --git a/css/settings.css b/css/settings.css
index 41bf99ce..829a727e 100644
--- a/css/settings.css
+++ b/css/settings.css
@@ -56,6 +56,13 @@
position: relative;
}
+.tool-icon.local-storage-icon {
+ background-image: url(../img/local-storage-icon.png);
+ background-position: 10px 12px;
+ background-size: 30px;
+ position: relative;
+}
+
.tool-icon.import-icon {
background-image: url(../img/import-icon.png);
background-position: 10px 5px;
@@ -247,12 +254,10 @@
}
#save-status {
- margin-left: 10px;
+ margin-top: 10px;
}
.status {
height: 1.5rem;
-
- word-break : break-all;
vertical-align: middle;
font-weight: normal;
text-shadow: none;
@@ -264,4 +269,33 @@
[name*=checkbox] {
vertical-align: middle;
+}
+
+.local-piskels-list {
+ width: 100%;
+}
+
+.local-piskel-item {
+ height: 3em;
+}
+
+.local-piskel-name {
+ width: 40%;
+}
+
+.local-piskel-save-date {
+ font-weight : normal;
+}
+
+.local-piskels-list a {
+ text-decoration: none;
+}
+.local-piskels-list a:hover {
+ text-decoration: underline;
+}
+.local-piskel-load-link {
+ color : gold;
+}
+.local-piskel-delete-link {
+ color : red;
}
\ No newline at end of file
diff --git a/css/spectrum/spectrum-overrides.css b/css/spectrum/spectrum-overrides.css
index 1bd874ea..2b214ded 100644
--- a/css/spectrum/spectrum-overrides.css
+++ b/css/spectrum/spectrum-overrides.css
@@ -25,13 +25,18 @@
/* Remove the padding on the input container */
.sp-replacer {
- padding: 3px;
+ padding: 4px;
height: 100%;
border-width: 0;
box-sizing: border-box;
-moz-box-sizing:border-box;
background-color: #444;
}
+
+.sp-replacer:hover {
+ background-color: #888;
+}
+
/* Hide the triangle */
.sp-dd {
display: none;
diff --git a/css/tools.css b/css/tools.css
index 28242a37..bda3cdee 100644
--- a/css/tools.css
+++ b/css/tools.css
@@ -136,6 +136,36 @@
cursor: url(../img/icons/dropper.png) 0 15, pointer;
}
+.swap-colors-icon {
+ background-image: url(../img/tools/swap-arrow-square-small-grey.png);
+ position: relative;
+ top: 50px;
+ left: 6px;
+ height: 18px;
+ width: 18px;
+ background-size: 18px;
+ opacity : 0.3;
+ cursor : pointer;
+}
+
+.swap-colors-icon:hover {
+ opacity : 1;
+}
+
+.tool-color-picker {
+ position:relative;
+}
+
+.tool-color-picker:nth-child(1) {
+ z-index : 100;
+}
+
+.tool-color-picker:nth-child(2) {
+ z-index : 90;
+ margin-top: 20px;
+ margin-left:-20px;
+}
+
.tool-color-picker input {
width: 16px;
height: 16px;
@@ -155,6 +185,7 @@
.palette-wrapper {
margin-top: 10px;
+ margin-left: 10px;
}
.palette-color[data-color=TRANSPARENT] {
diff --git a/img/local-storage-icon.png b/img/local-storage-icon.png
new file mode 100644
index 00000000..d9b757e7
Binary files /dev/null and b/img/local-storage-icon.png differ
diff --git a/img/tools/swap-arrow-square-small-grey.png b/img/tools/swap-arrow-square-small-grey.png
new file mode 100644
index 00000000..ce91d240
Binary files /dev/null and b/img/tools/swap-arrow-square-small-grey.png differ
diff --git a/index.html b/index.html
index 08bbd11b..05d758b1 100644
--- a/index.html
+++ b/index.html
@@ -9,8 +9,8 @@
-
+
diff --git a/js/Events.js b/js/Events.js
index aebb0306..b7b4523e 100644
--- a/js/Events.js
+++ b/js/Events.js
@@ -29,6 +29,8 @@ var Events = {
*/
PISKEL_RESET: "PISKEL_RESET",
+ PISKEL_SAVED: "PISKEL_SAVED",
+
FRAME_SIZE_CHANGED : "FRAME_SIZE_CHANGED",
SELECTION_CREATED: "SELECTION_CREATED",
diff --git a/js/app.js b/js/app.js
index 3f37fcc0..c8a93526 100644
--- a/js/app.js
+++ b/js/app.js
@@ -75,6 +75,9 @@
this.cheatsheetService = new pskl.service.keyboard.CheatsheetService();
this.cheatsheetService.init();
+ this.savedStatusService = new pskl.service.SavedStatusService(this.piskelController);
+ this.savedStatusService.init();
+
if (this.isAppEngineVersion) {
this.storageService = new pskl.service.AppEngineStorageService(this.piskelController);
} else {
@@ -103,8 +106,6 @@
"content" : "Loading animation with id : [" + framesheetId + "]"
}]);
this.loadFramesheetFromService(framesheetId);
- } else {
- this.localStorageService.displayRestoreNotification();
}
},
@@ -118,6 +119,10 @@
}
},
+ isLoggedIn : function () {
+ return pskl.appEnginePiskelData_ && pskl.appEnginePiskelData_.isLoggedIn;
+ },
+
initTooltips_ : function () {
$('body').tooltip({
selector: '[rel=tooltip]'
diff --git a/js/controller/PaletteController.js b/js/controller/PaletteController.js
index 93f658e7..786ced1e 100644
--- a/js/controller/PaletteController.js
+++ b/js/controller/PaletteController.js
@@ -43,6 +43,8 @@
secondaryColorPicker.change({isPrimary : false}, $.proxy(this.onPickerChange_, this));
this.setTitleOnPicker_(Constants.TRANSPARENT_COLOR, secondaryColorPicker);
+ var swapColorsIcon = $('.swap-colors-icon');
+ swapColorsIcon.click(this.swapColors.bind(this));
};
/**
diff --git a/js/controller/ToolController.js b/js/controller/ToolController.js
index 0e2d42dc..9126cb44 100644
--- a/js/controller/ToolController.js
+++ b/js/controller/ToolController.js
@@ -36,7 +36,7 @@
// Set SimplePen as default selected tool:
this.selectTool_(this.tools[0]);
// Activate listener on tool panel:
- $("#tool-section").click($.proxy(this.onToolIconClicked_, this));
+ $("#tool-section").mousedown($.proxy(this.onToolIconClicked_, this));
};
/**
@@ -47,6 +47,7 @@
var previousSelectedToolClass = stage.data("selected-tool-class");
if(previousSelectedToolClass) {
stage.removeClass(previousSelectedToolClass);
+ stage.removeClass(pskl.drawingtools.Move.TOOL_ID);
}
stage.addClass(tool.instance.toolId);
stage.data("selected-tool-class", tool.instance.toolId);
diff --git a/js/controller/settings/LocalStorageController.js b/js/controller/settings/LocalStorageController.js
new file mode 100644
index 00000000..9e6fc80f
--- /dev/null
+++ b/js/controller/settings/LocalStorageController.js
@@ -0,0 +1,70 @@
+(function () {
+ var ns = $.namespace("pskl.controller.settings");
+
+ ns.LocalStorageController = function () {};
+
+ /**
+ * @public
+ */
+ ns.LocalStorageController.prototype.init = function() {
+ this.localStorageItemTemplate_ = pskl.utils.Template.get("local-storage-item-template");
+ this.service_ = pskl.app.localStorageService;
+ this.piskelsList = $('.local-piskels-list');
+
+ this.fillLocalPiskelsList_();
+
+ this.piskelsList.click(this.onPiskelsListClick_.bind(this));
+ };
+
+ ns.LocalStorageController.prototype.onPiskelsListClick_ = function (evt) {
+ var action = evt.target.getAttribute('data-action');
+ var name = evt.target.getAttribute('data-name');
+ if (action === 'load') {
+ if (window.confirm('This will erase your current piskel. Continue ?')) {
+ this.service_.load(name);
+ $.publish(Events.CLOSE_SETTINGS_DRAWER);
+ }
+ } else if (action === 'delete') {
+ if (window.confirm('This will permanently DELETE this piskel from your computer. Continue ?')) {
+ this.service_.remove(name);
+ $.publish(Events.CLOSE_SETTINGS_DRAWER);
+ }
+ }
+ };
+
+ ns.LocalStorageController.prototype.fillLocalPiskelsList_ = function () {
+ var html = "";
+ var keys = this.service_.getKeys();
+
+ var pad = function (num) {
+ if (num < 10) {
+ return "0" + num;
+ } else {
+ return "" + num;
+ }
+ };
+
+
+ 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 = new Date(key.date);
+ var formattedDate = pskl.utils.Template.replace("{{Y}}/{{M}}/{{D}} {{H}}:{{m}}", {
+ Y : date.getFullYear(),
+ M : pad(date.getMonth() + 1),
+ D : pad(date.getDate()),
+ H : pad(date.getHours()),
+ m : pad(date.getMinutes())
+ });
+ html += pskl.utils.Template.replace(this.localStorageItemTemplate_, {name : key.name, date : formattedDate});
+ }).bind(this));
+
+ var tableBody_ = this.piskelsList.get(0).tBodies[0];
+ tableBody_.innerHTML = html;
+ };
+
+})();
\ No newline at end of file
diff --git a/js/controller/settings/SaveController.js b/js/controller/settings/SaveController.js
index 0b10eebe..01c7a6f9 100644
--- a/js/controller/settings/SaveController.js
+++ b/js/controller/settings/SaveController.js
@@ -13,7 +13,12 @@
this.nameInput = $('#save-name');
this.descriptionInput = $('#save-description');
this.isPublicCheckbox = $('input[name=save-public-checkbox]');
- this.saveButton = $('#save-button');
+ this.saveCloudButton = $('#save-cloud-button');
+ this.saveLocalButton = $('#save-local-button');
+
+ // Only available in app-engine mode ...
+ this.piskelName = $('.piskel-name').get(0);
+
this.status = $('#save-status');
var descriptor = this.piskelController.piskel.getDescriptor();
@@ -22,21 +27,22 @@
this.isPublicCheckbox.prop('checked', descriptor.isPublic);
- if (!pskl.app.isAppEngineVersion) {
- this.nameInput.attr('disabled', 'disabled');
- this.descriptionInput.attr('disabled', 'disabled');
- this.isPublicCheckbox.attr('disabled', 'disabled');
+ if (!pskl.app.isLoggedIn()) {
+ this.saveCloudButton.attr('disabled', 'disabled');
+ this.status.html('You are not logged in. Only Local Save is available.');
+ } else {
+ this.saveForm.submit(this.onSaveFormSubmit_.bind(this));
}
- this.saveForm.submit(this.onSaveFormSubmit_.bind(this));
+ this.saveLocalButton.click(this.onSaveLocalClick_.bind(this));
};
ns.SaveController.prototype.onSaveFormSubmit_ = function (evt) {
evt.preventDefault();
evt.stopPropagation();
- var name = this.nameInput.val();
- var description = this.descriptionInput.val();
+ var name = this.getName();
+ var description = this.getDescription();
var isPublic = !!this.isPublicCheckbox.prop('checked');
var descriptor = new pskl.model.piskel.Descriptor(name, description, isPublic);
@@ -50,15 +56,46 @@
});
};
+ ns.SaveController.prototype.onSaveLocalClick_ = function (evt) {
+ var localStorageService = pskl.app.localStorageService;
+ var isOk = true;
+ var name = this.getName();
+ var description = this.getDescription();
+ if (localStorageService.getPiskel(name)) {
+ isOk = window.confirm('There is already a piskel saved as ' + name + '. Override ?');
+ }
+
+ if (isOk) {
+ this.beforeSaving_();
+ localStorageService.save(name, description, pskl.app.piskelController.serialize());
+ window.setTimeout(function () {
+ this.onSaveSuccess_();
+ this.afterSaving_();
+ }.bind(this), 1000);
+ }
+ };
+
+ ns.SaveController.prototype.getName = function () {
+ return this.nameInput.val();
+ };
+
+ ns.SaveController.prototype.getDescription = function () {
+ return this.descriptionInput.val();
+ };
+
ns.SaveController.prototype.beforeSaving_ = function () {
- this.saveButton.attr('disabled', true);
+ this.saveCloudButton.attr('disabled', true);
this.status.html('Saving ...');
- $('.piskel-name').get(0).classList.add('piskel-name-saving');
+
+ if (this.piskelName) {
+ this.piskelName.classList.add('piskel-name-saving');
+ }
};
ns.SaveController.prototype.onSaveSuccess_ = function () {
$.publish(Events.CLOSE_SETTINGS_DRAWER);
$.publish(Events.SHOW_NOTIFICATION, [{"content": "Successfully saved !"}]);
+ $.publish(Events.PISKEL_SAVED);
};
ns.SaveController.prototype.onSaveError_ = function (status) {
@@ -66,9 +103,12 @@
};
ns.SaveController.prototype.afterSaving_ = function () {
- this.saveButton.attr('disabled', false);
+ this.saveCloudButton.attr('disabled', false);
this.status.html('');
- $('.piskel-name').get(0).classList.remove('piskel-name-saving');
+
+ if (this.piskelName) {
+ this.piskelName.classList.remove('piskel-name-saving');
+ }
window.setTimeout($.publish.bind($, Events.HIDE_NOTIFICATION), 2000);
};
diff --git a/js/controller/settings/SettingsController.js b/js/controller/settings/SettingsController.js
index bc1f0318..de4a0bd9 100644
--- a/js/controller/settings/SettingsController.js
+++ b/js/controller/settings/SettingsController.js
@@ -18,6 +18,10 @@
template : 'templates/settings/import.html',
controller : ns.ImportController
},
+ 'localstorage' : {
+ template : 'templates/settings/localstorage.html',
+ controller : ns.LocalStorageController
+ },
'save' : {
template : 'templates/settings/save.html',
controller : ns.SaveController
diff --git a/js/drawingtools/Move.js b/js/drawingtools/Move.js
index 1635e0cf..0ff49857 100644
--- a/js/drawingtools/Move.js
+++ b/js/drawingtools/Move.js
@@ -7,7 +7,7 @@
var ns = $.namespace("pskl.drawingtools");
ns.Move = function() {
- this.toolId = "tool-move";
+ this.toolId = ns.Move.TOOL_ID;
this.helpText = "Move tool";
// Stroke's first point coordinates (set in applyToolAt)
@@ -15,6 +15,8 @@
this.startRow = null;
};
+ ns.Move.TOOL_ID = "tool-move";
+
pskl.utils.inherit(ns.Move, ns.BaseTool);
/**
diff --git a/js/drawingtools/selectiontools/BaseSelect.js b/js/drawingtools/selectiontools/BaseSelect.js
index b5ca0da1..b6cb3b0b 100644
--- a/js/drawingtools/selectiontools/BaseSelect.js
+++ b/js/drawingtools/selectiontools/BaseSelect.js
@@ -7,7 +7,7 @@
var ns = $.namespace("pskl.drawingtools");
ns.BaseSelect = function() {
- this.secondaryToolId = "tool-move";
+ this.secondaryToolId = pskl.drawingtools.Move.TOOL_ID;
this.BodyRoot = $('body');
// Select's first point coordinates (set in applyToolAt)
diff --git a/js/service/LocalStorageService.js b/js/service/LocalStorageService.js
index c68cee38..62a174a9 100644
--- a/js/service/LocalStorageService.js
+++ b/js/service/LocalStorageService.js
@@ -7,83 +7,74 @@
throw "Bad LocalStorageService initialization: ";
}
this.piskelController = piskelController;
- this.localStorageThrottler_ = null;
};
- /**
- * @public
- */
- ns.LocalStorageService.prototype.init = function(piskelController) {
- $.subscribe(Events.LOCALSTORAGE_REQUEST, $.proxy(this.persistToLocalStorageRequest_, this));
+ ns.LocalStorageService.prototype.init = function() {};
+
+// localStorage.setItem('piskel_bkp', pskl.app.piskelController.serialize())
+
+ ns.LocalStorageService.prototype.save = function(name, description, piskel) {
+ this.removeFromKeys_(name);
+ this.addToKeys_(name, description, Date.now());
+ window.localStorage.setItem('piskel.' + name, piskel);
};
- /**
- * @private
- */
- ns.LocalStorageService.prototype.persistToLocalStorageRequest_ = function () {
- // Persist to localStorage when drawing. We throttle localStorage accesses
- // for high frequency drawing (eg mousemove).
- if(this.localStorageThrottler_ !== null) {
- window.clearTimeout(this.localStorageThrottler_);
- }
- this.localStorageThrottler_ = window.setTimeout($.proxy(function() {
- this.persistToLocalStorage_();
- this.localStorageThrottler_ = null;
- }, this), 1000);
- };
+ ns.LocalStorageService.prototype.load = function(name) {
+ var piskelString = this.getPiskel(name);
+ var key = this.getKey_(name);
- /**
- * @private
- */
- ns.LocalStorageService.prototype.persistToLocalStorage_ = function() {
- console.log('[LocalStorage service]: Snapshot stored');
- window.localStorage.snapShot = this.piskelController.serialize();
- };
-
- /**
- * @private
- */
- ns.LocalStorageService.prototype.restoreFromLocalStorage_ = function() {
- var framesheet = JSON.parse(window.localStorage.snapShot);
-
- pskl.utils.serialization.Deserializer.deserialize(framesheet, function (piskel) {
+ pskl.utils.serialization.Deserializer.deserialize(JSON.parse(piskelString), function (piskel) {
+ piskel.setDescriptor(new pskl.model.piskel.Descriptor(name, key.description, true));
pskl.app.piskelController.setPiskel(piskel);
});
};
- /**
- * @private
- */
- ns.LocalStorageService.prototype.cleanLocalStorage_ = function() {
- console.log('[LocalStorage service]: Snapshot removed');
- delete window.localStorage.snapShot;
+ ns.LocalStorageService.prototype.remove = function(name) {
+ this.removeFromKeys_(name);
+ window.localStorage.removeItem('piskel.' + name);
};
- /**
- * @public
- */
- ns.LocalStorageService.prototype.displayRestoreNotification = function() {
- if(window.localStorage && window.localStorage.snapShot) {
- var reloadLink = "reload ";
- var discardLink = "discard ";
- var content = "Non saved version found. " + reloadLink + " or " + discardLink;
+ ns.LocalStorageService.prototype.saveKeys_ = function(keys) {
+ window.localStorage.setItem('piskel.keys', JSON.stringify(keys));
+ };
- $.publish(Events.SHOW_NOTIFICATION, [{
- "content": content,
- "behavior": $.proxy(function(rootNode) {
- rootNode = $(rootNode);
- rootNode.click($.proxy(function(evt) {
- var target = $(evt.target);
- if(target.hasClass("localstorage-restore")) {
- this.restoreFromLocalStorage_();
- }
- else if (target.hasClass("localstorage-discard")) {
- this.cleanLocalStorage_();
- }
- $.publish(Events.HIDE_NOTIFICATION);
- }, this));
- }, this)
- }]);
+ ns.LocalStorageService.prototype.removeFromKeys_ = function(name) {
+ var keys = this.getKeys();
+ var otherKeys = keys.filter(function (key) {
+ return key.name !== name;
+ });
+
+ this.saveKeys_(otherKeys);
+ };
+
+ ns.LocalStorageService.prototype.getKey_ = function(name) {
+ var matches = this.getKeys().filter(function (key) {
+ return key.name === name;
+ });
+ if (matches.length > 0) {
+ return matches[0];
+ } else {
+ return null;
}
};
+
+ ns.LocalStorageService.prototype.addToKeys_ = function(name, description, date) {
+ var keys = this.getKeys();
+ keys.push({
+ name : name,
+ description : description,
+ date : date
+ });
+ this.saveKeys_(keys);
+ };
+
+ ns.LocalStorageService.prototype.getPiskel = function(name) {
+ return window.localStorage.getItem('piskel.' + name);
+ };
+
+ ns.LocalStorageService.prototype.getKeys = function(name) {
+ var keysString = window.localStorage.getItem('piskel.keys');
+ return JSON.parse(keysString) || [];
+ };
+
})();
\ No newline at end of file
diff --git a/js/service/SavedStatusService.js b/js/service/SavedStatusService.js
new file mode 100644
index 00000000..e2445ef4
--- /dev/null
+++ b/js/service/SavedStatusService.js
@@ -0,0 +1,64 @@
+(function () {
+ var ns = $.namespace('pskl.service');
+
+ ns.SavedStatusService = function (piskelController) {
+ this.piskelController_ = piskelController;
+ };
+
+ ns.SavedStatusService.prototype.init = function () {
+ $.subscribe(Events.TOOL_RELEASED, this.onToolReleased.bind(this));
+ $.subscribe(Events.PISKEL_RESET, this.onPiskelReset.bind(this));
+
+ $.subscribe(Events.PISKEL_SAVED, this.onPiskelSaved.bind(this));
+
+ window.addEventListener("beforeunload", this.onBeforeUnload.bind(this));
+ };
+
+ ns.SavedStatusService.prototype.onPiskelReset = function () {
+ var piskel = this.piskelController_.piskel;
+ // A first PISKEL_RESET is triggered during the load of a new Piskel, it should be ignored
+ // putting a firstResetDone flag as a nasty workaround for this
+ if (piskel.firstResetDone_) {
+ this.updateDirtyStatus(true);
+ } else {
+ piskel.firstResetDone_ = true;
+ }
+ };
+
+ ns.SavedStatusService.prototype.onToolReleased = function () {
+ this.updateDirtyStatus(true);
+ };
+
+ ns.SavedStatusService.prototype.onPiskelSaved = function () {
+ this.updateDirtyStatus(false);
+ };
+
+ ns.SavedStatusService.prototype.updateDirtyStatus = function (status) {
+ var piskel = this.piskelController_.piskel;
+ if (piskel.isDirty_ !== status) {
+ // Redraw piskel name only if dirty status actually changed
+ piskel.isDirty_ = status;
+ this.updatePiskelName();
+ }
+ };
+
+ ns.SavedStatusService.prototype.updatePiskelName = function () {
+ var piskel = this.piskelController_.piskel;
+ var name = piskel.getDescriptor().name;
+ if (piskel.isDirty_) {
+ $('.piskel-name').html(name + ' *');
+ } else {
+ $('.piskel-name').html(name);
+ }
+ };
+
+ ns.SavedStatusService.prototype.onBeforeUnload = function (evt) {
+ var piskel = this.piskelController_.piskel;
+ if (piskel.isDirty_) {
+ var confirmationMessage = "Your Piskel seem to have unsaved changes";
+
+ (evt || window.event).returnValue = confirmationMessage;
+ return confirmationMessage;
+ }
+ };
+})();
\ No newline at end of file
diff --git a/js/utils/serialization/Deserializer.js b/js/utils/serialization/Deserializer.js
index 799b1f09..5c35322a 100644
--- a/js/utils/serialization/Deserializer.js
+++ b/js/utils/serialization/Deserializer.js
@@ -20,11 +20,12 @@
deserializer.deserialize();
};
- ns.Deserializer.prototype.deserialize = function () {
+ ns.Deserializer.prototype.deserialize = function (name) {
var data = this.data_;
var piskelData = data.piskel;
+ name = name || 'Deserialized piskel';
- var descriptor = new pskl.model.piskel.Descriptor('Deserialized piskel', '');
+ var descriptor = new pskl.model.piskel.Descriptor(name, '');
this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height, descriptor);
this.layersToLoad_ = piskelData.layers.length;
diff --git a/piskel-script-list.js b/piskel-script-list.js
index 27286eaa..65247a70 100644
--- a/piskel-script-list.js
+++ b/piskel-script-list.js
@@ -72,6 +72,7 @@ exports.scripts = [
"js/controller/settings/ApplicationSettingsController.js",
"js/controller/settings/ResizeController.js",
"js/controller/settings/GifExportController.js",
+ "js/controller/settings/LocalStorageController.js",
"js/controller/settings/SaveController.js",
"js/controller/settings/ImportController.js",
// Settings controller
@@ -82,6 +83,7 @@ exports.scripts = [
"js/service/GithubStorageService.js",
"js/service/AppEngineStorageService.js",
"js/service/HistoryService.js",
+ "js/service/SavedStatusService.js",
"js/service/keyboard/ShortcutService.js",
"js/service/keyboard/KeycodeTranslator.js",
"js/service/keyboard/CheatsheetService.js",
diff --git a/resources/swap-arrow-small.png b/resources/swap-arrow-small.png
new file mode 100644
index 00000000..7a60355b
Binary files /dev/null and b/resources/swap-arrow-small.png differ
diff --git a/resources/swap-arrow-square-small-grey.png b/resources/swap-arrow-square-small-grey.png
new file mode 100644
index 00000000..ce91d240
Binary files /dev/null and b/resources/swap-arrow-square-small-grey.png differ
diff --git a/resources/swap-arrow-square-small.png b/resources/swap-arrow-square-small.png
new file mode 100644
index 00000000..be49877f
Binary files /dev/null and b/resources/swap-arrow-square-small.png differ
diff --git a/resources/swap-arrow-square.svg b/resources/swap-arrow-square.svg
new file mode 100644
index 00000000..d708004c
--- /dev/null
+++ b/resources/swap-arrow-square.svg
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/swap-arrow.svg b/resources/swap-arrow.svg
new file mode 100644
index 00000000..00ecd7f6
--- /dev/null
+++ b/resources/swap-arrow.svg
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/templates/drawing-tools.html b/templates/drawing-tools.html
index 6f93ab45..47ac6c8e 100644
--- a/templates/drawing-tools.html
+++ b/templates/drawing-tools.html
@@ -3,12 +3,28 @@
diff --git a/templates/settings.html b/templates/settings.html
index 80ac056c..bb12a438 100644
--- a/templates/settings.html
+++ b/templates/settings.html
@@ -27,6 +27,13 @@
rel="tooltip" data-placement="left">
+
+
+
\ No newline at end of file
diff --git a/templates/settings/save.html b/templates/settings/save.html
index a2ac1030..54e6a126 100644
--- a/templates/settings/save.html
+++ b/templates/settings/save.html
@@ -15,8 +15,9 @@
Public :
-
-
+
+
+
\ No newline at end of file