Look like SaraFem and SaraSuite: no custom title bar

The window keeps its own title bar (or none, per HideWindowTitleBar), with
SaraSuite's header (name, Add to app menu for AppImages, gear). Settings
is now a dialog laid out like SaraFem's, with export/import on the
SaraSuite tab, and notifications use SaraFem's tinted toast style.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
sarahandClaude Opus 5.5 committed 2026-10-07 22:36:04 +02:00
1 parent f7ed23edf9
commit e90f59bc16
6 files changed
+338 -219

No files matched your search

+17
View File
@@ -277,6 +277,23 @@ def BuildTheme() -> ft.Theme:
)
def BlendHex(BaseHex: str, AccentHex: str, Ratio: float) -> str:
"""Solid colour mixing Base and Accent (0 = pure Base, 1 = pure Accent),
for tinted-but-still-dark backgrounds. Same as SaraFem's."""
BaseRGB = tuple(int(BaseHex.lstrip("#")[i : i + 2], 16) for i in (0, 2, 4))
AccentRGB = tuple(int(AccentHex.lstrip("#")[i : i + 2], 16) for i in (0, 2, 4))
Mixed = (round(B + (A - B) * Ratio) for B, A in zip(BaseRGB, AccentRGB))
return "#" + "".join(f"{C:02x}" for C in Mixed)
def NotificationStyle(Kind: str) -> tuple[str, str]:
"""(Background, AccentColor) for a toast, from the theme's own colours like
SaraFem's: "success" green, "error" red, plus "warning" yellow and "info"
mauve for SaraSort's restart-needed and plain notes."""
Accent = {"success": Palette.Green, "error": Palette.Red, "warning": Palette.Yellow}.get(Kind, Palette.Mauve)
return BlendHex(Palette.Mantle, Accent, 0.12), Accent
def ChipButtonStyle(Accent: str = "") -> ft.ButtonStyle:
"""SaraView's button look: SurfaceAlt fill, 1px accent border, small radius."""
return ft.ButtonStyle(
+56 -18
View File
@@ -1,15 +1,19 @@
"""SaraSort: a GUI for configuring SaraSortd. It doesn't sort anything itself;
it reads and edits SaraSortd's configs and controls its systemd service."""
import os
import shutil
import flet as ft
from ConfigPage import ConfigPage
from Core import (
AppName, ApplyAppearance, AssetsDir, BuildTheme, BundledFontFamily, EffectiveSettings, LoadConfig, Palette,
AppName, ApplyAppearance, AssetsDir, BuildTheme, BundledFontFamily, ChipButtonStyle, DataHome, EffectiveSettings,
LoadConfig, Palette,
)
from FlowPage import FlowPage
from ServicePage import ServicePage
from SettingsPage import SettingsPage
from Settings import ShowSettingsDialog
from TemplatesPage import TemplatesPage
from ToolsPage import ToolsPage
from Widgets import App
@@ -20,10 +24,31 @@ Destinations = [
("Configs", ft.Icons.TUNE_OUTLINED, ft.Icons.TUNE),
("Templates", ft.Icons.DASHBOARD_CUSTOMIZE_OUTLINED, ft.Icons.DASHBOARD_CUSTOMIZE),
("Tools", ft.Icons.BUILD_OUTLINED, ft.Icons.BUILD),
("Settings", ft.Icons.SETTINGS_OUTLINED, ft.Icons.SETTINGS),
]
Names = [Name for Name, _, _ in Destinations]
# When running as an AppImage, SaraSort can put itself in the app menu (like SaraSuite).
_AppImage = os.environ.get("APPIMAGE", "")
_MenuEntry = DataHome / "applications" / "SaraSort.desktop"
_MenuIcon = DataHome / "SaraSort" / "SaraSort.png"
def _SetMenuEntry(Enabled: bool) -> None:
if not Enabled:
_MenuEntry.unlink(missing_ok=True)
_MenuIcon.unlink(missing_ok=True)
return
_MenuIcon.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(AssetsDir / "Icon.png", _MenuIcon)
_MenuEntry.parent.mkdir(parents=True, exist_ok=True)
_MenuEntry.write_text(
"[Desktop Entry]\nType=Application\nName=SaraSort\n"
"Comment=Configure SaraSortd, the file sorting daemon\n"
f"Exec={_AppImage}\nIcon={_MenuIcon}\nTerminal=false\n"
"Categories=Utility;\nStartupNotify=true\n"
)
def Main(Page: ft.Page) -> None:
Config = LoadConfig()
@@ -70,7 +95,6 @@ def Main(Page: ft.Page) -> None:
"Configs": ConfigPage(TheApp),
"Templates": TemplatesPage(TheApp),
"Tools": ToolsPage(TheApp),
"Settings": SettingsPage(TheApp, OnSaved=lambda: None),
})
Rail = ft.NavigationRail(
@@ -84,23 +108,37 @@ def Main(Page: ft.Page) -> None:
on_change=lambda e: Navigate(Names[e.control.selected_index]),
)
async def CloseWindow(e) -> None:
await Page.window.close()
def ToggleMenuEntry(e) -> None:
_SetMenuEntry(not _MenuEntry.exists())
MenuButton.content = "Remove from app menu" if _MenuEntry.exists() else "Add to app menu"
MenuButton.update()
TitleRow = [ft.WindowDragArea(
ft.Container(ft.Text(AppName, size=18, color=Palette.Mauve), padding=ft.Padding.only(left=16, top=6, bottom=6)),
expand=True,
)]
if Appearance.HideWindowTitleBar:
TitleRow += [
ft.IconButton(ft.Icons.MINIMIZE, icon_size=16, tooltip="Minimize",
on_click=lambda e: (setattr(Page.window, "minimized", True), Page.update())),
ft.IconButton(ft.Icons.CLOSE, icon_size=16, tooltip="Close", on_click=CloseWindow),
]
MenuButton = ft.OutlinedButton(
content="Remove from app menu" if _MenuEntry.exists() else "Add to app menu",
style=ChipButtonStyle(),
visible=bool(_AppImage),
on_click=ToggleMenuEntry,
)
SettingsButton = ft.IconButton(
icon=ft.Icons.SETTINGS_OUTLINED,
icon_color=Palette.Mauve,
tooltip="Settings",
# SaraSortd's paths may have changed, so reload the page afterwards.
on_click=lambda e: ShowSettingsDialog(TheApp, lambda: Navigate(State["Current"])),
)
Page.add(ft.Column([
ft.Container(ft.Row(TitleRow, spacing=0), bgcolor=Palette.Mantle,
border=ft.Border.only(bottom=ft.BorderSide(1, Palette.Surface1))),
ft.Container(
content=ft.Row([
ft.Text(AppName, size=24, color=Palette.Mauve),
ft.Container(expand=True),
MenuButton,
SettingsButton,
]),
padding=ft.Padding.symmetric(horizontal=16, vertical=8),
bgcolor=Palette.Mantle,
border=ft.Border.only(bottom=ft.BorderSide(1, Palette.Surface1)),
),
ft.Row([Rail, ft.VerticalDivider(width=1, color=Palette.Surface1), Body], expand=True, spacing=0,
vertical_alignment=ft.CrossAxisAlignment.STRETCH),
], expand=True, spacing=0))
+6 -2
View File
@@ -26,8 +26,11 @@ Needs SaraSortd **7.1** or newer for arrows with their own patterns (per-entry `
- **Tools**: warnings about the setup (Unset values, version mismatches, outputs nothing reaches, inputs
without outputs, entries shadowed by a catch-all), a filename tester that shows where a file would go
and why, and the files in FailedDir with a retry button.
- **Settings**: the shared SaraSuite appearance, SaraSort's own settings, where SaraSortd lives, and
export/import in the same file format as SaraFem's (`[SaraSuite]` plus `[SaraSort]`).
The gear button opens **Settings**, laid out like SaraFem's and SaraSuite's: the shared SaraSuite
appearance and export/import (same file format as SaraFem's, `[SaraSuite]` plus `[SaraSort]`), SaraSort's
own settings, and where SaraSortd lives. Run as an AppImage, the header also has an "Add to app menu"
button, like SaraSuite's.
After a change to `GlobalConf.toml` (new inputs or outputs) SaraSort restarts SaraSortd, since SaraSortd
only reads that file when it starts. That can be turned off in Settings.
@@ -60,5 +63,6 @@ only reads that file when it starts. That can be turned off in Settings.
| `Sortd.py` | SaraSortd's configs: the graph, edits, the filename simulator, warnings |
| `Service.py` | systemctl and journalctl |
| `Templates.py` | built-in and your own templates |
| `Settings.py` | the Settings dialog |
| `Widgets.py` | the shared App context and small controls |
| `*Page.py` | one per page |
+253
View File
@@ -0,0 +1,253 @@
"""Settings dialog: the shared SaraSuite settings (with export/import),
SaraSort's own, and where SaraSortd lives. Laid out like SaraFem's and
SaraSuite's Settings (a category list on the left, the chosen category on the
right)."""
import subprocess
from pathlib import Path
from typing import Callable
import flet as ft
import tomlkit
from Core import (
AppConfig, AvailableThemes, BundledFontFamily, ChildEnvironment, ChipButtonStyle, ExportSettings, Home,
ImportSettings, LoadSharedSettings, Palette, SharedSettings,
)
from Widgets import App
DialogWidth = 700
DialogHeight = 520
CategoryListWidth = 150
RestartNote = "Theme, font and title bar changes apply the next time SaraSort starts."
StartPages = ["Flow", "Service", "Configs", "Templates", "Tools"]
def _Note(Text: str) -> ft.Text:
return ft.Text(Text, color=Palette.Overlay0, size=12)
def _Fonts() -> list[str]:
"""Installed font families (fontconfig), with the bundled font first."""
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)
def _NumberField(Label: str, Value: int) -> ft.TextField:
return ft.TextField(label=Label, value=str(Value), keyboard_type=ft.KeyboardType.NUMBER)
def _ReadInt(Field: ft.TextField, Fallback: int, Minimum: int) -> int:
try:
return max(Minimum, int(Field.value))
except (TypeError, ValueError):
return Fallback # keep the previous value rather than crash on bad input
def ShowSettingsDialog(TheApp: App, OnSaved: Callable[[], None]) -> None:
Page = TheApp.Page
Config: AppConfig = TheApp.Config
Themes = AvailableThemes()
Fonts = _Fonts()
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:
# Type to filter the long list.
Names = Fonts if Value in Fonts else [Value] + 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],
)
# SaraSuite: ~/.config/SaraSuite/Config.toml, read by every SaraSuite program.
Shared = LoadSharedSettings(Config)
UseSharedSwitch = ft.Switch(label="SaraSort uses these shared settings", value=Config.UseSharedSettings)
SharedTheme = ThemePicker(Shared.Theme)
SharedFont = FontPicker(Shared.FontFamily)
SharedTitleBar = ft.Switch(label="Hide window title bar", value=Shared.HideWindowTitleBar)
SettingsFileField = ft.TextField(value=str(Home / "SaraSort-Settings.toml"), label="Settings file path")
# SaraSort's own appearance, only used with shared settings off.
OwnTheme = ThemePicker(Config.Theme)
OwnFont = FontPicker(Config.FontFamily)
OwnTitleBar = ft.Switch(label="Hide window title bar", value=Config.HideWindowTitleBar)
OwnAppearance = ft.Column(
[_Note("SaraSort's own appearance, used when shared settings are off"), OwnTheme, OwnFont, OwnTitleBar],
spacing=12,
disabled=Config.UseSharedSettings,
opacity=0.5 if Config.UseSharedSettings else 1,
)
def OnUseSharedChanged(e) -> None:
OwnAppearance.disabled = UseSharedSwitch.value
OwnAppearance.opacity = 0.5 if UseSharedSwitch.value else 1
OwnAppearance.update()
UseSharedSwitch.on_change = OnUseSharedChanged
StartPage = ft.Dropdown(
label="Start page", value=Config.StartPage if Config.StartPage in StartPages else "Flow",
options=[ft.DropdownOption(key=Name, text=Name) for Name in StartPages],
)
LabelLength = _NumberField("Arrow label length before it's shortened", Config.LabelLength)
ConfigDirField = ft.TextField(label="SaraSortd config folder", value=Config.SortdConfigDir)
AppDirField = ft.TextField(label="SaraSortd program folder", value=Config.SortdAppDir)
UnitField = ft.TextField(label="systemd user unit", value=Config.ServiceName)
AutoRestartSwitch = ft.Switch(label="Restart SaraSortd after changes to GlobalConf.toml", value=Config.AutoRestart)
RefreshField = _NumberField("Service page refresh (seconds)", Config.RefreshSeconds)
LogLinesField = _NumberField("Log lines to show", Config.LogLines)
def OnExport(e) -> None:
PathText = (SettingsFileField.value or "").strip()
if not PathText:
return
try:
ExportSettings(Config, Path(PathText).expanduser())
except OSError as Error:
TheApp.Notify(f"Couldn't export settings: {Error}", "error")
return
TheApp.Notify("Settings exported.", "success")
def OnImport(e) -> None:
PathText = (SettingsFileField.value or "").strip()
if not PathText:
return
try:
ImportSettings(Config, Path(PathText).expanduser())
except (OSError, tomlkit.exceptions.TOMLKitError) as Error:
TheApp.Notify(f"Couldn't import settings: {Error}", "error")
return
# The open dialog still shows the pre-import values; closing it
# rather than leaving a stale Save that would overwrite the import.
Page.pop_dialog()
OnSaved()
TheApp.Notify("Settings imported. Reopen Settings to see them; restart for Theme/Font/Title bar.", "success")
Categories: dict[str, ft.Control] = {
"SaraSuite": ft.Column(
[
_Note("Shared by every SaraSuite program (~/.config/SaraSuite/Config.toml). "
"Other programs pick changes up the next time they start."),
UseSharedSwitch,
ft.Divider(height=1, color=Palette.Surface1),
SharedTheme,
SharedFont,
SharedTitleBar,
ft.Divider(height=1, color=Palette.Surface1),
_Note("Export/import settings as one file: this tab's shared settings plus everything else "
"in Settings. Same format as SaraFem's."),
SettingsFileField,
ft.Row([
ft.OutlinedButton(content="Export", style=ChipButtonStyle(), on_click=OnExport),
ft.OutlinedButton(content="Import", style=ChipButtonStyle(), on_click=OnImport),
]),
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
"General": ft.Column(
[
StartPage,
LabelLength,
ft.Divider(height=1, color=Palette.Surface1),
OwnAppearance,
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
"SaraSortd": ft.Column(
[
_Note("Where SaraSortd lives. The defaults are where SaraSuite installs it."),
ConfigDirField,
AppDirField,
UnitField,
AutoRestartSwitch,
_Note("SaraSortd only reads GlobalConf.toml when it starts."),
ft.Divider(height=1, color=Palette.Surface1),
RefreshField,
LogLinesField,
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
}
DetailArea = ft.Container(Categories["General"], expand=True, padding=ft.Padding.only(left=12))
CategoryTiles: dict[str, ft.ListTile] = {}
def SelectCategory(Name: str) -> None:
for CategoryName, Tile in CategoryTiles.items():
Tile.selected = CategoryName == Name
DetailArea.content = Categories[Name]
DetailArea.update()
CategoryList.update()
for Name in Categories:
CategoryTiles[Name] = ft.ListTile(
title=Name,
selected=Name == "General",
selected_tile_color=Palette.Surface1,
selected_color=Palette.Mauve,
on_click=lambda e, n=Name: SelectCategory(n),
)
CategoryList = ft.Column(list(CategoryTiles.values()), spacing=0, scroll=ft.ScrollMode.AUTO)
def Save(e) -> None:
Config.UseSharedSettings = UseSharedSwitch.value
Config.Theme = OwnTheme.value or Config.Theme
Config.FontFamily = OwnFont.value or Config.FontFamily
Config.HideWindowTitleBar = OwnTitleBar.value
Config.StartPage = StartPage.value or Config.StartPage
Config.LabelLength = _ReadInt(LabelLength, Config.LabelLength, 6)
Config.SortdConfigDir = (ConfigDirField.value or "").strip() or Config.SortdConfigDir
Config.SortdAppDir = (AppDirField.value or "").strip() or Config.SortdAppDir
Config.ServiceName = (UnitField.value or "").strip() or Config.ServiceName
Config.AutoRestart = AutoRestartSwitch.value
Config.RefreshSeconds = _ReadInt(RefreshField, Config.RefreshSeconds, 1)
Config.LogLines = _ReadInt(LogLinesField, Config.LogLines, 10)
Config.Save()
SharedSettings(
Theme=SharedTheme.value,
FontFamily=SharedFont.value or Shared.FontFamily,
HideWindowTitleBar=SharedTitleBar.value,
).Save()
Page.pop_dialog()
OnSaved()
TheApp.Notify("Settings saved.", "success")
Page.show_dialog(ft.AlertDialog(
title="Settings",
content=ft.Column(
[
ft.Row(
[
ft.Container(
CategoryList, width=CategoryListWidth, bgcolor=Palette.Surface0, border_radius=4, padding=4,
),
DetailArea,
],
expand=True,
vertical_alignment=ft.CrossAxisAlignment.STRETCH,
),
ft.Divider(height=1, color=Palette.Surface1),
_Note(RestartNote),
],
width=DialogWidth,
height=DialogHeight,
),
actions=[
ft.OutlinedButton(content="Cancel", style=ChipButtonStyle(), on_click=lambda e: Page.pop_dialog()),
ft.OutlinedButton(content="Save", style=ChipButtonStyle(), on_click=Save),
],
))
-196
View File
@@ -1,196 +0,0 @@
"""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()
+6 -3
View File
@@ -7,7 +7,7 @@ from typing import Callable, Optional
import flet as ft
from Core import AppConfig, ChildEnvironment, ChipButton, Note, Palette
from Core import AppConfig, ChildEnvironment, ChipButton, Note, NotificationStyle, Palette
from Service import RestartIfRunning
from Sortd import Setup, SortdError
@@ -20,9 +20,12 @@ class App:
self.Navigate: Callable[..., None] = lambda Name, **Arguments: None # set by Main
def Notify(self, Text: str, Kind: str = "info") -> None:
Color = {"success": Palette.Green, "error": Palette.Red, "warning": Palette.Yellow}.get(Kind, Palette.Text)
Background, Accent = NotificationStyle(Kind)
self.Page.show_dialog(ft.SnackBar(
ft.Text(Text, color=Color), bgcolor=Palette.Surface0, duration=ft.Duration(seconds=4),
content=ft.Text(Text, color=Accent),
bgcolor=Background,
shape=ft.RoundedRectangleBorder(radius=4, side=ft.BorderSide(1, Accent)),
open=True,
))
def LoadSetup(self) -> tuple[Optional[Setup], str]: