diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index 6ed9dd7a..7233a982 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -149,7 +149,7 @@ class ConfigurationOutput: warnings: list[str] = [] if not isinstance(self._config.network_config, NetworkConfiguration): - warnings.append(tr('Warning: no network configuration selected. Network will need to be set up manually on the installed system.')) + warnings.append(tr('No network configuration selected. Network will need to be set up manually on the installed system.')) return warnings diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 050e915b..cea8a218 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -1,7 +1,5 @@ from typing import override -from rich.markup import escape as _escape_markup - from archinstall.default_profiles.profile import GreeterType from archinstall.lib.applications.application_menu import ApplicationMenu from archinstall.lib.args import ArchConfig @@ -35,7 +33,7 @@ from archinstall.lib.pacman.config import PacmanConfig from archinstall.lib.pacman.pacman_menu import PacmanMenu from archinstall.lib.translationhandler import Language, tr, translation_handler from archinstall.tui.ui.components import tui -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup, MsgLevelType, PreviewResult class GlobalMenu(AbstractMenu[None]): @@ -185,7 +183,6 @@ class GlobalMenu(AbstractMenu[None]): MenuItem( text=tr('Install'), preview_action=self._prev_install_invalid_config, - preview_markup=True, key=SpecialMenuKey.INSTALL.value, ), MenuItem( @@ -497,42 +494,40 @@ class GlobalMenu(AbstractMenu[None]): return None - def _prev_install_invalid_config(self, item: MenuItem) -> str | None: + def _prev_install_invalid_config(self, item: MenuItem) -> str | PreviewResult | list[PreviewResult] | None: self.sync_all_to_config() config_output = ConfigurationOutput(self._arch_config) warnings = config_output.get_install_warnings() - warnings_text = '' - if warnings: - warnings_text = f'\n\n[yellow]{_escape_markup(tr("Warnings:"))}\n' - for w in warnings: - warnings_text += f'- {_escape_markup(w)}\n' - warnings_text = warnings_text.rstrip('\n') + '[/yellow]' - - blocks = '' + sections: list[PreviewResult] = [] + errors = '' if missing := self._missing_configs(): - text = f'[red]{_escape_markup(tr("Missing configurations:"))}\n' - for m in missing: - text += f'- {_escape_markup(m)}\n' - blocks += text.rstrip('\n') + '[/red]' + errors += f'{tr("Missing configurations:")}\n' + errors += '\n'.join(f'- {m}' for m in missing) disk_item = self._item_group.find_by_key('disk_config') if disk_item.has_value(): if error := self._validate_bootloader(): - if blocks: - blocks += '\n\n' - text = f'[red]{_escape_markup(tr("Invalid configuration:"))}\n' - text += f'- {_escape_markup(error)}' - blocks += text + '[/red]' + if errors: + errors += '\n\n' + errors += f'{tr("Invalid configuration:")}\n- {error}' - if blocks: - return blocks + warnings_text + if errors: + sections.append(PreviewResult(errors, MsgLevelType.MsgError)) + else: + sections.append(PreviewResult(tr('Ready to install'), MsgLevelType.MsgInfo)) - summary = config_output.as_summary() - if summary: - return f'[green]{_escape_markup(tr("Ready to install"))}[/green]{warnings_text}\n\n{_escape_markup(summary)}' - return f'[green]{_escape_markup(tr("Ready to install"))}[/green]{warnings_text}' + if warnings: + text = f'{tr("Warnings:")}\n' + '\n'.join(f'- {w}' for w in warnings) + sections.append(PreviewResult(text, MsgLevelType.MsgWarning)) + + if not errors: + summary = config_output.as_summary() + if summary: + sections.append(PreviewResult(summary, MsgLevelType.MsgNone)) + + return sections def _prev_profile(self, item: MenuItem) -> str | None: profile_config: ProfileConfiguration | None = item.value diff --git a/archinstall/lib/menu/util.py b/archinstall/lib/menu/util.py index ff3dea47..5348e049 100644 --- a/archinstall/lib/menu/util.py +++ b/archinstall/lib/menu/util.py @@ -5,7 +5,8 @@ from pathlib import Path from archinstall.lib.menu.helpers import Confirmation, Input from archinstall.lib.models.users import Password, PasswordStrength from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.components import InputInfo, InputInfoType, tui +from archinstall.tui.ui.components import InputInfo, tui +from archinstall.tui.ui.menu_item import MsgLevelType from archinstall.tui.ui.result import ResultType @@ -20,11 +21,11 @@ async def get_password( return None strength = PasswordStrength.strength(value) if strength in (PasswordStrength.VERY_WEAK, PasswordStrength.WEAK): - return InputInfo(message=tr('Password strength: Weak'), info_type=InputInfoType.MsgError) + return InputInfo(message=tr('Password strength: Weak'), msg_level=MsgLevelType.MsgError) elif strength == PasswordStrength.MODERATE: - return InputInfo(message=tr('Password strength: Moderate'), info_type=InputInfoType.MsgWarning) + return InputInfo(message=tr('Password strength: Moderate'), msg_level=MsgLevelType.MsgWarning) elif strength == PasswordStrength.STRONG: - return InputInfo(message=tr('Password strength: Strong'), info_type=InputInfoType.MsgInfo) + return InputInfo(message=tr('Password strength: Strong'), msg_level=MsgLevelType.MsgInfo) return None while True: diff --git a/archinstall/tui/ui/components.py b/archinstall/tui/ui/components.py index 07bf9d90..0d21c2ed 100644 --- a/archinstall/tui/ui/components.py +++ b/archinstall/tui/ui/components.py @@ -2,7 +2,6 @@ import sys from abc import ABC, abstractmethod from collections.abc import Awaitable, Callable from dataclasses import dataclass, replace -from enum import Enum, auto from typing import Any, ClassVar, Literal, TypeVar, cast, override from rich.text import Text @@ -22,12 +21,39 @@ from textual.worker import WorkerCancelled from archinstall.lib.output import debug from archinstall.lib.translationhandler import tr -from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup +from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup, MsgLevelType, PreviewResult from archinstall.tui.ui.result import Result, ResultType ValueT = TypeVar('ValueT') +_LEVEL_STYLE: dict[MsgLevelType, str] = { + MsgLevelType.MsgNone: '', + MsgLevelType.MsgError: 'red', + MsgLevelType.MsgWarning: 'bright_yellow', + MsgLevelType.MsgInfo: 'green', +} + + +def _update_preview(widget: Label, result: str | PreviewResult | list[PreviewResult] | None) -> None: + if result is None: + widget.update('') + return + + if isinstance(result, str): + widget.update(result) + elif isinstance(result, PreviewResult): + text = Text(result.message, style=_LEVEL_STYLE[result.msg_level]) + widget.update(text) + else: + text = Text() + for i, section in enumerate(result): + if i > 0: + text.append('\n\n') + text.append(section.message, style=_LEVEL_STYLE[section.msg_level]) + widget.update(text) + + def _translate_bindings(source: BindingsMap | None, target: BindingsMap) -> None: """Translate binding descriptions from source to target. @@ -357,14 +383,9 @@ class OptionListScreen(BaseScreen[ValueT]): item = self._group.find_by_id(item_id) if item.preview_action is not None: - maybe_preview = item.preview_action(item) - - if maybe_preview is not None: - content = Text.from_markup(maybe_preview) if item.preview_markup else maybe_preview - preview_widget.update(content) - return - - preview_widget.update('') + _update_preview(preview_widget, item.preview_action(item)) + else: + _update_preview(preview_widget, None) class _SelectionList(SelectionList[ValueT]): @@ -601,13 +622,9 @@ class SelectListScreen(BaseScreen[ValueT]): preview_widget = self.query_one('#preview_content', Label) if item.preview_action is not None: - maybe_preview = item.preview_action(item) - if maybe_preview is not None: - content = Text.from_markup(maybe_preview) if item.preview_markup else maybe_preview - preview_widget.update(content) - return - - preview_widget.update('') + _update_preview(preview_widget, item.preview_action(item)) + else: + _update_preview(preview_widget, None) # DEPRECATED: Removed when switching to async @@ -723,14 +740,8 @@ class ConfirmationScreen(BaseScreen[ValueT]): if self._preview_header is not None: preview = self.query_one('#preview_content', Label) - - if focused.preview_action is None: - preview.update('') - else: - text = focused.preview_action(focused) - if text is not None: - content = Text.from_markup(text) if focused.preview_markup else text - preview.update(content) + result = focused.preview_action(focused) if focused.preview_action else None + _update_preview(preview, result) else: button.remove_class('-active') @@ -757,16 +768,10 @@ class NotifyScreen(ConfirmationScreen[ValueT]): super().__init__(group, header) -class InputInfoType(Enum): - MsgInfo = auto() - MsgWarning = auto() - MsgError = auto() - - @dataclass class InputInfo: message: str - info_type: InputInfoType + msg_level: MsgLevelType class InputScreen(BaseScreen[str]): @@ -878,11 +883,11 @@ class InputScreen(BaseScreen[str]): result = self._info_callback(event.value) if result: css_class = '' - if result.info_type == InputInfoType.MsgError: + if result.msg_level == MsgLevelType.MsgError: css_class = 'input-hint-msg-error' - elif result.info_type == InputInfoType.MsgWarning: + elif result.msg_level == MsgLevelType.MsgWarning: css_class = 'input-hint-msg-warning' - elif result.info_type == InputInfoType.MsgInfo: + elif result.msg_level == MsgLevelType.MsgInfo: css_class = 'input-hint-msg-info' info_label.update(result.message) info_label.set_classes(css_class) @@ -1127,14 +1132,7 @@ class TableSelectionScreen(BaseScreen[ValueT]): return preview_widget = self.query_one('#preview_content', Label) - - maybe_preview = item.preview_action(item) - if maybe_preview is not None: - content = Text.from_markup(maybe_preview) if item.preview_markup else maybe_preview - preview_widget.update(content) - return - - preview_widget.update('') + _update_preview(preview_widget, item.preview_action(item)) def _set_cursor(self, row_index: int) -> None: data_table = self.query_one(DataTable) @@ -1271,6 +1269,7 @@ class _AppInstance(App[ValueT]): background: black; border-left: vkey white 20%; } + """ def __init__(self, main: InstanceRunnable[ValueT] | Callable[[], Awaitable[ValueT]]) -> None: diff --git a/archinstall/tui/ui/menu_item.py b/archinstall/tui/ui/menu_item.py index 728289b0..0e3552fe 100644 --- a/archinstall/tui/ui/menu_item.py +++ b/archinstall/tui/ui/menu_item.py @@ -1,12 +1,27 @@ +from __future__ import annotations + from collections.abc import Awaitable, Callable, Iterable from dataclasses import dataclass, field -from enum import Enum +from enum import Enum, auto from functools import cached_property from typing import Any, ClassVar, Self, override from archinstall.lib.translationhandler import tr +class MsgLevelType(Enum): + MsgNone = auto() + MsgInfo = auto() + MsgWarning = auto() + MsgError = auto() + + +@dataclass +class PreviewResult: + message: str + msg_level: MsgLevelType + + @dataclass class MenuItem: text: str @@ -18,8 +33,7 @@ class MenuItem: dependencies: list[str | Callable[[], bool]] = field(default_factory=list) dependencies_not: list[str] = field(default_factory=list) display_action: Callable[[Any], str] | None = None - preview_action: Callable[[Self], str | None] | None = None - preview_markup: bool = False + preview_action: Callable[[Self], str | PreviewResult | list[PreviewResult] | None] | None = None key: str | None = None _id: str = ''