2026-10-30 12:28:48 +01:00
|
|
|
// Generated, do not edit. sha256:bf3b89b4669e19b78df9f4741f695e6025ab616c2c0a5f205b84e84c1ce21f92
|
2026-10-29 11:57:29 +01:00
|
|
|
import * as db from '../db.js';
|
|
|
|
|
import { ASSET_DEFINITIONS, DASHBOARD } from '../../shared/config.js';
|
|
|
|
|
import { toSearchName } from '../../shared/search-name.js';
|
|
|
|
|
import { getPref, setPref } from './preferences.js';
|
|
|
|
|
import { createAsset } from './assets.js';
|
|
|
|
|
import { createAccount } from './accounts.js';
|
|
|
|
|
import { activeProfile, addWidget, listWidgets } from './dashboard.js';
|
|
|
|
|
import { currentCount, capacityReachedFor } from './_limits.js';
|
|
|
|
|
|
|
|
|
|
export function setupStatus() {
|
|
|
|
|
const done = Number(getPref('app', 'setup_done')) === 1;
|
|
|
|
|
const empty = currentCount('assets') === 0;
|
|
|
|
|
const own = db.one(`SELECT (SELECT COUNT(*) FROM Account) + (SELECT COUNT(*) FROM Event)
|
|
|
|
|
+ (SELECT COUNT(*) FROM Valuation) AS n`).n;
|
|
|
|
|
return { needed: !done && empty, done, empty, persona: getPref('app', 'persona'),
|
|
|
|
|
currencyOpen: !done && empty && own === 0 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function assertKnownAsset(symbol) {
|
|
|
|
|
if (!ASSET_DEFINITIONS[symbol]) {
|
|
|
|
|
throw new Error(`Unknown asset '${symbol}'. Allowed: ${Object.keys(ASSET_DEFINITIONS).join(', ')}.`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function ensureAsset(symbol) {
|
|
|
|
|
const def = ASSET_DEFINITIONS[symbol];
|
|
|
|
|
const existing = db.one(`SELECT id FROM Asset WHERE symbol = ?`, [def.symbol]);
|
|
|
|
|
if (existing) return existing.id;
|
|
|
|
|
return capacityReachedFor('assets') === null ? createAsset(def) : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function ensureAccount({ name, kind }) {
|
|
|
|
|
const existing = db.one(`SELECT id FROM Account WHERE searchName = ?`, [toSearchName(name)]);
|
|
|
|
|
if (existing) return existing.id;
|
|
|
|
|
if (kind === 'bank' && capacityReachedFor('fiatAccounts') !== null) return null;
|
|
|
|
|
const own = kind === 'wallet' || kind === 'vault';
|
|
|
|
|
return createAccount({ name, kind, isOwnCustody: own, scanEnabled: false, taxCategory: null });
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-30 12:28:48 +01:00
|
|
|
const SETUP_SPAN = 4;
|
|
|
|
|
|
2026-10-29 11:57:29 +01:00
|
|
|
function placeInFreeSpace(rows, items) {
|
|
|
|
|
const cols = DASHBOARD.GRID_COLUMNS;
|
|
|
|
|
const heightOf = (type, h) => (h > 0 ? h : DASHBOARD.WIDGETS[type]?.defaultH || 1);
|
|
|
|
|
const bottom = new Array(cols).fill(0);
|
|
|
|
|
for (const r of rows) {
|
|
|
|
|
const end = r.gridY + heightOf(r.type, r.spanHeight);
|
|
|
|
|
for (let c = r.gridX; c < Math.min(cols, r.gridX + r.spanWidth); c++) bottom[c] = Math.max(bottom[c], end);
|
|
|
|
|
}
|
|
|
|
|
return items.map(({ prefer, ...w }) => {
|
2026-10-30 12:28:48 +01:00
|
|
|
const span = w.spanWidth ?? DASHBOARD.WIDGETS[w.type].defaultSpan;
|
2026-10-29 11:57:29 +01:00
|
|
|
let bestX = 0, bestY = Infinity;
|
|
|
|
|
for (let x = 0; x + span <= cols; x++) {
|
|
|
|
|
const y = Math.max(...bottom.slice(x, x + span));
|
|
|
|
|
if (y < bestY || (y === bestY && x === prefer)) { bestY = y; bestX = x; }
|
|
|
|
|
}
|
|
|
|
|
for (let c = bestX; c < bestX + span; c++) bottom[c] = bestY + heightOf(w.type, null);
|
|
|
|
|
return { ...w, gridX: bestX, gridY: bestY };
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function completeSetup({ assets = [], persona = 'broad', accounts = [], widgets = [] } = {}) {
|
|
|
|
|
assets.forEach(assertKnownAsset);
|
|
|
|
|
await setPref('app', 'persona', persona);
|
|
|
|
|
|
|
|
|
|
const assetIds = [];
|
|
|
|
|
for (const s of assets) {
|
|
|
|
|
const id = await ensureAsset(s);
|
|
|
|
|
if (id != null) assetIds.push(id);
|
|
|
|
|
}
|
|
|
|
|
let accountCount = 0;
|
|
|
|
|
for (const a of accounts) if ((await ensureAccount(a)) != null) accountCount++;
|
|
|
|
|
|
|
|
|
|
const profileId = activeProfile().id;
|
|
|
|
|
const add = [];
|
|
|
|
|
if (widgets.includes('standing')) add.push({ type: 'standing', prefer: 4 });
|
|
|
|
|
if (widgets.includes('signal') && persona === 'bitcoin') add.push({ type: 'signal', prefer: 4 });
|
2026-10-30 12:28:48 +01:00
|
|
|
if (widgets.includes('spot')) assetIds.forEach(id => add.push({ type: 'spot', instanceKey: `spot-${id}`, assetId: id, prefer: 0, settings: { momentum: false }, spanWidth: SETUP_SPAN }));
|
2026-10-29 11:57:29 +01:00
|
|
|
let widgetCount = 0;
|
|
|
|
|
for (const w of placeInFreeSpace(listWidgets(profileId), add)) if (await addWidget(profileId, w)) widgetCount++;
|
|
|
|
|
|
|
|
|
|
await setPref('app', 'setup_done', 1);
|
|
|
|
|
return { persona, assetIds, accounts: accountCount, widgets: widgetCount };
|
|
|
|
|
}
|