| |
| 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(); |
| } |