feat: Аккаунт › Вид - tab icons left, right or hidden, terminal font, compact file lists, the time column, animations; kept per user on the server. Uploads show a ring filling as they go
This commit is contained in:
1 parent
c003b51b4c
commit
5216aed237
10 files changed
+167
-3
No files matched your search
@@ -56,6 +56,13 @@
|
||||
' <option value="0">каждый раз</option><option value="5">5 минут</option><option value="15">15 минут</option>' +
|
||||
' <option value="30">30 минут</option><option value="60">60 минут</option></select></label>' +
|
||||
' <p class="acct-pinmsg" aria-live="polite"></p><p class="muted" style="font-size:12px;margin:8px 0 0">Спрашивается в каждой новой вкладке; при перезагрузке той же вкладки - нет.</p></details>' +
|
||||
' <details class="acct-view"><summary>Вид</summary><div class="acct-viewin">' +
|
||||
' <label><span>Значки вкладок</span><select data-pref="tabIcons"><option value="">слева от названия</option><option value="right">справа от названия</option><option value="hide">не показывать</option></select></label>' +
|
||||
' <label><span>Шрифт терминала</span><select data-pref="termFont" data-num><option value="0">обычный</option><option value="12">12</option><option value="13">13</option><option value="14">14</option><option value="15">15</option><option value="16">16</option><option value="18">18</option><option value="20">20</option></select></label>' +
|
||||
' <label><span>Списки файлов</span><select data-pref="compact" data-bool><option value="">обычные</option><option value="1">компактные</option></select></label>' +
|
||||
' <label><span>Время изменения файлов</span><select data-pref="fileTime"><option value="">показывать</option><option value="hide">скрыть</option></select></label>' +
|
||||
' <label><span>Анимации</span><select data-pref="motion"><option value="">включены</option><option value="off">выключены</option></select></label>' +
|
||||
' </div></details>' +
|
||||
' <div class="acct-keysec">' +
|
||||
' <h3>Ключи доступа</h3>' +
|
||||
' <p class="muted acct-hint">Вход отпечатком, лицом, PIN-кодом устройства или аппаратным ключом. Ключей может быть несколько - например, телефон и ноутбук.</p>' +
|
||||
@@ -69,6 +76,15 @@
|
||||
dlg.querySelector('.acct-out [name=csrf]').value = decodeURIComponent(csrf);
|
||||
var $ = function (sel) { return dlg.querySelector(sel); };
|
||||
$('[data-close]').onclick = function () { dlg.close(); };
|
||||
// Вид: each change applies at once and is kept on the server.
|
||||
dlg.querySelectorAll('[data-pref]').forEach(function (sel) {
|
||||
var k = sel.dataset.pref, cur = window.Prefs ? Prefs.get()[k] : '';
|
||||
sel.value = sel.hasAttribute('data-bool') ? (cur ? '1' : '') : String(cur || (sel.hasAttribute('data-num') ? 0 : ''));
|
||||
sel.onchange = function () {
|
||||
var v = sel.hasAttribute('data-bool') ? sel.value === '1' : sel.hasAttribute('data-num') ? Number(sel.value) : sel.value;
|
||||
if (window.Prefs) Prefs.set(k, v).then(function (ok) { if (!ok) msg('не сохранилось', true); });
|
||||
};
|
||||
});
|
||||
var supported = !!(window.PublicKeyCredential && navigator.credentials && window.isSecureContext);
|
||||
|
||||
function msg(text, bad) {
|
||||
|
||||
@@ -1003,3 +1003,25 @@ details[open]::details-content { block-size: auto; opacity: 1; }
|
||||
.ai-act:hover:not(:disabled) { filter:brightness(1.1); }
|
||||
.ai-act:disabled { background:#1f2733; color:var(--muted); cursor:default; }
|
||||
.ai-code.ai-action pre { margin:0; border:0; border-radius:0; background:transparent; }
|
||||
/* Аккаунт › Вид. */
|
||||
.acct-view { margin-top:12px; padding-top:12px; border-top:1px solid var(--border); }
|
||||
.acct-view summary { font-size:14px; color:var(--text); }
|
||||
.acct-viewin { display:grid; gap:8px; margin-top:10px; }
|
||||
.acct-viewin label { display:flex; align-items:center; justify-content:space-between; gap:10px; font-size:13px; color:var(--muted); }
|
||||
.acct-viewin select { width:auto; flex:none; min-width:170px; }
|
||||
/* The look chosen. */
|
||||
body[data-tabicons="right"] .tab-kinds { order:2; }
|
||||
body[data-tabicons="right"] .tab-title { order:1; }
|
||||
body[data-tabicons="right"] .tab-x { order:3; }
|
||||
body[data-tabicons="hide"] .tab-kinds { display:none; }
|
||||
body.compact table.fm td { padding-top:3px; padding-bottom:3px; }
|
||||
body.compact table.fm td .btn.icon { width:28px; height:28px; }
|
||||
body.no-ftime table.fm .c-mt, body.no-ftime table.fm td.mt { display:none; }
|
||||
body.no-motion *, body.no-motion *::before, body.no-motion *::after, body.no-motion::backdrop { transition:none !important; animation:none !important; }
|
||||
/* Uploading: a small ring that fills as the files go up. */
|
||||
.fp-ring { display:none; transform:rotate(-90deg); flex:none; }
|
||||
.busy.uploading .spinner { display:none; }
|
||||
.busy.uploading .fp-ring { display:block; }
|
||||
.fp-ring circle { fill:none; stroke-width:2.6; }
|
||||
.fp-ring .bg { stroke:var(--border); }
|
||||
.fp-ring .fg { stroke:var(--accent); stroke-linecap:round; stroke-dasharray:100; stroke-dashoffset:calc(100 - var(--up, 0)); transition:stroke-dashoffset .25s ease; }
|
||||
@@ -104,7 +104,9 @@
|
||||
' <button class="btn ghost small icon sq fp-up" type="button" title="Вверх" aria-label="Вверх"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5M5 12l7-7 7 7"/></svg></button>' +
|
||||
' <div class="fm-pathbox"><input class="fm-path fp-path" value="/" spellcheck="false" autocapitalize="off" autocomplete="off" aria-label="Папка">' +
|
||||
' <button type="button" class="btn ghost small icon sq fp-dirprops" title="Свойства папки" aria-label="Свойства папки"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 4h-7M10 4H3M21 12h-9M8 12H3M21 20h-5M12 20H3M14 2v4M8 10v4M16 18v4"/></svg></button></div>' +
|
||||
' <button type="button" class="busy fp-busy" title=""><span class="spinner"></span><span class="fp-uppct"></span></button>' +
|
||||
' <button type="button" class="busy fp-busy" title=""><span class="spinner"></span>' +
|
||||
'<svg class="fp-ring" width="18" height="18" viewBox="0 0 20 20" aria-hidden="true"><circle class="bg" cx="10" cy="10" r="8"/><circle class="fg" cx="10" cy="10" r="8" pathLength="100"/></svg>' +
|
||||
'<span class="fp-uppct"></span></button>' +
|
||||
' <button class="btn ghost small icon sq fp-refresh" type="button" title="Обновить" aria-label="Обновить"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-3-6.7L21 8"/><path d="M21 3v5h-5"/></svg></button>' +
|
||||
'</div>' +
|
||||
'<div class="fm-bar">' +
|
||||
@@ -378,6 +380,9 @@
|
||||
busy.classList.toggle('up', ups.length > 0);
|
||||
busy.title = ups.length ? 'Показать загружаемые файлы' : '';
|
||||
if (pend.length) pct.textContent = Math.floor(100 * sent / total) + '%' + (counted.length > 1 ? ' · ' + (counted.length - pend.length) + '/' + counted.length : '');
|
||||
// While files go up, the round mark fills as they do.
|
||||
busy.classList.toggle('uploading', pend.length > 0);
|
||||
busy.style.setProperty('--up', String(pend.length ? 100 * sent / total : 0));
|
||||
pct.classList.toggle('on', pend.length > 0);
|
||||
FV.uploadsChanged(this);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// The look of the panel, as each user set it (Аккаунт › Вид): applied at
|
||||
// once on load from what the page came with, and again on every change.
|
||||
(function () {
|
||||
'use strict';
|
||||
let p = Object.assign({}, (window.APP && window.APP.prefs) || {});
|
||||
function apply() {
|
||||
const b = document.body;
|
||||
b.dataset.tabicons = p.tabIcons || 'left';
|
||||
b.classList.toggle('compact', !!p.compact);
|
||||
b.classList.toggle('no-ftime', p.fileTime === 'hide');
|
||||
b.classList.toggle('no-motion', p.motion === 'off');
|
||||
// Open terminals take the new size at once.
|
||||
if (window.Workspace) Workspace.panes().forEach(t => {
|
||||
if (t.term && t.term.options.fontSize !== termFont()) { t.term.options.fontSize = termFont(); t.fit(); }
|
||||
});
|
||||
}
|
||||
const termFont = () => p.termFont || (matchMedia('(pointer: coarse)').matches ? 13 : 14);
|
||||
async function set(k, v) {
|
||||
p[k] = v;
|
||||
apply();
|
||||
const r = await fetch('/api/panel/account/prefs', {method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
credentials: 'same-origin', body: JSON.stringify(p)}).then(x => x.json()).catch(() => ({ok: false}));
|
||||
return r.ok;
|
||||
}
|
||||
apply();
|
||||
window.Prefs = {get: () => Object.assign({}, p), set, apply, termFont};
|
||||
})();
|
||||
@@ -283,7 +283,7 @@ window.TermKeys = (function () {
|
||||
this.el.querySelector('.pane-title').title = ssh ? 'SSH через ' + agent.name : agent.name;
|
||||
if (ssh) this.el.querySelector('.dot').insertAdjacentHTML('afterend', '<span class="badge-ssh">SSH</span>');
|
||||
this.term = new Terminal({
|
||||
cursorBlink: true, fontSize: touch ? 13 : 14,
|
||||
cursorBlink: true, fontSize: window.Prefs ? Prefs.termFont() : (touch ? 13 : 14),
|
||||
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', theme: { background: '#0c0f13' }
|
||||
});
|
||||
this.fitter = new FitAddon.FitAddon();
|
||||
|
||||
Reference in new issue
Block a user