Skip to content

Commit 78b3a55

Browse files
authored
Update app.py
1 parent a44d8d2 commit 78b3a55

File tree

1 file changed

+62
-94
lines changed

1 file changed

+62
-94
lines changed

app.py

Lines changed: 62 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,10 @@ def load_db():
4040
if "mod_library" not in data: data["mod_library"] = []
4141
if "server_configs" not in data: data["server_configs"] = []
4242
if "projects" not in data: data["projects"] = []
43-
4443
for m in data.get("mods", []):
4544
if "read" not in m: m["read"] = True
4645
for p in data.get("projects", []):
4746
if "read" not in p: p["read"] = True
48-
4947
return data
5048
except json.JSONDecodeError: return {}
5149

@@ -82,6 +80,48 @@ def fetch_mod_details(mod_input):
8280
except Exception as e:
8381
return mod_id, None, None, str(e)
8482

83+
# --- SMART JSON INSERTER (FIXES BRACKET ISSUES) ---
84+
def inject_mod(current_text, mod_obj):
85+
"""
86+
Intelligently inserts a mod object into a JSON list string.
87+
Handles trailing whitespace and malformed JSON better.
88+
"""
89+
# 1. Clean input
90+
s = current_text.strip()
91+
92+
# 2. Try Strict Parse (Best Case)
93+
try:
94+
data = json.loads(s)
95+
if isinstance(data, list):
96+
data.append(mod_obj)
97+
return json.dumps(data, indent=4)
98+
except:
99+
pass # Fallback to string manipulation if JSON is currently invalid (common while editing)
100+
101+
# 3. String Manipulation Fallback
102+
snippet = json.dumps(mod_obj, indent=4)
103+
104+
if s.endswith("]"):
105+
# We found the end of the list.
106+
# Check if list is effectively empty "[]"
107+
if len(s) < 3:
108+
return f"[\n{snippet}\n]"
109+
110+
# Peel off the last ']'
111+
# rstrip() removes whitespace/newlines before the bracket to prevent weird gaps
112+
content = s[:-1].rstrip()
113+
114+
# Add comma, new object, and close bracket
115+
return f"{content},\n{snippet}\n]"
116+
117+
elif not s:
118+
# Empty editor -> Start new list
119+
return f"[\n{snippet}\n]"
120+
121+
else:
122+
# No closing bracket found? Just append (Safe fallback)
123+
return s + ",\n" + snippet
124+
85125
# --- LOCAL SESSION STATE ---
86126
if "logged_in" not in st.session_state: st.session_state.logged_in = False
87127
if "current_user" not in st.session_state: st.session_state.current_user = None
@@ -90,7 +130,6 @@ def fetch_mod_details(mod_input):
90130
if "selected_project_id" not in st.session_state: st.session_state.selected_project_id = None
91131
if "editor_content" not in st.session_state: st.session_state.editor_content = "[\n\n]"
92132
if "fetched_mod" not in st.session_state: st.session_state.fetched_mod = None
93-
# KEY FIX: Refresh counter to force text area update
94133
if "editor_key" not in st.session_state: st.session_state.editor_key = 0
95134

96135
# --- CALLBACK TO SYNC EDITOR ---
@@ -205,18 +244,14 @@ def get_mod_status():
205244

206245
st.sidebar.button("📢 Announcements", on_click=navigate_to, args=("view_announcements",))
207246

208-
# MOVED: Mod Studio to Top (Super Admin Only)
209247
if user_role == "SUPER_ADMIN":
210248
st.sidebar.button("📝 Mod Studio", on_click=navigate_to, args=("json_editor",))
211249

212-
# --- ADMIN SECTION ---
213250
if user_role in ["admin", "SUPER_ADMIN"]:
214251
st.sidebar.subheader("Server Admin")
215252
st.sidebar.button(f"{get_mod_status()} Report Broken Mod", on_click=navigate_to, args=("report_broken_mod", None))
216-
# NEW JOB BUTTON (ADMIN ONLY)
217253
st.sidebar.button("🚀 Submit New Job", on_click=navigate_to, args=("create_project",))
218254

219-
# --- CLP SECTION ---
220255
if user_role in ["CLPLEAD", "SUPER_ADMIN", "CLP"]:
221256
st.sidebar.subheader("CLP Management")
222257
if user_role in ["CLPLEAD", "SUPER_ADMIN"]:
@@ -259,70 +294,48 @@ def get_mod_status():
259294
st.caption(f"{a['date']} by {a['author']}")
260295
st.markdown(a['content'], unsafe_allow_html=True)
261296

262-
# --- CREATE PROJECT PAGE (Saves to PROJECTS) ---
263297
elif st.session_state.page == "create_project":
264298
st.title("🚀 Submit New Job / Project")
265299
st.caption("This will create a task in the 'New Work' tab.")
266-
267300
with st.container(border=True):
268301
p_name = st.text_input("Project Title")
269302
p_assign = st.text_input("Lead Developer/Assignee")
270303
p_sev = st.slider("Severity / Priority", 1, 10, 5)
271304
st.write("Project Brief:")
272305
p_desc = st_quill(key="proj_desc_page", html=True)
273-
274306
if st.button("Create Project", type="primary"):
275-
# SAVE TO 'projects' ONLY
276307
DB['projects'].append({
277-
"id": len(DB['projects']),
278-
"name": p_name,
279-
"assigned": p_assign,
280-
"severity": p_sev,
281-
"description": p_desc,
282-
"complete": False,
283-
"discussion": [],
284-
"read": False
308+
"id": len(DB['projects']), "name": p_name, "assigned": p_assign, "severity": p_sev,
309+
"description": p_desc, "complete": False, "discussion": [], "read": False
285310
})
286311
save_db(DB)
287312
st.success("Project Created! Saved to New Work.")
288313
st.session_state.page = "view_projects"
289314
st.rerun()
290315

291-
# --- REPORT BROKEN MOD PAGE (Saves to MODS) ---
292316
elif st.session_state.page == "report_broken_mod":
293317
st.title("Report Broken Mod")
294318
st.caption("This will create a ticket in the 'Broken Mods' tab.")
295-
296319
name = st.text_input("Mod Name")
297320
json_code = st.text_area("JSON Code", height=100)
298321
sev = st.slider("Severity", 1, 10)
299322
assign = st.text_input("Assign To")
300323
st.write("Description:")
301324
desc = st_quill(key="mod_desc", html=True)
302325
if st.button("Submit Report"):
303-
# SAVE TO 'mods' ONLY
304326
DB['mods'].append({
305-
"id": len(DB['mods']),
306-
"name": name,
307-
"json_data": json_code,
308-
"severity": sev,
309-
"assignment": assign,
310-
"description": desc,
311-
"complete": False,
312-
"discussion": [],
313-
"read": False
327+
"id": len(DB['mods']), "name": name, "json_data": json_code, "severity": sev,
328+
"assignment": assign, "description": desc, "complete": False, "discussion": [], "read": False
314329
})
315330
save_db(DB)
316331
st.success("Submitted! Saved to Broken Mods.")
317332
st.session_state.page = "view_broken_mods"
318333
st.rerun()
319334

320-
# --- VIEW BROKEN MODS (Reads from MODS) ---
321335
elif st.session_state.page == "view_broken_mods":
322336
if user_role not in ["admin", "SUPER_ADMIN"]: st.error("Access Denied.")
323337
else:
324338
st.title("Active Broken Mods")
325-
# READ FROM 'mods' ONLY
326339
active = [m for m in DB['mods'] if not m['complete']]
327340
if not active: st.success("No active issues.")
328341
for m in active:
@@ -333,17 +346,12 @@ def get_mod_status():
333346
st.subheader(f"{prefix}{m['name']}")
334347
st.caption(f"Severity: {m['severity']} | Assigned: {m['assignment']}")
335348
with c2:
336-
if st.button("Details", key=f"d_{m['id']}", on_click=navigate_to, args=("mod_detail", m['id'], None)):
337-
pass
349+
if st.button("Details", key=f"d_{m['id']}", on_click=navigate_to, args=("mod_detail", m['id'], None)): pass
338350

339-
# --- VIEW PROJECTS (Reads from PROJECTS) ---
340351
elif st.session_state.page == "view_projects":
341352
st.title("New Work / Active Projects")
342-
# READ FROM 'projects' ONLY
343353
active_projs = [p for p in DB['projects'] if not p['complete']]
344-
345-
if not active_projs:
346-
st.info("No active projects.")
354+
if not active_projs: st.info("No active projects.")
347355
else:
348356
for p in active_projs:
349357
with st.container(border=True):
@@ -354,10 +362,8 @@ def get_mod_status():
354362
sev = p.get('severity', 1)
355363
st.caption(f"Lead: {p['assigned']} | Severity: {sev}/10")
356364
with c2:
357-
if st.button("Open", key=f"p_{p['id']}", on_click=navigate_to, args=("project_detail", None, p['id'])):
358-
pass
365+
if st.button("Open", key=f"p_{p['id']}", on_click=navigate_to, args=("project_detail", None, p['id'])): pass
359366

360-
# --- VIEW FIXED MODS ---
361367
elif st.session_state.page == "view_fixed_mods":
362368
if user_role not in ["admin", "SUPER_ADMIN"]: st.error("Access Denied.")
363369
else:
@@ -370,15 +376,12 @@ def get_mod_status():
370376
with c1: st.subheader(f"✅ {m['name']}")
371377
with c2: st.button("Archive View", key=f"a_{m['id']}", on_click=navigate_to, args=("mod_detail", m['id'], None))
372378

373-
# --- MOD DETAIL ---
374379
elif st.session_state.page == "mod_detail":
375380
m = next((x for x in DB['mods'] if x['id'] == st.session_state.selected_mod_id), None)
376-
377381
if m and not m.get('read', True) and user_role in ["admin", "SUPER_ADMIN"]:
378382
m['read'] = True
379383
save_db(DB)
380384
st.rerun()
381-
382385
if m:
383386
st.title(f"Issue: {m['name']}")
384387
c1, c2 = st.columns([2,1])
@@ -404,25 +407,20 @@ def get_mod_status():
404407
with c2:
405408
st.subheader("Discussion")
406409
chat = st.container(height=400, border=True)
407-
for msg in m.get('discussion', []):
408-
chat.markdown(f"**{msg['user']}**: {msg['text']}")
409-
410+
for msg in m.get('discussion', []): chat.markdown(f"**{msg['user']}**: {msg['text']}")
410411
with st.form("chat"):
411412
txt = st.text_input("Message")
412413
if st.form_submit_button("Send") and txt:
413414
m.setdefault('discussion', []).append({"user": USER_NAME, "text": txt, "time": str(datetime.now())})
414415
save_db(DB)
415416
st.rerun()
416417

417-
# --- PROJECT DETAIL ---
418418
elif st.session_state.page == "project_detail":
419419
p = next((x for x in DB['projects'] if x['id'] == st.session_state.selected_project_id), None)
420-
421420
if p and not p.get('read', True) and user_role in ["admin", "SUPER_ADMIN"]:
422421
p['read'] = True
423422
save_db(DB)
424423
st.rerun()
425-
426424
if p:
427425
st.title(f"Project: {p['name']}")
428426
c1, c2 = st.columns([2,1])
@@ -438,15 +436,11 @@ def get_mod_status():
438436
st.success("Completed!")
439437
st.session_state.page = "view_projects"
440438
st.rerun()
441-
else:
442-
st.success("Project Completed.")
439+
else: st.success("Project Completed.")
443440
with c2:
444441
st.subheader("Discussion")
445-
# Unified Chat Style (No Dividers)
446442
chat = st.container(height=400, border=True)
447-
for msg in p.get('discussion', []):
448-
chat.markdown(f"**{msg['user']}**: {msg['text']}")
449-
443+
for msg in p.get('discussion', []): chat.markdown(f"**{msg['user']}**: {msg['text']}")
450444
with st.form("p_chat"):
451445
txt = st.text_input("Message")
452446
if st.form_submit_button("Send") and txt:
@@ -554,7 +548,7 @@ def get_mod_status():
554548
found = next((c for c in DB['server_configs'] if c['name'] == selected_conf), None)
555549
if found:
556550
st.session_state.editor_content = found['content']
557-
st.session_state.editor_key += 1 # INCREMENT KEY
551+
st.session_state.editor_key += 1
558552
st.success(f"Loaded '{selected_conf}'!")
559553
st.rerun()
560554
with c_save:
@@ -576,48 +570,41 @@ def get_mod_status():
576570
st.subheader("Active JSON Editor")
577571
st.caption("Press 'Ctrl+A' then 'Ctrl+C' inside the box to copy everything.")
578572

579-
# --- FIX: DYNAMIC KEY TO FORCE UPDATE ---
580573
json_text = st.text_area(
581574
"JSON Output",
582575
value=st.session_state.editor_content,
583576
height=600,
584-
key=f"json_area_{st.session_state.editor_key}", # DYNAMIC KEY
577+
key=f"json_area_{st.session_state.editor_key}",
585578
on_change=sync_editor
586579
)
587580

588581
with col_tools:
582+
# FIX: Place Tabs OUTSIDE the scrollable container
589583
tab_search, tab_saved, tab_import = st.tabs(["🌐 Search", "💾 Library", "📥 Import"])
590584

591585
with tab_search:
592586
st.info("💡 **Tip:** Type a name to find the link, then Paste the URL to fetch data.")
593587
search_term = st.text_input("1. Search Term", placeholder="e.g. RHS Status Quo")
594588
if search_term:
595589
st.link_button(f"🌐 Open Search: '{search_term}'", f"https://reforger.armaplatform.com/workshop?search={search_term}")
596-
597590
st.divider()
598591
st.write("**2. Paste Workshop URL**")
599592
fetch_url = st.text_input("Paste URL here to auto-fetch", placeholder="https://reforger.armaplatform.com/workshop/...")
600-
601593
if st.button("🚀 Fetch Details"):
602594
if fetch_url:
603595
mid, mname, mimg, mver = fetch_mod_details(fetch_url)
604596
if mname:
605-
st.session_state.fetched_mod = {
606-
"modId": mid, "name": mname, "version": mver, "image_url": mimg
607-
}
597+
st.session_state.fetched_mod = {"modId": mid, "name": mname, "version": mver, "image_url": mimg}
608598
st.success("Found!")
609599
else: st.error("Could not find mod. Check URL.")
610600

611-
# Scrollable results for search
612601
with st.container(height=500, border=True):
613602
if st.session_state.fetched_mod:
614603
mod = st.session_state.fetched_mod
615604
if mod['image_url']: st.image(mod['image_url'])
616605
st.subheader(mod['name'])
617-
618606
clean_mod = {"modId": mod['modId'], "name": mod['name'], "version": ""}
619607
st.code(json.dumps(clean_mod, indent=4), language='json')
620-
621608
c1, c2 = st.columns(2)
622609
with c1:
623610
if st.button("💾 Save to Library"):
@@ -626,54 +613,36 @@ def get_mod_status():
626613
st.success("Saved!")
627614
with c2:
628615
if st.button("➕ Add to Editor"):
629-
snippet = json.dumps(clean_mod, indent=4)
630-
cur = st.session_state.editor_content.strip()
631-
if not cur: cur = "[]"
632-
if cur.endswith("]"):
633-
if len(cur) > 2: new_s = cur[:-1] + ",\n" + snippet + "\n]"
634-
else: new_s = "[\n" + snippet + "\n]"
635-
else: new_s = cur + ",\n" + snippet
636-
st.session_state.editor_content = new_s
637-
st.session_state.editor_key += 1 # TRIGGER REFRESH
616+
new_text = inject_mod(st.session_state.editor_content, clean_mod)
617+
st.session_state.editor_content = new_text
618+
st.session_state.editor_key += 1
638619
st.rerun()
639620

640621
with tab_saved:
641622
lib_search = st.text_input("Filter Library", placeholder="Filter by name...")
642-
643623
filtered = sorted(
644624
[m for m in DB['mod_library'] if lib_search.lower() in m.get('name','').lower()],
645625
key=lambda x: x.get('name', '').lower()
646626
)
647-
648627
with st.container(height=600, border=True):
649628
if not filtered: st.info("No saved mods.")
650629
for mod in filtered:
651630
with st.container(border=True):
652631
c_info, c_add, c_copy, c_del = st.columns([3, 1, 1, 1], vertical_alignment="center")
653-
654632
with c_info:
655633
st.write(f"**{mod['name']}**")
656634
mini_json = {"modId": mod['modId'], "name": mod['name'], "version": ""}
657635
json_str = json.dumps(mini_json, indent=4)
658-
659636
with c_add:
660637
if st.button("➕", key=f"ins_{mod['modId']}", help="Insert into Editor", use_container_width=True):
661-
snippet = json_str
662-
cur = st.session_state.editor_content.strip()
663-
if not cur: cur = "[]"
664-
if cur.endswith("]"):
665-
if len(cur) > 2: new_s = cur[:-1] + ",\n" + snippet + "\n]"
666-
else: new_s = "[\n" + snippet + "\n]"
667-
else: new_s = cur + ",\n" + snippet
668-
st.session_state.editor_content = new_s
669-
st.session_state.editor_key += 1 # TRIGGER REFRESH
638+
new_text = inject_mod(st.session_state.editor_content, mini_json)
639+
st.session_state.editor_content = new_text
640+
st.session_state.editor_key += 1
670641
st.rerun()
671-
672642
with c_copy:
673643
with st.popover("📋", use_container_width=True):
674644
st.code(json_str, language='json')
675645
st.caption("Click the icon in the corner to copy.")
676-
677646
with c_del:
678647
if st.button("🗑️", key=f"rm_{mod['modId']}", help="Delete from Library", use_container_width=True):
679648
idx = DB['mod_library'].index(mod)
@@ -685,7 +654,6 @@ def get_mod_status():
685654
st.subheader("Batch Importer")
686655
st.caption("Paste a full JSON file or a list of mods. We will extract every mod block and save it to your library.")
687656
import_text = st.text_area("Paste JSON Here", height=300)
688-
689657
if st.button("Process & Import Mods", type="primary"):
690658
try:
691659
pattern = r'\{[^{}]*"modId"[^{}]*\}'

0 commit comments

Comments
 (0)