diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index 3e9135bf..6ed9dd7a 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -124,13 +124,10 @@ class ConfigurationOutput: label_width = max(len(label) for label, _ in rows) + 2 return '\n'.join(f'{label:<{label_width}}{value}' for label, value in rows) - async def confirm_config(self, show_install_warnings: bool = False) -> bool: + async def confirm_config(self) -> bool: header = f'{tr("The specified configuration will be applied")}. ' header += tr('Would you like to continue?') + '\n' - if show_install_warnings: - header += self._render_install_warnings() - group = MenuItemGroup.yes_no() group.set_preview_for_all(lambda x: self.user_config_to_json()) @@ -156,14 +153,6 @@ class ConfigurationOutput: return warnings - def _render_install_warnings(self) -> str: - warnings = self.get_install_warnings() - - if not warnings: - return '' - - return '\n' + '\n'.join(f'[yellow]{w}[/]' for w in warnings) + '\n' - def _is_valid_path(self, dest_path: Path) -> bool: dest_path_ok = dest_path.exists() and dest_path.is_dir() if not dest_path_ok: diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index cdd820a9..c68b717e 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -1,5 +1,7 @@ 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 @@ -183,6 +185,7 @@ class GlobalMenu(AbstractMenu[None]): MenuItem( text=tr('Install'), preview_action=self._prev_install_invalid_config, + preview_markup=True, key=SpecialMenuKey.INSTALL.value, ), MenuItem( @@ -495,20 +498,30 @@ class GlobalMenu(AbstractMenu[None]): return None def _prev_install_invalid_config(self, item: MenuItem) -> str | 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]' + if missing := self._missing_configs(): - text = tr('Missing configurations:\n') + text = f'[red]{_escape_markup(tr("Missing configurations:"))}\n' for m in missing: - text += f'- {m}\n' - return text[:-1] # remove last new line + text += f'- {_escape_markup(m)}\n' + return text.rstrip('\n') + '[/red]' + warnings_text if error := self._validate_bootloader(): - return tr(f'Invalid configuration: {error}') + return f'[red]{_escape_markup(tr(f"Invalid configuration: {error}"))}[/red]' + warnings_text - self.sync_all_to_config() - summary = ConfigurationOutput(self._arch_config).as_summary() + summary = config_output.as_summary() if summary: - return f'{tr("Ready to install")}\n\n{summary}' - return tr('Ready to install') + 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}' def _prev_profile(self, item: MenuItem) -> str | None: profile_config: ProfileConfiguration | None = item.value diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index 21228480..5cdcdad9 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -226,7 +226,7 @@ def main(arch_config_handler: ArchConfigHandler | None = None) -> None: if not arch_config_handler.args.silent: aborted = False - res: bool = tui.run(lambda: config.confirm_config(show_install_warnings=True)) + res: bool = tui.run(config.confirm_config) if not res: debug('Installation aborted') diff --git a/archinstall/scripts/minimal.py b/archinstall/scripts/minimal.py index 681d4266..4e897142 100644 --- a/archinstall/scripts/minimal.py +++ b/archinstall/scripts/minimal.py @@ -77,7 +77,7 @@ async def main(arch_config_handler: ArchConfigHandler | None = None) -> None: if not arch_config_handler.args.silent: aborted = False - res: bool = tui.run(lambda: config.confirm_config(show_install_warnings=True)) + res: bool = tui.run(config.confirm_config) if not res: debug('Installation aborted') diff --git a/archinstall/tui/ui/components.py b/archinstall/tui/ui/components.py index da6dc66b..405f59af 100644 --- a/archinstall/tui/ui/components.py +++ b/archinstall/tui/ui/components.py @@ -5,6 +5,7 @@ from dataclasses import dataclass, replace from enum import Enum, auto from typing import Any, ClassVar, Literal, TypeVar, cast, override +from rich.markup import escape as _escape_markup from textual import work from textual.app import App, ComposeResult from textual.binding import Binding, BindingsMap @@ -280,7 +281,7 @@ class OptionListScreen(BaseScreen[ValueT]): with Container(): yield option_list yield Rule(orientation=rule_orientation) - yield ScrollableContainer(Label('', id='preview_content', markup=False)) + yield ScrollableContainer(Label('', id='preview_content', markup=True)) if self._filter: yield Input(placeholder='/filter', id='filter-input') @@ -359,6 +360,8 @@ class OptionListScreen(BaseScreen[ValueT]): maybe_preview = item.preview_action(item) if maybe_preview is not None: + if not item.preview_markup: + maybe_preview = _escape_markup(maybe_preview) preview_widget.update(maybe_preview) return @@ -510,7 +513,7 @@ class SelectListScreen(BaseScreen[ValueT]): with Container(): yield selection_list yield Rule(orientation=rule_orientation) - yield ScrollableContainer(Label('', id='preview_content', markup=False)) + yield ScrollableContainer(Label('', id='preview_content', markup=True)) if self._filter: yield Input(placeholder='/filter', id='filter-input') @@ -601,6 +604,8 @@ class SelectListScreen(BaseScreen[ValueT]): if item.preview_action is not None: maybe_preview = item.preview_action(item) if maybe_preview is not None: + if not item.preview_markup: + maybe_preview = _escape_markup(maybe_preview) preview_widget.update(maybe_preview) return @@ -688,7 +693,7 @@ class ConfirmationScreen(BaseScreen[ValueT]): yield Rule(orientation='horizontal') if self._preview_header is not None: yield Label(self._preview_header, classes='preview-header', id='preview_header') - yield ScrollableContainer(Label('', id='preview_content', markup=False)) + yield ScrollableContainer(Label('', id='preview_content', markup=True)) yield Footer() @@ -726,6 +731,8 @@ class ConfirmationScreen(BaseScreen[ValueT]): else: text = focused.preview_action(focused) if text is not None: + if not focused.preview_markup: + text = _escape_markup(text) preview.update(text) else: button.remove_class('-active') @@ -1016,7 +1023,7 @@ class TableSelectionScreen(BaseScreen[ValueT]): yield Rule(orientation='horizontal') if self._preview_header is not None: yield Label(self._preview_header, classes='preview-header', id='preview-header') - yield ScrollableContainer(Label('', id='preview_content', markup=False)) + yield ScrollableContainer(Label('', id='preview_content', markup=True)) yield Footer() @@ -1126,6 +1133,8 @@ class TableSelectionScreen(BaseScreen[ValueT]): maybe_preview = item.preview_action(item) if maybe_preview is not None: + if not item.preview_markup: + maybe_preview = _escape_markup(maybe_preview) preview_widget.update(maybe_preview) return diff --git a/archinstall/tui/ui/menu_item.py b/archinstall/tui/ui/menu_item.py index 4c10e275..728289b0 100644 --- a/archinstall/tui/ui/menu_item.py +++ b/archinstall/tui/ui/menu_item.py @@ -19,6 +19,7 @@ class MenuItem: 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 key: str | None = None _id: str = ''