Skip to content

Commit 3b568b2

Browse files
authored
Merge pull request #1 from gregory-fanous/persist-custom-assets
Persist custom assets (client + server) and apply "Use current date & time" behavior on load
2 parents 96431d1 + cb9d30a commit 3b568b2

File tree

2 files changed

+364
-50
lines changed

2 files changed

+364
-50
lines changed

templates/demo_new.html

Lines changed: 294 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,7 @@ <h4 class="panel-title">
946946
<label class="form-label">
947947
<i class="fas fa-coins"></i> Asset
948948
</label>
949-
<div class="asset-button-grid">
949+
<div class="asset-button-grid" id="assetButtonGrid">
950950
<button type="button" class="asset-btn" data-asset="BTC" onclick="selectAsset(this, 'BTC')">
951951
<i class="fas fa-bitcoin"></i> BTC
952952
</button>
@@ -965,7 +965,7 @@ <h4 class="panel-title">
965965
<button type="button" class="asset-btn" data-asset="TSLA" onclick="selectAsset(this, 'TSLA')">
966966
<i class="fas fa-car"></i> TSLA
967967
</button>
968-
<button type="button" class="asset-btn custom-asset-btn" onclick="showCustomAssetInput()">
968+
<button type="button" class="asset-btn custom-asset-btn" id="customAssetBtn" onclick="showCustomAssetInput()">
969969
<i class="fas fa-plus"></i> Custom
970970
</button>
971971
</div>
@@ -1163,7 +1163,186 @@ <h4 class="panel-title">
11631163
let currentAsset = '';
11641164
let currentTimeframe = '';
11651165

1166-
// Initialize page
1166+
// Persisted state keys
1167+
const STORAGE_KEYS = {
1168+
CUSTOM_ASSETS: 'quantagent_custom_assets',
1169+
SELECTED_ASSET: 'quantagent_selected_asset',
1170+
SELECTED_TIMEFRAME: 'quantagent_selected_timeframe'
1171+
};
1172+
1173+
// Add or render a custom asset button into the grid
1174+
function addCustomAssetButton(symbol, isServerLoaded = false) {
1175+
const grid = document.getElementById('assetButtonGrid');
1176+
if (!grid) return;
1177+
1178+
// Prevent duplicates (check existing data-asset values)
1179+
const existing = Array.from(grid.querySelectorAll('.asset-btn')).some(btn => btn.dataset.asset === symbol);
1180+
if (existing) return;
1181+
1182+
const btn = document.createElement('button');
1183+
btn.type = 'button';
1184+
btn.className = 'asset-btn';
1185+
btn.dataset.asset = symbol;
1186+
btn.innerHTML = `<i class="fas fa-tag"></i> ${symbol}`;
1187+
btn.addEventListener('click', () => selectAsset(btn, symbol));
1188+
1189+
// Insert before the "Custom" button if present
1190+
const customBtn = document.getElementById('customAssetBtn');
1191+
if (customBtn && customBtn.parentNode === grid) {
1192+
grid.insertBefore(btn, customBtn);
1193+
} else {
1194+
grid.appendChild(btn);
1195+
}
1196+
1197+
// If loaded from server or localStorage, do not auto-select unless it matches stored selection
1198+
const storedSelected = localStorage.getItem(STORAGE_KEYS.SELECTED_ASSET);
1199+
if (storedSelected === symbol) {
1200+
// mimic click to set active and store
1201+
btn.click();
1202+
}
1203+
}
1204+
1205+
// Load custom assets from localStorage and server, render them
1206+
function loadCustomAssets() {
1207+
// Load from localStorage first (fast)
1208+
try {
1209+
const local = localStorage.getItem(STORAGE_KEYS.CUSTOM_ASSETS);
1210+
if (local) {
1211+
const list = JSON.parse(local);
1212+
list.forEach(sym => addCustomAssetButton(sym, false));
1213+
}
1214+
} catch (e) {
1215+
console.warn('Failed to parse local custom assets', e);
1216+
}
1217+
1218+
// Then attempt to load server-persisted custom assets and merge
1219+
fetch('/api/custom-assets')
1220+
.then(r => r.json())
1221+
.then(data => {
1222+
if (Array.isArray(data.custom_assets)) {
1223+
data.custom_assets.forEach(sym => addCustomAssetButton(sym, true));
1224+
// Sync server list into localStorage (merge, dedupe)
1225+
try {
1226+
const local = JSON.parse(localStorage.getItem(STORAGE_KEYS.CUSTOM_ASSETS) || '[]');
1227+
const merged = Array.from(new Set([...(local || []), ...data.custom_assets]));
1228+
localStorage.setItem(STORAGE_KEYS.CUSTOM_ASSETS, JSON.stringify(merged));
1229+
} catch (e) {
1230+
console.warn('Failed to sync custom assets to localStorage', e);
1231+
}
1232+
}
1233+
})
1234+
.catch(err => {
1235+
// Not fatal - server might not be reachable
1236+
console.warn('Could not fetch server custom assets', err);
1237+
});
1238+
}
1239+
1240+
// Override confirmCustomAsset to create a persistent custom asset and persist to server/localStorage
1241+
function confirmCustomAsset() {
1242+
const customAssetEl = document.getElementById('customAssetInput');
1243+
const customAsset = customAssetEl.value.trim();
1244+
1245+
if (!customAsset) {
1246+
alert('Please enter a custom asset symbol.');
1247+
return;
1248+
}
1249+
1250+
// Normalize (trim)
1251+
const symbol = customAsset;
1252+
1253+
// Save to localStorage list
1254+
try {
1255+
const listRaw = localStorage.getItem(STORAGE_KEYS.CUSTOM_ASSETS);
1256+
const list = listRaw ? JSON.parse(listRaw) : [];
1257+
if (!list.includes(symbol)) {
1258+
list.push(symbol);
1259+
localStorage.setItem(STORAGE_KEYS.CUSTOM_ASSETS, JSON.stringify(list));
1260+
}
1261+
} catch (e) {
1262+
console.warn('Could not persist custom asset to localStorage', e);
1263+
}
1264+
1265+
// Persist to server (best-effort)
1266+
fetch('/api/save-custom-asset', {
1267+
method: 'POST',
1268+
headers: { 'Content-Type': 'application/json' },
1269+
body: JSON.stringify({ symbol })
1270+
}).then(r => r.json())
1271+
.then(resp => {
1272+
if (!resp.success) {
1273+
console.warn('Server did not save custom asset', resp);
1274+
}
1275+
}).catch(err => {
1276+
console.warn('Error saving custom asset to server', err);
1277+
});
1278+
1279+
// Add button and select it
1280+
addCustomAssetButton(symbol);
1281+
// Select the new asset: find button and click
1282+
const grid = document.getElementById('assetButtonGrid');
1283+
const newBtn = Array.from(grid.querySelectorAll('.asset-btn')).find(b => b.dataset.asset === symbol);
1284+
if (newBtn) {
1285+
newBtn.click();
1286+
}
1287+
1288+
// Hide custom asset input
1289+
document.getElementById('customAssetDiv').style.display = 'none';
1290+
1291+
// Clear input
1292+
customAssetEl.value = '';
1293+
1294+
// Notify user
1295+
// Use a non-blocking notification (replace alert with console + subtle DOM message if desired)
1296+
console.log(`Custom asset "${symbol}" selected and persisted`);
1297+
}
1298+
1299+
// Updated selectAsset to persist selection to localStorage
1300+
function selectAsset(button, asset) {
1301+
// Remove active class from all asset buttons
1302+
document.querySelectorAll('.asset-btn').forEach(btn => {
1303+
btn.classList.remove('active');
1304+
});
1305+
1306+
// Add active class to clicked button
1307+
button.classList.add('active');
1308+
1309+
// Store selected asset
1310+
selectedAsset = asset;
1311+
try {
1312+
localStorage.setItem(STORAGE_KEYS.SELECTED_ASSET, asset);
1313+
} catch (e) {
1314+
console.warn('Could not persist selected asset', e);
1315+
}
1316+
1317+
// Hide custom asset input if it was showing
1318+
const cav = document.getElementById('customAssetDiv');
1319+
if (cav) cav.style.display = 'none';
1320+
1321+
console.log('Selected asset:', asset);
1322+
}
1323+
1324+
// Persist timeframe selection as well
1325+
function selectTimeframe(button, timeframe) {
1326+
// Remove active class from all timeframe buttons
1327+
document.querySelectorAll('.timeframe-btn').forEach(btn => {
1328+
btn.classList.remove('active');
1329+
});
1330+
1331+
// Add active class to clicked button
1332+
button.classList.add('active');
1333+
1334+
// Store selected timeframe
1335+
selectedTimeframe = timeframe;
1336+
try {
1337+
localStorage.setItem(STORAGE_KEYS.SELECTED_TIMEFRAME, timeframe);
1338+
} catch (e) {
1339+
console.warn('Could not persist selected timeframe', e);
1340+
}
1341+
1342+
console.log('Selected timeframe:', timeframe);
1343+
}
1344+
1345+
// On page load, restore selected asset/timeframe and custom assets
11671346
document.addEventListener('DOMContentLoaded', function() {
11681347
// Set default dates
11691348
const now = new Date();
@@ -1180,20 +1359,60 @@ <h4 class="panel-title">
11801359
// Set default end date (today)
11811360
document.getElementById('endDate').value = formatDate(now);
11821361

1183-
// Set default active timeframe
1184-
const defaultTimeframeBtn = document.querySelector('[data-timeframe="1h"]');
1185-
if (defaultTimeframeBtn) {
1186-
defaultTimeframeBtn.classList.add('active');
1187-
selectedTimeframe = '1h';
1188-
}
1362+
// Apply the current-time checkbox behavior immediately on load
1363+
// (this will disable & populate end date/time if the checkbox is checked by default)
1364+
try { handleUseCurrentTimeChange(); } catch (e) { console.warn('handleUseCurrentTimeChange not available yet', e); }
11891365

1190-
// Set default asset
1191-
const defaultAssetBtn = document.querySelector('[data-asset="BTC"]');
1192-
if (defaultAssetBtn) {
1193-
defaultAssetBtn.classList.add('active');
1194-
selectedAsset = 'BTC';
1366+
// Load custom assets and server-synced assets
1367+
loadCustomAssets();
1368+
1369+
// Restore selected timeframe (if saved)
1370+
try {
1371+
const savedTF = localStorage.getItem(STORAGE_KEYS.SELECTED_TIMEFRAME);
1372+
if (savedTF) {
1373+
const tfBtn = Array.from(document.querySelectorAll('.timeframe-btn')).find(b => b.dataset.timeframe === savedTF);
1374+
if (tfBtn) {
1375+
tfBtn.classList.add('active');
1376+
selectedTimeframe = savedTF;
1377+
}
1378+
}
1379+
} catch (e) {
1380+
console.warn('Could not restore timeframe from localStorage', e);
11951381
}
1196-
1382+
1383+
// Restore selected asset (if saved). If it is a custom asset that hasn't been added yet,
1384+
// add it and then select it (addCustomAssetButton handles checking duplicates).
1385+
try {
1386+
const savedAsset = localStorage.getItem(STORAGE_KEYS.SELECTED_ASSET);
1387+
if (savedAsset) {
1388+
// Try to find an existing button
1389+
let btn = Array.from(document.querySelectorAll('.asset-btn')).find(b => b.dataset.asset === savedAsset);
1390+
if (!btn) {
1391+
// Add it as custom and then select
1392+
addCustomAssetButton(savedAsset, false);
1393+
btn = Array.from(document.querySelectorAll('.asset-btn')).find(b => b.dataset.asset === savedAsset);
1394+
}
1395+
if (btn) {
1396+
btn.classList.add('active');
1397+
selectedAsset = savedAsset;
1398+
}
1399+
} else {
1400+
// fallback defaults from original code
1401+
const defaultTimeframeBtn = document.querySelector('[data-timeframe="1h"]');
1402+
if (defaultTimeframeBtn && !selectedTimeframe) {
1403+
defaultTimeframeBtn.classList.add('active');
1404+
selectedTimeframe = '1h';
1405+
}
1406+
const defaultAssetBtn = document.querySelector('[data-asset="BTC"]');
1407+
if (defaultAssetBtn && !selectedAsset) {
1408+
defaultAssetBtn.classList.add('active');
1409+
selectedAsset = 'BTC';
1410+
}
1411+
}
1412+
} catch (e) {
1413+
console.warn('Could not restore selected asset from localStorage', e);
1414+
}
1415+
11971416
document.getElementById('useCurrentTime').addEventListener('change', handleUseCurrentTimeChange);
11981417

11991418
// Set up date/time validation
@@ -1255,10 +1474,16 @@ <h4 class="panel-title">
12551474

12561475
// Store selected asset
12571476
selectedAsset = asset;
1258-
1477+
try {
1478+
localStorage.setItem(STORAGE_KEYS.SELECTED_ASSET, asset);
1479+
} catch (e) {
1480+
console.warn('Could not persist selected asset', e);
1481+
}
1482+
12591483
// Hide custom asset input if it was showing
1260-
document.getElementById('customAssetDiv').style.display = 'none';
1261-
1484+
const cav = document.getElementById('customAssetDiv');
1485+
if (cav) cav.style.display = 'none';
1486+
12621487
console.log('Selected asset:', asset);
12631488
}
12641489

@@ -1277,38 +1502,61 @@ <h4 class="panel-title">
12771502
}
12781503

12791504
function confirmCustomAsset() {
1280-
const customAsset = document.getElementById('customAssetInput').value.trim();
1281-
1505+
const customAssetEl = document.getElementById('customAssetInput');
1506+
const customAsset = customAssetEl.value.trim();
1507+
12821508
if (!customAsset) {
12831509
alert('Please enter a custom asset symbol.');
12841510
return;
12851511
}
1286-
1287-
// Store selected asset
1288-
selectedAsset = customAsset;
1289-
1290-
// Hide custom asset input
1291-
document.getElementById('customAssetDiv').style.display = 'none';
1292-
1293-
// Show success message
1294-
alert(`Custom asset "${customAsset}" selected successfully!`);
1295-
1296-
console.log('Selected custom asset:', customAsset);
1297-
}
1298-
1299-
function selectTimeframe(button, timeframe) {
1300-
// Remove active class from all timeframe buttons
1301-
document.querySelectorAll('.timeframe-btn').forEach(btn => {
1302-
btn.classList.remove('active');
1303-
});
1304-
1305-
// Add active class to clicked button
1306-
button.classList.add('active');
1307-
1308-
// Store selected timeframe
1309-
selectedTimeframe = timeframe;
1310-
1311-
console.log('Selected timeframe:', timeframe);
1512+
1513+
// Normalize (trim)
1514+
const symbol = customAsset;
1515+
1516+
// Save to localStorage list
1517+
try {
1518+
const listRaw = localStorage.getItem(STORAGE_KEYS.CUSTOM_ASSETS);
1519+
const list = listRaw ? JSON.parse(listRaw) : [];
1520+
if (!list.includes(symbol)) {
1521+
list.push(symbol);
1522+
localStorage.setItem(STORAGE_KEYS.CUSTOM_ASSETS, JSON.stringify(list));
1523+
}
1524+
} catch (e) {
1525+
console.warn('Could not persist custom asset to localStorage', e);
1526+
}
1527+
1528+
// Persist to server (best-effort)
1529+
fetch('/api/save-custom-asset', {
1530+
method: 'POST',
1531+
headers: { 'Content-Type': 'application/json' },
1532+
body: JSON.stringify({ symbol })
1533+
}).then(r => r.json())
1534+
.then(resp => {
1535+
if (!resp.success) {
1536+
console.warn('Server did not save custom asset', resp);
1537+
}
1538+
}).catch(err => {
1539+
console.warn('Error saving custom asset to server', err);
1540+
});
1541+
1542+
// Add button and select it
1543+
addCustomAssetButton(symbol);
1544+
// Select the new asset: find button and click
1545+
const grid = document.getElementById('assetButtonGrid');
1546+
const newBtn = Array.from(grid.querySelectorAll('.asset-btn')).find(b => b.dataset.asset === symbol);
1547+
if (newBtn) {
1548+
newBtn.click();
1549+
}
1550+
1551+
// Hide custom asset input
1552+
document.getElementById('customAssetDiv').style.display = 'none';
1553+
1554+
// Clear input
1555+
customAssetEl.value = '';
1556+
1557+
// Notify user
1558+
// Use a non-blocking notification (replace alert with console + subtle DOM message if desired)
1559+
console.log(`Custom asset "${symbol}" selected and persisted`);
13121560
}
13131561

13141562
function runAnalysis() {

0 commit comments

Comments
 (0)