Back to OScope
oscope/public/js/settings.js
Code
94 lines (87 loc) · 5.16 KB
// 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();
}