mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-05 09:54:06 +08:00
167 lines
6.4 KiB
JavaScript
167 lines
6.4 KiB
JavaScript
'use strict';
|
|
'require baseclass';
|
|
'require rpc';
|
|
'require ui';
|
|
|
|
var callGet = rpc.declare({ object: 'luci.argon_appearance', method: 'get', expect: { '': {} }, raise: true });
|
|
var callSet = rpc.declare({ object: 'luci.argon_appearance', method: 'set', params: ['mode'], expect: { '': {} }, raise: true });
|
|
var callAccess = rpc.declare({ object: 'session', method: 'access', params: ['scope', 'object', 'function'], expect: { access: false }, raise: true });
|
|
|
|
return baseclass.extend({
|
|
__init__: function() {
|
|
this.root = document.getElementById('argon-appearance');
|
|
if (!this.root)
|
|
return;
|
|
this.mode = document.documentElement.dataset.argonMode;
|
|
this.busy = false;
|
|
this.preference = window.matchMedia('(prefers-color-scheme: dark)');
|
|
return Promise.all([ callGet(), callAccess('ubus', 'luci.argon_appearance', 'set') ]).then(function(data) {
|
|
if (data[0].result !== 0 || !this.validMode(data[0].mode))
|
|
return;
|
|
this.writable = data[1] === true;
|
|
this.render();
|
|
this.applyMode(data[0].mode);
|
|
this.root.hidden = false;
|
|
var onSystemChange = function() {
|
|
if (this.mode === 'normal')
|
|
this.applyMode('normal');
|
|
}.bind(this);
|
|
if (this.preference.addEventListener)
|
|
this.preference.addEventListener('change', onSystemChange);
|
|
else
|
|
this.preference.addListener(onSystemChange);
|
|
}.bind(this)).catch(function() {
|
|
// Leave the server-rendered theme intact when the service is unavailable.
|
|
});
|
|
},
|
|
|
|
validMode: function(mode) {
|
|
return [ 'normal', 'light', 'dark' ].includes(mode);
|
|
},
|
|
|
|
render: function() {
|
|
this.labels = { light: _('Light mode'), dark: _('Dark mode'), normal: _('Follow system') };
|
|
this.trigger = E('button', {
|
|
type: 'button', 'class': 'argon-appearance-trigger',
|
|
'aria-label': _('Theme mode'), 'aria-expanded': 'false', 'aria-controls': 'argon-appearance-options',
|
|
click: function() { this.open(this.panel.hidden); }.bind(this)
|
|
});
|
|
this.radios = Object.keys(this.labels).map(function(mode) {
|
|
return E('input', {
|
|
type: 'radio', name: 'argon-appearance-mode', value: mode,
|
|
'class': 'visually-hidden',
|
|
disabled: !this.writable || null,
|
|
change: function(event) {
|
|
if (event.target.checked)
|
|
this.saveMode(mode);
|
|
}.bind(this)
|
|
});
|
|
}.bind(this));
|
|
this.panel = E('fieldset', { id: 'argon-appearance-options', 'class': 'argon-appearance-panel', hidden: true }, [
|
|
E('legend', { 'class': 'visually-hidden' }, _('Theme mode')),
|
|
...this.radios.map(function(radio) {
|
|
return E('label', { 'class': 'argon-appearance-option', 'data-mode': radio.value }, [
|
|
radio, E('span', {}, this.labels[radio.value])
|
|
]);
|
|
}.bind(this)),
|
|
!this.writable ? E('p', {}, _('Read-only')) : ''
|
|
]);
|
|
this.message = E('span', { 'class': 'visually-hidden', role: 'status' });
|
|
this.root.appendChild(this.trigger);
|
|
this.root.appendChild(this.panel);
|
|
this.root.appendChild(this.message);
|
|
this.root.addEventListener('keydown', function(event) {
|
|
if (event.key === 'Escape' && !this.panel.hidden) {
|
|
event.preventDefault();
|
|
this.open(false, true);
|
|
}
|
|
}.bind(this));
|
|
document.addEventListener('click', function(event) {
|
|
if (!this.root.contains(event.target))
|
|
this.open(false);
|
|
}.bind(this));
|
|
this.root.addEventListener('focusout', function(event) {
|
|
if (event.relatedTarget && !this.root.contains(event.relatedTarget))
|
|
this.open(false);
|
|
// A pointer press on a label can leave focus on the page until release.
|
|
// With no destination, wait for label activation or the outside click.
|
|
}.bind(this));
|
|
window.addEventListener('blur', function() { this.open(false); }.bind(this));
|
|
},
|
|
|
|
open: function(show, restoreFocus) {
|
|
this.panel.hidden = !show;
|
|
this.trigger.setAttribute('aria-expanded', String(show));
|
|
if (show) {
|
|
var checked = this.radios.find(function(radio) { return radio.checked && !radio.disabled; });
|
|
if (checked)
|
|
checked.focus();
|
|
} else if (restoreFocus) {
|
|
this.trigger.focus();
|
|
}
|
|
},
|
|
|
|
applyMode: function(mode) {
|
|
var html = document.documentElement;
|
|
html.classList.add('argon-theme-changing');
|
|
html.dataset.argonMode = mode;
|
|
this.mode = mode;
|
|
this.root.dataset.mode = mode;
|
|
this.radios.forEach(function(radio) { radio.checked = radio.value === mode; });
|
|
this.trigger.title = _('Theme mode') + ': ' + this.labels[mode];
|
|
this.trigger.setAttribute('aria-label', this.trigger.title);
|
|
// Update browser chrome as well as the CSS palette, including system mode.
|
|
var dark = mode === 'dark' || (mode === 'normal' && this.preference.matches);
|
|
var color = getComputedStyle(html).getPropertyValue(dark ? '--dark-primary' : '--light-primary').trim();
|
|
document.querySelectorAll('meta[name="theme-color"]').forEach(function(meta, index) {
|
|
meta.removeAttribute('media');
|
|
if (index === 0)
|
|
meta.content = color;
|
|
else
|
|
meta.remove();
|
|
});
|
|
void html.offsetHeight;
|
|
cancelAnimationFrame(this.frame);
|
|
this.frame = requestAnimationFrame(function() {
|
|
html.classList.remove('argon-theme-changing');
|
|
});
|
|
},
|
|
|
|
saveMode: function(mode) {
|
|
if (!this.writable || this.busy || !this.validMode(mode) || mode === this.mode)
|
|
return Promise.resolve();
|
|
var previous = this.mode;
|
|
this.busy = true;
|
|
this.radios.forEach(function(radio) { radio.disabled = true; });
|
|
this.panel.setAttribute('aria-busy', 'true');
|
|
this.message.textContent = _('Saving theme mode…');
|
|
this.applyMode(mode);
|
|
this.open(false, true);
|
|
return callSet(mode).then(function(result) {
|
|
if (result.result !== 0 || result.mode !== mode)
|
|
throw new Error('save failed');
|
|
this.saved(mode);
|
|
}.bind(this)).catch(function() {
|
|
// A lost reply may follow a successful commit; read back before reverting.
|
|
return callGet().catch(function() { return {}; }).then(function(state) {
|
|
this.applyMode(state.result === 0 && this.validMode(state.mode) ? state.mode : previous);
|
|
if (state.result === 0 && state.mode === mode) {
|
|
this.saved(mode);
|
|
return;
|
|
}
|
|
this.message.textContent = _('Unable to save theme mode. Please try again.');
|
|
ui.addNotification(null, E('p', { role: 'alert' }, this.message.textContent), 'error');
|
|
}.bind(this));
|
|
}.bind(this)).finally(function() {
|
|
this.busy = false;
|
|
this.panel.removeAttribute('aria-busy');
|
|
this.radios.forEach(function(radio) { radio.disabled = !this.writable; }.bind(this));
|
|
}.bind(this));
|
|
},
|
|
|
|
saved: function(mode) {
|
|
this.message.textContent = _('Theme mode saved.');
|
|
window.dispatchEvent(new CustomEvent('argon-appearance-saved', { detail: { mode: mode } }));
|
|
}
|
|
});
|