mirror of
https://git.srhtnin.garden/sarah/SaraSuite.git
synced 2026-10-13 18:18:12 +02:00
168 lines
6.8 KiB
Python
168 lines
6.8 KiB
Python
"""Install, Update and Remove dialogs. Each only gathers choices and shows
|
|
what's about to happen; the actual work runs back in Main.py."""
|
|
|
|
from typing import Callable
|
|
|
|
import flet as ft
|
|
|
|
from Core import ChipButtonStyle, Home, Palette
|
|
from Installer import ConfigPlan, DefaultAnswers, DefaultOptions, RemovalItem, RemovalPlan, Status
|
|
from Manifests import Program
|
|
|
|
_MaxListedItems = 12
|
|
|
|
|
|
def _Short(PathText: str) -> str:
|
|
return PathText.replace(str(Home), "~", 1) if PathText.startswith(str(Home)) else PathText
|
|
|
|
|
|
def _Heading(Text: str) -> ft.Text:
|
|
return ft.Text(Text, size=14, color=Palette.Mauve)
|
|
|
|
|
|
def _ItemList(Items: list[RemovalItem]) -> ft.Column:
|
|
Lines = []
|
|
for Item in Items[:_MaxListedItems]:
|
|
Spans = [ft.TextSpan(_Short(Item.Path), ft.TextStyle(color=Palette.Text))]
|
|
if Item.Note:
|
|
Spans.append(ft.TextSpan(f" {Item.Note}", ft.TextStyle(color=Palette.Overlay0)))
|
|
Lines.append(ft.Text(spans=Spans, size=12, selectable=True))
|
|
if len(Items) > _MaxListedItems:
|
|
Lines.append(ft.Text(f"+ {len(Items) - _MaxListedItems} more", size=12, color=Palette.Overlay0))
|
|
if not Items:
|
|
Lines.append(ft.Text("(nothing)", size=12, color=Palette.Overlay0))
|
|
return ft.Column(Lines, spacing=2)
|
|
|
|
|
|
def _Dialog(Page: ft.Page, Title: str, Body: list[ft.Control], ConfirmText: str, OnConfirm: Callable[[], None], Danger: bool = False) -> ft.AlertDialog:
|
|
def Confirm(e) -> None:
|
|
Page.pop_dialog()
|
|
OnConfirm()
|
|
|
|
return ft.AlertDialog(
|
|
title=Title,
|
|
content=ft.Container(
|
|
content=ft.Column(Body, spacing=10, scroll=ft.ScrollMode.AUTO, tight=True),
|
|
width=640,
|
|
),
|
|
actions=[
|
|
ft.OutlinedButton(content="Cancel", style=ChipButtonStyle(), on_click=lambda e: Page.pop_dialog()),
|
|
ft.OutlinedButton(
|
|
content=ConfirmText,
|
|
style=ChipButtonStyle(Palette.Red if Danger else ""),
|
|
on_click=Confirm,
|
|
),
|
|
],
|
|
)
|
|
|
|
|
|
def ShowInstallDialog(Page: ft.Page, Program: Program, OnConfirm: Callable[[dict[str, bool], dict[str, str]], None]) -> None:
|
|
# Start from the user's defaults (Settings → this program's tab), which
|
|
# fall back to the manifest's.
|
|
Answers = DefaultAnswers(Program)
|
|
Ticked = DefaultOptions(Program)
|
|
Fields = {Question.Id: ft.TextField(label=Question.Prompt, value=Answers[Question.Id]) for Question in Program.Questions}
|
|
Boxes = {Option.Id: ft.Checkbox(label=Option.Label, value=Ticked[Option.Id]) for Option in Program.Options}
|
|
|
|
Body: list[ft.Control] = [ft.Text(Program.Description, size=13, color=Palette.Overlay0)]
|
|
Body += list(Fields.values())
|
|
if Boxes:
|
|
Body.append(_Heading("Options"))
|
|
Body += list(Boxes.values())
|
|
|
|
Page.show_dialog(_Dialog(
|
|
Page, f"Install {Program.Name}", Body, "Install",
|
|
lambda: OnConfirm(
|
|
{Id: bool(Box.value) for Id, Box in Boxes.items()},
|
|
{Id: (Field.value or "").strip() for Id, Field in Fields.items()},
|
|
),
|
|
))
|
|
|
|
|
|
def ShowOptionsDialog(Page: ft.Page, Program: Program, Current: Status, OnConfirm: Callable[[dict[str, bool]], None]) -> None:
|
|
"""Change an installed program's options. Turning one off takes out what
|
|
it placed (and puts back any file it had replaced); turning one on places it."""
|
|
Recorded = Current.Receipt.Options if Current.Receipt else {}
|
|
Ticked = DefaultOptions(Program)
|
|
Boxes = {
|
|
Option.Id: ft.Checkbox(label=Option.Label, value=bool(Recorded.get(Option.Id, Ticked[Option.Id])))
|
|
for Option in Program.Options
|
|
}
|
|
Body: list[ft.Control] = list(Boxes.values())
|
|
Body.append(ft.Text(
|
|
"Only what these options control changes; the program stays at the version it's on.",
|
|
size=12, color=Palette.Overlay0,
|
|
))
|
|
Page.show_dialog(_Dialog(
|
|
Page, f"{Program.Name} options", Body, "Apply",
|
|
lambda: OnConfirm({Id: bool(Box.value) for Id, Box in Boxes.items()}),
|
|
))
|
|
|
|
|
|
def ShowUpdateDialog(
|
|
Page: ft.Page, Program: Program, Current: Status, NewVersion: str, NewCommit: str,
|
|
Plans: list[ConfigPlan], OnConfirm: Callable[[], None],
|
|
) -> None:
|
|
Old = Current.Receipt
|
|
OldLabel = f"{Old.Version} ({Old.Commit[:7]})".strip() if Old else "?"
|
|
NewLabel = f"{NewVersion} ({NewCommit[:7]})".strip()
|
|
Body: list[ft.Control] = [ft.Text(f"{OldLabel} -> {NewLabel}", size=14, color=Palette.Text)]
|
|
if Old is not None and Old.Commit == NewCommit:
|
|
Body.append(ft.Text(
|
|
"Same commit: this reapplies the install (e.g. a newer SaraSuite changed how it's set up).",
|
|
size=12, color=Palette.Overlay0,
|
|
))
|
|
Body.append(ft.Text(
|
|
"Program files, desktop entries and services are replaced. Configs are merged, so your edits stay.",
|
|
size=12, color=Palette.Overlay0,
|
|
))
|
|
if Plans:
|
|
Body.append(_Heading("Configs"))
|
|
for Plan in Plans:
|
|
Color = Palette.Peach if Plan.Result.Action == "KeepAndNew" else Palette.Text
|
|
Body.append(ft.Text(spans=[
|
|
ft.TextSpan(_Short(Plan.Target), ft.TextStyle(color=Color)),
|
|
ft.TextSpan(f" {Plan.Result.Describe()}", ft.TextStyle(color=Palette.Overlay0)),
|
|
], size=12))
|
|
for Change in Plan.Result.Changes:
|
|
Body.append(ft.Text(f" {Change}", size=12, color=Palette.Overlay0))
|
|
|
|
Page.show_dialog(_Dialog(Page, f"Update {Program.Name}", Body, "Update", OnConfirm))
|
|
|
|
|
|
def ShowRemoveDialog(Page: ft.Page, Program: Program, Plan: RemovalPlan, Installed: bool, OnConfirm: Callable[[bool, bool], None]) -> None:
|
|
ConfigItems = Plan.Group("Config")
|
|
RuntimeItems = Plan.Group("Runtime")
|
|
ConfigList = _ItemList(ConfigItems)
|
|
RuntimeList = _ItemList(RuntimeItems)
|
|
ConfigList.opacity = 0.5
|
|
|
|
ConfigBox = ft.Checkbox(label="Remove configs", value=False)
|
|
RuntimeBox = ft.Checkbox(label="Remove files it made while running", value=True)
|
|
|
|
def Dim(e) -> None:
|
|
ConfigList.opacity = 1 if ConfigBox.value else 0.5
|
|
RuntimeList.opacity = 1 if RuntimeBox.value else 0.5
|
|
ConfigList.update()
|
|
RuntimeList.update()
|
|
|
|
ConfigBox.on_change = Dim
|
|
RuntimeBox.on_change = Dim
|
|
|
|
Body: list[ft.Control] = []
|
|
Always = Plan.Group("Program") + Plan.Group("Integration")
|
|
if Always:
|
|
Body += [_Heading("Always removed"), _ItemList(Always)]
|
|
Body += [ConfigBox, ConfigList, RuntimeBox, RuntimeList]
|
|
Body.append(ft.Text(
|
|
"Your own files are never removed: folders that might hold them are only removed when empty.",
|
|
size=12, color=Palette.Overlay0,
|
|
))
|
|
|
|
Page.show_dialog(_Dialog(
|
|
Page, f"{'Remove' if Installed else 'Clean up'} {Program.Name}", Body,
|
|
"Remove" if Installed else "Clean up",
|
|
lambda: OnConfirm(bool(ConfigBox.value), bool(RuntimeBox.value)),
|
|
Danger=True,
|
|
))
|