Versie 0.2.0
This commit is contained in:
commit
e77d8fd373
495 files changed
+81026
No files matched your search
+272
@@ -0,0 +1,272 @@
|
||||
// Generated, do not edit. sha256:8874091aa075a54dd49f5d1aad9244d40ef61ac47ce7c83d7d4e346d8133207a
|
||||
import { configureSharedUi } from './ui/shared-setup.js';
|
||||
import './edition.js';
|
||||
import { assertConfig, ROUTE_KEYS, hashFromLocation } from '../shared/config.js';
|
||||
import { listAssets } from './data/assets.js';
|
||||
import { refreshNotifications } from './data/notifications.js';
|
||||
import { initCommands } from './commands.js';
|
||||
import { loadAppSettings, appSetting } from './app-settings.js';
|
||||
import { loadLicense } from './license.js';
|
||||
import { t, setLocale } from './i18n/index.js';
|
||||
import { initStatusBar } from './ui/status-bar.js';
|
||||
import { renderErrorPanel } from './ui/error-panel.js';
|
||||
import { createRenderTurns } from './ui/render-turn.js';
|
||||
import { finishSplash, initSplashReplay } from './ui/splash.js';
|
||||
import { refreshActivePortfolio, watchPortfolioChanges, cacheSleutels } from './portfolio.js';
|
||||
import { applyAurora } from './ui/aurora.js';
|
||||
import { initScheduler } from './ui/scheduler.js';
|
||||
import { refreshAttention, attentionForRoute, attentionDotHtml } from './ui/attention.js';
|
||||
import { registerAttentionSources } from './ui/attention-sources.js';
|
||||
import { clearPageHint } from './ui/page-hint.js';
|
||||
import { refreshRecentCloses } from './price/providers.js';
|
||||
import * as home from './pages/home.js';
|
||||
import * as conversionBitcoin from './pages/conversion-bitcoin.js';
|
||||
import * as priceHistory from './pages/price-history.js';
|
||||
import * as wealth from './pages/wealth.js';
|
||||
import * as events from './pages/events.js';
|
||||
import * as tracking from './pages/tracking.js';
|
||||
import * as system from './pages/system.js';
|
||||
import * as actions from './pages/actions.js';
|
||||
import * as guide from './pages/guide.js';
|
||||
import { makeStub } from './pages/stub.js';
|
||||
import { ADJUST } from './ui/icons.js';
|
||||
import { esc } from './ui/escape.js';
|
||||
import * as dialog from './ui/dialog.js';
|
||||
import { maybeStartSetup } from './onboarding/setup.js';
|
||||
import { registerRoute, routePage } from './routes.js';
|
||||
|
||||
registerRoute(ROUTE_KEYS.HOME, home);
|
||||
registerRoute(ROUTE_KEYS.WEALTH, wealth);
|
||||
registerRoute(ROUTE_KEYS.EVENTS, events);
|
||||
registerRoute(ROUTE_KEYS.VALIDATION, makeStub('validation'));
|
||||
registerRoute(ROUTE_KEYS.TRACKING, tracking);
|
||||
registerRoute(ROUTE_KEYS.CONVERSION_BITCOIN, conversionBitcoin);
|
||||
registerRoute(ROUTE_KEYS.PRICE_HISTORY, priceHistory);
|
||||
registerRoute(ROUTE_KEYS.SYSTEM, system);
|
||||
registerRoute(ROUTE_KEYS.ACTIONS, actions);
|
||||
registerRoute(ROUTE_KEYS.GUIDE, guide);
|
||||
|
||||
const NAV = [
|
||||
{ items: ['home', 'actions', 'events'] },
|
||||
{ section: 'onchain', items: ['tracking'] },
|
||||
{ section: 'tax', items: ['wealth', 'taxes'] },
|
||||
{ section: 'observe', items: ['conversion/bitcoin', 'price-history', 'charts'] },
|
||||
{ items: ['guide', 'system'], bottom: true },
|
||||
];
|
||||
|
||||
function currentRoute() {
|
||||
const key = hashFromLocation(location.hash).slice(2);
|
||||
if (routePage(key)) return key;
|
||||
const head = key.split('/')[0];
|
||||
return routePage(head) ? head : 'home';
|
||||
}
|
||||
|
||||
const LOOK_ATTRS = Object.freeze({ profile: 'profile', glow: 'glow', glowshape: 'glowshape', motion: 'motion', rowsel: 'rowsel', glass_render: 'render' });
|
||||
const LOOK_TOKENS = Object.freeze({
|
||||
glass_transparency: ['--glass-transparency', '%'],
|
||||
glass_frost: ['--glass-frost', '%'],
|
||||
glass_blur: ['--dialog-transparency', ''],
|
||||
});
|
||||
|
||||
function applyTheme(s) {
|
||||
const { theme, accent, depth } = s;
|
||||
document.documentElement.dataset.theme = theme;
|
||||
document.documentElement.dataset.accent = accent;
|
||||
document.documentElement.dataset.depth = depth;
|
||||
const look = {};
|
||||
for (const [key, attr] of Object.entries(LOOK_ATTRS)) {
|
||||
document.documentElement.dataset[attr] = s[key];
|
||||
look[attr] = s[key];
|
||||
}
|
||||
for (const [key, [token, unit]] of Object.entries(LOOK_TOKENS)) {
|
||||
document.documentElement.style.setProperty(token, `${s[key]}${unit}`);
|
||||
look[token] = `${s[key]}${unit}`;
|
||||
}
|
||||
applyAurora(s);
|
||||
try {
|
||||
const per = cacheSleutels();
|
||||
localStorage.setItem(per.theme, theme);
|
||||
localStorage.setItem(per.accent, accent);
|
||||
localStorage.setItem(per.depth, depth);
|
||||
localStorage.setItem(per.look, JSON.stringify(look));
|
||||
} catch { }
|
||||
}
|
||||
|
||||
function applyPageTitle(show) {
|
||||
document.documentElement.dataset.pagetitle = show ? 'on' : 'off';
|
||||
}
|
||||
|
||||
function applyPageHint(show) {
|
||||
document.documentElement.dataset.pagehint = show ? 'on' : 'off';
|
||||
}
|
||||
|
||||
const hasBitcoin = async () => (await listAssets()).some(a => a.symbol === 'BTC');
|
||||
const NAV_WHEN = {
|
||||
[ROUTE_KEYS.WEALTH]: async () => (await listAssets()).length > 0,
|
||||
[ROUTE_KEYS.CHARTS]: async () => appSetting('persona') !== 'broad',
|
||||
[ROUTE_KEYS.CONVERSION_BITCOIN]: hasBitcoin,
|
||||
[ROUTE_KEYS.TRACKING]: hasBitcoin,
|
||||
};
|
||||
|
||||
const NAV_CUSTOMIZABLE = new Set(['home']);
|
||||
const editModeRoutes = new Set();
|
||||
|
||||
function toggleEdit(key) {
|
||||
if (editModeRoutes.has(key)) editModeRoutes.delete(key); else editModeRoutes.add(key);
|
||||
if (currentRoute() === key) renderPage();
|
||||
else location.hash = `#/${key}`;
|
||||
}
|
||||
|
||||
function setEditMode(key, on) {
|
||||
if (editModeRoutes.has(key) === !!on) return;
|
||||
toggleEdit(key);
|
||||
}
|
||||
|
||||
let kanSluiten = false;
|
||||
|
||||
async function onNavCloseClick(e) {
|
||||
if (!e.target.closest('[data-app-close]')) return;
|
||||
e.preventDefault();
|
||||
if (!await dialog.confirm({ title: t('appWindow.confirmTitle'), message: t('appWindow.confirmClose'),
|
||||
confirmLabel: t('appWindow.close') })) return;
|
||||
const r = await fetch('/api/app/close', { method: 'POST' }).catch(() => null);
|
||||
if (r && !r.ok) dialog.alert({ title: t('appWindow.confirmTitle'), message: t('appWindow.closeUnavailable') });
|
||||
}
|
||||
|
||||
function onNavEditClick(e) {
|
||||
const tog = e.target.closest('[data-edit]');
|
||||
if (!tog) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleEdit(tog.dataset.edit);
|
||||
}
|
||||
|
||||
async function renderSidebar() {
|
||||
const nav = document.getElementById('nav');
|
||||
const active = currentRoute();
|
||||
const navToolsSlot = !appSetting('show_page_title');
|
||||
const zichtbaar = new Set();
|
||||
for (const [route, voorwaarde] of Object.entries(NAV_WHEN)) {
|
||||
if (await voorwaarde()) zichtbaar.add(route);
|
||||
}
|
||||
const keepTools = nav.querySelector('#nav-tools');
|
||||
nav.innerHTML = NAV.map(group => {
|
||||
const title = group.section ? `<div class="t-label t-label--ter sidebar-section-title">${t(`nav.${group.section}`)}</div>` : '';
|
||||
const links = group.items.filter(k => routePage(k) && (!NAV_WHEN[k] || zichtbaar.has(k))).map(k => {
|
||||
const dot = attentionDotHtml(attentionForRoute(k));
|
||||
const editTog = NAV_CUSTOMIZABLE.has(k)
|
||||
? `<span class="nav-edit-toggle${editModeRoutes.has(k) ? ' is-on' : ''}" data-edit="${k}" role="button" tabindex="-1" title="${t('nav.customize')}" aria-label="${t('nav.customize')}">${ADJUST}</span>`
|
||||
: '';
|
||||
const tools = navToolsSlot && k === active && editModeRoutes.has(k) ? '<div class="nav-tools" id="nav-tools"></div>' : '';
|
||||
const link = `<a href="#/${k}" class="${k === active ? 'active' : ''}${dot ? ' has-attention' : ''}${NAV_CUSTOMIZABLE.has(k) ? ' is-customizable' : ''}"><span class="nav-label">${t(`nav.${k}`)}</span>${dot}${editTog}</a>${tools}`;
|
||||
return tools ? `<div class="nav-edit-group">${link}</div>` : link;
|
||||
}).join('');
|
||||
const sluiten = group.bottom && kanSluiten
|
||||
? `<a href="#" class="nav-close" data-app-close><span class="nav-label">${esc(t('appWindow.close'))}</span></a>` : '';
|
||||
return links ? (group.bottom ? '<div class="nav-spacer"></div>' : '') + title + links + sluiten : '';
|
||||
}).join('');
|
||||
const slot = nav.querySelector('#nav-tools');
|
||||
if (slot && keepTools && keepTools.firstChild) slot.replaceWith(keepTools);
|
||||
}
|
||||
|
||||
function renderPageHeader(key) {
|
||||
const host = document.getElementById('page-header');
|
||||
if (!host) return;
|
||||
const mod = routePage(key);
|
||||
const naam = mod?.headerInColumn || typeof mod?.title !== 'function' ? '' : mod.title();
|
||||
if (!naam) { host.innerHTML = ''; return; }
|
||||
const icoon = typeof mod.titleIcon === 'function' ? mod.titleIcon() : '';
|
||||
host.innerHTML = `<div class="page-header">`
|
||||
+ `<h1 class="page-title">${icoon}${esc(naam)}</h1><div class="page-tools" id="page-tools"></div></div>`;
|
||||
}
|
||||
|
||||
const renderTurns = createRenderTurns();
|
||||
|
||||
async function renderPage() {
|
||||
const turn = renderTurns.take();
|
||||
const key = currentRoute();
|
||||
for (const k of [...editModeRoutes]) if (k !== key) editModeRoutes.delete(k);
|
||||
document.getElementById('sidebar').classList.remove('open');
|
||||
const view = document.getElementById('view');
|
||||
renderPageHeader(key);
|
||||
clearPageHint();
|
||||
try {
|
||||
await renderSidebar();
|
||||
await routePage(key).render(view, { edit: editModeRoutes.has(key), setEdit: on => setEditMode(key, on) });
|
||||
} catch (err) {
|
||||
if (!renderTurns.isCurrent(turn)) { console.warn(`[render] ${key} overtaken:`, err); return; }
|
||||
renderErrorPanel(view, err, key);
|
||||
}
|
||||
}
|
||||
|
||||
async function boot() {
|
||||
try {
|
||||
assertConfig();
|
||||
const s = await loadAppSettings();
|
||||
setLocale(s.locale);
|
||||
configureSharedUi();
|
||||
await loadLicense();
|
||||
await refreshNotifications();
|
||||
applyTheme(s);
|
||||
applyPageTitle(s.show_page_title);
|
||||
applyPageHint(s.show_page_hint);
|
||||
|
||||
document.getElementById('logo-text').textContent = t('app.name');
|
||||
document.getElementById('mobile-logo-text').textContent = t('app.name');
|
||||
initSplashReplay();
|
||||
refreshActivePortfolio();
|
||||
watchPortfolioChanges();
|
||||
document.getElementById('menu-btn').onclick =
|
||||
() => document.getElementById('sidebar').classList.toggle('open');
|
||||
|
||||
document.addEventListener('settings-changed', async () => {
|
||||
const s2 = await loadAppSettings();
|
||||
setLocale(s2.locale);
|
||||
configureSharedUi();
|
||||
applyTheme(s2);
|
||||
applyPageTitle(s2.show_page_title);
|
||||
applyPageHint(s2.show_page_hint);
|
||||
renderPage();
|
||||
});
|
||||
window.addEventListener('hashchange', renderPage);
|
||||
document.getElementById('nav').addEventListener('click', onNavEditClick);
|
||||
document.getElementById('nav').addEventListener('click', onNavCloseClick);
|
||||
kanSluiten = await fetch('/api/app/window').then(r => (r.ok ? r.json() : {}))
|
||||
.then(j => j.canClose === true).catch(() => false);
|
||||
|
||||
registerAttentionSources();
|
||||
document.addEventListener('attention-changed',
|
||||
() => renderSidebar().catch(e => console.error('[app] refreshing the sidebar failed:', e)));
|
||||
for (const signaal of ['providers-changed', 'connections-changed', 'ai-activity-changed',
|
||||
'ai-proposals-changed', 'price-gaps-changed']) {
|
||||
document.addEventListener(signaal, () => refreshAttention());
|
||||
}
|
||||
window.addEventListener('hashchange', () => refreshAttention());
|
||||
|
||||
document.addEventListener('app-refresh', () => {
|
||||
refreshAttention();
|
||||
renderPage();
|
||||
});
|
||||
|
||||
initStatusBar();
|
||||
initScheduler();
|
||||
initCommands();
|
||||
await renderPage();
|
||||
document.documentElement.dataset.app = 'ready';
|
||||
finishSplash();
|
||||
maybeStartSetup();
|
||||
|
||||
refreshAttention();
|
||||
listAssets()
|
||||
.then(assets => refreshRecentCloses(assets))
|
||||
.then(({ written }) => { if (written) document.dispatchEvent(new CustomEvent('price-gaps-changed')); })
|
||||
.catch(e => console.error('[app] refreshing the recent closes failed:', e));
|
||||
} catch (err) {
|
||||
document.body.innerHTML =
|
||||
`<div style="padding:3rem;font-family:sans-serif;color:#ff4545">
|
||||
<h2>Start afgebroken</h2><pre>${esc(err.message)}</pre></div>`;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
boot();
|
||||
Reference in new issue
Block a user