Fixed some UI issues with updater.
This commit is contained in:
@@ -4,4 +4,4 @@ from .paths import AppPaths
|
||||
|
||||
__all__ = ["AppPaths", "__version__"]
|
||||
|
||||
__version__ = "0.9.8"
|
||||
__version__ = "0.9.11"
|
||||
|
||||
@@ -7,7 +7,6 @@ from typing import Any, Callable
|
||||
from ...paths import AppPaths
|
||||
from ...self_update import (
|
||||
LauncherRemoteHistory,
|
||||
LauncherRemoteRelease,
|
||||
LauncherUpdateCheck,
|
||||
LauncherUpdateHistory,
|
||||
inspect_launcher_update_history,
|
||||
@@ -52,6 +51,46 @@ def release_notes_preview(markdown: str | None) -> str:
|
||||
return preview
|
||||
|
||||
|
||||
def update_version_heading(
|
||||
running_version: str,
|
||||
latest_version: str,
|
||||
channel: str,
|
||||
*,
|
||||
update_available: bool,
|
||||
) -> str:
|
||||
"""Describe update versions without confusing an older channel head as current."""
|
||||
|
||||
if update_available:
|
||||
return f"Update: {running_version} → {latest_version}"
|
||||
return (
|
||||
f"Current version: {running_version}\n"
|
||||
f"Latest {channel.title()} release: {latest_version}"
|
||||
)
|
||||
|
||||
|
||||
def release_history_state(
|
||||
release_status: str,
|
||||
*,
|
||||
is_running: bool,
|
||||
local_active: bool,
|
||||
local_retained: bool,
|
||||
is_older: bool,
|
||||
) -> str:
|
||||
"""Label running and installed releases without treating them as synonymous."""
|
||||
|
||||
if is_running:
|
||||
return "Current"
|
||||
if local_active:
|
||||
return "Installed"
|
||||
if release_status != "active":
|
||||
return release_status.title()
|
||||
if local_retained:
|
||||
return "Retained locally"
|
||||
if is_older:
|
||||
return "Available rollback"
|
||||
return "Published"
|
||||
|
||||
|
||||
class LauncherUpdateDialog:
|
||||
"""Own the update window and expose small refresh/error entry points."""
|
||||
|
||||
@@ -97,6 +136,8 @@ class LauncherUpdateDialog:
|
||||
self._history: Any = None
|
||||
self._history_filter_value: Any = None
|
||||
self._history_items: dict[str, tuple[str, str, str | None, bool]] = {}
|
||||
self._release_notes_frame: Any = None
|
||||
self._release_notes_text: Any = None
|
||||
self._release_notes_button: Any = None
|
||||
self._check_button: Any = None
|
||||
self._rollback_button: Any = None
|
||||
@@ -120,7 +161,7 @@ class LauncherUpdateDialog:
|
||||
dialog.withdraw()
|
||||
self._window = dialog
|
||||
dialog.title("PristonTale EU — Launcher Updates")
|
||||
dialog.resizable(False, False)
|
||||
dialog.resizable(False, True)
|
||||
dialog.configure(background="#0c0908")
|
||||
dialog.transient(self._root)
|
||||
self._apply_window_icon(dialog)
|
||||
@@ -201,6 +242,53 @@ class LauncherUpdateDialog:
|
||||
sticky="e",
|
||||
padx=(12, 0),
|
||||
)
|
||||
self._release_notes_frame = ttk.Frame(
|
||||
overview,
|
||||
style="SettingsCard.TFrame",
|
||||
)
|
||||
self._release_notes_frame.grid(
|
||||
row=2,
|
||||
column=0,
|
||||
columnspan=2,
|
||||
sticky="ew",
|
||||
pady=(10, 0),
|
||||
)
|
||||
self._release_notes_frame.columnconfigure(0, weight=1)
|
||||
ttk.Label(
|
||||
self._release_notes_frame,
|
||||
text="WHAT'S NEW",
|
||||
style="SettingsText.TLabel",
|
||||
).grid(row=0, column=0, columnspan=2, sticky="w", pady=(0, 4))
|
||||
self._release_notes_text = tk.Text(
|
||||
self._release_notes_frame,
|
||||
name="launcher_update_release_notes",
|
||||
width=1,
|
||||
height=3,
|
||||
wrap="word",
|
||||
background="#100d0c",
|
||||
foreground="#c8b8a8",
|
||||
selectbackground="#6a482d",
|
||||
selectforeground="#ffffff",
|
||||
font=("Sans", 8),
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
padx=8,
|
||||
pady=6,
|
||||
state="disabled",
|
||||
takefocus=False,
|
||||
)
|
||||
release_notes_scrollbar = ttk.Scrollbar(
|
||||
self._release_notes_frame,
|
||||
orient="vertical",
|
||||
style="News.Vertical.TScrollbar",
|
||||
command=self._release_notes_text.yview,
|
||||
)
|
||||
self._release_notes_text.configure(
|
||||
yscrollcommand=release_notes_scrollbar.set,
|
||||
)
|
||||
self._release_notes_text.grid(row=1, column=0, sticky="ew")
|
||||
release_notes_scrollbar.grid(row=1, column=1, sticky="ns", padx=(6, 0))
|
||||
self._release_notes_frame.grid_remove()
|
||||
|
||||
history_card = ttk.LabelFrame(
|
||||
content,
|
||||
@@ -244,7 +332,7 @@ class LauncherUpdateDialog:
|
||||
history_card,
|
||||
columns=("version", "channel", "state", "installed"),
|
||||
show="headings",
|
||||
height=6,
|
||||
height=4,
|
||||
style="Update.Treeview",
|
||||
selectmode="browse",
|
||||
)
|
||||
@@ -333,8 +421,8 @@ class LauncherUpdateDialog:
|
||||
self._check_again()
|
||||
dialog.update_idletasks()
|
||||
updates_width, updates_height = fitted_window_size(
|
||||
760,
|
||||
620,
|
||||
820,
|
||||
680,
|
||||
scale=self._ui_scale,
|
||||
screen_width=dialog.winfo_screenwidth(),
|
||||
screen_height=dialog.winfo_screenheight(),
|
||||
@@ -365,6 +453,8 @@ class LauncherUpdateDialog:
|
||||
assert self._status_value is not None
|
||||
assert self._details_value is not None
|
||||
assert self._history is not None
|
||||
assert self._release_notes_frame is not None
|
||||
assert self._release_notes_text is not None
|
||||
assert self._release_notes_button is not None
|
||||
assert self._check_button is not None
|
||||
assert self._rollback_button is not None
|
||||
@@ -381,10 +471,11 @@ class LauncherUpdateDialog:
|
||||
if check is None:
|
||||
self._status_value.set("Checking the signed update channel…")
|
||||
self._details_value.set(
|
||||
"Installed launcher "
|
||||
"Running launcher "
|
||||
+ self._launcher_version
|
||||
+ " · release information pending"
|
||||
)
|
||||
self._set_release_notes_preview("")
|
||||
self._install_button.configure(state="disabled")
|
||||
self._refresh_history_actions(busy=busy)
|
||||
return
|
||||
@@ -400,13 +491,14 @@ class LauncherUpdateDialog:
|
||||
for release in (
|
||||
remote_history.releases if remote_history is not None else ()
|
||||
)
|
||||
if release.version == check.current_version and release.status != "active"
|
||||
if release.version == self._launcher_version
|
||||
and release.status != "active"
|
||||
),
|
||||
None,
|
||||
)
|
||||
if installed_status is not None:
|
||||
status_prefix += (
|
||||
f"Warning: installed launcher {check.current_version} is "
|
||||
f"Warning: running launcher {self._launcher_version} is "
|
||||
f"{installed_status}; install the next active eligible release.\n"
|
||||
)
|
||||
self._status_value.set(status_prefix + check.reason)
|
||||
@@ -418,18 +510,24 @@ class LauncherUpdateDialog:
|
||||
if check.manifest.channel == "prerelease"
|
||||
else ""
|
||||
)
|
||||
summary_line = f"{release_summary}\n" if not notes_preview else ""
|
||||
version_line = update_version_heading(
|
||||
self._launcher_version,
|
||||
check.latest_version,
|
||||
check.manifest.channel,
|
||||
update_available=check.available,
|
||||
)
|
||||
details = (
|
||||
f"{check.current_version} → {check.latest_version}\n"
|
||||
f"{version_line}\n"
|
||||
f"{release_title}{prerelease_label}\n"
|
||||
f"{release_summary}\n"
|
||||
f"{summary_line}"
|
||||
f"Channel: {check.manifest.channel.title()} · "
|
||||
f"{format_bytes(check.manifest.archive_size)} · "
|
||||
f"published {check.manifest.published_at} · "
|
||||
f"checked {check.checked_at or 'time unavailable'}"
|
||||
)
|
||||
if notes_preview:
|
||||
details += f"\n\nWhat's new:\n{notes_preview}"
|
||||
self._details_value.set(details)
|
||||
self._set_release_notes_preview(notes_preview)
|
||||
self._install_button.configure(
|
||||
state=(
|
||||
"normal"
|
||||
@@ -442,6 +540,20 @@ class LauncherUpdateDialog:
|
||||
)
|
||||
self._refresh_history_actions(busy=busy)
|
||||
|
||||
def _set_release_notes_preview(self, preview: str) -> None:
|
||||
assert self._release_notes_frame is not None
|
||||
assert self._release_notes_text is not None
|
||||
self._release_notes_text.configure(state="normal")
|
||||
self._release_notes_text.delete("1.0", "end")
|
||||
if preview:
|
||||
self._release_notes_text.insert("1.0", preview)
|
||||
self._release_notes_text.configure(state="disabled")
|
||||
self._release_notes_text.yview_moveto(0.0)
|
||||
if preview:
|
||||
self._release_notes_frame.grid()
|
||||
else:
|
||||
self._release_notes_frame.grid_remove()
|
||||
|
||||
def _populate_history(self, *, busy: bool) -> None:
|
||||
assert self._history is not None
|
||||
assert self._history_filter_value is not None
|
||||
@@ -461,16 +573,22 @@ class LauncherUpdateDialog:
|
||||
else ()
|
||||
)
|
||||
}
|
||||
current_version = (
|
||||
installed_current_version = (
|
||||
self._installed_history.current_version
|
||||
if self._installed_history is not None
|
||||
and self._installed_history.current_version is not None
|
||||
else self._launcher_version
|
||||
)
|
||||
try:
|
||||
current_semantic = SemanticVersion.parse(current_version)
|
||||
installed_current_semantic = SemanticVersion.parse(
|
||||
installed_current_version
|
||||
)
|
||||
except ReleaseModelError:
|
||||
current_semantic = None
|
||||
installed_current_semantic = None
|
||||
try:
|
||||
running_channel = SemanticVersion.parse(self._launcher_version).channel
|
||||
except ReleaseModelError:
|
||||
running_channel = "unknown"
|
||||
filter_name = str(self._history_filter_value.get()).casefold()
|
||||
|
||||
def channel_visible(channel: str) -> bool:
|
||||
@@ -478,20 +596,27 @@ class LauncherUpdateDialog:
|
||||
|
||||
remote_history = self._get_remote_history()
|
||||
remote_versions: set[str] = set()
|
||||
displayed_versions: set[str] = set()
|
||||
if remote_history is not None:
|
||||
for index, release in enumerate(remote_history.releases):
|
||||
if not channel_visible(release.channel):
|
||||
continue
|
||||
remote_versions.add(release.version)
|
||||
displayed_versions.add(release.version)
|
||||
local = installed.get(release.version)
|
||||
is_older = (
|
||||
current_semantic is not None
|
||||
and SemanticVersion.parse(release.version) < current_semantic
|
||||
installed_current_semantic is not None
|
||||
and SemanticVersion.parse(release.version)
|
||||
< installed_current_semantic
|
||||
)
|
||||
can_rollback = release.status == "active" and is_older
|
||||
state = self._remote_release_state(
|
||||
release,
|
||||
local_active=bool(local is not None and local.active),
|
||||
local_active = bool(local is not None and local.active)
|
||||
can_rollback = (
|
||||
release.status == "active" and is_older and not local_active
|
||||
)
|
||||
state = release_history_state(
|
||||
release.status,
|
||||
is_running=release.version == self._launcher_version,
|
||||
local_active=local_active,
|
||||
local_retained=local is not None,
|
||||
is_older=is_older,
|
||||
)
|
||||
@@ -517,14 +642,23 @@ class LauncherUpdateDialog:
|
||||
for index, release in enumerate(self._installed_history.releases):
|
||||
if release.version in remote_versions or not channel_visible(release.channel):
|
||||
continue
|
||||
displayed_versions.add(release.version)
|
||||
try:
|
||||
is_older = (
|
||||
current_semantic is not None
|
||||
and SemanticVersion.parse(release.version) < current_semantic
|
||||
installed_current_semantic is not None
|
||||
and SemanticVersion.parse(release.version)
|
||||
< installed_current_semantic
|
||||
)
|
||||
except ReleaseModelError:
|
||||
is_older = False
|
||||
state = "Current" if release.active else "Retained locally"
|
||||
state = release_history_state(
|
||||
"active",
|
||||
is_running=release.version == self._launcher_version,
|
||||
local_active=release.active,
|
||||
local_retained=True,
|
||||
is_older=is_older,
|
||||
)
|
||||
can_rollback = is_older and not release.active
|
||||
iid = f"local:{index}:{release.version}"
|
||||
self._history.insert(
|
||||
"",
|
||||
@@ -541,8 +675,30 @@ class LauncherUpdateDialog:
|
||||
release.version,
|
||||
release.channel,
|
||||
None,
|
||||
is_older,
|
||||
can_rollback,
|
||||
)
|
||||
if (
|
||||
self._launcher_version not in displayed_versions
|
||||
and channel_visible(running_channel)
|
||||
):
|
||||
iid = f"running:{self._launcher_version}"
|
||||
self._history.insert(
|
||||
"",
|
||||
0,
|
||||
iid=iid,
|
||||
values=(
|
||||
self._launcher_version,
|
||||
running_channel.title(),
|
||||
"Current",
|
||||
"Running now",
|
||||
),
|
||||
)
|
||||
self._history_items[iid] = (
|
||||
self._launcher_version,
|
||||
running_channel,
|
||||
None,
|
||||
False,
|
||||
)
|
||||
if not self._history.get_children():
|
||||
iid = "placeholder"
|
||||
self._history.insert(
|
||||
@@ -567,24 +723,6 @@ class LauncherUpdateDialog:
|
||||
self._history.focus(selected_iid)
|
||||
self._refresh_history_actions(busy=busy)
|
||||
|
||||
@staticmethod
|
||||
def _remote_release_state(
|
||||
release: LauncherRemoteRelease,
|
||||
*,
|
||||
local_active: bool,
|
||||
local_retained: bool,
|
||||
is_older: bool,
|
||||
) -> str:
|
||||
if local_active:
|
||||
return "Current"
|
||||
if release.status != "active":
|
||||
return release.status.title()
|
||||
if local_retained:
|
||||
return "Retained locally"
|
||||
if is_older:
|
||||
return "Available rollback"
|
||||
return "Published"
|
||||
|
||||
def _history_selected(self, _event: object | None = None) -> None:
|
||||
self._refresh_history_actions(busy=self._is_busy())
|
||||
|
||||
@@ -621,6 +759,7 @@ class LauncherUpdateDialog:
|
||||
assert self._check_button is not None
|
||||
self._status_value.set("The signed update channel could not be checked.")
|
||||
self._details_value.set(message)
|
||||
self._set_release_notes_preview("")
|
||||
self._check_button.configure(state="normal")
|
||||
|
||||
def _check_again(self) -> None:
|
||||
@@ -630,6 +769,7 @@ class LauncherUpdateDialog:
|
||||
assert self._install_button is not None
|
||||
self._status_value.set("Checking the signed update channel…")
|
||||
self._details_value.set("Authenticating the selected channel manifests and signatures.")
|
||||
self._set_release_notes_preview("")
|
||||
self._check_button.configure(state="disabled")
|
||||
self._install_button.configure(state="disabled")
|
||||
self._request_check()
|
||||
@@ -637,8 +777,9 @@ class LauncherUpdateDialog:
|
||||
def _view_release_notes(self) -> None:
|
||||
selected = self._selected_history_item()
|
||||
if selected is not None:
|
||||
if selected[2] is not None:
|
||||
self._open_article(selected[2])
|
||||
notes_url = selected[2]
|
||||
if notes_url is not None:
|
||||
self._open_article(notes_url)
|
||||
return
|
||||
check = self._get_check()
|
||||
if check is not None and check.manifest.notes_url is not None:
|
||||
|
||||
Reference in New Issue
Block a user