Versie 0.2.0

This commit is contained in:
Atreides Assets committed 2026-10-29 11:57:29 +01:00
commit e77d8fd373
495 files changed
+81026

No files matched your search

+272
View File
@@ -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();