synodic-client 0.0.1.dev79__tar.gz → 0.0.1.dev81__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.dev79 → synodic_client-0.0.1.dev81}/PKG-INFO +3 -3
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/pyproject.toml +6 -6
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/action_card.py +92 -15
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/plugin_row.py +23 -3
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/screen.py +25 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/tool_update_controller.py +1 -0
- synodic_client-0.0.1.dev81/synodic_client/application/screen/wsl.py +419 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/theme.py +46 -13
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/operations/install.py +1 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/operations/schema.py +2 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/updater.py +3 -4
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/LICENSE.md +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/README.md +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/__init__.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/__main__.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/__init__.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/bootstrap.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/config_store.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/data.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/debug.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/icon.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/init.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/instance.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/package_state.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/qt.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/schema.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/__init__.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/card.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/install.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/install_workers.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/log_panel.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/projects.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/schema.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/settings.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/sidebar.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/spinner.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/tray.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/screen/update_banner.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/update_controller.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/update_model.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/application/uri.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/cli/__init__.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/cli/config.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/cli/context.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/cli/debug.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/cli/install.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/cli/output.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/cli/project.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/cli/tool.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/cli/update.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/client.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/config.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/logging.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/operations/__init__.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/operations/bootstrap.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/operations/config.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/operations/project.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/operations/tool.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/operations/update.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/protocol.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/py.typed +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/resolution.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/schema.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/startup.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/synodic_client/subprocess_patch.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/__init__.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/conftest.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/__init__.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/operations/__init__.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/operations/test_config.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/operations/test_install.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/operations/test_install_plan.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/operations/test_project.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/operations/test_tool.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/operations/test_update.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/__init__.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/conftest.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/test_action_card.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/test_gather_packages.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/test_install_preview.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/test_log_panel.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/test_logging.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/test_package_state.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/test_preview_model.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/test_settings.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/test_sidebar.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/test_tray_window_show.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/test_update_banner.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/test_update_controller.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/qt/test_update_feedback.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/test_bootstrap.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/test_cli.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/test_client_updater.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/test_client_version.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/test_config.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/test_examples.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/test_init.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/test_install.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/test_resolution.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/test_updater.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/test_uri.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/test_workers.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/windows/__init__.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/windows/conftest.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/windows/test_protocol.py +0 -0
- {synodic_client-0.0.1.dev79 → synodic_client-0.0.1.dev81}/tests/unit/windows/test_startup.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.dev81
|
|
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.dev88
|
|
12
12
|
Requires-Dist: qasync>=0.28.0
|
|
13
|
-
Requires-Dist: velopack>=0.0.
|
|
13
|
+
Requires-Dist: velopack>=0.0.1535.dev45597
|
|
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.dev88",
|
|
14
14
|
"qasync>=0.28.0",
|
|
15
|
-
"velopack>=0.0.
|
|
15
|
+
"velopack>=0.0.1535.dev45597",
|
|
16
16
|
"typer>=0.24.1",
|
|
17
17
|
]
|
|
18
|
-
version = "0.0.1.
|
|
18
|
+
version = "0.0.1.dev81"
|
|
19
19
|
|
|
20
20
|
[project.license]
|
|
21
21
|
text = "LGPL-3.0-or-later"
|
|
@@ -35,12 +35,12 @@ build = [
|
|
|
35
35
|
"pyinstaller>=6.19.0",
|
|
36
36
|
]
|
|
37
37
|
lint = [
|
|
38
|
-
"ruff>=0.15.
|
|
39
|
-
"pyrefly>=0.57.
|
|
38
|
+
"ruff>=0.15.7",
|
|
39
|
+
"pyrefly>=0.57.1",
|
|
40
40
|
]
|
|
41
41
|
test = [
|
|
42
42
|
"pytest>=9.0.2",
|
|
43
|
-
"pytest-cov>=7.
|
|
43
|
+
"pytest-cov>=7.1.0",
|
|
44
44
|
"pytest-mock>=3.15.1",
|
|
45
45
|
]
|
|
46
46
|
|
|
@@ -40,6 +40,7 @@ from synodic_client.application.screen.spinner import SpinnerCanvas
|
|
|
40
40
|
from synodic_client.application.theme import (
|
|
41
41
|
ACTION_CARD_COMMAND_STYLE,
|
|
42
42
|
ACTION_CARD_DESC_STYLE,
|
|
43
|
+
ACTION_CARD_DISTRO_BADGE_STYLE,
|
|
43
44
|
ACTION_CARD_EXECUTING_STYLE,
|
|
44
45
|
ACTION_CARD_PACKAGE_STYLE,
|
|
45
46
|
ACTION_CARD_SKELETON_BAR_STYLE,
|
|
@@ -64,6 +65,7 @@ from synodic_client.application.theme import (
|
|
|
64
65
|
COPY_BTN_STYLE,
|
|
65
66
|
COPY_FEEDBACK_MS,
|
|
66
67
|
COPY_ICON,
|
|
68
|
+
WSL_DISTRO_HEADER_STYLE,
|
|
67
69
|
)
|
|
68
70
|
|
|
69
71
|
logger = logging.getLogger(__name__)
|
|
@@ -224,7 +226,7 @@ class ActionCard(QFrame):
|
|
|
224
226
|
outer.addWidget(self._build_command_row())
|
|
225
227
|
|
|
226
228
|
def _build_top_row(self) -> QHBoxLayout:
|
|
227
|
-
"""Build the top row: type badge | package name ... version | status/spinner | prerelease."""
|
|
229
|
+
"""Build the top row: type badge | [distro badge] | package name ... version | status/spinner | prerelease."""
|
|
228
230
|
top = QHBoxLayout()
|
|
229
231
|
top.setSpacing(8)
|
|
230
232
|
|
|
@@ -232,6 +234,11 @@ class ActionCard(QFrame):
|
|
|
232
234
|
self._type_badge.setStyleSheet(ACTION_CARD_TYPE_BADGE_STYLE)
|
|
233
235
|
top.addWidget(self._type_badge)
|
|
234
236
|
|
|
237
|
+
self._distro_badge = QLabel()
|
|
238
|
+
self._distro_badge.setStyleSheet(ACTION_CARD_DISTRO_BADGE_STYLE)
|
|
239
|
+
self._distro_badge.hide()
|
|
240
|
+
top.addWidget(self._distro_badge)
|
|
241
|
+
|
|
235
242
|
self._package_label = QLabel()
|
|
236
243
|
self._package_label.setStyleSheet(ACTION_CARD_PACKAGE_STYLE)
|
|
237
244
|
self._package_label.setTextInteractionFlags(
|
|
@@ -360,6 +367,12 @@ class ActionCard(QFrame):
|
|
|
360
367
|
if action.installer:
|
|
361
368
|
self._type_badge.setToolTip(f'Plugin: {action.installer}')
|
|
362
369
|
|
|
370
|
+
if action.distro:
|
|
371
|
+
self._distro_badge.setText(action.distro)
|
|
372
|
+
self._distro_badge.show()
|
|
373
|
+
else:
|
|
374
|
+
self._distro_badge.hide()
|
|
375
|
+
|
|
363
376
|
package_text = str(action.package) if action.package else action.description
|
|
364
377
|
self._package_label.setText(package_text)
|
|
365
378
|
|
|
@@ -635,6 +648,32 @@ class ActionCard(QFrame):
|
|
|
635
648
|
super().mousePressEvent(event)
|
|
636
649
|
|
|
637
650
|
|
|
651
|
+
# ---------------------------------------------------------------------------
|
|
652
|
+
# _DistroGroupHeader — section divider between native and WSL action groups
|
|
653
|
+
# ---------------------------------------------------------------------------
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
class _DistroGroupHeader(QLabel):
|
|
657
|
+
"""Thin section divider between native and per-distro action groups.
|
|
658
|
+
|
|
659
|
+
Shows ``HOST`` for the native group or ``WSL — <distro>`` for each
|
|
660
|
+
WSL2 distro group. Only inserted when both native and WSL actions
|
|
661
|
+
are present.
|
|
662
|
+
"""
|
|
663
|
+
|
|
664
|
+
def __init__(self, distro_name: str | None, parent: QWidget | None = None) -> None:
|
|
665
|
+
"""Initialise the header.
|
|
666
|
+
|
|
667
|
+
Args:
|
|
668
|
+
distro_name: WSL distro name, or ``None`` for the native group.
|
|
669
|
+
parent: Optional parent widget.
|
|
670
|
+
"""
|
|
671
|
+
label = 'HOST' if distro_name is None else f'WSL \u2014 {distro_name}'.upper()
|
|
672
|
+
super().__init__(label, parent)
|
|
673
|
+
self.setObjectName('wslDistroHeader')
|
|
674
|
+
self.setStyleSheet(WSL_DISTRO_HEADER_STYLE)
|
|
675
|
+
|
|
676
|
+
|
|
638
677
|
# ---------------------------------------------------------------------------
|
|
639
678
|
# ActionCardList — card container
|
|
640
679
|
# ---------------------------------------------------------------------------
|
|
@@ -667,6 +706,7 @@ class ActionCardList(QWidget):
|
|
|
667
706
|
self._layout.addStretch()
|
|
668
707
|
|
|
669
708
|
self._cards: list[ActionCard] = []
|
|
709
|
+
self._group_headers: list[QLabel] = []
|
|
670
710
|
self._action_map: dict[SetupAction, ActionCard] = {}
|
|
671
711
|
self._index_map: dict[int, ActionCard] = {}
|
|
672
712
|
|
|
@@ -701,25 +741,58 @@ class ActionCardList(QWidget):
|
|
|
701
741
|
) -> None:
|
|
702
742
|
"""Replace skeleton cards with real action cards.
|
|
703
743
|
|
|
744
|
+
When actions include WSL distro entries (``action.distro is not
|
|
745
|
+
None``), the list is split into groups. Native host actions
|
|
746
|
+
appear first under a ``HOST`` section header; each WSL distro
|
|
747
|
+
gets its own ``WSL — <distro>`` section header below. If all
|
|
748
|
+
actions are native, no headers are inserted.
|
|
749
|
+
|
|
704
750
|
Args:
|
|
705
751
|
actions: The setup actions to display.
|
|
706
752
|
plugin_installed: Plugin name → installed mapping.
|
|
707
753
|
prerelease_overrides: Package names with user pre-release overrides.
|
|
708
754
|
"""
|
|
709
755
|
self.clear()
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
756
|
+
|
|
757
|
+
# Partition into native and per-distro groups.
|
|
758
|
+
native_actions: list[SetupAction] = []
|
|
759
|
+
distro_actions: dict[str, list[SetupAction]] = {}
|
|
760
|
+
for act in actions:
|
|
761
|
+
if act.distro is None:
|
|
762
|
+
native_actions.append(act)
|
|
763
|
+
else:
|
|
764
|
+
distro_actions.setdefault(act.distro, []).append(act)
|
|
765
|
+
|
|
766
|
+
has_wsl = bool(distro_actions)
|
|
767
|
+
|
|
768
|
+
def _add_group(group_actions: list[SetupAction]) -> None:
|
|
769
|
+
for act in sorted(group_actions, key=action_sort_key):
|
|
770
|
+
card = ActionCard(self)
|
|
771
|
+
card.populate(
|
|
772
|
+
act,
|
|
773
|
+
plugin_installed=plugin_installed,
|
|
774
|
+
prerelease_overrides=prerelease_overrides,
|
|
775
|
+
)
|
|
776
|
+
card.prerelease_toggled.connect(self.prerelease_toggled.emit)
|
|
777
|
+
card.navigate_to_tool.connect(self.navigate_to_tool.emit)
|
|
778
|
+
self._layout.insertWidget(self._layout.count() - 1, card)
|
|
779
|
+
self._cards.append(card)
|
|
780
|
+
self._action_map[act] = card
|
|
781
|
+
|
|
782
|
+
def _add_header(distro_name: str | None) -> None:
|
|
783
|
+
header = _DistroGroupHeader(distro_name, self)
|
|
784
|
+
self._layout.insertWidget(self._layout.count() - 1, header)
|
|
785
|
+
self._group_headers.append(header)
|
|
786
|
+
|
|
787
|
+
# Native actions (with host header only when WSL groups also exist)
|
|
788
|
+
if has_wsl and native_actions:
|
|
789
|
+
_add_header(None)
|
|
790
|
+
_add_group(native_actions)
|
|
791
|
+
|
|
792
|
+
# Per-distro groups
|
|
793
|
+
for distro_name in sorted(distro_actions):
|
|
794
|
+
_add_header(distro_name)
|
|
795
|
+
_add_group(distro_actions[distro_name])
|
|
723
796
|
|
|
724
797
|
# Build original-index → card mapping so callers can look up by
|
|
725
798
|
# the action index porringer emits, which is independent of the
|
|
@@ -776,7 +849,11 @@ class ActionCardList(QWidget):
|
|
|
776
849
|
card.finalize_checking()
|
|
777
850
|
|
|
778
851
|
def clear(self) -> None:
|
|
779
|
-
"""Remove all cards."""
|
|
852
|
+
"""Remove all cards and group headers."""
|
|
853
|
+
for header in self._group_headers:
|
|
854
|
+
self._layout.removeWidget(header)
|
|
855
|
+
header.deleteLater()
|
|
856
|
+
self._group_headers.clear()
|
|
780
857
|
for card in self._cards:
|
|
781
858
|
self._layout.removeWidget(card)
|
|
782
859
|
card.deleteLater()
|
|
@@ -25,6 +25,7 @@ from synodic_client.application.screen.schema import PluginRowData, ProjectInsta
|
|
|
25
25
|
from synodic_client.application.screen.spinner import SpinnerCanvas
|
|
26
26
|
from synodic_client.application.theme import (
|
|
27
27
|
FILTER_CHIP_STYLE,
|
|
28
|
+
PLUGIN_CHECK_STYLE,
|
|
28
29
|
PLUGIN_KIND_HEADER_STYLE,
|
|
29
30
|
PLUGIN_PROVIDER_NAME_STYLE,
|
|
30
31
|
PLUGIN_PROVIDER_RUNTIME_TAG_DEFAULT_STYLE,
|
|
@@ -138,6 +139,9 @@ class PluginProviderHeader(QFrame):
|
|
|
138
139
|
auto_update_toggled = Signal(str, bool)
|
|
139
140
|
"""Emitted with ``(plugin_name, enabled)`` when the auto-update toggle changes."""
|
|
140
141
|
|
|
142
|
+
check_requested = Signal(str)
|
|
143
|
+
"""Emitted with the plugin name when the manual check-for-updates button is clicked."""
|
|
144
|
+
|
|
141
145
|
update_requested = Signal(str)
|
|
142
146
|
"""Emitted with the plugin name when the per-plugin *Update* button is clicked."""
|
|
143
147
|
|
|
@@ -159,6 +163,7 @@ class PluginProviderHeader(QFrame):
|
|
|
159
163
|
self._runtime_tag = ''
|
|
160
164
|
self._signal_key = plugin.name
|
|
161
165
|
self._update_btn: QPushButton | None = None
|
|
166
|
+
self._check_btn: QPushButton | None = None
|
|
162
167
|
self._checking_spinner: _RowSpinner | None = None
|
|
163
168
|
|
|
164
169
|
self._layout = QHBoxLayout(self)
|
|
@@ -230,8 +235,8 @@ class PluginProviderHeader(QFrame):
|
|
|
230
235
|
auto_update: bool,
|
|
231
236
|
has_updates: bool,
|
|
232
237
|
) -> None:
|
|
233
|
-
"""Build
|
|
234
|
-
toggle_btn = QPushButton('
|
|
238
|
+
"""Build auto-update toggle, check, and Update control buttons."""
|
|
239
|
+
toggle_btn = QPushButton('\u21ba')
|
|
235
240
|
toggle_btn.setCheckable(True)
|
|
236
241
|
toggle_btn.setChecked(auto_update)
|
|
237
242
|
toggle_btn.setStyleSheet(PLUGIN_TOGGLE_STYLE)
|
|
@@ -241,6 +246,15 @@ class PluginProviderHeader(QFrame):
|
|
|
241
246
|
)
|
|
242
247
|
layout.addWidget(toggle_btn)
|
|
243
248
|
|
|
249
|
+
check_btn = QPushButton('\u27f3')
|
|
250
|
+
check_btn.setStyleSheet(PLUGIN_CHECK_STYLE)
|
|
251
|
+
check_btn.setToolTip('Check for updates now')
|
|
252
|
+
check_btn.clicked.connect(
|
|
253
|
+
lambda: self.check_requested.emit(self._signal_key),
|
|
254
|
+
)
|
|
255
|
+
self._check_btn = check_btn
|
|
256
|
+
layout.addWidget(check_btn)
|
|
257
|
+
|
|
244
258
|
self._checking_spinner = _RowSpinner(self)
|
|
245
259
|
layout.addWidget(self._checking_spinner)
|
|
246
260
|
|
|
@@ -258,6 +272,8 @@ class PluginProviderHeader(QFrame):
|
|
|
258
272
|
toggle_btn.setEnabled(False)
|
|
259
273
|
toggle_btn.setChecked(False)
|
|
260
274
|
toggle_btn.setToolTip('Not installed \u2014 cannot auto-update')
|
|
275
|
+
check_btn.setEnabled(False)
|
|
276
|
+
check_btn.setToolTip('Not installed \u2014 cannot check for updates')
|
|
261
277
|
update_btn.setEnabled(False)
|
|
262
278
|
update_btn.setToolTip('Not installed \u2014 cannot update')
|
|
263
279
|
|
|
@@ -281,10 +297,14 @@ class PluginProviderHeader(QFrame):
|
|
|
281
297
|
return
|
|
282
298
|
if checking:
|
|
283
299
|
self._checking_spinner.start()
|
|
300
|
+
if self._check_btn is not None:
|
|
301
|
+
self._check_btn.hide()
|
|
284
302
|
if self._update_btn is not None:
|
|
285
303
|
self._update_btn.hide()
|
|
286
304
|
else:
|
|
287
305
|
self._checking_spinner.stop()
|
|
306
|
+
if self._check_btn is not None:
|
|
307
|
+
self._check_btn.show()
|
|
288
308
|
|
|
289
309
|
def set_error(self, message: str) -> None:
|
|
290
310
|
"""Show a transient inline error that auto-hides after ~5 seconds."""
|
|
@@ -443,7 +463,7 @@ class PluginRow(QFrame):
|
|
|
443
463
|
|
|
444
464
|
def _build_toggle(self, layout: QHBoxLayout, data: PluginRowData) -> None:
|
|
445
465
|
"""Add the auto-update toggle button."""
|
|
446
|
-
toggle_btn = QPushButton('
|
|
466
|
+
toggle_btn = QPushButton('\u21ba')
|
|
447
467
|
toggle_btn.setCheckable(True)
|
|
448
468
|
toggle_btn.setChecked(data.auto_update)
|
|
449
469
|
toggle_btn.setStyleSheet(PLUGIN_ROW_TOGGLE_STYLE)
|
|
@@ -50,6 +50,7 @@ from synodic_client.application.screen.schema import (
|
|
|
50
50
|
)
|
|
51
51
|
from synodic_client.application.screen.spinner import LoadingIndicator
|
|
52
52
|
from synodic_client.application.screen.update_banner import UpdateBanner
|
|
53
|
+
from synodic_client.application.screen.wsl import WslView
|
|
53
54
|
from synodic_client.application.theme import (
|
|
54
55
|
COMPACT_MARGINS,
|
|
55
56
|
FILTER_CHIP_SPACING,
|
|
@@ -95,6 +96,9 @@ class ToolsView(QWidget):
|
|
|
95
96
|
update_all_requested = Signal()
|
|
96
97
|
"""Emitted when the global *Update All* button is clicked."""
|
|
97
98
|
|
|
99
|
+
plugin_check_requested = Signal(str)
|
|
100
|
+
"""Emitted with a plugin name when its manual check-for-updates button is clicked."""
|
|
101
|
+
|
|
98
102
|
plugin_update_requested = Signal(str)
|
|
99
103
|
"""Emitted with a plugin name when its per-plugin *Update* button is clicked."""
|
|
100
104
|
|
|
@@ -495,6 +499,7 @@ class ToolsView(QWidget):
|
|
|
495
499
|
)
|
|
496
500
|
provider.set_runtime(rt.tag, label=tag_text)
|
|
497
501
|
provider.auto_update_toggled.connect(self._on_auto_update_toggled)
|
|
502
|
+
provider.check_requested.connect(self.plugin_check_requested.emit)
|
|
498
503
|
provider.update_requested.connect(self.plugin_update_requested.emit)
|
|
499
504
|
self._insert_section_widget(provider)
|
|
500
505
|
|
|
@@ -558,6 +563,7 @@ class ToolsView(QWidget):
|
|
|
558
563
|
parent=self._container,
|
|
559
564
|
)
|
|
560
565
|
provider.auto_update_toggled.connect(self._on_auto_update_toggled)
|
|
566
|
+
provider.check_requested.connect(self.plugin_check_requested.emit)
|
|
561
567
|
provider.update_requested.connect(self.plugin_update_requested.emit)
|
|
562
568
|
self._insert_section_widget(provider)
|
|
563
569
|
|
|
@@ -1429,6 +1435,7 @@ class MainWindow(QMainWindow):
|
|
|
1429
1435
|
_tabs: QTabWidget | None = None
|
|
1430
1436
|
_tools_view: ToolsView | None = None
|
|
1431
1437
|
_projects_view: ProjectsView | None = None
|
|
1438
|
+
_wsl_view: WslView | None = None
|
|
1432
1439
|
|
|
1433
1440
|
def __init__(
|
|
1434
1441
|
self,
|
|
@@ -1512,6 +1519,22 @@ class MainWindow(QMainWindow):
|
|
|
1512
1519
|
self._tabs.addTab(self._tools_view, 'Tools')
|
|
1513
1520
|
self.tools_view_created.emit(self._tools_view)
|
|
1514
1521
|
|
|
1522
|
+
# WSL tab — only on Windows hosts with WSL available.
|
|
1523
|
+
try:
|
|
1524
|
+
from porringer.plugin.wsl.utility import is_wsl_host
|
|
1525
|
+
|
|
1526
|
+
if is_wsl_host():
|
|
1527
|
+
self._wsl_view = WslView(
|
|
1528
|
+
self._porringer,
|
|
1529
|
+
self._store,
|
|
1530
|
+
self,
|
|
1531
|
+
coordinator=self._coordinator,
|
|
1532
|
+
package_store=self._package_store,
|
|
1533
|
+
)
|
|
1534
|
+
self._tabs.addTab(self._wsl_view, 'WSL')
|
|
1535
|
+
except Exception:
|
|
1536
|
+
logger.debug('Could not initialise WSL tab', exc_info=True)
|
|
1537
|
+
|
|
1515
1538
|
# Navigate-to-project: switch to Projects tab and select directory
|
|
1516
1539
|
self._tools_view.navigate_to_project_requested.connect(self._navigate_to_project)
|
|
1517
1540
|
|
|
@@ -1541,6 +1564,8 @@ class MainWindow(QMainWindow):
|
|
|
1541
1564
|
self._tools_view.refresh()
|
|
1542
1565
|
if self._projects_view is not None:
|
|
1543
1566
|
self._projects_view.refresh()
|
|
1567
|
+
if self._wsl_view is not None:
|
|
1568
|
+
self._wsl_view.refresh()
|
|
1544
1569
|
|
|
1545
1570
|
def _navigate_to_project(self, path_str: str) -> None:
|
|
1546
1571
|
"""Switch to the Projects tab and select the given directory."""
|
|
@@ -148,6 +148,7 @@ class ToolUpdateOrchestrator:
|
|
|
148
148
|
def connect_tools_view(self, tools_view: ToolsView) -> None:
|
|
149
149
|
"""Wire ToolsView signals once the view is lazily created."""
|
|
150
150
|
tools_view.update_all_requested.connect(self.on_tool_update)
|
|
151
|
+
tools_view.plugin_check_requested.connect(self.on_single_plugin_update)
|
|
151
152
|
tools_view.plugin_update_requested.connect(self.on_single_plugin_update)
|
|
152
153
|
tools_view.package_update_requested.connect(self.on_single_package_update)
|
|
153
154
|
tools_view.package_remove_requested.connect(self.on_single_package_remove)
|