Skip to content

Commit 913bd14

Browse files
committed
v0.6.13: single Save All button in settings, fix snack bar
- Replace all per-field Save buttons with one Save All Settings button at the bottom of settings — saves connection, API keys, and all tunable constants in a single click - Refresh Courses button moved alongside Save All at the bottom - Fix snack bar: use page.snack_bar + open=True (Flet 0.82 compatible) - Remove _key_row Save/per-constant Save buttons; keep Default reset buttons
1 parent f36e0be commit 913bd14

1 file changed

Lines changed: 83 additions & 98 deletions

File tree

gui.py

Lines changed: 83 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,19 +1043,18 @@ def build_settings(page: ft.Page,
10431043
on_courses_changed: callable | None = None) -> ft.Column:
10441044

10451045
def _snack(msg: str, ok: bool = True) -> None:
1046-
page.open(ft.SnackBar(
1047-
ft.Text(msg, color=ft.Colors.BLACK),
1046+
page.snack_bar = ft.SnackBar(
1047+
content=ft.Text(msg, color=ft.Colors.BLACK),
10481048
bgcolor=C_SUCCESS if ok else C_ERROR,
10491049
duration=2500,
1050-
))
1050+
open=True,
1051+
)
1052+
page.update()
10511053

1052-
def _key_row(label: str, tf: ft.TextField, on_save) -> ft.Row:
1054+
def _field_row(label: str, tf: ft.TextField) -> ft.Row:
10531055
return ft.Row(controls=[
10541056
ft.Text(label, size=12, color=ft.Colors.WHITE, width=140),
10551057
tf,
1056-
ft.FilledButton("Save", icon=ft.Icons.SAVE_OUTLINED,
1057-
on_click=on_save, style=ft.ButtonStyle(
1058-
bgcolor=C_PRIMARY, color=ft.Colors.BLACK)),
10591058
], spacing=8)
10601059

10611060
# ── Connection settings (config.json) ────────────────────────────────────
@@ -1065,9 +1064,9 @@ def _key_row(label: str, tf: ft.TextField, on_save) -> ft.Row:
10651064
def _load_config() -> dict:
10661065
return json.load(open(config_file)) if config_file.exists() else {}
10671066

1068-
def _save_config(key: str, value: str) -> None:
1067+
def _save_config_all(data: dict) -> None:
10691068
cfg = _load_config()
1070-
cfg[key] = value
1069+
cfg.update(data)
10711070
with open(config_file, "w") as f:
10721071
json.dump(cfg, f, indent=2)
10731072

@@ -1083,28 +1082,14 @@ def _save_config(key: str, value: str) -> None:
10831082
bgcolor=C_OUTPUT_BG, border_color=C_PRIMARY, text_size=12,
10841083
)
10851084

1086-
def _save_canvas_url(_):
1087-
try:
1088-
_save_config("CANVAS_URL", tf_canvas_url.value.strip())
1089-
_snack("Canvas URL saved successfully!")
1090-
except Exception as e:
1091-
_snack(f"Error: {e}", ok=False)
1092-
1093-
def _save_panopto(_):
1094-
try:
1095-
_save_config("PANOPTO_HOST", tf_panopto.value.strip())
1096-
_snack("Panopto host saved successfully!")
1097-
except Exception as e:
1098-
_snack(f"Error: {e}", ok=False)
1099-
11001085
conn_card = _card(ft.Column(controls=[
11011086
ft.Text("Connection", size=13,
11021087
weight=ft.FontWeight.BOLD, color=ft.Colors.WHITE),
11031088
ft.Text("Saved to config.json in the project directory.",
11041089
size=11, color=ft.Colors.with_opacity(0.5, ft.Colors.WHITE)),
11051090
ft.Container(height=4),
1106-
_key_row("Canvas URL", tf_canvas_url, _save_canvas_url),
1107-
_key_row("Panopto Host", tf_panopto, _save_panopto),
1091+
_field_row("Canvas URL", tf_canvas_url),
1092+
_field_row("Panopto Host", tf_panopto),
11081093
], spacing=10))
11091094

11101095
# ── API Keys ──────────────────────────────────────────────────────────────
@@ -1146,7 +1131,6 @@ def _do_refresh():
11461131
refresh_status.value = "Refreshing…"
11471132
refresh_status.color = ft.Colors.with_opacity(0.6, ft.Colors.WHITE)
11481133
page.update()
1149-
# Load courses while this settings page is still attached
11501134
err = _load_courses_from_canvas()
11511135
n = len(COURSES)
11521136
if n:
@@ -1159,56 +1143,19 @@ def _do_refresh():
11591143
refresh_status.value = "No courses found after filtering."
11601144
refresh_status.color = C_WARN
11611145
page.update()
1162-
# Rebuild pages AFTER updating status (rebuild replaces the settings page)
11631146
if on_courses_changed:
11641147
on_courses_changed()
11651148

1166-
def _save_canvas(_):
1167-
try:
1168-
canvas_file.write_text(tf_canvas.value.strip())
1169-
_snack("Canvas token saved successfully!")
1170-
except Exception as e:
1171-
_snack(f"Error: {e}", ok=False)
1172-
1173-
def _save_openai(_):
1174-
try:
1175-
openai_file.write_text(tf_openai.value.strip())
1176-
_snack("OpenAI key saved successfully!")
1177-
except Exception as e:
1178-
_snack(f"Error: {e}", ok=False)
1179-
1180-
def _save_anthropic(_):
1181-
try:
1182-
anthropic_file.write_text(tf_anthropic.value.strip())
1183-
_snack("Anthropic key saved successfully!")
1184-
except Exception as e:
1185-
_snack(f"Error: {e}", ok=False)
1186-
1187-
def _save_gemini(_):
1188-
try:
1189-
gemini_file.write_text(tf_gemini.value.strip())
1190-
_snack("Gemini key saved successfully!")
1191-
except Exception as e:
1192-
_snack(f"Error: {e}", ok=False)
1193-
11941149
keys_card = _card(ft.Column(controls=[
11951150
ft.Text("API Keys & Credentials", size=13,
11961151
weight=ft.FontWeight.BOLD, color=ft.Colors.WHITE),
11971152
ft.Text("Keys are stored in plaintext files in the project directory.",
11981153
size=11, color=ft.Colors.with_opacity(0.5, ft.Colors.WHITE)),
11991154
ft.Container(height=4),
1200-
_key_row("Canvas Token", tf_canvas, _save_canvas),
1201-
ft.Row(controls=[
1202-
ft.FilledButton(
1203-
"Refresh Courses", icon=ft.Icons.REFRESH,
1204-
on_click=lambda _: _do_refresh(),
1205-
style=ft.ButtonStyle(bgcolor=C_SECONDARY, color=ft.Colors.BLACK),
1206-
),
1207-
refresh_status,
1208-
], spacing=10),
1209-
_key_row("OpenAI API Key", tf_openai, _save_openai),
1210-
_key_row("Anthropic API Key",tf_anthropic, _save_anthropic),
1211-
_key_row("Gemini API Key", tf_gemini, _save_gemini),
1155+
_field_row("Canvas Token", tf_canvas),
1156+
_field_row("OpenAI API Key", tf_openai),
1157+
_field_row("Anthropic API Key", tf_anthropic),
1158+
_field_row("Gemini API Key", tf_gemini),
12121159
ft.Row(controls=[
12131160
ft.Text("Project dir", size=11,
12141161
color=ft.Colors.with_opacity(0.5, ft.Colors.WHITE)),
@@ -1302,6 +1249,9 @@ def _save_gemini(_):
13021249
("generate", "QUALITY_TARGET", "Self-score target", "8.0", None),
13031250
]
13041251

1252+
# Collect (ctrl, script, name, default) for bulk save
1253+
_const_ctrls: list[tuple] = []
1254+
13051255
def _const_row(script: str, name: str, desc: str, default: str,
13061256
options: list[tuple[str, str]] | None = None) -> ft.Row:
13071257
cur = _read_constant(script, name)
@@ -1315,41 +1265,22 @@ def _const_row(script: str, name: str, desc: str, default: str,
13151265
bgcolor=C_OUTPUT_BG, border_color=accent, text_size=12,
13161266
content_padding=ft.padding.symmetric(horizontal=8, vertical=4),
13171267
)
1318-
1319-
def _save(_, c=ctrl, s=script, n=name):
1320-
if _write_constant(s, n, c.value or default):
1321-
_snack(f"{n} saved.")
1322-
else:
1323-
_snack(f"Failed to save {n}.", ok=False)
1324-
1325-
def _reset(_, c=ctrl, s=script, n=name, d=default):
1326-
c.value = d
1327-
c.update()
1328-
if _write_constant(s, n, d):
1329-
_snack(f"{n} reset to default.")
1330-
else:
1331-
_snack(f"Failed to reset {n}.", ok=False)
1332-
13331268
else:
13341269
ctrl = ft.TextField(
13351270
value=cur, expand=True, dense=True,
13361271
bgcolor=C_OUTPUT_BG, border_color=accent,
13371272
text_size=12, content_padding=ft.padding.symmetric(horizontal=8, vertical=6),
13381273
)
13391274

1340-
def _save(_, c=ctrl, s=script, n=name):
1341-
if _write_constant(s, n, c.value.strip()):
1342-
_snack(f"{n} saved.")
1343-
else:
1344-
_snack(f"Failed to save {n}.", ok=False)
1275+
_const_ctrls.append((ctrl, script, name, default))
13451276

1346-
def _reset(_, c=ctrl, s=script, n=name, d=default):
1347-
c.value = d
1348-
c.update()
1349-
if _write_constant(s, n, d):
1350-
_snack(f"{n} reset to default ({d}).")
1351-
else:
1352-
_snack(f"Failed to reset {n}.", ok=False)
1277+
def _reset(_, c=ctrl, s=script, n=name, d=default):
1278+
c.value = d
1279+
c.update()
1280+
if _write_constant(s, n, d):
1281+
_snack(f"{n} reset to default.")
1282+
else:
1283+
_snack(f"Failed to reset {n}.", ok=False)
13531284

13541285
return ft.Row(controls=[
13551286
ft.Container(
@@ -1362,10 +1293,6 @@ def _reset(_, c=ctrl, s=script, n=name, d=default):
13621293
ft.Text(name, size=11, color=ft.Colors.with_opacity(0.5, ft.Colors.WHITE),
13631294
width=160, italic=True),
13641295
ctrl,
1365-
ft.FilledButton(
1366-
"Save", icon=ft.Icons.SAVE_OUTLINED, on_click=_save,
1367-
style=ft.ButtonStyle(bgcolor=C_PRIMARY, color=ft.Colors.BLACK),
1368-
),
13691296
ft.OutlinedButton(
13701297
"Default", on_click=_reset,
13711298
style=ft.ButtonStyle(side=ft.BorderSide(1, C_SECONDARY),
@@ -1385,13 +1312,71 @@ def _reset(_, c=ctrl, s=script, n=name, d=default):
13851312
*const_rows,
13861313
], spacing=10))
13871314

1315+
# ── Single Save All button ────────────────────────────────────────────────
1316+
1317+
def _save_all(_):
1318+
errors: list[str] = []
1319+
try:
1320+
_save_config_all({
1321+
"CANVAS_URL": tf_canvas_url.value.strip(),
1322+
"PANOPTO_HOST": tf_panopto.value.strip(),
1323+
})
1324+
except Exception as e:
1325+
errors.append(f"Connection: {e}")
1326+
for path, tf in [
1327+
(canvas_file, tf_canvas),
1328+
(openai_file, tf_openai),
1329+
(anthropic_file, tf_anthropic),
1330+
(gemini_file, tf_gemini),
1331+
]:
1332+
try:
1333+
if tf.value.strip():
1334+
path.write_text(tf.value.strip())
1335+
except Exception as e:
1336+
errors.append(str(e))
1337+
for ctrl, script, name, default in _const_ctrls:
1338+
val = (ctrl.value or default) if isinstance(ctrl, ft.Dropdown) \
1339+
else ctrl.value.strip()
1340+
if not _write_constant(script, name, val):
1341+
errors.append(f"Failed to write {name}")
1342+
if errors:
1343+
_snack("Errors: " + "; ".join(errors), ok=False)
1344+
else:
1345+
_snack("All settings saved successfully!")
1346+
1347+
save_btn = ft.FilledButton(
1348+
"Save All Settings",
1349+
icon=ft.Icons.SAVE_OUTLINED,
1350+
on_click=_save_all,
1351+
style=ft.ButtonStyle(
1352+
bgcolor=C_PRIMARY, color=ft.Colors.BLACK,
1353+
padding=ft.padding.symmetric(horizontal=28, vertical=14),
1354+
),
1355+
)
1356+
refresh_btn_settings = ft.OutlinedButton(
1357+
"Refresh Courses",
1358+
icon=ft.Icons.REFRESH,
1359+
on_click=lambda _: _do_refresh(),
1360+
style=ft.ButtonStyle(
1361+
side=ft.BorderSide(1, C_SECONDARY), color=C_SECONDARY,
1362+
padding=ft.padding.symmetric(horizontal=20, vertical=14),
1363+
),
1364+
)
1365+
13881366
return ft.Column(
13891367
controls=[
13901368
ft.Column(controls=[
13911369
_section_title("Settings", ft.Icons.SETTINGS_OUTLINED),
13921370
conn_card,
13931371
keys_card,
13941372
const_card,
1373+
ft.Container(height=8),
1374+
ft.Row(controls=[
1375+
save_btn,
1376+
refresh_btn_settings,
1377+
refresh_status,
1378+
], spacing=12),
1379+
ft.Container(height=16),
13951380
], spacing=12, scroll=ft.ScrollMode.AUTO, expand=True),
13961381
],
13971382
expand=True,

0 commit comments

Comments
 (0)