Files
AtreidesAssetsFree/client/ui/source-matrix.js
T

147 lines
7.9 KiB
JavaScript
Raw Permalink Normal View History

2026-10-29 11:57:29 +01:00
// Generated, do not edit. sha256:e64fde93e62c826f1ddca6217aab270e6309de4394ce5cb0e258a0e0fdb72d5c
import { t } from '../i18n/index.js';
import { listPriceSource, setPriceSourceRank, clearPriceSourceCell, confirmPriceSourceCell } from '../data/price-source.js';
import { listProviders, getProvider } from '../data/providers.js';
import { rateSourceFor } from '../price/providers.js';
import { serviceCoverage } from '../data/market.js';
import { SOURCE_CAPABILITIES, SOURCE_ENDPOINTS } from '../../shared/config.js';
import { baseCurrency, appSetting } from '../app-settings.js';
import { assetLabelHtml } from './asset-label.js';
import { esc } from './escape.js';
import * as dialog from './dialog.js';
const endpointsUnder = (p, cap) => (p.endpoints || []).filter(e => SOURCE_ENDPOINTS[cap].includes(e.capability));
const answers = (p, cap) => endpointsUnder(p, cap).length > 0;
function quoteOf(p, cap) {
const e = endpointsUnder(p, cap).find(x => x.quoteCurrency && x.quoteCurrency !== baseCurrency());
return e ? e.quoteCurrency : null;
}
const currenciesOf = p => [...new Set(SOURCE_CAPABILITIES.map(c => quoteOf(p, c)).filter(Boolean))];
export async function loadMatrixContext() {
const alle = (await Promise.all((await listProviders()).map(p => getProvider(p.key)))).filter(Boolean);
const providers = alle.filter(p => !p.isSystem);
const cell = new Map();
for (const r of await listPriceSource()) {
if (r.rank == null) continue;
cell.set(`${r.assetId}|${r.capability}|${r.providerId}`,
{ rank: r.rank, proposed: r.status === 'proposed', own: r.status === 'manual' });
}
const rateSource = new Map();
for (const p of providers) {
for (const c of currenciesOf(p)) if (!rateSource.has(c)) rateSource.set(c, await rateSourceFor(c));
}
return { alle, providers, cell, rateSource, service: await serviceContext() };
}
async function serviceContext() {
let mode = 'off';
try { mode = appSetting('market_spot'); } catch { }
if (mode === 'off') return { mode, covered: new Set() };
try {
const r = await serviceCoverage();
return { mode, covered: new Set(r.status === 'ok' ? r.assetIds : []) };
} catch { return { mode, covered: new Set() }; }
}
export function serviceOrder(chosen, mode) {
const byRank = [...chosen].sort((x, y) => x.rank - y.rank);
const order = mode === 'first'
? [...byRank.filter(c => c.own), 'service', ...byRank.filter(c => !c.own)]
: [...byRank, 'service'];
const ranks = new Map();
let service = null;
order.forEach((c, i) => { if (c === 'service') service = i + 1; else ranks.set(c.providerId, i + 1); });
return { ranks, service };
}
const marker = (ctx, v) => `<span class="mx-cur${ctx.rateSource.get(v) ? '' : ' t-neg'}">${esc(v)}</span>`;
const offTitle = p => (p.isActive ? '' : ` title="${esc(t('integrations.inactiveNoEffect'))}"`);
function kopVan(a, stand, title = true) {
if (!title && !stand) return '';
return `<div class="mx-card-head">
${title ? `<div class="wealth-head"><strong>${assetLabelHtml(a)}</strong><span class="badge">${esc(t('enums.asset_kind.' + a.kind))}</span></div>` : ''}
${stand ? `<div class="mx-stand t-caption">${esc(stand)}</div>` : ''}
</div>`;
}
export function matrixCardHtml(a, ctx, { title = true } = {}) {
const cols = ctx.providers.filter(p => (p.coverage || []).includes(a.symbol));
const svc = ctx.service && ctx.service.mode !== 'off' && ctx.service.covered.has(a.id);
if (!cols.length && !svc) {
const systeem = ctx.alle.find(p => p.isSystem && (p.coverage || []).includes(a.symbol));
const tekst = systeem ? esc(t('integrations.systemSource').replace('{naam}', systeem.name)) : esc(t('integrations.noCoverage'));
return `<div class="card mx-card">${kopVan(a, '', title)}<div class="mx-empty">${tekst}</div></div>`;
}
const svcTitle = svc ? esc(t('integrations.mxServiceHint_' + ctx.service.mode)) : '';
const naamKop = `<tr><th class="mx-lab"></th>${cols.map(p =>
`<th class="mx-col${p.isActive ? '' : ' is-off'}" title="${esc(p.isActive ? p.name : `${p.name}: ${t('integrations.inactiveNoEffect')}`)}"><span class="mx-name">${esc(p.name)}</span></th>`).join('')}${
svc ? `<th class="mx-col" title="${svcTitle}"><span class="mx-name">Atreides Services</span></th>` : ''}</tr>`;
const muntKop = `<tr class="mx-cur-row"><th class="mx-lab"></th>${cols.map(p =>
`<th>${currenciesOf(p).map(v => marker(ctx, v)).join(' ')}</th>`).join('')}${svc ? '<th></th>' : ''}</tr>`;
const head = naamKop + muntKop;
const rank = (c, p, shown = c.rank) =>
`<span class="mx-rank mx-rank-${Math.min(shown, 2)}${c.proposed ? ' is-proposed' : ''}${p.isActive ? '' : ' is-off'}"${offTitle(p)}>${shown}</span>`;
let open = 0, voorstellen = 0;
const leeg = [];
const rows = SOURCE_CAPABILITIES.filter(cap => cols.some(p => answers(p, cap)) || (svc && cap === 'spot'));
const body = rows.map(cap => {
const metDienst = svc && cap === 'spot';
const volgorde = metDienst
? serviceOrder(cols.map(p => ({ providerId: p.id, ...ctx.cell.get(`${a.id}|${cap}|${p.id}`) }))
.filter(c => c.rank != null), ctx.service.mode)
: null;
if (!metDienst && !cols.some(p => ctx.cell.has(`${a.id}|${cap}|${p.id}`))) leeg.push(t('integrations.sourceCap.' + cap));
const dienstCel = svc
? (metDienst
? `<td class="mx-svc" title="${svcTitle}"><span class="mx-rank mx-rank-${Math.min(volgorde.service, 2)} is-service">${volgorde.service}</span></td>`
: `<td class="mx-na"></td>`)
: '';
const cells = cols.map(p => {
if (!answers(p, cap)) return `<td class="mx-na"></td>`;
const c = ctx.cell.get(`${a.id}|${cap}|${p.id}`);
const valuta = quoteOf(p, cap);
if (valuta && !ctx.rateSource.get(valuta) && !c) {
return `<td class="mx-na" title="${esc(t('integrations.noRateSourceHint', { valuta }))}"></td>`;
}
if (!c) open++;
else if (c.proposed) voorstellen++;
const inner = c ? rank(c, p, volgorde ? volgorde.ranks.get(p.id) : c.rank) : `<span class="mx-pick">+</span>`;
return `<td class="mx-cell" data-a="${a.id}" data-cap="${cap}" data-p="${p.id}" data-has="${c ? 1 : 0}" data-proposed="${c && c.proposed ? 1 : 0}">${inner}</td>`;
}).join('');
return `<tr><th class="mx-lab">${esc(t('integrations.sourceCap.' + cap))}</th>${cells}${dienstCel}</tr>`;
}).join('');
const stand = [
leeg.length ? t('integrations.mxToSetUp', { wat: leeg.join(' en ') }) : '',
voorstellen ? t(voorstellen === 1 ? 'integrations.mxProposalsOne' : 'integrations.mxProposalsMany', { n: voorstellen }) : '',
open ? t(open === 1 ? 'integrations.mxOpenOne' : 'integrations.mxOpenMany', { n: open }) : t('integrations.mxAllChosen'),
].filter(Boolean).join(' · ');
return `<div class="card mx-card">${kopVan(a, stand.charAt(0).toUpperCase() + stand.slice(1), title)}<div class="mx-wrap"><table class="mx-table"><thead>${head}</thead><tbody>${body}</tbody></table></div></div>`;
}
export function wireMatrix(host, redraw) {
host.querySelectorAll('.mx-cell').forEach(td => td.onclick = async () => {
const d = td.dataset;
const a = Number(d.a), p = Number(d.p);
try {
if (d.has === '1' && d.proposed === '1') await confirmPriceSourceCell(a, d.cap, p);
else if (d.has === '1') await clearPriceSourceCell(a, d.cap, p);
else await setPriceSourceRank(a, d.cap, p);
document.dispatchEvent(new CustomEvent('providers-changed'));
} catch (e) { dialog.alert({ title: t('dialog.error'), message: e.message }); }
await redraw();
});
}
export function matrixLegendRows(ctx) {
const uit = ctx.providers.some(p => !p.isActive)
? [{ sample: '<span class="mx-rank mx-rank-1 is-off" style="--mx-rank:1.1rem">1</span>', text: esc(t('integrations.inactiveHint')) }]
: [];
const munten = [...ctx.rateSource].map(([v, via]) => ({
sample: marker(ctx, v),
text: esc(via ? t('integrations.convertedVia', { naam: via.name, valuta: v, munt: baseCurrency() }) : t('integrations.noRateSource', { valuta: v })),
}));
return [...uit, ...munten];
}