Color-code install preview: red for errors, yellow for warnings, green for ready
Add preview_markup opt-in field to MenuItem with automatic Rich markup escaping for all existing previews. Show missing configs and bootloader errors in red, network warning in yellow, "Ready to install" in green. Move network warning from confirmation dialog to install preview so it is visible earlier.
This commit is contained in:
parent
76629ecc15
commit
0b235cc8f8
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = ''
|
||||
|
|
|
|||
Loading…
Reference in New Issue