add a browse backups dialog
This commit is contained in:
parent
30ea7fa079
commit
ee45cdcc45
14 changed files with 327 additions and 41 deletions
|
@ -75,9 +75,10 @@
|
|||
rel="tooltip" data-placement="left" title="Performance problem detected, learn more."> </div>
|
||||
|
||||
<!-- dialogs partials -->
|
||||
@@include('templates/dialogs/create-palette.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', {})
|
||||
|
|
|
@ -18,6 +18,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();
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
|
81
src/js/controller/dialogs/backups/BrowseBackups.js
Normal file
81
src/js/controller/dialogs/backups/BrowseBackups.js
Normal 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;
|
||||
|
||||
// Merge data object used by steps to communicate and share their
|
||||
// results.
|
||||
this.mergeData = {
|
||||
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;
|
||||
};
|
||||
})();
|
60
src/js/controller/dialogs/backups/steps/SelectSession.js
Normal file
60
src/js/controller/dialogs/backups/steps/SelectSession.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
(function () {
|
||||
var ns = $.namespace('pskl.controller.dialogs.backups.steps');
|
||||
|
||||
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 = '';
|
||||
if (sessions.length === 0) {
|
||||
html = 'No session found ...';
|
||||
} else {
|
||||
var sessionItemTemplate = pskl.utils.Template.get('session-list-item');
|
||||
var html = '';
|
||||
sessions.forEach(function (session) {
|
||||
html += pskl.utils.Template.replace(sessionItemTemplate, session);
|
||||
});
|
||||
}
|
||||
this.container.querySelector('.session-list').innerHTML = html;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
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.mergeData.selectedSession = sessionId;
|
||||
this.backupsController.next();
|
||||
} else if (action == 'delete') {
|
||||
pskl.app.backupService.deleteSession(sessionId).then(function () {
|
||||
// Refresh the list of sessions
|
||||
this.update();
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
55
src/js/controller/dialogs/backups/steps/SessionDetails.js
Normal file
55
src/js/controller/dialogs/backups/steps/SessionDetails.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
(function () {
|
||||
var ns = $.namespace('pskl.controller.dialogs.backups.steps');
|
||||
|
||||
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.mergeData.selectedSession;
|
||||
pskl.app.backupService.getSnapshotsBySessionId(sessionId).then(function (snapshots) {
|
||||
var html = '';
|
||||
if (snapshots.length === 0) {
|
||||
html = 'No snapshot found...';
|
||||
} else {
|
||||
var sessionItemTemplate = pskl.utils.Template.get('snapshot-list-item');
|
||||
var html = '';
|
||||
snapshots.forEach(function (snapshot) {
|
||||
snapshot.date = pskl.utils.DateUtils.format(snapshot.date, '{{Y}}/{{M}}/{{D}} {{H}}:{{m}}');
|
||||
html += pskl.utils.Template.replace(sessionItemTemplate, snapshot);
|
||||
});
|
||||
}
|
||||
this.container.querySelector('.snapshot-list').innerHTML = html;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ns.SessionDetails.prototype.onBackClick_ = function () {
|
||||
this.backupsController.back(this);
|
||||
};
|
||||
|
||||
ns.SessionDetails.prototype.onContainerClick_ = function (evt) {
|
||||
var action = evt.target.dataset.action;
|
||||
if (action == 'load') {
|
||||
var snapshotId = evt.target.dataset.snapshotId * 1;
|
||||
pskl.app.backupService.loadSnapshotById(snapshotId).then(function () {
|
||||
$.publish(Events.DIALOG_HIDE);
|
||||
});
|
||||
}
|
||||
};
|
||||
})();
|
|
@ -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,25 +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');
|
||||
pskl.app.backupService.getPreviousPiskelInfo().then(function (previousInfo) {
|
||||
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.';
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ns.ImportController.prototype.closeDrawer_ = function () {
|
||||
|
@ -78,6 +60,13 @@
|
|||
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)) {
|
||||
$.publish(Events.DIALOG_SHOW, {
|
||||
|
|
|
@ -89,6 +89,18 @@
|
|||
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
|
||||
|
@ -175,6 +187,7 @@
|
|||
startDate: snapshot.date,
|
||||
endDate: snapshot.date,
|
||||
name: snapshot.name,
|
||||
description: snapshot.description,
|
||||
id: snapshot.session_id
|
||||
};
|
||||
};
|
||||
|
@ -183,8 +196,12 @@
|
|||
var s = sessions[snapshot.session_id];
|
||||
s.startDate = Math.min(s.startDate, snapshot.date);
|
||||
s.endDate = Math.max(s.endDate, snapshot.date);
|
||||
if (s.endDate === snapshot.endDate) {
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -16,9 +16,6 @@
|
|||
this.descriptor = descriptor;
|
||||
this.savePath = null;
|
||||
this.fps = fps;
|
||||
// This id is used to keep track of sessions in the BackupService.
|
||||
this.sessionId = pskl.utils.Uuid.generate();
|
||||
|
||||
} else {
|
||||
throw 'Missing arguments in Piskel constructor : ' + Array.prototype.join.call(arguments, ',');
|
||||
}
|
||||
|
|
|
@ -14,7 +14,9 @@
|
|||
|
||||
ns.BackupService = function (piskelController, backupDatabase) {
|
||||
this.piskelController = piskelController;
|
||||
this.lastHash = null;
|
||||
// Immediately store the current when initializing the Service to avoid storing
|
||||
// empty sessions.
|
||||
this.lastHash = this.piskelController.getPiskel().getHash();
|
||||
this.nextSnapshotDate = -1;
|
||||
|
||||
// backupDatabase can be provided for testing purposes.
|
||||
|
@ -49,14 +51,14 @@
|
|||
var descriptor = piskel.getDescriptor();
|
||||
var date = this.currentDate_();
|
||||
var snapshot = {
|
||||
session_id: piskel.sessionId,
|
||||
session_id: pskl.app.sessionId,
|
||||
date: date,
|
||||
name: descriptor.name,
|
||||
description: descriptor.description,
|
||||
serialized: pskl.utils.serialization.Serializer.serialize(piskel)
|
||||
};
|
||||
|
||||
return this.backupDatabase.getSnapshotsBySessionId(piskel.sessionId).then(function (snapshots) {
|
||||
return this.getSnapshotsBySessionId(pskl.app.sessionId).then(function (snapshots) {
|
||||
var latest = snapshots[0];
|
||||
|
||||
if (latest && date < this.nextSnapshotDate) {
|
||||
|
@ -87,7 +89,7 @@
|
|||
return s1.startDate - s2.startDate;
|
||||
})[0].id;
|
||||
|
||||
return this.backupDatabase.deleteSnapshotsForSession(oldestSession);
|
||||
return this.deleteSession(oldestSession);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}
|
||||
|
@ -96,21 +98,54 @@
|
|||
});
|
||||
};
|
||||
|
||||
ns.BackupService.prototype.getSnapshotsBySessionId = function (sessionId) {
|
||||
return this.backupDatabase.getSnapshotsBySessionId(sessionId);
|
||||
};
|
||||
|
||||
ns.BackupService.prototype.deleteSession = function (sessionId) {
|
||||
return this.backupDatabase.deleteSnapshotsForSession(sessionId);
|
||||
};
|
||||
|
||||
ns.BackupService.prototype.getPreviousPiskelInfo = function () {
|
||||
var sessionId = this.piskelController.getPiskel().sessionId;
|
||||
return this.backupDatabase.findLastSnapshot(function (snapshot) {
|
||||
return snapshot.session_id !== sessionId;
|
||||
return snapshot.session_id !== pskl.app.sessionId;
|
||||
});
|
||||
};
|
||||
|
||||
ns.BackupService.prototype.list = function() {
|
||||
return this.backupDatabase.getSessions();
|
||||
};
|
||||
|
||||
ns.BackupService.prototype.loadSnapshotById = function(snapshotId) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
this.backupDatabase.getSnapshot(snapshotId).then(function (snapshot) {
|
||||
pskl.utils.serialization.Deserializer.deserialize(
|
||||
JSON.parse(snapshot.serialized),
|
||||
function (piskel) {
|
||||
pskl.app.piskelController.setPiskel(piskel);
|
||||
deferred.resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
// Load "latest" backup snapshot.
|
||||
ns.BackupService.prototype.load = function() {
|
||||
var deferred = Q.defer();
|
||||
|
||||
this.getPreviousPiskelInfo().then(function (snapshot) {
|
||||
pskl.utils.serialization.Deserializer.deserialize(
|
||||
JSON.parse(snapshot.serialized),
|
||||
function (piskel) {
|
||||
pskl.app.piskelController.setPiskel(piskel);
|
||||
deferred.resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -149,6 +149,9 @@
|
|||
"js/controller/dialogs/CreatePaletteController.js",
|
||||
"js/controller/dialogs/BrowseLocalController.js",
|
||||
"js/controller/dialogs/CheatsheetController.js",
|
||||
"js/controller/dialogs/backups/steps/SelectSession.js",
|
||||
"js/controller/dialogs/backups/steps/SessionDetails.js",
|
||||
"js/controller/dialogs/backups/BrowseBackups.js",
|
||||
"js/controller/dialogs/importwizard/steps/AbstractImportStep.js",
|
||||
"js/controller/dialogs/importwizard/steps/AdjustSize.js",
|
||||
"js/controller/dialogs/importwizard/steps/ImageImport.js",
|
||||
|
|
43
src/templates/dialogs/browse-backups.html
Normal file
43
src/templates/dialogs/browse-backups.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
<script type="text/template" id="templates/dialogs/browse-backups.html">
|
||||
<div class="dialog-wrapper">
|
||||
<h3 class="dialog-head">
|
||||
Browse backups
|
||||
<span class="dialog-close">X</span>
|
||||
</h3>
|
||||
<div class="dialog-content backups-wizard-container"></div>
|
||||
<div class="browse-backups-disclaimer"></div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="backups-select-session">
|
||||
<div class="backups-step-container">
|
||||
<div class="backups-step-content">
|
||||
<div class="session-list">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="session-list-item">
|
||||
<div class="session-item">
|
||||
{{name}} - {{description}}
|
||||
<button class="button" data-session-id="{{id}}" data-action="delete">Delete</button>
|
||||
<button class="button button-primary" data-session-id="{{id}}" data-action="view">View</button>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="backups-session-details">
|
||||
<div class="backups-step-container">
|
||||
<div class="backups-step-content">
|
||||
<div class="snapshot-list"></div>
|
||||
</div>
|
||||
<button class="button back-button">back</button>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="snapshot-list-item">
|
||||
<div class="snapshot-item">
|
||||
{{name}} - {{description}}. Snapshot taken at {{date}}.
|
||||
<button class="button button-primary" data-action="load" data-snapshot-id="{{id}}">Load</button>
|
||||
</div>
|
||||
</script>
|
|
@ -38,16 +38,11 @@
|
|||
<div class="settings-title">
|
||||
Recover recent sessions
|
||||
</div>
|
||||
<div class="settings-item previous-session">
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="previous-session-info-template">
|
||||
<div>
|
||||
Restore a backup of <span class="highlight">{{name}}</span>, saved at <span style="color:white">{{date}}</span> ?
|
||||
<div class="settings-item">
|
||||
Browse backups of previous sessions.
|
||||
<div style="margin-top:10px;">
|
||||
<button type="button" class="button button-primary restore-session-button">Restore</button>
|
||||
<button type="button" class="button button-primary browse-backups-button">Browse backups</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
|
@ -74,7 +74,10 @@ describe('BackupService test', function () {
|
|||
};
|
||||
|
||||
var preparePiskelMocks = function (session_id, name, description, hash, serialized) {
|
||||
mockPiskel.sessionId = session_id;
|
||||
// Update the session id.
|
||||
pskl.app.sessionId = session_id;
|
||||
|
||||
// Update the piskel mock.
|
||||
mockPiskel._descriptor.name = name;
|
||||
mockPiskel._descriptor.description = description;
|
||||
mockPiskel._hash = hash;
|
||||
|
|
Loading…
Reference in a new issue