further work on backup ui

This commit is contained in:
Alex Pankratov
2021-04-18 13:55:59 +02:00
parent 1ca1d5d612
commit d8543daa12

View File

@@ -1054,75 +1054,64 @@
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
display: none;
color: #000;
display: none; /* flex */
justify-content: center;
}
.overlay > div {
font-size: calc(12rem / 11);
line-height: calc(16 / 12);
background: #fff;
align-self: start;
margin-top: 100px;
border-radius: 1px;
box-shadow: 0 2px 6px #000;
}
/***/
.overlay .license {
font-size: calc(12rem / 11);
line-height: calc(16 / 12);
.overlay > div.license {
white-space: pre-wrap;
width: 384px;
height: auto;
max-height: 600px;
padding: 20px 25px 22px;
overflow-y: auto;
background: #fff;
position: absolute;
top: 100px;
left: 50%;
margin-left: -192px;
width: calc( (var(--fs) * 36 + 6) * 1px ); /* 402px, 476px */
}
.overlay .license a {
.overlay > div.license a {
color: #1489db;
}
.overlay .license span {
.overlay > div.license span {
display: inline-block;
padding-right: 8px;
}
/***/
.overlay .about {
font-size: calc(12rem / 11);
line-height: calc(16 / 12);
.overlay > div.about {
text-align: center;
height: auto;
padding: 50px 25px;
background: #fff;
position: absolute;
top: 100px;
left: 50%;
margin-left: -145px;
padding: 50px 50px;
}
.overlay .about,
.overlay .about div {
width: calc( (var(--fs) * 18 + 39) * 1px ); /* 240px, 277px */
}
.overlay .about h1 {
.overlay > div.about h1 {
font-size: calc(15rem / 11);
font-weight: 500;
margin-bottom: 10px;
}
.overlay .about a {
.overlay > div.about a {
display: block;
color: #1489db;
}
.overlay .about div {
.overlay > div.about div {
position: absolute;
bottom: -30px;
color: #eee;
}
.overlay .about div span {
.overlay > div.about div span {
display: inline-block;
text-align: left;
padding-right: 10px;
@@ -1130,6 +1119,137 @@
/***/
.overlay .backup-conf {
padding: 20px 40px;
min-width: 300px;
}
.overlay .backup-conf h3 {
font-size: calc(15rem / 11);
font-weight: 500;
text-align: center;
margin: 20px 0 0 0;
}
.overlay .backup-conf .what {
display: block;
text-align: center;
border-bottom: 1px solid #888;
margin: 0 -10px 20px;
padding: 0 10px 30px;
}
.overlay .backup-conf .what a {
color: #1489db;
}
.overlay .backup-conf .opt {
margin-top: 5px;
}
.overlay .backup-conf .opt:before {
--col: #333;
content: ' ';
display: inline-block;
width: 20px;
font-size: calc(13rem / 11);
margin-top: -1px;
display: inline-block;
content: ' ';
width: calc(9rem / 11);
height: calc(9rem / 11);
margin-right: 8px;
position: relative;
top: 1px;
background: var(--col);
border: 1px solid var(--col);
border-radius: 1px;
box-shadow: 0 2px 0 #fff inset, 0 -2px 0 #fff inset, 2px 0 0 #fff inset, -2px 0 0 #fff inset;
}
.overlay .backup-conf .opt:hover:before {
--col: #1489db;
}
.overlay .backup-conf .off .opt:before {
background: #fff;
}
.overlay .backup-conf .opt:hover {
--col: #1489db;
color: #1489db;
}
.overlay .backup-conf .opt {
cursor: pointer;
}
.overlay .backup-conf .etc {
padding: 5px 0 0 19px;
margin-bottom: 10px;
}
.overlay .backup-conf .off .etc {
display: none;
}
.overlay .backup-conf .etc span {
display: inline-block;
min-width: calc( 3px + var(--fs) * 7px );
margin-right: calc( var(--fs) * 1px );
}
.overlay .backup-conf .etc input {
display: inline-block;
width: 170px;
padding: 0 5px;
line-height: calc(20 / 11);
margin-bottom: 3px;
border: 1px solid #aaa;
}
.overlay .backup-conf .etc input:focus {
border: 1px solid #666;
xbox-shadow: 0 1px 2px #ccc;
}
.overlay .backup-conf .etc input::placeholder {
font-weight: 400;
font-size: calc(10rem / 11);
line-height: calc(20 / 10);
text-transform: uppercase;
color: #000;
opacity: 0.4;
}
.overlay .backup-conf .etc .last-err {
display: none;
color: #d20;
margin: 0;
}
.overlay .backup-conf .etc .last-err i {
font-style: normal;
}
.overlay .backup-conf .save {
display: block;
text-align: center;
margin: 20px -10px 5px;
padding: 20px 10px 0;
border-top: 1px solid #888;
word-spacing: 30px;
}
.overlay .backup-conf .save a {
color: #1489db;
}
/***/
@media print {
.logo, .config,
.board .head .teaser,
@@ -1489,6 +1609,32 @@
<div class=license>
</div>
<div class=backup-conf>
<h3>Auto-backup</h3>
<div class=what>
<a href=https://nullboard.io/backups target=_blank>What is this?</a>
</div>
<div class=loc>
<div class=opt>Local backup</div>
<div class=etc>
<span>Access token</span> <input class=auth placeholder="required"><br>
<p class=last-err><span>Last error</span> <i></i></p>
</div>
</div>
<div class=rem>
<div class=opt>Remote backup</div>
<div class=etc>
<span>URL</span> <input class=base placeholder="API entry point"><br>
<span>Access token</span> <input class=auth placeholder="required"><br>
<p class=last-err><span>Last error</span> <i></i></p>
</div>
</div>
<div class=save><a href=# class=ok>Apply</a> <a href=# class=cancel>Cancel</a></div>
</div>
</tt>
</body>
@@ -1509,20 +1655,21 @@
function AppConfig()
{
this.format = NB.confVersion;
this.verLast = null; // last used codeVersion
this.verSeen = null; // latest codeVersion they saw the changelog for
this.format = NB.confVersion;
this.verLast = null; // last used codeVersion
this.verSeen = null; // latest codeVersion they saw the changelog for
this.maxUndo = 50; // board revisions to keep
this.fontName = null; // font-family
this.fontSize = null; // font-size
this.lineHeight = null; // line-height
this.listWidth = null; // list-width
this.theme = null; // default or 'dark'
this.maxUndo = 50; // board revisions to keep
this.fontName = null; // font-family
this.fontSize = null; // font-size
this.lineHeight = null; // line-height
this.listWidth = null; // list-width
this.theme = null; // default or 'dark'
this.backups = [ ]; // [ { id: '?', conf: { } } ];
this.backups = [ ]; // [ { type, id, enabled, conf } ];
this.nextBackupId = 1;
this.board = null; // active board
this.board = null; // active board
}
function BoardMeta()
@@ -1864,25 +2011,33 @@
self.setBackupStatus('');
this.conf.backups.forEach(function(agent){
var T = NB.backupTypes.get(agent.type);
this.conf.backups.forEach(function(b){
var T = NB.backupTypes.get(b.type);
if (! T)
{
console.log( `Unknown backup type "${agent.type}" - skipped` );
console.log( `Unknown backup type "${b.type}" - skipped` );
return;
}
var store = new T(agent.conf);
store.id = `${store.type}-${store_id++}`;
if (! b.enabled)
return;
var store = new T(b.id, b.conf);
self.backups.push(store);
console.log( `Added backup storage of type '${store.type}' -> '${store.id}'` );
console.log( `Added backup storage - type '${store.type}', id '${store.id}'` );
self.setBackupStatus('busy');
pending++;
store.checkStatus(function(ok){
store.checkStatus(function(ok, xhr){
console.log( `Backup storage '${store.id}' is ${ok ? 'ready' : 'NOT ready'} ` );
var text = xhr.responseText;
if (! text) text = xhr.status ? `Request failed with ${xhr.status}` : 'Offline or CORS-blocked';
store.last = { ok: ok, text: text, code: xhr.status };
success &= ok;
if (--pending)
@@ -2166,11 +2321,11 @@
*/
class BackupStorage
{
constructor(conf)
constructor(id, conf)
{
this.id = '?';
this.type = '?';
this.id = id;
this.conf = conf;
}
@@ -2182,21 +2337,28 @@
class SimpleBackup extends BackupStorage
{
constructor(conf)
constructor(id, conf)
{
super();
super(id, null);
this.type = 'simp';
this.conf = { base: 'http://127.0.0.1:10001', auth: '' }
this.conf = { base: '', auth: '' }
this.conf = Object.assign(this.conf, conf);
}
checkStatus(cb)
{
var self = this;
var test = new Image;
$.get(this.conf.base + '/status')
.done(function(){ if (cb) cb.call(self, true); })
.fail(function(){ if (cb) cb.call(self, false); })
$.ajax({
url: this.conf.base + '/test',
type: 'put',
headers: { 'X-Access-Token': this.conf.auth },
})
.done(function(d, s, x) { if (cb) cb.call(self, true, x, s, d); })
.fail(function(x, s, e) { if (cb) cb.call(self, false, x, s, e); })
}
saveConfig(conf, cb)
@@ -2209,8 +2371,8 @@
headers: { 'X-Access-Token': this.conf.auth },
data: JSON.stringify(conf),
})
.done(function(){ if (cb) cb.call(self, true); })
.fail(function(){ if (cb) cb.call(self, false); })
.done(function(d, s, x) { if (cb) cb.call(self, true, x, s, d); })
.fail(function(x, s, e) { if (cb) cb.call(self, false, x, s, e); })
}
saveBoard(id, data, meta, cb)
@@ -2228,8 +2390,8 @@
},
dataType: 'json',
})
.done(function(){ if (cb) cb.call(self, true); })
.fail(function(){ if (cb) cb.call(self, false); })
.done(function(d, s, x) { if (cb) cb.call(self, true, x, s, d); })
.fail(function(x, s, e) { if (cb) cb.call(self, false, x, s, e); })
}
nukeBoard(id, cb)
@@ -2241,8 +2403,8 @@
type: 'delete',
headers: { 'X-Access-Token': this.conf.auth },
})
.done(function(){ if (cb) cb.call(self, true); })
.fail(function(){ if (cb) cb.call(self, false); })
.done(function(d, s, x) { if (cb) cb.call(self, true, x, s, d); })
.fail(function(x, s, e) { if (cb) cb.call(self, false, x, s, e); })
}
}
@@ -3185,17 +3347,139 @@
/*
*
*/
function toggleBackups()
function findBackup(which)
{
var s = null;
NB.storage.backups.forEach(function(store){
if (store.type == which.type &&
store.conf.auth == which.conf.auth &&
store.conf.base == which.conf.base)
{
s = store;
}
});
return s;
}
function configBackups()
{
var conf = NB.storage.getConfig();
if (conf.backups.length)
conf.backups = [];
else
conf.backups.push( { type: 'simp', conf: { auth: 'hello.there' } });
var $div = $('tt .backup-conf').clone();
var $loc = $div.find('.loc');
var $rem = $div.find('.rem');
NB.storage.saveConfig();
NB.storage.initBackups(onBackupStatus);
var typ = (new SimpleBackup).type;
if (conf.backups.length != 2 ||
conf.backups[0].type != typ || conf.backups[0].conf.base != 'http://127.0.0.1:10001' ||
conf.backups[1].type != typ)
{
console.log('Unexpected backup config, will re-initialize.', conf.backups);
conf.backups.push({
type: typ,
id: typ + '-' + (conf.nextBackupId++),
enabled: false,
conf: { base: 'http://127.0.0.1:10001', auth: '' }
})
conf.backups.push({
type: typ,
id: typ + '-' + (conf.nextBackupId++),
enabled: false,
conf: { base: '', auth: '' }
})
NB.storage.saveConfig();
}
var loc = conf.backups[0];
var rem = conf.backups[1];
$loc.find('.auth').val( loc.conf.auth );
$rem.find('.auth').val( rem.conf.auth );
$rem.find('.base').val( rem.conf.base );
if (loc.enabled)
{
var b = findBackup(loc);
var last = b && b.last;
if (last && ! last.ok)
{
$loc.find('.last-err i').html( `${last.text}` );
$loc.find('.last-err').css({ display: 'block' });
}
}
else
{
$loc.addClass('off');
}
if (rem.enabled)
{
var b = findBackup(rem);
var last = b && b.last;
if (last && ! last.ok)
{
$rem.find('.last-err i').html( `${last.text}` );
$rem.find('.last-err').css({ display: 'block' });
}
}
else
{
$rem.addClass('off');
}
$div.find('a.cancel').click(function(){
hideOverlay();
});
$div.find('a.ok').click(function(){
var locEnabled = ! $loc.hasClass('off');
var locAuth = $loc.find('.auth').val();
var remEnabled = ! $rem.hasClass('off');
var remBase = $rem.find('.base').val();
var remAuth = $rem.find('.auth').val();
if (locEnabled && locAuth == '')
return shakeControl($loc.find('.auth'));
if (remEnabled)
{
if (remBase == '')
return shakeControl($rem.find('.base'));
if (remAuth == '')
return shakeControl($rem.find('.auth'));
}
if (locEnabled && ! loc.enabled)
loc.id = typ + '-' + (conf.nextBackupId++);
if (remEnabled && ! rem.enabled)
rem.id = typ + '-' + (conf.nextBackupId++);
loc.enabled = locEnabled;
loc.conf.auth = locAuth;
rem.enabled = remEnabled;
rem.conf.auth = remAuth;
rem.conf.base = remBase;
NB.storage.saveConfig();
NB.storage.initBackups(onBackupStatus);
hideOverlay();
});
showOverlay($div);
}
function onBackupStatus(_status)
@@ -3368,6 +3652,20 @@
else if (document.selection) { document.selection.empty(); }
}
function shakeControl($x)
{
$x
.css({ position: 'relative' })
.animate({ left: '+4px' }, 60)
.animate({ left: '-3px' }, 60)
.animate({ left: '+2px' }, 60)
.animate({ left: '0px' }, 60)
.delay(300)
.queue(function(){
$x.css({ position: '', left: '' }).focus().dequeue();
});
}
/*
* inline editing
*/
@@ -3476,7 +3774,7 @@
$('.overlay')
.html('')
.append($div)
.css({ opacity: 0, display: 'block' })
.css({ opacity: 0, display: 'flex' })
.animate({ opacity: 1 });
}
@@ -4210,7 +4508,7 @@
});
$('.config .auto-backup').on('click', function(){
toggleBackups();
configBackups();
});
//
@@ -4331,6 +4629,16 @@
$('.logo').removeClass('updated');
});
/***/
$('.overlay').on('click', '.backup-conf .opt', function(){
var $div = $(this).parent();
$div.toggleClass('off');
if (! $div.hasClass('off'))
$div.find('input').first().delay(500).queue(function(){ $(this).focus().dequeue(); });
return false;
});
$(window).resize(adjustLayout);
$('body').on('dragstart', function(){ return false; });