from __future__ import annotations import json from typing import Callable import customtkinter as ctk from tkinter import filedialog from gui._constants import COLOR_OK, COLOR_ERROR, PROJECT_ROOT class SetupWizard(ctk.CTkToplevel): """Modal first-run wizard that writes config.json.""" def __init__( self, parent: ctk.CTk, on_complete: Callable[[], None], ) -> None: super().__init__(parent) self.title("Setup — Arma Mod Manager") self.geometry("500x420") self.resizable(False, False) self.grab_set() self.lift() self.focus_force() self._on_complete = on_complete self._url = ctk.StringVar(value="https://") self._user = ctk.StringVar() self._pw = ctk.StringVar() self._arma = ctk.StringVar() self._body = ctk.CTkFrame(self, fg_color="transparent") self._body.pack(fill="both", expand=True, padx=28, pady=24) self._show(0) def _clear(self) -> None: for w in self._body.winfo_children(): w.destroy() def _show(self, step: int) -> None: self._clear() [self._page_server, self._page_paths, self._page_review][step]() # ── Page 1: server ─────────────────────────────────────────────────────── def _page_server(self) -> None: ctk.CTkLabel( self._body, text="Step 1 of 3 — Server Connection", font=ctk.CTkFont(size=16, weight="bold"), ).pack(anchor="w") ctk.CTkLabel( self._body, text="Enter the details for your Caddy mod server.", text_color="gray", ).pack(anchor="w", pady=(4, 18)) for lbl, var, show in [ ("Server URL", self._url, ""), ("Username", self._user, ""), ("Password", self._pw, "•"), ]: ctk.CTkLabel(self._body, text=lbl).pack(anchor="w") ctk.CTkEntry(self._body, textvariable=var, width=440, show=show).pack( anchor="w", pady=(2, 10)) foot = ctk.CTkFrame(self._body, fg_color="transparent") foot.pack(fill="x", pady=(8, 0)) self._conn_lbl = ctk.CTkLabel(foot, text="", text_color="gray") self._conn_lbl.pack(side="left") ctk.CTkButton(foot, text="Next →", width=90, command=lambda: self._show(1)).pack(side="right") ctk.CTkButton(foot, text="Test Connection", width=140, fg_color="transparent", border_width=1, text_color=("gray10", "gray90"), command=self._test).pack(side="right", padx=(0, 8)) def _test(self) -> None: self._conn_lbl.configure(text="Testing…", text_color="gray") self.update() try: import requests r = requests.get(self._url.get(), auth=(self._user.get(), self._pw.get()), timeout=8) if r.ok: self._conn_lbl.configure(text="✓ Connected", text_color=COLOR_OK) else: self._conn_lbl.configure(text=f"✗ HTTP {r.status_code}", text_color=COLOR_ERROR) except Exception as e: self._conn_lbl.configure(text=f"✗ {e}", text_color=COLOR_ERROR) # ── Page 2: paths ──────────────────────────────────────────────────────── def _page_paths(self) -> None: ctk.CTkLabel( self._body, text="Step 2 of 3 — Arma 3 Server Folder", font=ctk.CTkFont(size=16, weight="bold"), ).pack(anchor="w") ctk.CTkLabel( self._body, text="Point to your Arma 3 Server installation. " "Links (junctions) will be created here.", text_color="gray", wraplength=440, justify="left", ).pack(anchor="w", pady=(4, 18)) ctk.CTkLabel(self._body, text="Arma 3 Server folder").pack(anchor="w") row = ctk.CTkFrame(self._body, fg_color="transparent") row.pack(fill="x", pady=(2, 8)) ctk.CTkEntry(row, textvariable=self._arma, width=350).pack(side="left") ctk.CTkButton(row, text="Browse", width=80, command=self._browse_arma).pack(side="left", padx=8) ctk.CTkLabel( self._body, text="All other folders (downloads, presets) will be created " "automatically next to this tool.", text_color="gray", font=ctk.CTkFont(size=11), wraplength=440, justify="left", ).pack(anchor="w", pady=(8, 0)) foot = ctk.CTkFrame(self._body, fg_color="transparent") foot.pack(fill="x", pady=(20, 0)) ctk.CTkButton(foot, text="← Back", width=80, fg_color="transparent", border_width=1, text_color=("gray10", "gray90"), command=lambda: self._show(0)).pack(side="left") ctk.CTkButton(foot, text="Next →", width=80, command=lambda: self._show(2)).pack(side="right") def _browse_arma(self) -> None: d = filedialog.askdirectory(title="Select Arma 3 Server folder") if d: self._arma.set(d) # ── Page 3: review + save ──────────────────────────────────────────────── def _page_review(self) -> None: ctk.CTkLabel( self._body, text="Step 3 of 3 — Review & Save", font=ctk.CTkFont(size=16, weight="bold"), ).pack(anchor="w") ctk.CTkLabel( self._body, text="Check your settings, then click Save.", text_color="gray", ).pack(anchor="w", pady=(4, 14)) summary = ( f"Server URL: {self._url.get()}\n" f"Username: {self._user.get()}\n" f"Arma folder: {self._arma.get() or '(not set)'}\n" ) box = ctk.CTkTextbox(self._body, height=90, font=ctk.CTkFont(family="Consolas", size=12)) box.insert("1.0", summary) box.configure(state="disabled") box.pack(fill="x", pady=(0, 16)) foot = ctk.CTkFrame(self._body, fg_color="transparent") foot.pack(fill="x") ctk.CTkButton(foot, text="← Back", width=80, fg_color="transparent", border_width=1, text_color=("gray10", "gray90"), command=lambda: self._show(1)).pack(side="left") ctk.CTkButton(foot, text="Save & Open", width=120, command=self._save).pack(side="right") def _save(self) -> None: cfg = { "server": { "base_url": self._url.get(), "username": self._user.get(), "password": self._pw.get(), }, "paths": { "arma_dir": self._arma.get(), "downloads": "downloads", "modlist_html": "modlist_html", "modlist_json": "modlist_json", }, } (PROJECT_ROOT / "config.json").write_text( json.dumps(cfg, indent=2), encoding="utf-8") self.destroy() self._on_complete()