topsecrettraders commited on
Commit
1fa1102
·
verified ·
1 Parent(s): 07489c9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +102 -21
app.py CHANGED
@@ -427,6 +427,19 @@ HTML_TEMPLATE = """
427
 
428
  .form-section label { font-size: 11px; text-transform: uppercase; color: var(--text-secondary); font-weight: 600; margin-bottom: 6px; display: block; }
429
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
  /* SERIES ROW STRUCTURE (POLISHED UI) */
431
  .series-row {
432
  background: var(--bg-panel); padding: 12px; border-radius: 8px; border: 1px solid var(--border); margin-bottom: 10px;
@@ -528,13 +541,19 @@ HTML_TEMPLATE = """
528
  <div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
529
  <div class="form-section">
530
  <label>Symbol</label>
531
- <input type="text" id="cfgRoot" list="listRoots" onchange="Config.onRootChange()" oninput="this.value=this.value.toUpperCase();" style="width:100%" placeholder="Type or select...">
532
- <datalist id="listRoots"></datalist>
 
 
 
533
  </div>
534
  <div class="form-section">
535
  <label>Expiry</label>
536
- <input type="text" id="cfgExp" list="listExps" onchange="Config.onExpChange()" style="width:100%" placeholder="Type or select...">
537
- <datalist id="listExps"></datalist>
 
 
 
538
  </div>
539
  </div>
540
 
@@ -550,8 +569,11 @@ HTML_TEMPLATE = """
550
  <div id="cfgNormalFields">
551
  <div class="form-section">
552
  <label>Instrument</label>
553
- <input type="text" id="cfgInst" list="listInsts" style="width:100%" placeholder="Type or select...">
554
- <datalist id="listInsts"></datalist>
 
 
 
555
  </div>
556
  </div>
557
 
@@ -616,6 +638,69 @@ HTML_TEMPLATE = """
616
  </div>
617
 
618
  <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
619
  const App = {
620
  state: { date: null, mode: 'LIVE', columns: 3, layout: [[],[],[]], charts: {}, refreshRate: 15 },
621
  presets: {
@@ -1061,10 +1146,10 @@ const Config = {
1061
 
1062
  this.updatePresetsDropdown();
1063
 
1064
- const rList = document.getElementById('listRoots');
1065
  const res = await fetch(`/api/roots?date=${App.state.date}`);
1066
  const roots = await res.json();
1067
- rList.innerHTML = roots.map(r=>`<option value="${r}"></option>`).join('');
1068
 
1069
  const cfg = App.state.charts[id].config;
1070
  document.getElementById('cfgTF').value = cfg.timeframe || '1min';
@@ -1118,8 +1203,8 @@ const Config = {
1118
 
1119
  async onRootChange(preExp, preInst) {
1120
  const root = document.getElementById('cfgRoot').value.toUpperCase();
1121
- const eList = document.getElementById('listExps');
1122
- eList.innerHTML = '';
1123
 
1124
  if (!preExp) document.getElementById('cfgExp').value = '';
1125
  if (!preInst) document.getElementById('cfgInst').value = '';
@@ -1128,14 +1213,13 @@ const Config = {
1128
 
1129
  const res = await fetch(`/api/expiries?date=${App.state.date}&root=${root}`);
1130
  const exps = await res.json();
1131
- eList.innerHTML = exps.map(e=>`<option value="${e}"></option>`).join('');
1132
 
1133
  if(preExp && exps.includes(preExp)) {
1134
  document.getElementById('cfgExp').value = preExp;
1135
  await this.onExpChange(preInst);
1136
  }
1137
  else if (exps.length > 0) {
1138
- // Auto-select logic
1139
  const auto = await fetch(`/api/auto_config?date=${App.state.date}&root=${root}`).then(r=>r.json());
1140
  if(auto.current) {
1141
  document.getElementById('cfgExp').value = auto.current.expiry;
@@ -1146,20 +1230,17 @@ const Config = {
1146
 
1147
  async onExpChange(preInst) {
1148
  const root = document.getElementById('cfgRoot').value.toUpperCase();
1149
- const exp = document.getElementById('cfgExp').value;
1150
- const iList = document.getElementById('listInsts');
1151
- iList.innerHTML = '';
1152
 
1153
  if (!root || !exp) return;
1154
 
1155
  const res = await fetch(`/api/instruments?date=${App.state.date}&root=${root}&expiry=${exp}`);
1156
  const data = await res.json();
1157
 
1158
- let html = '';
1159
- data.spot_fut.forEach(i => html += `<option value="${i}"></option>`);
1160
- data.options.forEach(i => html += `<option value="${i}"></option>`);
1161
 
1162
- iList.innerHTML = html;
1163
  if(preInst) document.getElementById('cfgInst').value = preInst;
1164
  },
1165
 
@@ -1246,8 +1327,8 @@ const Config = {
1246
  }
1247
  else {
1248
  const root = document.getElementById('cfgRoot').value.toUpperCase();
1249
- const exp = document.getElementById('cfgExp').value;
1250
- const inst = document.getElementById('cfgInst').value;
1251
 
1252
  if(!root || !exp) return alert("Incomplete Details. Please type or select valid options.");
1253
 
 
427
 
428
  .form-section label { font-size: 11px; text-transform: uppercase; color: var(--text-secondary); font-weight: 600; margin-bottom: 6px; display: block; }
429
 
430
+ /* CUSTOM DROPDOWN UI */
431
+ .input-wrapper { position: relative; width: 100%; }
432
+ .input-arrow { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); pointer-events: none; color: var(--text-secondary); font-size: 10px; }
433
+ .custom-dd {
434
+ position: absolute; top: 100%; left: 0; right: 0; background: var(--bg-panel); border: 1px solid var(--border);
435
+ border-radius: 6px; max-height: 200px; overflow-y: auto; z-index: 1000; display: none;
436
+ box-shadow: 0 4px 12px rgba(0,0,0,0.5); margin-top: 4px;
437
+ }
438
+ .custom-dd-opt { padding: 8px 12px; font-size: 12px; cursor: pointer; color: var(--text-primary); border-bottom: 1px solid var(--border); }
439
+ .custom-dd-opt:last-child { border-bottom: none; }
440
+ .custom-dd-opt:hover { background: var(--bg-input); color: var(--accent); }
441
+ .custom-dd-group { padding: 6px 12px; font-size: 10px; font-weight: 700; color: var(--text-secondary); background: var(--bg-body); text-transform: uppercase; }
442
+
443
  /* SERIES ROW STRUCTURE (POLISHED UI) */
444
  .series-row {
445
  background: var(--bg-panel); padding: 12px; border-radius: 8px; border: 1px solid var(--border); margin-bottom: 10px;
 
541
  <div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
542
  <div class="form-section">
543
  <label>Symbol</label>
544
+ <div class="input-wrapper">
545
+ <input type="text" id="cfgRoot" onfocus="CustomDD.open('cfgRoot')" oninput="CustomDD.filter('cfgRoot')" style="width:100%; text-transform: uppercase;" placeholder="Type or select..." autocomplete="off">
546
+ <div class="input-arrow">▼</div>
547
+ <div id="dd-cfgRoot" class="custom-dd"></div>
548
+ </div>
549
  </div>
550
  <div class="form-section">
551
  <label>Expiry</label>
552
+ <div class="input-wrapper">
553
+ <input type="text" id="cfgExp" onfocus="CustomDD.open('cfgExp')" oninput="CustomDD.filter('cfgExp')" style="width:100%; text-transform: uppercase;" placeholder="Type or select..." autocomplete="off">
554
+ <div class="input-arrow">▼</div>
555
+ <div id="dd-cfgExp" class="custom-dd"></div>
556
+ </div>
557
  </div>
558
  </div>
559
 
 
569
  <div id="cfgNormalFields">
570
  <div class="form-section">
571
  <label>Instrument</label>
572
+ <div class="input-wrapper">
573
+ <input type="text" id="cfgInst" onfocus="CustomDD.open('cfgInst')" oninput="CustomDD.filter('cfgInst')" style="width:100%; text-transform: uppercase;" placeholder="Type or select..." autocomplete="off">
574
+ <div class="input-arrow">▼</div>
575
+ <div id="dd-cfgInst" class="custom-dd"></div>
576
+ </div>
577
  </div>
578
  </div>
579
 
 
638
  </div>
639
 
640
  <script>
641
+ // --- CUSTOM SEARCHABLE DROPDOWN LOGIC ---
642
+ document.addEventListener('click', (e) => {
643
+ if (!e.target.closest('.input-wrapper')) {
644
+ document.querySelectorAll('.custom-dd').forEach(el => el.style.display = 'none');
645
+ }
646
+ });
647
+
648
+ const CustomDD = {
649
+ data: { cfgRoot: [], cfgExp: [], cfgInst:[] },
650
+
651
+ clear(id) {
652
+ this.data[id] =[];
653
+ document.getElementById(id).value = '';
654
+ },
655
+ set(id, items, groupName = null) {
656
+ if (!groupName) {
657
+ this.data[id] = items.map(i => ({val: i, group: null}));
658
+ } else {
659
+ this.data[id].push(...items.map(i => ({val: i, group: groupName})));
660
+ }
661
+ },
662
+ open(id) {
663
+ document.querySelectorAll('.custom-dd').forEach(el => el.style.display = 'none');
664
+ const inp = document.getElementById(id);
665
+ inp.select();
666
+ this.render(id, ''); // Show all options
667
+ document.getElementById(`dd-${id}`).style.display = 'block';
668
+ },
669
+ filter(id) {
670
+ this.render(id, document.getElementById(id).value);
671
+ document.getElementById(`dd-${id}`).style.display = 'block';
672
+ },
673
+ render(id, query) {
674
+ const el = document.getElementById(`dd-${id}`);
675
+ const q = query.toLowerCase();
676
+ let html = '';
677
+ let currentGroup = null;
678
+
679
+ const filtered = this.data[id].filter(item => item.val.toLowerCase().includes(q));
680
+
681
+ if (filtered.length === 0) {
682
+ el.innerHTML = '<div class="custom-dd-opt" style="color:var(--text-secondary); cursor:default;">No matches found</div>';
683
+ return;
684
+ }
685
+
686
+ filtered.forEach(item => {
687
+ if (item.group && item.group !== currentGroup) {
688
+ html += `<div class="custom-dd-group">${item.group}</div>`;
689
+ currentGroup = item.group;
690
+ }
691
+ html += `<div class="custom-dd-opt" onclick="CustomDD.pick('${id}', '${item.val}')">${item.val}</div>`;
692
+ });
693
+ el.innerHTML = html;
694
+ },
695
+ pick(id, val) {
696
+ document.getElementById(id).value = val;
697
+ document.getElementById(`dd-${id}`).style.display = 'none';
698
+ if(id === 'cfgRoot') Config.onRootChange();
699
+ if(id === 'cfgExp') Config.onExpChange();
700
+ }
701
+ };
702
+ // ----------------------------------------
703
+
704
  const App = {
705
  state: { date: null, mode: 'LIVE', columns: 3, layout: [[],[],[]], charts: {}, refreshRate: 15 },
706
  presets: {
 
1146
 
1147
  this.updatePresetsDropdown();
1148
 
1149
+ CustomDD.clear('cfgRoot');
1150
  const res = await fetch(`/api/roots?date=${App.state.date}`);
1151
  const roots = await res.json();
1152
+ CustomDD.set('cfgRoot', roots);
1153
 
1154
  const cfg = App.state.charts[id].config;
1155
  document.getElementById('cfgTF').value = cfg.timeframe || '1min';
 
1203
 
1204
  async onRootChange(preExp, preInst) {
1205
  const root = document.getElementById('cfgRoot').value.toUpperCase();
1206
+ CustomDD.clear('cfgExp');
1207
+ CustomDD.clear('cfgInst');
1208
 
1209
  if (!preExp) document.getElementById('cfgExp').value = '';
1210
  if (!preInst) document.getElementById('cfgInst').value = '';
 
1213
 
1214
  const res = await fetch(`/api/expiries?date=${App.state.date}&root=${root}`);
1215
  const exps = await res.json();
1216
+ CustomDD.set('cfgExp', exps);
1217
 
1218
  if(preExp && exps.includes(preExp)) {
1219
  document.getElementById('cfgExp').value = preExp;
1220
  await this.onExpChange(preInst);
1221
  }
1222
  else if (exps.length > 0) {
 
1223
  const auto = await fetch(`/api/auto_config?date=${App.state.date}&root=${root}`).then(r=>r.json());
1224
  if(auto.current) {
1225
  document.getElementById('cfgExp').value = auto.current.expiry;
 
1230
 
1231
  async onExpChange(preInst) {
1232
  const root = document.getElementById('cfgRoot').value.toUpperCase();
1233
+ const exp = document.getElementById('cfgExp').value.toUpperCase();
1234
+ CustomDD.clear('cfgInst');
 
1235
 
1236
  if (!root || !exp) return;
1237
 
1238
  const res = await fetch(`/api/instruments?date=${App.state.date}&root=${root}&expiry=${exp}`);
1239
  const data = await res.json();
1240
 
1241
+ if (data.spot_fut.length > 0) CustomDD.set('cfgInst', data.spot_fut, 'Futures & Equity');
1242
+ if (data.options.length > 0) CustomDD.set('cfgInst', data.options, 'Options');
 
1243
 
 
1244
  if(preInst) document.getElementById('cfgInst').value = preInst;
1245
  },
1246
 
 
1327
  }
1328
  else {
1329
  const root = document.getElementById('cfgRoot').value.toUpperCase();
1330
+ const exp = document.getElementById('cfgExp').value.toUpperCase();
1331
+ const inst = document.getElementById('cfgInst').value.toUpperCase();
1332
 
1333
  if(!root || !exp) return alert("Incomplete Details. Please type or select valid options.");
1334