mirror of
https://git.srhtnin.garden/sarah/SaraView.git
synced 2026-10-14 18:38:11 +02:00
Add Flet desktop client
This commit is contained in:
1 parent
16787aba14
commit
0eecfba224
18 files changed
+2588
No files matched your search
@@ -0,0 +1,267 @@
|
||||
"""Settings dialog, laid out like SaraFem's and SaraSort's: a category list on
|
||||
the left (SaraSuite / General / Connection), the chosen category on the right."""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
import flet as ft
|
||||
import tomlkit
|
||||
|
||||
from App import App
|
||||
from Core import (
|
||||
AppConfig, AssetsDir, AvailableThemes, ChipButton, DataHome, ExportSettings, Home, ImportSettings,
|
||||
InstalledFontFamilies, LoadSharedSettings, Palette, SharedSettings,
|
||||
)
|
||||
|
||||
DialogWidth = 700
|
||||
DialogHeight = 520
|
||||
CategoryListWidth = 150
|
||||
RestartNote = "Theme, font and title bar changes apply the next time SaraView starts."
|
||||
|
||||
# When running as an AppImage, SaraView can put itself in the app menu (like SaraSort).
|
||||
AppImagePath = os.environ.get("APPIMAGE", "")
|
||||
MenuEntryPath = DataHome / "applications" / "SaraView.desktop"
|
||||
MenuIconPath = DataHome / "SaraView" / "SaraView.png"
|
||||
|
||||
|
||||
def SetMenuEntry(Enabled: bool) -> None:
|
||||
if not Enabled:
|
||||
MenuEntryPath.unlink(missing_ok=True)
|
||||
MenuIconPath.unlink(missing_ok=True)
|
||||
return
|
||||
MenuIconPath.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy(AssetsDir / "Icon.png", MenuIconPath)
|
||||
MenuEntryPath.parent.mkdir(parents=True, exist_ok=True)
|
||||
MenuEntryPath.write_text(
|
||||
"[Desktop Entry]\nType=Application\nName=SaraView\n"
|
||||
"Comment=View and edit files, locally or on a SaraView server\n"
|
||||
f"Exec={AppImagePath}\nIcon={MenuIconPath}\nTerminal=false\n"
|
||||
"Categories=Utility;\nStartupNotify=true\n"
|
||||
)
|
||||
|
||||
|
||||
def _Note(Text: str) -> ft.Text:
|
||||
return ft.Text(Text, color=Palette.Overlay0, size=12)
|
||||
|
||||
|
||||
def _ReadInt(Field: ft.TextField, Fallback: int, Minimum: int, Maximum: int) -> int:
|
||||
try:
|
||||
return min(Maximum, 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[[bool], None], StartCategory: str = "General") -> None:
|
||||
"""OnSaved(Reconnect): Reconnect is True when the source itself changed
|
||||
(mode, root, server or local listing options)."""
|
||||
Page = TheApp.Page
|
||||
Config: AppConfig = TheApp.Config
|
||||
Themes = AvailableThemes()
|
||||
Fonts = InstalledFontFamilies()
|
||||
|
||||
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 = 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="SaraView 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 / "SaraView-Settings.toml"), label="Settings file path")
|
||||
|
||||
# SaraView'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("SaraView'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
|
||||
|
||||
# General.
|
||||
OpenInAppSwitch = ft.Switch(label="Double-click opens files in SaraView (off: in their default program)",
|
||||
value=Config.OpenFilesInApp)
|
||||
ConfirmDeleteSwitch = ft.Switch(label="Ask before deleting", value=Config.ConfirmDelete)
|
||||
SidebarSwitch = ft.Switch(label="Show the directory tree (Ctrl+B)", value=Config.ShowSidebar)
|
||||
FontSizeField = ft.TextField(label="Text size", value=str(Config.FontSize), keyboard_type=ft.KeyboardType.NUMBER, width=160)
|
||||
|
||||
def ToggleMenuEntry(e) -> None:
|
||||
SetMenuEntry(not MenuEntryPath.exists())
|
||||
MenuButton.content = "Remove from app menu" if MenuEntryPath.exists() else "Add to app menu"
|
||||
MenuButton.update()
|
||||
|
||||
MenuButton = ChipButton("Remove from app menu" if MenuEntryPath.exists() else "Add to app menu", ToggleMenuEntry)
|
||||
MenuButton.visible = bool(AppImagePath)
|
||||
|
||||
# Connection.
|
||||
ModePicker = ft.RadioGroup(
|
||||
ft.Row([ft.Radio(value="Local", label="Local files"), ft.Radio(value="Server", label="SaraView server")]),
|
||||
value=Config.Mode,
|
||||
)
|
||||
LocalRootField = ft.TextField(label="Root directory", value=Config.LocalRoot, expand=True)
|
||||
|
||||
async def BrowseRoot(e) -> None:
|
||||
Start = os.path.expanduser(LocalRootField.value or "~")
|
||||
Chosen = await TheApp.Picker.get_directory_path(dialog_title="Root directory",
|
||||
initial_directory=Start if os.path.isdir(Start) else None)
|
||||
if Chosen:
|
||||
LocalRootField.value = Chosen
|
||||
LocalRootField.update()
|
||||
|
||||
ShowHiddenSwitch = ft.Switch(label="Show hidden directories and files", value=Config.ShowHiddenFolders)
|
||||
ShowSvSwitch = ft.Switch(label="Show .sv_ directory notes", value=Config.ShowSvFiles)
|
||||
HideInternalSwitch = ft.Switch(label="Hide SaraTag's own files (Books, .saratag)", value=Config.HideInternalFiles)
|
||||
LocalOptions = ft.Column([
|
||||
ft.Row([LocalRootField, ft.IconButton(ft.Icons.FOLDER_OPEN_OUTLINED, tooltip="Browse", on_click=BrowseRoot)]),
|
||||
ShowHiddenSwitch, ShowSvSwitch, HideInternalSwitch,
|
||||
], spacing=12, visible=Config.Mode == "Local")
|
||||
|
||||
ServerUrlField = ft.TextField(label="Server address", value=Config.ServerUrl, hint_text="http://spines:8420")
|
||||
RememberSwitch = ft.Switch(label="Stay logged in between starts", value=Config.RememberLogin)
|
||||
ServerOptions = ft.Column([
|
||||
ServerUrlField,
|
||||
_Note("Over Tailscale, like the web view. Logged out, you browse as Guest; log in from the "
|
||||
"connection button in the toolbar to change files. Hidden files and the theme on the "
|
||||
"server follow your SaraView user settings."),
|
||||
RememberSwitch,
|
||||
], spacing=12, visible=Config.Mode == "Server")
|
||||
|
||||
def OnModeChanged(e) -> None:
|
||||
LocalOptions.visible = ModePicker.value == "Local"
|
||||
ServerOptions.visible = ModePicker.value == "Server"
|
||||
LocalOptions.update()
|
||||
ServerOptions.update()
|
||||
|
||||
ModePicker.on_change = OnModeChanged
|
||||
|
||||
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 old values; close it rather than
|
||||
# leave a stale Save that would overwrite the import.
|
||||
TheApp.Close()
|
||||
OnSaved(True)
|
||||
TheApp.Notify("Settings imported. 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 (never your login). Same format as SaraFem's."),
|
||||
SettingsFileField,
|
||||
ft.Row([ChipButton("Export", OnExport), ChipButton("Import", OnImport)]),
|
||||
], spacing=12, scroll=ft.ScrollMode.AUTO),
|
||||
"General": ft.Column([
|
||||
OpenInAppSwitch, ConfirmDeleteSwitch, SidebarSwitch, FontSizeField, MenuButton,
|
||||
ft.Divider(height=1, color=Palette.Surface1),
|
||||
OwnAppearance,
|
||||
], spacing=12, scroll=ft.ScrollMode.AUTO),
|
||||
"Connection": ft.Column([
|
||||
_Note("Browse a directory on this computer, or connect to a SaraView server."),
|
||||
ModePicker, LocalOptions, ServerOptions,
|
||||
], spacing=12, scroll=ft.ScrollMode.AUTO),
|
||||
}
|
||||
|
||||
DetailArea = ft.Container(Categories[StartCategory], 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 == StartCategory, 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:
|
||||
SourceBefore = (Config.Mode, Config.LocalRoot, Config.ServerUrl, Config.ShowHiddenFolders,
|
||||
Config.ShowSvFiles, Config.HideInternalFiles)
|
||||
Config.UseSharedSettings = UseSharedSwitch.value
|
||||
Config.Theme = OwnTheme.value or Config.Theme
|
||||
Config.FontFamily = OwnFont.value or Config.FontFamily
|
||||
Config.HideWindowTitleBar = OwnTitleBar.value
|
||||
Config.OpenFilesInApp = OpenInAppSwitch.value
|
||||
Config.ConfirmDelete = ConfirmDeleteSwitch.value
|
||||
Config.ShowSidebar = SidebarSwitch.value
|
||||
Config.FontSize = _ReadInt(FontSizeField, Config.FontSize, 8, 32)
|
||||
Config.Mode = ModePicker.value or Config.Mode
|
||||
Config.LocalRoot = (LocalRootField.value or "").strip() or "~"
|
||||
Config.ServerUrl = (ServerUrlField.value or "").strip().rstrip("/")
|
||||
Config.RememberLogin = RememberSwitch.value
|
||||
Config.ShowHiddenFolders = ShowHiddenSwitch.value
|
||||
Config.ShowSvFiles = ShowSvSwitch.value
|
||||
Config.HideInternalFiles = HideInternalSwitch.value
|
||||
Config.Save()
|
||||
SharedSettings(
|
||||
Theme=SharedTheme.value,
|
||||
FontFamily=SharedFont.value or Shared.FontFamily,
|
||||
HideWindowTitleBar=SharedTitleBar.value,
|
||||
).Save()
|
||||
SourceAfter = (Config.Mode, Config.LocalRoot, Config.ServerUrl, Config.ShowHiddenFolders,
|
||||
Config.ShowSvFiles, Config.HideInternalFiles)
|
||||
TheApp.Close()
|
||||
OnSaved(SourceAfter != SourceBefore)
|
||||
TheApp.Notify("Settings saved.", "success")
|
||||
|
||||
TheApp.DialogOpen = True
|
||||
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=[ChipButton("Cancel", lambda e: TheApp.Close()), ChipButton("Save", Save)],
|
||||
on_dismiss=lambda e: setattr(TheApp, "DialogOpen", False),
|
||||
))
|
||||
Reference in new issue
Block a user