feat(android): a settings screen, and the unlock code can finally be removed
Three things of different natures lived in collapsible panels on the home screen - repository credentials, the unlock code, the log - on the one screen where one mostly wants to open one's vault. They move to a settings sheet, reachable from the list too. A sheet, not a fourth view of the rail: each view costs its transform rules and a place in back-key navigation, and settings is not somewhere one moves about - it is a parenthesis one opens and closes. The unlock code can now be REMOVED. It could not before: clearing the field meant "nothing typed", not "drop the protection", so one could set a code and never get out. It requires an open vault, and that is the proof being asked for - the cache only let itself be read under the right code. Without that, anyone holding the unlocked phone could lift the protection without knowing it. The refusal that guards every other path stays: it is what stopped a sync from silently undoing a code. Removing is the one deliberate way out, and it says so at the call site. The old fields stay in the document as the source Delphi knows; the sheet is only a view of them. Emptying them would lose the configuration at the first sync.
This commit is contained in:
+148
-7
@@ -305,6 +305,20 @@
|
||||
font: 11px/1.45 monospace; white-space: pre-wrap; word-break: break-all;
|
||||
-webkit-user-select: text; user-select: text;
|
||||
}
|
||||
/* LES REGLAGES SONT UN VOILE, PAS UNE QUATRIEME VUE DU RAIL. Chaque vue du
|
||||
rail coute ses regles de transformation et une place dans la navigation
|
||||
par la touche retour ; les reglages ne sont pas un lieu ou l'on circule,
|
||||
c'est une parenthese qu'on ouvre et qu'on referme. */
|
||||
.sheet { position: fixed; inset: 0; z-index: 80; display: none;
|
||||
flex-direction: column;
|
||||
background: linear-gradient(var(--bg-top), var(--bg-bot)); }
|
||||
body.on-settings .sheet { display: flex; }
|
||||
.sheet h2 { margin: 18px 4px 6px; font-size: 13px; font-weight: 600;
|
||||
color: var(--muted); text-transform: uppercase;
|
||||
letter-spacing: .06em; }
|
||||
.sheet .note { color: var(--muted); font-size: 13px; margin: 6px 4px 0;
|
||||
line-height: 1.45; }
|
||||
|
||||
[hidden] { display: none !important; }
|
||||
|
||||
.toast {
|
||||
@@ -363,8 +377,9 @@
|
||||
<button class="btn wide main" id="btnOpen">➜ Open vault</button>
|
||||
<button class="btn wide" id="btnSync">↻ Sync</button>
|
||||
<div class="pair">
|
||||
<button class="btn" id="btnCfg" data-panel="cfg">⚙ Server
|
||||
settings<span class="chev">▾</span></button>
|
||||
<!-- Plus de chevron : ce bouton n'ouvre plus un panneau sous lui,
|
||||
il ouvre un ecran. Le garder promettrait un depliage. -->
|
||||
<button class="btn" id="btnCfg">⚙ Settings</button>
|
||||
<button class="btn" id="btnLog" data-panel="log">≡
|
||||
Log<span class="chev">▾</span></button>
|
||||
</div>
|
||||
@@ -446,6 +461,13 @@
|
||||
autocapitalize="none" spellcheck="false">
|
||||
<div class="clear" id="qx">×</div>
|
||||
</div>
|
||||
<button class="ico" id="gear" aria-label="Settings">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="3.2" />
|
||||
<path d="M12 3.5v2M12 18.5v2M3.5 12h2M18.5 12h2M6 6l1.4 1.4M16.6 16.6L18 18M18 6l-1.4 1.4M7.4 16.6L6 18" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="ico" id="new" aria-label="New entry">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round">
|
||||
@@ -481,6 +503,53 @@
|
||||
<div class="menu" id="menu" hidden></div>
|
||||
</div>
|
||||
|
||||
<!-- REGLAGES. Ils rassemblent ce qui vivait en deux panneaux repliables sur
|
||||
l'ecran d'accueil : les coordonnees du depot d'un cote, le code de
|
||||
deverrouillage de l'autre, et le journal en troisieme. Trois choses de
|
||||
nature differente sur l'ecran ou l'on veut surtout ouvrir son coffre. -->
|
||||
<div class="sheet" id="settings">
|
||||
<div class="head">
|
||||
<button class="btn" id="setBack">←</button>
|
||||
<h1>Settings</h1>
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<h2>Repository</h2>
|
||||
<div class="in-wrap">
|
||||
<input class="field-in" id="sUrl" placeholder="endpoint URL"
|
||||
autocomplete="off" autocorrect="off" autocapitalize="none"
|
||||
spellcheck="false" inputmode="url">
|
||||
<div class="in-x" data-clear="sUrl">×</div>
|
||||
</div>
|
||||
<div class="in-wrap">
|
||||
<input class="field-in" id="sTok" placeholder="access token"
|
||||
type="password" autocomplete="off">
|
||||
<div class="in-x" data-clear="sTok">×</div>
|
||||
</div>
|
||||
<div class="in-wrap">
|
||||
<input class="field-in" id="sPwd" placeholder="master password"
|
||||
type="password" autocomplete="off">
|
||||
<div class="in-x" data-clear="sPwd">×</div>
|
||||
</div>
|
||||
<div class="note">The master password never leaves this device. It is what
|
||||
decrypts the vault; the token only opens the repository.</div>
|
||||
|
||||
<h2>Unlock code</h2>
|
||||
<div class="in-wrap">
|
||||
<input class="field-in" id="sPinNew" placeholder="new unlock code"
|
||||
type="password" inputmode="numeric" autocomplete="off">
|
||||
<div class="in-x" data-clear="sPinNew">×</div>
|
||||
</div>
|
||||
<button class="btn wide" id="sPinSet">Set this code</button>
|
||||
<button class="btn wide danger" id="sPinClear">Remove the unlock code</button>
|
||||
<div class="note">The code seals the copy kept on this device. Removing it
|
||||
leaves the vault readable by anyone holding the phone unlocked.</div>
|
||||
|
||||
<h2>Diagnostics</h2>
|
||||
<button class="btn wide" id="sLog">Show the log</button>
|
||||
<div class="note" id="sVersion"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="busy" id="busy"><div></div></div>
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
@@ -570,6 +639,14 @@ document.getElementById('btnSync').addEventListener('click', function () {
|
||||
// cible. Pose sur l'ECRAN, pas sur le groupe des reglages - le code a son
|
||||
// propre bloc depuis qu'il n'appartient plus au serveur, et sa croix serait
|
||||
// restee sans effet.
|
||||
document.getElementById('settings').addEventListener('click', function (ev) {
|
||||
var id = ev.target.getAttribute('data-clear');
|
||||
if (!id) return;
|
||||
var el = document.getElementById(id);
|
||||
el.value = '';
|
||||
el.focus();
|
||||
});
|
||||
|
||||
document.getElementById('unlock').addEventListener('click', function (ev) {
|
||||
var id = ev.target.getAttribute('data-clear');
|
||||
if (!id) return;
|
||||
@@ -591,9 +668,7 @@ function togglePanel(name) {
|
||||
document.querySelector('[data-panel="' + n + '"]').classList.toggle('open', open);
|
||||
});
|
||||
}
|
||||
document.getElementById('btnCfg').addEventListener('click', function () {
|
||||
togglePanel('cfg');
|
||||
});
|
||||
document.getElementById('btnCfg').addEventListener('click', openSettings);
|
||||
document.getElementById('btnLog').addEventListener('click', function () {
|
||||
togglePanel('log');
|
||||
});
|
||||
@@ -1162,6 +1237,7 @@ document.getElementById('fields').addEventListener('click', function (ev) {
|
||||
+ '&k=' + newKind);
|
||||
});
|
||||
document.getElementById('new').addEventListener('click', openNewEntry);
|
||||
document.getElementById('gear').addEventListener('click', openSettings);
|
||||
|
||||
// LE MENU DE L'ENTREE, et la suppression qui y vit.
|
||||
//
|
||||
@@ -1322,6 +1398,60 @@ document.addEventListener('click', function (ev) {
|
||||
closeMenu();
|
||||
});
|
||||
|
||||
// LES REGLAGES. Ils lisent et ecrivent les MEMES champs que l'accueil - la
|
||||
// page n'en garde qu'une copie, Delphi reste la seule source.
|
||||
function openSettings() {
|
||||
document.getElementById('sUrl').value = elUrl.value;
|
||||
document.getElementById('sTok').value = elTok.value;
|
||||
document.getElementById('sPwd').value = elPwd.value;
|
||||
document.getElementById('sPinNew').value = '';
|
||||
document.body.classList.add('on-settings');
|
||||
send('state?view=settings');
|
||||
}
|
||||
|
||||
function closeSettings() {
|
||||
// Ce qui a ete tape ici redescend vers Delphi avant de fermer, sinon un
|
||||
// reglage modifie puis referme sans quitter le champ serait perdu.
|
||||
elUrl.value = document.getElementById('sUrl').value.trim();
|
||||
elTok.value = document.getElementById('sTok').value.trim();
|
||||
elPwd.value = document.getElementById('sPwd').value;
|
||||
pushConfig();
|
||||
document.body.classList.remove('on-settings');
|
||||
send('state?view=' + view);
|
||||
}
|
||||
|
||||
document.getElementById('setBack').addEventListener('click', closeSettings);
|
||||
document.getElementById('sLog').addEventListener('click', function () {
|
||||
closeSettings();
|
||||
if (document.getElementById('log').hidden) togglePanel('log');
|
||||
});
|
||||
document.getElementById('sPinSet').addEventListener('click', function () {
|
||||
var v = document.getElementById('sPinNew').value;
|
||||
if (!v) { toast('Type a code first'); return; }
|
||||
// LE MEME CHEMIN QUE L'ACCUEIL : c'est l'ouverture du coffre qui pose ou
|
||||
// change le sceau, et elle seule. Ici on ne fait que remplir le champ.
|
||||
document.getElementById('pin2').value = v;
|
||||
pushConfig();
|
||||
closeSettings();
|
||||
toast('Open the vault to apply the new code');
|
||||
});
|
||||
document.getElementById('sPinClear').addEventListener('click', function (ev) {
|
||||
var b = ev.currentTarget;
|
||||
if (b.getAttribute('data-armed') === null) {
|
||||
b.setAttribute('data-armed', '1');
|
||||
b.textContent = 'Really remove - tap again';
|
||||
setTimeout(function () {
|
||||
b.removeAttribute('data-armed');
|
||||
b.textContent = 'Remove the unlock code';
|
||||
}, 4000);
|
||||
return;
|
||||
}
|
||||
b.removeAttribute('data-armed');
|
||||
b.textContent = 'Remove the unlock code';
|
||||
send('pinclear');
|
||||
closeSettings();
|
||||
});
|
||||
|
||||
function goList() {
|
||||
closeMenu();
|
||||
document.body.classList.remove('on-detail');
|
||||
@@ -1391,11 +1521,17 @@ var PM = {
|
||||
o.style.opacity = busy ? .4 : 1;
|
||||
s.style.opacity = busy ? .4 : 1;
|
||||
},
|
||||
/// LES CHAMPS RESTENT LA SOURCE, l'ecran de reglages n'en est qu'une vue.
|
||||
/// Ils ne s'affichent plus - le panneau repliable a cede la place au voile -
|
||||
/// mais c'est toujours d'eux que part pushConfig, et Delphi ne connait qu'eux.
|
||||
/// Les vider ferait perdre la configuration a la premiere synchronisation.
|
||||
setConfig: function (url, tok, pwd, show) {
|
||||
// elPin volontairement absent : rien ne le restaure, jamais.
|
||||
elUrl.value = url; elTok.value = tok; elPwd.value = pwd;
|
||||
// Replier des champs vides revient a cacher la seule chose a faire.
|
||||
if (show !== !document.getElementById('cfg').hidden) togglePanel('cfg');
|
||||
// Rien a deplier : c'est le voile des reglages qui les montre desormais.
|
||||
// Un depot pas encore configure s'annonce donc autrement - le bouton
|
||||
// d'accueil dit "Download vault" et l'ecran, "No vault on this device".
|
||||
if (show) toast('Set the repository in Settings');
|
||||
},
|
||||
|
||||
/// LE JOURNAL SORT DE LA VUE EN RELEASE, sans disparaitre.
|
||||
@@ -1492,6 +1628,9 @@ var PM = {
|
||||
document.getElementById('lock').classList.toggle('ahead', !!on);
|
||||
document.getElementById('btnSync').classList.toggle('ahead', !!on);
|
||||
},
|
||||
setVersion: function (v) {
|
||||
document.getElementById('sVersion').textContent = v;
|
||||
},
|
||||
setBusy: function (on) {
|
||||
if (on) document.getElementById('busy').firstChild.innerHTML =
|
||||
ico('loader', 'Working') + '<span>Please wait...</span>';
|
||||
@@ -1502,6 +1641,8 @@ var PM = {
|
||||
/// Sans cela, un geste d'annulation fermait le coffre - le contraire de ce
|
||||
/// qu'il veut dire.
|
||||
clearSelection: function () { clearSel(); },
|
||||
/// La touche retour referme les reglages avant de toucher au coffre.
|
||||
closeSettings: function () { closeSettings(); },
|
||||
// Efface tout ce qui est en clair. Appele au passage en arriere-plan et au
|
||||
// verrouillage : le tas JS n'est pas efface par le masquage de la vue.
|
||||
wipe: function () {
|
||||
|
||||
Reference in New Issue
Block a user