JSON canvas initial
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
// save.js — writing the canvas back.
|
||||
//
|
||||
// The whole document goes up in one request: a canvas is a single JSON object
|
||||
// whose edges reference node ids, so a partial patch would have to reason about
|
||||
// cross-references the server would then have to re-validate anyway.
|
||||
//
|
||||
// Two server rejections are worth handling rather than dumping on the user:
|
||||
// a 409 (someone else changed the file) and the empty-canvas guard (which asks
|
||||
// for explicit confirmation before letting a save blank a populated canvas).
|
||||
window.Canvas = window.Canvas || {};
|
||||
|
||||
Canvas.Save = (function () {
|
||||
var Doc = Canvas.Doc;
|
||||
|
||||
var action = '';
|
||||
var saving = false;
|
||||
var listeners = [];
|
||||
|
||||
function emit(state, message) {
|
||||
for (var i = 0; i < listeners.length; i++) listeners[i](state, message);
|
||||
}
|
||||
|
||||
function post(url) {
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Canvas-Version': Doc.version()
|
||||
},
|
||||
body: JSON.stringify(Doc.get())
|
||||
});
|
||||
}
|
||||
|
||||
function run(force) {
|
||||
if (saving) return Promise.resolve(false);
|
||||
if (!Doc.isDirty() && !force) {
|
||||
emit('clean', 'no changes');
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
saving = true;
|
||||
emit('saving', 'saving…');
|
||||
|
||||
var url = action + (force ? '&empty=1' : '');
|
||||
return post(url).then(function (res) {
|
||||
if (res.status === 204) {
|
||||
Doc.markSaved(res.headers.get('X-Canvas-Version') || '');
|
||||
emit('clean', 'saved');
|
||||
return true;
|
||||
}
|
||||
return res.text().then(function (msg) {
|
||||
if (res.status === 409) {
|
||||
conflict(msg);
|
||||
} else if (res.status === 400 && msg.indexOf('empty canvas') !== -1) {
|
||||
confirmEmpty(msg);
|
||||
} else {
|
||||
emit('error', msg || ('save failed (' + res.status + ')'));
|
||||
alert(msg || ('Save failed (' + res.status + ')'));
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}).catch(function () {
|
||||
emit('error', 'network error');
|
||||
alert('Could not reach the server — your changes are still here, try again.');
|
||||
return false;
|
||||
}).finally(function () {
|
||||
saving = false;
|
||||
});
|
||||
}
|
||||
|
||||
// conflict: the file changed underneath us. Overwriting silently would
|
||||
// discard whatever the other writer did, so the choice is the user's — and
|
||||
// reloading would discard *their* work, so that is spelled out too.
|
||||
function conflict(msg) {
|
||||
emit('error', 'conflict');
|
||||
openModal({
|
||||
title: 'Canvas changed on disk',
|
||||
body: msg + '\n\nReload to take the version on disk (your unsaved changes here are lost), ' +
|
||||
'or keep editing and save again to overwrite it.',
|
||||
confirm: {
|
||||
label: 'RELOAD',
|
||||
danger: true,
|
||||
onConfirm: function () {
|
||||
closeModal();
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
cancel: { label: 'KEEP EDITING', autofocus: true }
|
||||
});
|
||||
}
|
||||
|
||||
function confirmEmpty(msg) {
|
||||
emit('error', 'refused');
|
||||
openModal({
|
||||
title: 'Save an empty canvas?',
|
||||
body: msg + '\n\nThis removes every node from the file.',
|
||||
confirm: {
|
||||
label: 'EMPTY IT',
|
||||
danger: true,
|
||||
onConfirm: function () {
|
||||
closeModal();
|
||||
run(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init: function (postURL, name) {
|
||||
action = postURL + '?canvas=' + encodeURIComponent(name);
|
||||
// Unsaved work must not vanish on a stray Back or tab close. The
|
||||
// browser shows its own generic wording; the return value is only a
|
||||
// signal that there is something to lose.
|
||||
window.addEventListener('beforeunload', function (e) {
|
||||
if (!Doc.isDirty()) return;
|
||||
e.preventDefault();
|
||||
e.returnValue = '';
|
||||
});
|
||||
},
|
||||
|
||||
run: function () { return run(false); },
|
||||
isSaving: function () { return saving; },
|
||||
onState: function (fn) { listeners.push(fn); }
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user