- Groups CRUD in sidebar (create, rename, change color, reorder, delete) - Collapsible group headers with color dots and server count - "Move to Group" context menu on servers - Group dropdown in ServerDialog (add/edit) - 17 i18n keys (EN/RU/ZH) - Search auto-expands groups - Cleaned up old release binaries Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
111 lines
3.5 KiB
Python
111 lines
3.5 KiB
Python
"""
|
|
Dialog for creating / editing server groups.
|
|
"""
|
|
|
|
import customtkinter as ctk
|
|
from typing import Optional
|
|
|
|
from core.i18n import t
|
|
|
|
GROUP_COLORS = [
|
|
"#ef4444", # red
|
|
"#f97316", # orange
|
|
"#f59e0b", # amber
|
|
"#22c55e", # green
|
|
"#3b82f6", # blue
|
|
"#6366f1", # indigo
|
|
"#a855f7", # purple
|
|
"#ec4899", # pink
|
|
]
|
|
|
|
|
|
class GroupDialog(ctk.CTkToplevel):
|
|
"""Small dialog: group name + color picker (8 circles)."""
|
|
|
|
def __init__(self, master, store, group: Optional[dict] = None):
|
|
super().__init__(master)
|
|
self.store = store
|
|
self._group = group # None = add, dict = edit
|
|
self.result: Optional[dict] = None
|
|
|
|
self.title(t("edit_group") if group else t("add_group"))
|
|
self.geometry("340x200")
|
|
self.resizable(False, False)
|
|
self.transient(master)
|
|
self.grab_set()
|
|
|
|
# ── Name ──
|
|
ctk.CTkLabel(self, text=t("group_name"), anchor="w").pack(
|
|
fill="x", padx=20, pady=(15, 2))
|
|
self._name_var = ctk.StringVar(value=group["name"] if group else "")
|
|
self._name_entry = ctk.CTkEntry(self, textvariable=self._name_var)
|
|
self._name_entry.pack(fill="x", padx=20)
|
|
self._name_entry.focus()
|
|
|
|
# ── Color picker ──
|
|
ctk.CTkLabel(self, text=t("group_color"), anchor="w").pack(
|
|
fill="x", padx=20, pady=(10, 2))
|
|
|
|
color_frame = ctk.CTkFrame(self, fg_color="transparent")
|
|
color_frame.pack(fill="x", padx=20)
|
|
|
|
self._selected_color = group["color"] if group else GROUP_COLORS[0]
|
|
self._color_buttons: list[ctk.CTkButton] = []
|
|
|
|
for color in GROUP_COLORS:
|
|
btn = ctk.CTkButton(
|
|
color_frame, text="", width=28, height=28,
|
|
fg_color=color, hover_color=color,
|
|
border_width=3,
|
|
border_color=color,
|
|
corner_radius=14,
|
|
command=lambda c=color: self._pick_color(c),
|
|
)
|
|
btn.pack(side="left", padx=2)
|
|
self._color_buttons.append(btn)
|
|
|
|
self._highlight_selected_color()
|
|
|
|
# ── Buttons ──
|
|
btn_frame = ctk.CTkFrame(self, fg_color="transparent")
|
|
btn_frame.pack(fill="x", padx=20, pady=(15, 10))
|
|
|
|
ctk.CTkButton(btn_frame, text=t("cancel"), width=80,
|
|
fg_color="gray", command=self.destroy).pack(side="left")
|
|
ctk.CTkButton(btn_frame, text=t("save"), width=80,
|
|
command=self._save).pack(side="right")
|
|
|
|
# Enter to save
|
|
self.bind("<Return>", lambda e: self._save())
|
|
|
|
def _pick_color(self, color: str):
|
|
self._selected_color = color
|
|
self._highlight_selected_color()
|
|
|
|
def _highlight_selected_color(self):
|
|
for btn in self._color_buttons:
|
|
fg = btn.cget("fg_color")
|
|
if fg == self._selected_color:
|
|
btn.configure(border_color="white")
|
|
else:
|
|
btn.configure(border_color=fg)
|
|
|
|
def _save(self):
|
|
name = self._name_var.get().strip()
|
|
if not name:
|
|
self._name_entry.focus()
|
|
return
|
|
|
|
if self._group:
|
|
# Edit mode
|
|
self.store.update_group(
|
|
self._group["id"], name=name, color=self._selected_color)
|
|
self.result = {"id": self._group["id"], "name": name,
|
|
"color": self._selected_color}
|
|
else:
|
|
# Add mode
|
|
group = self.store.add_group(name, self._selected_color)
|
|
self.result = group
|
|
|
|
self.destroy()
|