synodic-client 0.0.1.dev76__tar.gz → 0.0.1.dev78__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/PKG-INFO +3 -3
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/pyproject.toml +4 -4
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/bootstrap.py +1 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/config_store.py +4 -3
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/debug.py +1 -1
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/package_state.py +23 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/schema.py +0 -33
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/action_card.py +47 -10
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/install.py +218 -97
- synodic_client-0.0.1.dev78/synodic_client/application/screen/install_workers.py +161 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/plugin_row.py +84 -28
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/projects.py +28 -13
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/schema.py +41 -28
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/screen.py +105 -26
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/tool_update_controller.py +76 -54
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/theme.py +19 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/update_controller.py +3 -2
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/update_model.py +15 -8
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/cli/__init__.py +2 -0
- synodic_client-0.0.1.dev78/synodic_client/cli/install.py +210 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/cli/output.py +5 -3
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/cli/tool.py +4 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/operations/__init__.py +14 -1
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/operations/config.py +31 -8
- synodic_client-0.0.1.dev78/synodic_client/operations/install.py +529 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/operations/schema.py +207 -2
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/operations/tool.py +107 -24
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/resolution.py +184 -211
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/operations/test_config.py +37 -1
- synodic_client-0.0.1.dev78/tests/unit/operations/test_install.py +424 -0
- synodic_client-0.0.1.dev78/tests/unit/operations/test_install_plan.py +207 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/operations/test_tool.py +23 -60
- synodic_client-0.0.1.dev78/tests/unit/qt/conftest.py +125 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/qt/test_action_card.py +112 -173
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/qt/test_gather_packages.py +46 -79
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/qt/test_install_preview.py +32 -25
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/qt/test_log_panel.py +47 -91
- synodic_client-0.0.1.dev78/tests/unit/qt/test_package_state.py +63 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/qt/test_preview_model.py +75 -62
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/qt/test_settings.py +30 -58
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/qt/test_sidebar.py +17 -32
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/qt/test_tray_window_show.py +3 -20
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/qt/test_update_controller.py +11 -40
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/qt/test_update_feedback.py +78 -11
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/test_cli.py +107 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/test_resolution.py +26 -7
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/test_updater.py +49 -150
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/test_workers.py +72 -1
- synodic_client-0.0.1.dev78/tests/unit/windows/conftest.py +19 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/windows/test_protocol.py +5 -7
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/windows/test_startup.py +17 -43
- synodic_client-0.0.1.dev76/synodic_client/application/screen/install_workers.py +0 -152
- synodic_client-0.0.1.dev76/synodic_client/application/workers.py +0 -20
- synodic_client-0.0.1.dev76/synodic_client/operations/install.py +0 -272
- synodic_client-0.0.1.dev76/tests/unit/operations/test_install.py +0 -221
- synodic_client-0.0.1.dev76/tests/unit/qt/conftest.py +0 -24
- synodic_client-0.0.1.dev76/tests/unit/windows/conftest.py +0 -9
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/LICENSE.md +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/README.md +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/__init__.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/__main__.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/__init__.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/data.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/icon.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/init.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/instance.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/qt.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/__init__.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/card.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/log_panel.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/settings.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/sidebar.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/spinner.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/tray.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/screen/update_banner.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/uri.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/cli/config.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/cli/context.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/cli/debug.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/cli/project.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/cli/update.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/client.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/config.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/logging.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/operations/bootstrap.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/operations/project.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/operations/update.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/protocol.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/py.typed +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/schema.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/startup.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/subprocess_patch.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/updater.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/__init__.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/conftest.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/__init__.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/operations/__init__.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/operations/test_project.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/operations/test_update.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/qt/__init__.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/qt/test_logging.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/qt/test_update_banner.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/test_client_updater.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/test_client_version.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/test_config.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/test_examples.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/test_init.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/test_install.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/test_uri.py +0 -0
- {synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/tests/unit/windows/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: synodic_client
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev78
|
|
4
4
|
Author-Email: Synodic Software <contact@synodic.software>
|
|
5
5
|
License: LGPL-3.0-or-later
|
|
6
6
|
Project-URL: homepage, https://github.com/synodic/synodic-client
|
|
@@ -8,9 +8,9 @@ Project-URL: repository, https://github.com/synodic/synodic-client
|
|
|
8
8
|
Requires-Python: <3.15,>=3.14
|
|
9
9
|
Requires-Dist: pyside6>=6.10.2
|
|
10
10
|
Requires-Dist: packaging>=26.0
|
|
11
|
-
Requires-Dist: porringer>=0.2.1.
|
|
11
|
+
Requires-Dist: porringer>=0.2.1.dev86
|
|
12
12
|
Requires-Dist: qasync>=0.28.0
|
|
13
|
-
Requires-Dist: velopack>=0.0.
|
|
13
|
+
Requires-Dist: velopack>=0.0.1521.dev61717
|
|
14
14
|
Requires-Dist: typer>=0.24.1
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
|
|
@@ -10,12 +10,12 @@ requires-python = ">=3.14, <3.15"
|
|
|
10
10
|
dependencies = [
|
|
11
11
|
"pyside6>=6.10.2",
|
|
12
12
|
"packaging>=26.0",
|
|
13
|
-
"porringer>=0.2.1.
|
|
13
|
+
"porringer>=0.2.1.dev86",
|
|
14
14
|
"qasync>=0.28.0",
|
|
15
|
-
"velopack>=0.0.
|
|
15
|
+
"velopack>=0.0.1521.dev61717",
|
|
16
16
|
"typer>=0.24.1",
|
|
17
17
|
]
|
|
18
|
-
version = "0.0.1.
|
|
18
|
+
version = "0.0.1.dev78"
|
|
19
19
|
|
|
20
20
|
[project.license]
|
|
21
21
|
text = "LGPL-3.0-or-later"
|
|
@@ -36,7 +36,7 @@ build = [
|
|
|
36
36
|
]
|
|
37
37
|
lint = [
|
|
38
38
|
"ruff>=0.15.6",
|
|
39
|
-
"pyrefly>=0.
|
|
39
|
+
"pyrefly>=0.57.0",
|
|
40
40
|
]
|
|
41
41
|
test = [
|
|
42
42
|
"pytest>=9.0.2",
|
{synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/bootstrap.py
RENAMED
|
@@ -55,6 +55,7 @@ def bootstrap() -> None:
|
|
|
55
55
|
run_startup_preamble(sys.executable)
|
|
56
56
|
|
|
57
57
|
# Heavy imports happen here — PySide6, porringer, etc.
|
|
58
|
+
|
|
58
59
|
from synodic_client.application.qt import application
|
|
59
60
|
|
|
60
61
|
application(uri=extract_uri_from_args(), dev_mode=dev_mode, debug=debug)
|
{synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/config_store.py
RENAMED
|
@@ -10,7 +10,8 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
from PySide6.QtCore import QObject, Signal
|
|
12
12
|
|
|
13
|
-
from synodic_client.
|
|
13
|
+
from synodic_client.operations.config import get_config, update_config
|
|
14
|
+
from synodic_client.schema import ResolvedConfig
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class ConfigStore(QObject):
|
|
@@ -33,7 +34,7 @@ class ConfigStore(QObject):
|
|
|
33
34
|
def __init__(self, config: ResolvedConfig | None = None, parent: QObject | None = None) -> None:
|
|
34
35
|
"""Create a new store, optionally seeded with *config*."""
|
|
35
36
|
super().__init__(parent)
|
|
36
|
-
self._config = config if config is not None else
|
|
37
|
+
self._config = config if config is not None else get_config()
|
|
37
38
|
|
|
38
39
|
@property
|
|
39
40
|
def config(self) -> ResolvedConfig:
|
|
@@ -52,7 +53,7 @@ class ConfigStore(QObject):
|
|
|
52
53
|
Returns:
|
|
53
54
|
The fresh :class:`ResolvedConfig`.
|
|
54
55
|
"""
|
|
55
|
-
self._config =
|
|
56
|
+
self._config = update_config(**changes)
|
|
56
57
|
self.changed.emit(self._config)
|
|
57
58
|
return self._config
|
|
58
59
|
|
{synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/debug.py
RENAMED
|
@@ -227,7 +227,7 @@ class DebugHandler:
|
|
|
227
227
|
needed = sum(1 for s in model.action_states if classify_status(s.status) == 'needed')
|
|
228
228
|
satisfied = sum(1 for s in model.action_states if classify_status(s.status) == 'satisfied')
|
|
229
229
|
pending = sum(1 for s in model.action_states if classify_status(s.status) == 'pending')
|
|
230
|
-
upgradable =
|
|
230
|
+
upgradable = sum(1 for s in model.action_states if s.status == 'Update available')
|
|
231
231
|
|
|
232
232
|
return json.dumps({
|
|
233
233
|
'path': str(target),
|
|
@@ -123,3 +123,26 @@ class PackageStateStore(QObject):
|
|
|
123
123
|
def clear(self) -> None:
|
|
124
124
|
"""Remove all recorded state."""
|
|
125
125
|
self._data.clear()
|
|
126
|
+
|
|
127
|
+
def record_updates_completed(
|
|
128
|
+
self,
|
|
129
|
+
signal_key: str,
|
|
130
|
+
version_map: dict[str, tuple[str, str]],
|
|
131
|
+
) -> None:
|
|
132
|
+
"""Mark packages as updated, clearing stale ``has_update`` flags.
|
|
133
|
+
|
|
134
|
+
Called after a successful tool update run. For each entry in
|
|
135
|
+
*version_map* (``{package_name: (old_version, new_version)}``),
|
|
136
|
+
the corresponding :class:`PackageState` is updated to reflect
|
|
137
|
+
the new installed version and ``has_update`` is cleared.
|
|
138
|
+
"""
|
|
139
|
+
changed = False
|
|
140
|
+
bucket = self._data.get(signal_key, {})
|
|
141
|
+
for pkg_name, (_, new_ver) in version_map.items():
|
|
142
|
+
existing = bucket.get(pkg_name)
|
|
143
|
+
if existing is not None:
|
|
144
|
+
existing.installed_version = new_ver
|
|
145
|
+
existing.has_update = False
|
|
146
|
+
changed = True
|
|
147
|
+
if changed:
|
|
148
|
+
self.state_changed.emit()
|
{synodic_client-0.0.1.dev76 → synodic_client-0.0.1.dev78}/synodic_client/application/schema.py
RENAMED
|
@@ -43,36 +43,3 @@ class Snapshot:
|
|
|
43
43
|
|
|
44
44
|
plugin_capabilities: dict[str, frozenset[PluginCapability]] = field(default_factory=dict)
|
|
45
45
|
"""Protocol capabilities reported for each discovered plugin."""
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@dataclass(slots=True)
|
|
49
|
-
class ToolUpdateResult:
|
|
50
|
-
"""Summary of a tool-update run across cached manifests."""
|
|
51
|
-
|
|
52
|
-
manifests_processed: int = 0
|
|
53
|
-
updated: int = 0
|
|
54
|
-
already_latest: int = 0
|
|
55
|
-
failed: int = 0
|
|
56
|
-
updated_packages: set[str] = field(default_factory=set)
|
|
57
|
-
"""Package names that were successfully upgraded."""
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@dataclass(frozen=True, slots=True)
|
|
61
|
-
class UpdateTarget:
|
|
62
|
-
"""Identifies the scope of a manual tool update.
|
|
63
|
-
|
|
64
|
-
Passed to the shared completion handler so it can clear the correct
|
|
65
|
-
updating state and derive timestamp keys. ``None`` (the default in
|
|
66
|
-
the handler) means the update was periodic / automatic.
|
|
67
|
-
|
|
68
|
-
When *package* is empty the update targeted an entire plugin;
|
|
69
|
-
otherwise it targeted one specific package within the plugin.
|
|
70
|
-
*plugin* always carries the signal key (possibly composite
|
|
71
|
-
``"plugin:tag"``).
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
plugin: str
|
|
75
|
-
"""Signal key for the plugin (may be composite ``"name:tag"``)."""
|
|
76
|
-
|
|
77
|
-
package: str = ''
|
|
78
|
-
"""Package name, or empty when the whole plugin was updated."""
|
|
@@ -17,11 +17,12 @@ from porringer.backend.command.core.action_builder import PHASE_ORDER
|
|
|
17
17
|
from porringer.schema import SetupAction, SetupActionResult
|
|
18
18
|
from porringer.schema.plugin import PluginKind
|
|
19
19
|
from PySide6.QtCore import Qt, QTimer, Signal
|
|
20
|
-
from PySide6.QtGui import QColor
|
|
20
|
+
from PySide6.QtGui import QColor, QCursor, QMouseEvent
|
|
21
21
|
from PySide6.QtWidgets import (
|
|
22
22
|
QApplication,
|
|
23
23
|
QCheckBox,
|
|
24
24
|
QFrame,
|
|
25
|
+
QGraphicsOpacityEffect,
|
|
25
26
|
QHBoxLayout,
|
|
26
27
|
QLabel,
|
|
27
28
|
QToolButton,
|
|
@@ -57,6 +58,7 @@ from synodic_client.application.theme import (
|
|
|
57
58
|
ACTION_CARD_STATUS_UPDATE,
|
|
58
59
|
ACTION_CARD_STYLE,
|
|
59
60
|
ACTION_CARD_TYPE_BADGE_STYLE,
|
|
61
|
+
ACTION_CARD_UPDATE_AVAILABLE_STYLE,
|
|
60
62
|
ACTION_CARD_VERSION_STYLE,
|
|
61
63
|
COPY_BTN_SIZE,
|
|
62
64
|
COPY_BTN_STYLE,
|
|
@@ -77,6 +79,15 @@ _SPINNER_INTERVAL = 50
|
|
|
77
79
|
#: display order always matches the order actions actually execute.
|
|
78
80
|
_KIND_ORDER: dict[PluginKind | None, int] = {kind: i for i, kind in enumerate(PHASE_ORDER)}
|
|
79
81
|
|
|
82
|
+
#: Mapping of resolved status label → stylesheet for dry-run badge styling.
|
|
83
|
+
_STATUS_STYLES: dict[str, str] = {
|
|
84
|
+
'Update available': ACTION_CARD_STATUS_UPDATE,
|
|
85
|
+
'Failed': ACTION_CARD_STATUS_FAILED,
|
|
86
|
+
'Pending': ACTION_CARD_STATUS_PENDING,
|
|
87
|
+
'Ready': ACTION_CARD_STATUS_SATISFIED,
|
|
88
|
+
'Needed': ACTION_CARD_STATUS_NEEDED,
|
|
89
|
+
}
|
|
90
|
+
|
|
80
91
|
|
|
81
92
|
def action_sort_key(action: SetupAction) -> int:
|
|
82
93
|
"""Return a sort key that groups cards by execution phase.
|
|
@@ -119,6 +130,10 @@ class ActionCard(QFrame):
|
|
|
119
130
|
"""Emitted with ``(package_name, checked)`` when the user toggles the
|
|
120
131
|
per-row pre-release checkbox."""
|
|
121
132
|
|
|
133
|
+
navigate_to_tool = Signal(str, str)
|
|
134
|
+
"""Emitted with ``(installer, package_name)`` when the user clicks an
|
|
135
|
+
'Update available' card to navigate to the Tools view."""
|
|
136
|
+
|
|
122
137
|
def __init__(
|
|
123
138
|
self,
|
|
124
139
|
parent: QWidget | None = None,
|
|
@@ -459,15 +474,6 @@ class ActionCard(QFrame):
|
|
|
459
474
|
|
|
460
475
|
self._stop_spinner()
|
|
461
476
|
|
|
462
|
-
# Status-to-style mapping
|
|
463
|
-
_STATUS_STYLES: dict[str, str] = {
|
|
464
|
-
'Update available': ACTION_CARD_STATUS_UPDATE,
|
|
465
|
-
'Failed': ACTION_CARD_STATUS_FAILED,
|
|
466
|
-
'Pending': ACTION_CARD_STATUS_PENDING,
|
|
467
|
-
'Ready': ACTION_CARD_STATUS_SATISFIED,
|
|
468
|
-
'Needed': ACTION_CARD_STATUS_NEEDED,
|
|
469
|
-
}
|
|
470
|
-
|
|
471
477
|
style = _STATUS_STYLES.get(status, ACTION_CARD_STATUS_SATISFIED)
|
|
472
478
|
display = status
|
|
473
479
|
|
|
@@ -491,6 +497,10 @@ class ActionCard(QFrame):
|
|
|
491
497
|
else:
|
|
492
498
|
self._status_label.setToolTip('')
|
|
493
499
|
|
|
500
|
+
# "Update available" — fade the card and make it clickable
|
|
501
|
+
if status == 'Update available':
|
|
502
|
+
self._apply_update_available_style()
|
|
503
|
+
|
|
494
504
|
# CLI command — update with resolved cli_command from result
|
|
495
505
|
assert self._action is not None
|
|
496
506
|
cmd_text = format_cli_command(self._action, result=result, suppress_description=True)
|
|
@@ -524,6 +534,15 @@ class ActionCard(QFrame):
|
|
|
524
534
|
self._version_label.setText(f'\u2192 {result.available_version}')
|
|
525
535
|
self._version_label.setStyleSheet(ACTION_CARD_VERSION_STYLE + ' color: grey;')
|
|
526
536
|
|
|
537
|
+
def _apply_update_available_style(self) -> None:
|
|
538
|
+
"""Fade the card and make it clickable for 'Update available' status."""
|
|
539
|
+
self.setStyleSheet(ACTION_CARD_UPDATE_AVAILABLE_STYLE)
|
|
540
|
+
opacity = QGraphicsOpacityEffect(self)
|
|
541
|
+
opacity.setOpacity(0.55)
|
|
542
|
+
self.setGraphicsEffect(opacity)
|
|
543
|
+
self.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
|
|
544
|
+
self.setToolTip('Manage this update in the Tools view')
|
|
545
|
+
|
|
527
546
|
def finalize_checking(self) -> None:
|
|
528
547
|
"""Resolve a still-pending 'Checking\u2026' status to 'Needed'.
|
|
529
548
|
|
|
@@ -605,6 +624,16 @@ class ActionCard(QFrame):
|
|
|
605
624
|
"""Return whether the card shows an 'Update available' status."""
|
|
606
625
|
return self.status_text() == 'Update available'
|
|
607
626
|
|
|
627
|
+
def mousePressEvent(self, event: QMouseEvent) -> None:
|
|
628
|
+
"""Navigate to Tools view when clicking an 'Update available' card."""
|
|
629
|
+
if self.is_update_available() and self._action is not None:
|
|
630
|
+
installer = self._action.installer or ''
|
|
631
|
+
package = str(self._action.package.name) if self._action.package else ''
|
|
632
|
+
if installer and package:
|
|
633
|
+
self.navigate_to_tool.emit(installer, package)
|
|
634
|
+
return
|
|
635
|
+
super().mousePressEvent(event)
|
|
636
|
+
|
|
608
637
|
|
|
609
638
|
# ---------------------------------------------------------------------------
|
|
610
639
|
# ActionCardList — card container
|
|
@@ -621,6 +650,13 @@ class ActionCardList(QWidget):
|
|
|
621
650
|
prerelease_toggled = Signal(str, bool)
|
|
622
651
|
"""Forwarded from child :class:`ActionCard` widgets."""
|
|
623
652
|
|
|
653
|
+
navigate_to_tool = Signal(str, str)
|
|
654
|
+
"""Forwarded from child :class:`ActionCard` widgets.
|
|
655
|
+
|
|
656
|
+
Emitted with ``(installer, package_name)`` when the user clicks an
|
|
657
|
+
'Update available' card.
|
|
658
|
+
"""
|
|
659
|
+
|
|
624
660
|
def __init__(self, parent: QWidget | None = None) -> None:
|
|
625
661
|
"""Initialise the card list."""
|
|
626
662
|
super().__init__(parent)
|
|
@@ -680,6 +716,7 @@ class ActionCardList(QWidget):
|
|
|
680
716
|
prerelease_overrides=prerelease_overrides,
|
|
681
717
|
)
|
|
682
718
|
card.prerelease_toggled.connect(self.prerelease_toggled.emit)
|
|
719
|
+
card.navigate_to_tool.connect(self.navigate_to_tool.emit)
|
|
683
720
|
self._layout.insertWidget(self._layout.count() - 1, card)
|
|
684
721
|
self._cards.append(card)
|
|
685
722
|
self._action_map[act] = card
|