Files
2026-10-29 11:57:29 +01:00

158 lines
8.4 KiB
JavaScript

// Generated, do not edit. sha256:148777e8a3d0dd6536d8b6d4b8cae6c0c4cbf2ed945fdf8bb8ed57c23e617bd8
import { listBookableAssets } from '../data/assets.js';
import { appSetting, setAppSetting } from '../app-settings.js';
import { t } from '../i18n/index.js';
import { renderNotifications } from '../actions/notifications-widget.js';
import { currentStanding } from '../wealth/data.js';
import { fmtBase, fmtBaseSigned, colouredSigns, fmtUnit, val } from '../ui/format.js';
import { esc } from '../ui/escape.js';
import { assetLabel, assetLabelHtml } from '../ui/asset-label.js';
import { originsHtml } from '../ui/price-origin.js';
import { setPageHint } from '../ui/page-hint.js';
import { CHEVRON_RIGHT } from '../ui/icons.js';
import { renderDashboard } from '../dashboard/dashboard.js';
import { renderSpotWidget, renderSpotSettings } from '../widgets/spot.js';
import { secondCurrencyOf, secondAmount, renderSecondCurrencySetting } from '../widgets/second-currency.js';
import { STANDING_GROUPINGS, standingGroupingOf, groupStanding, standingToday, standingShowsToday } from '../widgets/standing-groups.js';
import { renderMetricsWidget } from '../widgets/metrics.js';
import { renderClocksWidget, renderClocksSettings } from '../widgets/clocks.js';
import { renderReturnWidget, renderReturnSettings } from '../widgets/return.js';
import { registerWidget, availableWidgets } from '../widgets/registry.js';
import { DASHBOARD, TARGETS, targetHash } from '../../shared/config.js';
export const title = () => t('home.title');
registerWidget('notifications', { render: host => renderNotifications(host) });
registerWidget('standing', { render: (host, inst) => renderStandingWidget(host, inst), settings: renderStandingSettings });
registerWidget('spot', { render: (host, inst) => renderSpotWidget(host, inst), settings: renderSpotSettings });
registerWidget('metrics', { render: host => renderMetricsWidget(host) });
registerWidget('clocks', { render: (host, inst) => renderClocksWidget(host, inst), settings: renderClocksSettings });
registerWidget('return', { render: (host, inst) => renderReturnWidget(host, inst), settings: renderReturnSettings });
async function buildCatalog(layout) {
const present = new Set(layout.map(it => it.instId));
const persona = appSetting('persona');
const { renderers } = availableWidgets();
const items = [];
for (const [type, def] of Object.entries(DASHBOARD.WIDGETS)) {
if (def.instanceable || present.has(type) || !renderers[type]) continue;
if (def.persona && def.persona !== persona) continue;
items.push({ instId: type, type, params: null, label: t(def.label_key) });
}
let n = 1;
while (present.has(`return-${n}`)) n++;
items.push({ instId: `return-${n}`, type: 'return', params: null, label: t('dashboard.widget.return') });
for (const a of await listBookableAssets()) {
const instId = `spot-${a.id}`;
if (present.has(instId)) continue;
items.push({
instId, type: 'spot', params: { assetId: a.id },
label: `${t('dashboard.widget.spot')}: ${assetLabel(a)}`,
labelHtml: `${esc(t('dashboard.widget.spot'))}: ${assetLabelHtml(a)}`,
});
}
return items;
}
export async function render(el, opts = {}) {
if (!el.querySelector('#dashboard-root')) el.innerHTML = '<div id="dashboard-root"></div>';
const { renderers, settings } = availableWidgets();
await renderDashboard(el.querySelector('#dashboard-root'), {
renderers,
settings,
edit: !!opts.edit,
onEdit: opts.setEdit,
catalog: buildCatalog,
toolbarHost: document.getElementById('nav-tools') || document.getElementById('page-tools'),
});
setPageHint(el, { title: t('home.hintTitle'), rows: [
t('home.hintEdit'), t('home.hintDone'), t('home.hintAdd'), t('home.hintWidget'), t('home.hintProfiles'),
] });
}
function standingGroupLabel(g, by) {
const r = g.sample;
if (by === 'asset') return assetLabelHtml(r.asset);
if (by === 'assetKind') return esc(t('enums.asset_kind.' + r.asset.kind));
if (by === 'accountKind') return esc(g.key === 'none' ? t('wealth.noAccount') : t('enums.account_kind.' + g.key));
return esc(r.account.name);
}
function todayHtml(amount, cls, always = false) {
if (amount == null || (!always && Math.abs(amount) < 0.005)) return '';
const tone = amount > 0.005 ? 't-pos' : amount < -0.005 ? 't-neg' : '';
return `<span class="${[cls, tone].filter(Boolean).join(' ')}">${val(fmtBaseSigned(amount, colouredSigns()))}</span>`;
}
async function renderStandingSettings(host, instance, commit) {
host.classList.add('dash-settings--wide');
const huidig = () => standingGroupingOf(instance.params && instance.params.groupBy);
const label = { account: 'home.groupAccount', accountKind: 'home.groupAccountKind', asset: 'home.groupAsset', assetKind: 'home.groupAssetKind' };
const chips = () => STANDING_GROUPINGS.map(id => `<button type="button" class="fbar-item${huidig() === id ? ' is-on' : ''}"
data-standing-group="${id}" aria-pressed="${huidig() === id}">${esc(t(label[id]))}</button>`).join('');
const block = document.createElement('div');
block.className = 'dash-set-row';
block.innerHTML = `<span class="t-label t-label--ter">${esc(t('home.groupBy'))}</span><div class="fbar fbar--chip">${chips()}</div>`;
host.appendChild(block);
block.addEventListener('click', e => {
const chip = e.target.closest('[data-standing-group]');
if (!chip || chip.dataset.standingGroup === huidig()) return;
instance.params = { ...(instance.params || {}), groupBy: standingGroupingOf(chip.dataset.standingGroup) };
block.querySelector('.fbar').innerHTML = chips();
commit({ resize: true });
});
const today = document.createElement('div');
today.className = 'dash-set-row';
today.innerHTML = `<label class="clockw-check"><span class="toggle-switch"><input type="checkbox" data-standing-today${standingShowsToday(instance.params) ? ' checked' : ''}/><span class="toggle-track"></span></span><span class="clockw-check-name">${esc(t('home.showToday'))}</span></label>`;
host.appendChild(today);
today.addEventListener('change', e => {
const box = e.target.closest('[data-standing-today]');
if (!box) return;
instance.params = { ...(instance.params || {}), showToday: box.checked };
commit({ resize: true });
});
await renderSecondCurrencySetting(host, instance, commit);
}
async function renderStandingWidget(host, instance) {
const by = standingGroupingOf(instance && instance.params && instance.params.groupBy);
const second = secondCurrencyOf(instance);
host.innerHTML = `<div class="card standing-card">
<div class="flex-between">
<div class="t-label">${t('home.standing')}</div>
<a class="wealth-detail-btn card-go-btn" href="${targetHash(TARGETS.WEALTH)}" title="${esc(t('home.toWealth'))}" aria-label="${esc(t('home.toWealth'))}">${CHEVRON_RIGHT}</a>
</div>
<div id="standing-body" class="t-body-sm t-muted">…</div>
</div>`;
try {
const s = await currentStanding();
const body = host.querySelector('#standing-body');
if (s.rows.length === 0) { body.textContent = t('home.standingNone'); return; }
const toon = standingShowsToday(instance && instance.params);
const perGroup = toon ? standingToday(s.rows, by, s.today) : new Map();
const lines = groupStanding(s.rows, by).map(g => `<div class="dash-trow stw-acc">
<span class="tname"><span class="stw-dot" aria-hidden="true"></span>${standingGroupLabel(g, by)}</span>
<span class="stw-today-cell">${todayHtml(perGroup.get(g.key), 'stw-today-row')}</span>
<span class="n po-host">${val(fmtBase(g.value))}${originsHtml(g.origins)}</span>
</div>`).join('');
const vandaag = toon && s.today
? `<div class="stw-today">${todayHtml(s.today.total, '', true)}</div>` : '';
const tweede = second ? await secondAmount(s.total, second, { decimals: 0 }) : '';
body.className = '';
body.innerHTML = `
<div class="sigw-price po-host" style="margin:0.4rem 0 0.1rem">${val(fmtBase(s.total))}${tweede ? `<span class="amount-second">${val(tweede)}</span>` : ''}${originsHtml(s.rows.map(r => ({ origin: r.origin, asset: r.asset })))}</div>
${vandaag}
<div class="t-muted-cell" style="margin-bottom:0.6rem">${s.rows.every(r => r.asset.id === s.rows[0].asset.id) ? val(fmtUnit(s.totalSats, s.rows[0].asset)) : ''}</div>
<div class="dash-tables stw-grid">${lines}</div>`;
} catch (err) {
console.error('[standing-widget]', err);
const body = host.querySelector('#standing-body');
if (body) body.textContent = '⚠ ' + err.message;
}
}