fix(uploaders): convert master/builder to single-file slot pattern
Multi-file uploaders trigger Windows browsers' folder-select mode which bypasses the type filter. Switch to N single-file slots (matching the working set comparator pattern) so the OS picker stays in single-file mode where folder selection is impossible. Default 5 slots, expandable up to 20 via dropdown.
This commit is contained in:
+38
-25
@@ -592,6 +592,7 @@ def _init_state():
|
||||
"pb_portfolios": {},
|
||||
"pb_lot_overrides": {}, # label → float multiplier
|
||||
"pb_deposit": 10000.0,
|
||||
"pb_n_slots": 5, # number of file upload slots shown
|
||||
}.items():
|
||||
if k not in st.session_state:
|
||||
st.session_state[k] = v
|
||||
@@ -618,31 +619,43 @@ def render():
|
||||
# ── Upload panel ─────────────────────────────────────────────────────────
|
||||
with st.expander("📂 Upload Strategy Reports",
|
||||
expanded=not bool(st.session_state.pb_uploaded_files)):
|
||||
st.caption("Accepts: `.htm` · `.html` · `.csv`")
|
||||
uploaded = st.file_uploader(
|
||||
"Select HTM, HTML or CSV files",
|
||||
type=["htm", "html", "csv"],
|
||||
accept_multiple_files=True, key="pb_uploader",
|
||||
)
|
||||
if uploaded:
|
||||
# Streamlit already filters by type, but keep a defensive check
|
||||
# in case type filter is ever loosened
|
||||
rejected = [f.name for f in uploaded
|
||||
if not f.name.lower().endswith((".htm",".html",".csv"))]
|
||||
if rejected:
|
||||
st.warning(f"Ignored {len(rejected)} unsupported file(s): "
|
||||
+ ", ".join(rejected[:5])
|
||||
+ (" …" if len(rejected) > 5 else ""))
|
||||
uploaded = [f for f in uploaded
|
||||
if f.name.lower().endswith((".htm",".html",".csv"))]
|
||||
for f in uploaded:
|
||||
stem = os.path.splitext(f.name)[0]
|
||||
if stem not in st.session_state.pb_uploaded_files:
|
||||
df = _parse_uploaded(f)
|
||||
if df is not None:
|
||||
df = _ensure_columns(df, stem)
|
||||
st.session_state.pb_uploaded_files[stem] = df
|
||||
st.success(f"✅ **{stem}** — {len(df):,} trades")
|
||||
st.caption("Accepts: `.htm` · `.html` · `.csv` — one file per slot. "
|
||||
"Increase the slot count if you need more.")
|
||||
|
||||
sc1, sc2 = st.columns([1, 5])
|
||||
with sc1:
|
||||
n_slots = st.selectbox(
|
||||
"Slots",
|
||||
list(range(1, 21)),
|
||||
index=max(0, st.session_state.pb_n_slots - 1),
|
||||
key="pb_n_slots_select",
|
||||
)
|
||||
if n_slots != st.session_state.pb_n_slots:
|
||||
st.session_state.pb_n_slots = n_slots
|
||||
st.rerun()
|
||||
|
||||
slots_per_row = 5
|
||||
for row_start in range(0, st.session_state.pb_n_slots, slots_per_row):
|
||||
cols = st.columns(slots_per_row)
|
||||
for j in range(slots_per_row):
|
||||
idx = row_start + j
|
||||
if idx >= st.session_state.pb_n_slots:
|
||||
break
|
||||
with cols[j]:
|
||||
f = st.file_uploader(
|
||||
f"File {idx + 1}",
|
||||
type=["htm", "html", "csv"],
|
||||
accept_multiple_files=False,
|
||||
key=f"pb_upload_slot_{idx}",
|
||||
)
|
||||
if f is not None:
|
||||
stem = os.path.splitext(f.name)[0]
|
||||
if stem not in st.session_state.pb_uploaded_files:
|
||||
df = _parse_uploaded(f)
|
||||
if df is not None:
|
||||
df = _ensure_columns(df, stem)
|
||||
st.session_state.pb_uploaded_files[stem] = df
|
||||
st.success(f"✅ {stem} — {len(df):,} trades")
|
||||
|
||||
if st.session_state.pb_uploaded_files:
|
||||
st.markdown("**Loaded strategies:**")
|
||||
|
||||
+38
-26
@@ -478,6 +478,7 @@ def _init_state():
|
||||
"pm_thread_results": None,
|
||||
"pm_progress_q": None,
|
||||
"pm_uploader_key": 0,
|
||||
"pm_n_slots": 5,
|
||||
}.items():
|
||||
if k not in st.session_state:
|
||||
st.session_state[k] = v
|
||||
@@ -544,32 +545,43 @@ def render():
|
||||
# ── Upload ───────────────────────────────────────────────────────────────
|
||||
with st.expander("📂 Upload Backtest Files",
|
||||
expanded=not bool(st.session_state.pm_files)):
|
||||
st.caption("Accepts `.htm` · `.html` · `.csv` — selecting a folder will only "
|
||||
"import the supported files inside it (others are skipped).")
|
||||
uploaded = st.file_uploader(
|
||||
"Select files",
|
||||
type=["htm", "html", "csv"],
|
||||
accept_multiple_files=True,
|
||||
key=f"pm_uploader_{st.session_state.pm_uploader_key}",
|
||||
)
|
||||
if uploaded:
|
||||
# Some browsers ignore the type filter when a folder is selected —
|
||||
# filter again here and report what was skipped.
|
||||
valid = [f for f in uploaded
|
||||
if f.name.lower().endswith((".htm",".html",".csv"))]
|
||||
rejected = len(uploaded) - len(valid)
|
||||
if rejected:
|
||||
st.warning(f"⚠️ Skipped {rejected} non-supported file(s). "
|
||||
f"Importing {len(valid)} valid file(s).")
|
||||
uploaded = valid
|
||||
for f in uploaded:
|
||||
stem = os.path.splitext(f.name)[0]
|
||||
if stem not in st.session_state.pm_files:
|
||||
df = _parse_file(f)
|
||||
if df is not None:
|
||||
df = _normalise(df.copy(), stem)
|
||||
st.session_state.pm_files[stem] = df
|
||||
st.success(f"✅ **{stem}** — {len(df):,} trades")
|
||||
st.caption("Accepts `.htm` · `.html` · `.csv` — one file per slot. "
|
||||
"Increase the slot count if you need more.")
|
||||
|
||||
sc1, _ = st.columns([1, 5])
|
||||
with sc1:
|
||||
n_slots = st.selectbox(
|
||||
"Slots",
|
||||
list(range(1, 21)),
|
||||
index=max(0, st.session_state.pm_n_slots - 1),
|
||||
key="pm_n_slots_select",
|
||||
)
|
||||
if n_slots != st.session_state.pm_n_slots:
|
||||
st.session_state.pm_n_slots = n_slots
|
||||
st.rerun()
|
||||
|
||||
slots_per_row = 5
|
||||
for row_start in range(0, st.session_state.pm_n_slots, slots_per_row):
|
||||
cols = st.columns(slots_per_row)
|
||||
for j in range(slots_per_row):
|
||||
idx = row_start + j
|
||||
if idx >= st.session_state.pm_n_slots:
|
||||
break
|
||||
with cols[j]:
|
||||
f = st.file_uploader(
|
||||
f"File {idx + 1}",
|
||||
type=["htm", "html", "csv"],
|
||||
accept_multiple_files=False,
|
||||
key=f"pm_upload_slot_{st.session_state.pm_uploader_key}_{idx}",
|
||||
)
|
||||
if f is not None:
|
||||
stem = os.path.splitext(f.name)[0]
|
||||
if stem not in st.session_state.pm_files:
|
||||
df = _parse_file(f)
|
||||
if df is not None:
|
||||
df = _normalise(df.copy(), stem)
|
||||
st.session_state.pm_files[stem] = df
|
||||
st.success(f"✅ {stem} — {len(df):,} trades")
|
||||
|
||||
if st.session_state.pm_files:
|
||||
# Clear all button
|
||||
|
||||
Reference in New Issue
Block a user