mirror of
https://git.srhtnin.garden/sarah/SaraSort.git
synced 2026-10-14 18:48:11 +02:00
SaraSort: Flet GUI for configuring SaraSortd
Flowchart of inputs/outputs with editable arrows, service page (status, controls, autostart, version, logs), config editor (form + raw), templates, tools (warnings, filename tester, failed files) and settings with SaraFem-compatible export/import. AppImage build and a SaraSuite manifest in Packaging/. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
f7ed23edf9
22 files changed
+3399
No files matched your search
+196
@@ -0,0 +1,196 @@
|
||||
"""SaraSort's own settings (not SaraSortd's): the shared SaraSuite appearance,
|
||||
SaraSort's own, where SaraSortd lives, and exporting/importing settings in
|
||||
the same file format SaraFem uses ([SaraSuite] + one section per program)."""
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
import flet as ft
|
||||
import tomlkit
|
||||
|
||||
from Core import (
|
||||
AppConfig, AvailableThemes, BundledFontFamily, ChildEnvironment, ChipButton, Heading, LoadSharedSettings, Note,
|
||||
Palette, SharedSettings, ExportSettings, ImportSettings,
|
||||
)
|
||||
from Widgets import App
|
||||
|
||||
CategoryListWidth = 170
|
||||
RestartNote = "Theme, font and title bar changes apply the next time SaraSort starts."
|
||||
Pages = ["Flow", "Service", "Configs", "Templates", "Tools", "Settings"]
|
||||
|
||||
|
||||
def InstalledFonts() -> list[str]:
|
||||
try:
|
||||
Result = subprocess.run(["fc-list", ":", "family"], capture_output=True, text=True, timeout=5, env=ChildEnvironment())
|
||||
Families = {Line.split(",")[0].strip() for Line in Result.stdout.splitlines() if Line.strip()}
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
Families = set()
|
||||
Families.discard(BundledFontFamily)
|
||||
return [BundledFontFamily] + sorted(Families, key=str.lower)
|
||||
|
||||
|
||||
class SettingsPage(ft.Column):
|
||||
def __init__(self, TheApp: App, OnSaved: Callable[[], None]):
|
||||
super().__init__(expand=True, spacing=0)
|
||||
self.App = TheApp
|
||||
self.OnSaved = OnSaved
|
||||
self.Selected = "Shared"
|
||||
self.Detail = ft.Container(expand=True, padding=ft.Padding.only(left=16, right=16, top=8))
|
||||
self.CategoryList = ft.Column(spacing=0)
|
||||
self.Fonts: list[str] = []
|
||||
self.controls = [
|
||||
ft.Container(
|
||||
ft.Row([
|
||||
Heading("Settings"), ft.Container(expand=True),
|
||||
ChipButton("Import", self._Import, ft.Icons.FILE_DOWNLOAD_OUTLINED),
|
||||
ChipButton("Export", self._Export, ft.Icons.FILE_UPLOAD_OUTLINED),
|
||||
ChipButton("Revert", lambda e: self.Refresh(), ft.Icons.UNDO),
|
||||
ChipButton("Save", lambda e: self._Save(), ft.Icons.SAVE_OUTLINED),
|
||||
]),
|
||||
padding=ft.Padding.only(left=16, right=16, top=12, bottom=8),
|
||||
),
|
||||
ft.Divider(height=1, color=Palette.Surface1),
|
||||
ft.Row([
|
||||
ft.Container(self.CategoryList, width=CategoryListWidth, bgcolor=Palette.Mantle, padding=4),
|
||||
self.Detail,
|
||||
], expand=True, vertical_alignment=ft.CrossAxisAlignment.STRETCH),
|
||||
ft.Container(Note(RestartNote), padding=ft.Padding.only(left=16, bottom=10, top=6)),
|
||||
]
|
||||
|
||||
def Refresh(self) -> None:
|
||||
if not self.Fonts:
|
||||
self.Fonts = InstalledFonts()
|
||||
Config = self.App.Config
|
||||
Themes = AvailableThemes()
|
||||
|
||||
def ThemePicker(Value: str) -> ft.Dropdown:
|
||||
Names = Themes if Value in Themes else [Value] + Themes
|
||||
return ft.Dropdown(value=Value, label="Theme", options=[ft.DropdownOption(key=Name, text=Name) for Name in Names])
|
||||
|
||||
def FontPicker(Value: str) -> ft.Dropdown:
|
||||
Names = self.Fonts if Value in self.Fonts else [Value] + self.Fonts
|
||||
return ft.Dropdown(value=Value, label="Font", editable=True, enable_filter=True, menu_height=320,
|
||||
options=[ft.DropdownOption(key=Name, text=Name) for Name in Names])
|
||||
|
||||
Shared = LoadSharedSettings(Config)
|
||||
self.UseShared = ft.Switch(label="SaraSort uses these shared settings", value=Config.UseSharedSettings,
|
||||
on_change=lambda e: self._DimOwn())
|
||||
self.SharedTheme = ThemePicker(Shared.Theme)
|
||||
self.SharedFont = FontPicker(Shared.FontFamily)
|
||||
self.SharedTitleBar = ft.Switch(label="Hide window title bar", value=Shared.HideWindowTitleBar)
|
||||
|
||||
self.OwnTheme = ThemePicker(Config.Theme)
|
||||
self.OwnFont = FontPicker(Config.FontFamily)
|
||||
self.OwnTitleBar = ft.Switch(label="Hide window title bar", value=Config.HideWindowTitleBar)
|
||||
self.OwnAppearance = ft.Column([
|
||||
Note("SaraSort's own appearance, used when shared settings are off"),
|
||||
self.OwnTheme, self.OwnFont, self.OwnTitleBar,
|
||||
], spacing=12)
|
||||
|
||||
self.ConfigDir = ft.TextField(label="SaraSortd config folder (GlobalConf.toml and the templates)", value=Config.SortdConfigDir)
|
||||
self.AppDir = ft.TextField(label="SaraSortd program folder (SaraSortd.py)", value=Config.SortdAppDir)
|
||||
self.Unit = ft.TextField(label="systemd user unit", value=Config.ServiceName)
|
||||
self.AutoRestart = ft.Switch(label="Restart SaraSortd after changes to GlobalConf.toml", value=Config.AutoRestart)
|
||||
self.Refresh_ = ft.TextField(label="Service page refresh (seconds)", value=str(Config.RefreshSeconds), width=260,
|
||||
keyboard_type=ft.KeyboardType.NUMBER)
|
||||
self.LogLines = ft.TextField(label="Log lines to show", value=str(Config.LogLines), width=260,
|
||||
keyboard_type=ft.KeyboardType.NUMBER)
|
||||
self.LabelLength = ft.TextField(label="Arrow label length before shortening", value=str(Config.LabelLength), width=260,
|
||||
keyboard_type=ft.KeyboardType.NUMBER)
|
||||
self.StartPage = ft.Dropdown(label="Start page", value=Config.StartPage, width=260,
|
||||
options=[ft.DropdownOption(key=Name, text=Name) for Name in Pages[:-1]])
|
||||
|
||||
self.Categories = {
|
||||
"Shared": ft.Column([
|
||||
Note("Shared by every SaraSuite program (~/.config/SaraSuite/Config.toml)."),
|
||||
self.UseShared,
|
||||
ft.Divider(height=1, color=Palette.Surface1),
|
||||
self.SharedTheme, self.SharedFont, self.SharedTitleBar,
|
||||
], spacing=12, scroll=ft.ScrollMode.AUTO),
|
||||
"SaraSort": ft.Column([
|
||||
self.StartPage, self.LabelLength,
|
||||
ft.Divider(height=1, color=Palette.Surface1),
|
||||
self.OwnAppearance,
|
||||
], spacing=12, scroll=ft.ScrollMode.AUTO),
|
||||
"SaraSortd": ft.Column([
|
||||
Note("Defaults are where SaraSuite installs SaraSortd."),
|
||||
self.ConfigDir, self.AppDir, self.Unit, self.AutoRestart, self.Refresh_, self.LogLines,
|
||||
], spacing=12, scroll=ft.ScrollMode.AUTO),
|
||||
}
|
||||
self._DimOwn(Update=False)
|
||||
self._Select(self.Selected, Update=False)
|
||||
self.update()
|
||||
|
||||
def _DimOwn(self, Update: bool = True) -> None:
|
||||
self.OwnAppearance.disabled = bool(self.UseShared.value)
|
||||
self.OwnAppearance.opacity = 0.5 if self.UseShared.value else 1
|
||||
if Update:
|
||||
self.OwnAppearance.update()
|
||||
|
||||
def _Select(self, Name: str, Update: bool = True) -> None:
|
||||
self.Selected = Name
|
||||
self.CategoryList.controls = [
|
||||
ft.ListTile(title=Category, selected=Category == Name, selected_tile_color=Palette.Surface1,
|
||||
selected_color=Palette.Mauve, on_click=lambda e, Category=Category: self._Select(Category))
|
||||
for Category in self.Categories
|
||||
]
|
||||
self.Detail.content = self.Categories[Name]
|
||||
if Update:
|
||||
self.update()
|
||||
|
||||
@staticmethod
|
||||
def _Int(Field: ft.TextField, Fallback: int, Minimum: int) -> int:
|
||||
try:
|
||||
return max(Minimum, int(Field.value))
|
||||
except (TypeError, ValueError):
|
||||
return Fallback
|
||||
|
||||
def _Save(self) -> None:
|
||||
Config: AppConfig = self.App.Config
|
||||
Config.UseSharedSettings = bool(self.UseShared.value)
|
||||
Config.Theme = self.OwnTheme.value or Config.Theme
|
||||
Config.FontFamily = self.OwnFont.value or Config.FontFamily
|
||||
Config.HideWindowTitleBar = bool(self.OwnTitleBar.value)
|
||||
Config.SortdConfigDir = (self.ConfigDir.value or "").strip() or Config.SortdConfigDir
|
||||
Config.SortdAppDir = (self.AppDir.value or "").strip() or Config.SortdAppDir
|
||||
Config.ServiceName = (self.Unit.value or "").strip() or Config.ServiceName
|
||||
Config.AutoRestart = bool(self.AutoRestart.value)
|
||||
Config.RefreshSeconds = self._Int(self.Refresh_, Config.RefreshSeconds, 1)
|
||||
Config.LogLines = self._Int(self.LogLines, Config.LogLines, 10)
|
||||
Config.LabelLength = self._Int(self.LabelLength, Config.LabelLength, 6)
|
||||
Config.StartPage = self.StartPage.value or Config.StartPage
|
||||
Config.Save()
|
||||
Shared = LoadSharedSettings(Config)
|
||||
SharedSettings(
|
||||
Theme=self.SharedTheme.value or Shared.Theme,
|
||||
FontFamily=self.SharedFont.value or Shared.FontFamily,
|
||||
HideWindowTitleBar=bool(self.SharedTitleBar.value),
|
||||
).Save()
|
||||
self.App.Notify("Settings saved.", "success")
|
||||
self.OnSaved()
|
||||
|
||||
async def _Export(self, e) -> None:
|
||||
Chosen = await self.App.Picker.save_file(dialog_title="Export settings", file_name="SaraSortSettings.toml",
|
||||
allowed_extensions=["toml"])
|
||||
if not Chosen:
|
||||
return
|
||||
try:
|
||||
ExportSettings(self.App.Config, Path(Chosen))
|
||||
except OSError as Error:
|
||||
self.App.Notify(f"Couldn't export settings: {Error}", "error")
|
||||
return
|
||||
self.App.Notify("Settings exported.", "success")
|
||||
|
||||
async def _Import(self, e) -> None:
|
||||
Files = await self.App.Picker.pick_files(dialog_title="Import settings", allowed_extensions=["toml"])
|
||||
if not Files:
|
||||
return
|
||||
try:
|
||||
ImportSettings(self.App.Config, Path(Files[0].path))
|
||||
except (OSError, tomlkit.exceptions.TOMLKitError) as Error:
|
||||
self.App.Notify(f"Couldn't import settings: {Error}", "error")
|
||||
return
|
||||
self.Refresh()
|
||||
self.App.Notify("Settings imported. Theme, font and title bar apply on the next start.", "success")
|
||||
self.OnSaved()
|
||||
Reference in new issue
Block a user