// Settings: kept in this browser. import { h, store, toast } from './util.js'; import { icon } from './icons.js'; import { paper } from './paper.js'; import { trackers } from './trackers.js'; const DEF = { buyPresets: [0.1, 0.5, 1, 2], sellPresets: [25, 50, 75, 100], trackerToasts: true }; export const settings = { get() { return { ...DEF, ...store.get('settings', {}) }; }, set(patch) { store.set('settings', { ...this.get(), ...patch }); }, }; export function openModal(title, body) { const root = document.getElementById('modal-root'); const close = () => { back.remove(); document.removeEventListener('keydown', esc); }; const esc = e => { if (e.key === 'Escape') close(); }; const back = h('div.modal-back', { onmousedown: e => { if (e.target === back) close(); } }, h('div.modal', null, h('div.modal-h', null, h('h3', null, title), h('button.icon-btn', { html: icon('x', 15), onclick: () => close() })), h('div.modal-b', null, body))); root.append(back); document.addEventListener('keydown', esc); return close; } const list = (arr, n) => arr.map(v => String(v)).join(', '); const parse = (s, max) => s.split(/[\s,]+/).map(Number).filter(v => v > 0 && v <= max).slice(0, 4); export function openSettings() { const s = settings.get(); const buy = h('input.inp', { value: list(s.buyPresets), placeholder: '0.1, 0.5, 1, 2' }); const sell = h('input.inp', { value: list(s.sellPresets), placeholder: '25, 50, 75, 100' }); const start = h('input.inp', { value: paper.s.start, inputMode: 'decimal' }); const tg = h('button.tg' + (s.trackerToasts ? '.on' : ''), { onclick: () => tg.classList.toggle('on') }); const hiddenCount = (store.get('hidden', []) || []).length; let close; const body = [ h('div.field', null, h('span.lbl', null, 'Buy amounts on the coin page (SOL)'), buy), h('div.field', null, h('span.lbl', null, 'Sell amounts on the coin page (%)'), sell), h('div.switch', null, h('div', null, h('div', null, 'Tracked wallet pop-ups'), h('div.lbl', null, 'Show a note when a tracked wallet trades')), tg), h('div.field', null, h('span.lbl', null, 'Paper wallet start balance (SOL)'), h('div', { style: { display: 'flex', gap: '8px' } }, start, h('button.btn.danger', { onclick: () => { paper.reset(parseFloat(start.value) || 10); toast('Paper wallet reset', 'info'); } }, 'Reset wallet'))), h('div.field', null, h('span.lbl', null, 'Your data'), h('div', { style: { display: 'flex', gap: '8px', flexWrap: 'wrap' } }, h('button.btn', { onclick: () => exportData() }, 'Export'), h('button.btn', { onclick: () => importData() }, 'Import'), h('button.btn', { disabled: !hiddenCount, onclick: e => { store.set('hidden', []); e.currentTarget.disabled = true; e.currentTarget.textContent = 'Hidden coins shown'; } }, hiddenCount ? `Show ${hiddenCount} hidden coins` : 'No hidden coins'))), h('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: '8px' } }, h('button.btn.ghost', { onclick: () => close() }, 'Cancel'), h('button.btn.acc', { onclick: () => { const b = parse(buy.value, 1e4), se = parse(sell.value, 100); settings.set({ buyPresets: b.length ? b : DEF.buyPresets, sellPresets: se.length ? se : DEF.sellPresets, trackerToasts: tg.classList.contains('on') }); toast('Settings saved', 'info', 1500); close(); } }, 'Save')), ]; close = openModal('Settings', body); } function exportData() { const data = { app: 'oscope', v: 1, at: Date.now(), settings: settings.get(), paper: paper.s, trackers: trackers.list, hidden: store.get('hidden', []) }; const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([JSON.stringify(data, null, 1)], { type: 'application/json' })); a.download = 'oscope-data.json'; a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 1000); } function importData() { const inp = document.createElement('input'); inp.type = 'file'; inp.accept = 'application/json,.json'; inp.onchange = async () => { try { const d = JSON.parse(await inp.files[0].text()); if (d.app !== 'oscope') throw new Error(); if (d.settings && typeof d.settings === 'object') { const b = parse(String(d.settings.buyPresets), 1e4), se = parse(String(d.settings.sellPresets), 100); settings.set({ buyPresets: b.length ? b : DEF.buyPresets, sellPresets: se.length ? se : DEF.sellPresets, trackerToasts: d.settings.trackerToasts !== false }); } if (d.paper && typeof d.paper === 'object' && +d.paper.sol >= 0 && d.paper.pos && Array.isArray(d.paper.hist)) { paper.s = d.paper; paper.save(); } const addr = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/; if (Array.isArray(d.trackers)) { trackers.list = d.trackers.filter(t => t && addr.test(t.w)).map(t => ({ w: t.w, name: String(t.name || '').slice(0, 32) || t.w.slice(0, 4), emoji: String(t.emoji || '').slice(0, 4), alerts: t.alerts !== false, added: +t.added || Date.now() })); trackers.save(); } if (Array.isArray(d.hidden)) store.set('hidden', d.hidden.filter(m => addr.test(m)).slice(-3000)); toast('Imported', 'info'); } catch { toast('That file is not an OScope export', 'warn'); } }; inp.click(); }