209 lines
11 KiB
JavaScript
209 lines
11 KiB
JavaScript
// Generated, do not edit. sha256:2c8b0cb9cf7b6e6878eecfd14445f87e0333bfc949df19e5e2747b65137b5411
|
|
import { getSettings, updateSettings } from '../data/preferences.js';
|
|
import { t, setLocale, LOCALES as CATALOGUES } from '../i18n/index.js';
|
|
import { CONFIG, ENUMS, PREFERENCES } from '../../shared/config.js';
|
|
import { setPageHint } from '../ui/page-hint.js';
|
|
import { esc } from '../ui/escape.js';
|
|
import { locked } from '../license.js';
|
|
const LOCALES = Object.keys(CATALOGUES);
|
|
|
|
const GLOW_GLASS = Object.freeze({
|
|
off: { glow: 'off' },
|
|
still: { glow: 'on', motion: 'off' },
|
|
step: { glow: 'on', motion: 'step' },
|
|
always: { glow: 'on', motion: 'always' },
|
|
});
|
|
const GLOW_BUSINESS = Object.freeze({ off: { glow: 'off' }, on: { glow: 'on' } });
|
|
|
|
export function glowChoice(s) {
|
|
if (s.glow === 'off') return 'off';
|
|
if (s.profile !== 'glass') return 'on';
|
|
return s.motion === 'off' ? 'still' : (s.motion || 'step');
|
|
}
|
|
|
|
export function glowPatch(profile, choice) {
|
|
const table = profile === 'glass' ? GLOW_GLASS : GLOW_BUSINESS;
|
|
return Object.hasOwn(table, choice) ? { ...table[choice] } : null;
|
|
}
|
|
|
|
const media = q => typeof matchMedia === 'function' && matchMedia(q).matches;
|
|
|
|
export async function render(el) {
|
|
const s = await getSettings();
|
|
const glass = s.profile === 'glass';
|
|
const business = !glass;
|
|
const glowVisible = glass || s.depth === 'deep';
|
|
const motionBlocked = glass && s.glow !== 'off' && s.motion !== 'off' && media('(prefers-reduced-motion: reduce)');
|
|
const transparencyBlocked = glass && media('(prefers-reduced-transparency: reduce)');
|
|
const persona = !locked('signals');
|
|
const kiosk = !locked('kiosk');
|
|
|
|
const glowOptions = glass
|
|
? [['off', t('settings.glowOff')], ['still', t('settings.glowStill')], ['step', t('settings.glowStep')], ['always', t('settings.glowAlways')]]
|
|
: [['off', t('settings.glowOff')], ['on', t('settings.glowOn')]];
|
|
|
|
el.innerHTML = `
|
|
<div class="settings-cols">
|
|
<div class="card">
|
|
<div class="t-label" style="margin-bottom:1.2rem">${t('settings.appearance')}</div>
|
|
${pillRow('theme', t('settings.theme'), [['dark', t('controls.dark')], ['light', t('controls.light')]], s.theme, { maxWidth: 300 })}
|
|
${accentRow(s)}
|
|
${pillRow('profile', t('settings.profile'), [['business', t('controls.business')], ['glass', t('controls.glass')]], s.profile,
|
|
{ maxWidth: 300, notice: transparencyBlocked ? t('settings.transparencyBlocked') : '' })}
|
|
${business ? pillRow('depth', t('settings.depth'), [['flat', t('controls.flat')], ['deep', t('controls.deep')]], s.depth, { maxWidth: 300 }) : ''}
|
|
${glowVisible ? pillRow('glowmix', t(glass ? 'settings.glowBgMotion' : 'settings.glowBg'), glowOptions, glowChoice(s),
|
|
{ maxWidth: glass ? 420 : 300, notice: motionBlocked ? t('settings.motionBlocked') : '' }) : ''}
|
|
${glass && s.glow !== 'off' ? pillRow('glowshape', t('settings.glowShape'),
|
|
[['blobs', t('settings.glowShapeBlobs')], ['bands', t('settings.glowShapeBands')]], s.glowshape, { maxWidth: 300 }) : ''}
|
|
${glass ? pillRow('rowsel', t('settings.rowsel'),
|
|
[['light', t('settings.rowselLight')], ['tint', t('settings.rowselTint')], ['pill', t('settings.rowselPill')]], s.rowsel, { maxWidth: 360 }) : ''}
|
|
${glass ? pillRow('glass_render', t('settings.glassRender'),
|
|
[['mimic', t('settings.glassRenderMimic')], ['real', t('settings.glassRenderReal')]], s.glass_render, { maxWidth: 300 }) : ''}
|
|
${glass ? glassRows(s) : ''}
|
|
</div>
|
|
<div class="card">
|
|
<div class="t-label" style="margin-bottom:1.2rem">${t('settings.screen')}</div>
|
|
<div class="form-group" style="margin-bottom:1rem">
|
|
<label class="form-label">${t('settings.assetDisplay')}</label>
|
|
<div class="pill-toggle" data-asset-display style="max-width:360px">
|
|
<button data-toggle="asset_show_name" class="${s.asset_show_name ? 'on' : ''}">${t('assets.name')}</button>
|
|
<button data-toggle="asset_show_symbol" class="${s.asset_show_symbol ? 'on' : ''}">${t('assets.symbol')}</button>
|
|
</div>
|
|
</div>
|
|
${persona ? pillRow('persona', t('settings.persona'),
|
|
[['bitcoin', t('setup.persona_bitcoin')], ['broad', t('setup.persona_broad')]], s.persona, { maxWidth: 300 }) : ''}
|
|
${pillRow('show_page_title', t('settings.pageTitle'),
|
|
[['1', t('settings.pageTitleShow')], ['0', t('settings.pageTitleHide')]], String(s.show_page_title), { maxWidth: 300 })}
|
|
${pillRow('show_page_hint', t('settings.pageHint'),
|
|
[['1', t('settings.pageTitleShow')], ['0', t('settings.pageTitleHide')]], String(s.show_page_hint), { maxWidth: 300 })}
|
|
${pillRow('result_signs', t('settings.resultSigns'),
|
|
[['none', t('settings.resultSignsNone')], ['negative', t('settings.resultSignsNegative')], ['both', t('settings.resultSignsBoth')]],
|
|
s.result_signs, { maxWidth: 420 })}
|
|
${kiosk ? pillRow('kiosk', t('settings.kiosk'),
|
|
[['0', t('settings.kioskWindow')], ['1', t('settings.kioskFull')]], String(s.kiosk), { maxWidth: 300 }) : ''}
|
|
${pillRow('auto_blur_min', t('settings.autoBlur'),
|
|
[...CONFIG.AUTO_BLUR.PRESETS_MIN.map(m => [String(m), `${m} ${t('settings.minutesShort')}`]),
|
|
[String(CONFIG.AUTO_BLUR.NEVER_MIN), t('settings.autoBlurManual')]],
|
|
String(s.auto_blur_min), { maxWidth: 420 })}
|
|
<div class="form-group" style="margin-bottom:0">
|
|
<label class="form-label">${t('settings.language')}</label>
|
|
<select class="input" id="f-locale" style="max-width:200px">
|
|
${LOCALES.map(l => `<option value="${l}"${l === s.locale ? ' selected' : ''}>${t('settings.' + l)}</option>`).join('')}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
|
|
setPageHint(el, { title: t('settings.hintTitle'), rows: [
|
|
{ heading: t('settings.appearance') },
|
|
t('settings.hintProfile'),
|
|
t('settings.hintTheme'),
|
|
t('settings.depthHint'),
|
|
t('settings.hintGlow'),
|
|
t('settings.hintGlowShape'),
|
|
t('settings.hintRowsel'),
|
|
t('settings.hintGlassRender'),
|
|
t('settings.hintGlass'),
|
|
{ heading: t('settings.hintAiHeading') },
|
|
t('settings.hintAiIntro'),
|
|
t('settings.hintAiTempo'),
|
|
{ heading: t('settings.hintScreenHeading') },
|
|
`${t('settings.assetDisplay')}: ${t('settings.assetDisplayHint')}`,
|
|
...(persona ? [`${t('settings.persona')}: ${t('settings.personaHint')}`] : []),
|
|
`${t('settings.pageTitle')}: ${t('settings.pageTitleHint')}`,
|
|
`${t('settings.pageHint')}: ${t('settings.pageHintHint')}`,
|
|
`${t('settings.resultSigns')}: ${t('settings.resultSignsHint')}`,
|
|
...(kiosk ? [`${t('settings.kiosk')}: ${t('settings.kioskHint')}`] : []),
|
|
`${t('settings.autoBlur')}: ${t('settings.autoBlurHint')}`,
|
|
] });
|
|
|
|
el.querySelectorAll('[data-field]').forEach(group => {
|
|
group.onclick = e => {
|
|
const b = e.target.closest('[data-v]'); if (!b) return;
|
|
const field = group.dataset.field;
|
|
if (field === 'glowmix') {
|
|
const patch = glowPatch(s.profile, b.dataset.v);
|
|
if (patch) apply(el, patch);
|
|
return;
|
|
}
|
|
apply(el, { [field]: b.dataset.v });
|
|
};
|
|
});
|
|
el.querySelector('#f-locale').onchange = e => apply(el, { locale: e.target.value });
|
|
for (const input of el.querySelectorAll('[data-glass]')) {
|
|
syncSlider(input);
|
|
input.oninput = () => syncSlider(input);
|
|
input.onchange = () => apply(el, { [input.dataset.glass]: Number(input.value) });
|
|
}
|
|
const reset = el.querySelector('[data-glass-reset]');
|
|
if (reset) reset.onclick = () => apply(el, Object.fromEntries(['glass_render', ...GLASS.map(([key]) => key)]
|
|
.map(key => [key, PREFERENCES.app[key].initial])));
|
|
el.querySelector('[data-asset-display]').onclick = e => {
|
|
const b = e.target.closest('[data-toggle]'); if (!b) return;
|
|
const field = b.dataset.toggle;
|
|
const other = field === 'asset_show_name' ? 'asset_show_symbol' : 'asset_show_name';
|
|
const next = s[field] ? 0 : 1;
|
|
if (next === 0 && !s[other]) return;
|
|
apply(el, { [field]: next });
|
|
};
|
|
}
|
|
|
|
async function apply(el, patch) {
|
|
await updateSettings(patch);
|
|
if (patch.locale) setLocale(patch.locale);
|
|
document.dispatchEvent(new Event('settings-changed'));
|
|
render(el);
|
|
}
|
|
|
|
function pillRow(field, label, options, current, { maxWidth = 500, notice = '' } = {}) {
|
|
const buttons = options.map(([v, lbl]) =>
|
|
`<button data-v="${v}" class="${v === current ? 'on' : ''}">${lbl}</button>`).join('');
|
|
return `
|
|
<div class="form-group" style="margin-bottom:1rem">
|
|
<label class="form-label">${label}</label>
|
|
<div class="pill-toggle" data-field="${field}" style="max-width:${maxWidth}px">${buttons}</div>
|
|
${notice ? `<div class="form-hint">${notice}</div>` : ''}
|
|
</div>`;
|
|
}
|
|
|
|
const GLASS = Object.freeze([
|
|
['glass_transparency', 'settings.glassTransparency', '--glass-transparency', '%', '%'],
|
|
['glass_blur', 'settings.glassDialog', '--dialog-transparency', '%', ''],
|
|
['glass_frost', 'settings.glassFrost', '--glass-frost', '%', '%'],
|
|
]);
|
|
|
|
function glassRows(s) {
|
|
const rows = GLASS.map(([key, label, token, unit, tokenUnit]) => {
|
|
const { min, max } = PREFERENCES.app[key];
|
|
return `<label class="slider-row slider-row--wide" for="f-${key}"><span>${t(label)}</span>
|
|
<input class="slider" type="range" id="f-${key}" min="${min}" max="${max}" step="1" value="${Number(s[key])}"
|
|
data-glass="${key}" data-token="${token}" data-unit="${unit}" data-token-unit="${tokenUnit}"><output for="f-${key}"></output></label>`;
|
|
}).join('');
|
|
return `
|
|
<div class="form-group" style="margin-bottom:1rem; max-width:420px">
|
|
<label class="form-label">${t('settings.glass')}</label>
|
|
${rows}
|
|
<div><button class="btn btn-ghost btn-xs" type="button" data-glass-reset>${t('settings.glassDefault')}</button></div>
|
|
</div>`;
|
|
}
|
|
|
|
function syncSlider(input) {
|
|
const min = Number(input.min), max = Number(input.max), v = Number(input.value);
|
|
input.style.setProperty('--pct', `${((v - min) / (max - min)) * 100}%`);
|
|
const out = input.parentElement.querySelector('output');
|
|
if (out) out.textContent = `${v}${input.dataset.unit}`;
|
|
document.documentElement.style.setProperty(input.dataset.token, `${v}${input.dataset.tokenUnit}`);
|
|
}
|
|
|
|
function accentRow(s) {
|
|
const dots = ENUMS.ACCENT.map(a => {
|
|
const label = t(`controls.${a}`);
|
|
return `<button type="button" data-v="${a}" data-theme="${esc(s.theme)}" data-accent="${a}" class="${a === s.accent ? 'on' : ''}" title="${esc(label)}" aria-label="${esc(label)}"></button>`;
|
|
}).join('');
|
|
return `
|
|
<div class="form-group" style="margin-bottom:1rem">
|
|
<label class="form-label">${t('settings.accent')}</label>
|
|
<div class="accent-dots" data-field="accent">${dots}</div>
|
|
</div>`;
|
|
}
|