Skip to content

Commit 4c20331

Browse files
authored
Fix 3376 - header alignment (#3414)
1 parent c161d6f commit 4c20331

7 files changed

Lines changed: 118 additions & 183 deletions

File tree

archinstall/lib/disk/encryption_menu.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -262,21 +262,21 @@ def select_encrypted_password() -> Password | None:
262262

263263

264264
def select_hsm(preset: Fido2Device | None = None) -> Fido2Device | None:
265-
header = str(_('Select a FIDO2 device to use for HSM'))
265+
header = str(_('Select a FIDO2 device to use for HSM')) + '\n'
266266

267267
try:
268268
fido_devices = Fido2.get_fido2_devices()
269269
except ValueError:
270270
return None
271271

272272
if fido_devices:
273-
group, table_header = MenuHelper.create_table(data=fido_devices)
274-
header = f'{header}\n\n{table_header}'
273+
group = MenuHelper(data=fido_devices).create_menu_group()
275274

276275
result = SelectMenu[Fido2Device](
277276
group,
278277
header=header,
279278
alignment=Alignment.CENTER,
279+
allow_skip=True
280280
).run()
281281

282282
match result.type_:
@@ -307,13 +307,14 @@ def select_partitions_to_encrypt(
307307
avail_partitions = [p for p in partitions if not p.exists()]
308308

309309
if avail_partitions:
310-
group, header = MenuHelper.create_table(data=avail_partitions)
310+
group = MenuHelper(data=avail_partitions).create_menu_group()
311+
group.set_selected_by_value(preset)
311312

312313
result = SelectMenu[PartitionModification](
313314
group,
314-
header=header,
315315
alignment=Alignment.CENTER,
316-
multi=True
316+
multi=True,
317+
allow_skip=True
317318
).run()
318319

319320
match result.type_:
@@ -335,11 +336,10 @@ def select_lvm_vols_to_encrypt(
335336
volumes: list[LvmVolume] = lvm_config.get_all_volumes()
336337

337338
if volumes:
338-
group, header = MenuHelper.create_table(data=volumes)
339+
group = MenuHelper(data=volumes).create_menu_group()
339340

340341
result = SelectMenu[LvmVolume](
341342
group,
342-
header=header,
343343
alignment=Alignment.CENTER,
344344
multi=True
345345
).run()

archinstall/lib/interactions/disk_conf.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,12 @@ def _preview_device_selection(item: MenuItem) -> str | None:
6060
options = [d.device_info for d in devices]
6161
presets = [p.device_info for p in preset]
6262

63-
group, header = MenuHelper.create_table(data=options)
63+
group = MenuHelper(options).create_menu_group()
6464
group.set_selected_by_value(presets)
6565
group.set_preview_for_all(_preview_device_selection)
6666

6767
result = SelectMenu[_DeviceInfo](
6868
group,
69-
header=header,
7069
alignment=Alignment.CENTER,
7170
search_enabled=False,
7271
multi=True,

archinstall/lib/menu/abstract_menu.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ def __exit__(self, *args: Any, **kwargs: Any) -> None:
5353
self.sync_all_to_config()
5454

5555
def _sync_from_config(self) -> None:
56-
for item in self._menu_item_group.menu_items:
56+
for item in self._menu_item_group._menu_items:
5757
if item.key is not None and not item.key.startswith(CONFIG_KEY):
5858
config_value = getattr(self._config, item.key)
5959
if config_value is not None:
6060
item.value = config_value
6161

6262
def sync_all_to_config(self) -> None:
63-
for item in self._menu_item_group.menu_items:
63+
for item in self._menu_item_group._menu_items:
6464
if item.key:
6565
setattr(self._config, item.key, item.value)
6666

@@ -135,7 +135,7 @@ def __init__(
135135
allow_reset: bool = False
136136
):
137137
back_text = f'{Chars.Right_arrow} ' + str(_('Back'))
138-
item_group.menu_items.append(MenuItem(text=back_text))
138+
item_group.add_item(MenuItem(text=back_text))
139139

140140
super().__init__(
141141
item_group,

archinstall/lib/menu/list_manager.py

Lines changed: 12 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import copy
2-
from typing import TYPE_CHECKING, Any, cast
2+
from typing import TYPE_CHECKING, cast
33

4+
from archinstall.lib.menu.menu_helper import MenuHelper
45
from archinstall.tui.curses_menu import SelectMenu
56
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
67
from archinstall.tui.result import ResultType
78
from archinstall.tui.types import Alignment
89

9-
from ..output import FormattedOutput
10-
1110
if TYPE_CHECKING:
1211
from collections.abc import Callable
1312

@@ -63,23 +62,23 @@ def is_last_choice_cancel(self) -> bool:
6362
return False
6463

6564
def run(self) -> list[ValueT]:
66-
while True:
67-
# this will return a dictionary with the key as the menu entry to be displayed
68-
# and the value is the original value from the self._data container
69-
data_formatted = self.reformat(self._data)
70-
options = self._prepare_selection(data_formatted)
65+
additional_options = self._base_actions + self._terminate_actions
7166

72-
header = self._get_header(data_formatted)
67+
while True:
68+
group = MenuHelper(
69+
data=self._data,
70+
additional_options=additional_options
71+
).create_menu_group()
7372

73+
prompt = None
7474
if self._prompt is not None:
75-
header = f'{self._prompt}\n\n{header}'
75+
prompt = f'{self._prompt}\n\n'
7676

77-
items = [MenuItem(o[0], value=o[1]) for o in options]
78-
group = MenuItemGroup(items, sort_items=False)
77+
prompt = None
7978

8079
result = SelectMenu[ValueT | str](
8180
group,
82-
header=header,
81+
header=prompt,
8382
search_enabled=False,
8483
allow_skip=False,
8584
alignment=Alignment.CENTER
@@ -109,25 +108,6 @@ def run(self) -> list[ValueT]:
109108
else:
110109
return self._data
111110

112-
def _get_header(self, data_formatted: dict[str, Any]) -> str:
113-
table_header = [key for key, val in data_formatted.items() if val is None]
114-
header = '\n'.join(table_header)
115-
return header
116-
117-
def _prepare_selection(self, data_formatted: dict[str, Any]) -> list[tuple[str, str | ValueT]]:
118-
# header rows are mapped to None so make sure
119-
# to exclude those from the selectable data
120-
options = [(key, val) for key, val in data_formatted.items() if val is not None]
121-
122-
if len(options) > 0:
123-
options.append((self._separator, None))
124-
125-
additional_options = self._base_actions + self._terminate_actions
126-
for o in additional_options:
127-
options.append((o, o))
128-
129-
return options
130-
131111
def _run_actions_on_entry(self, entry: ValueT) -> None:
132112
options = self.filter_options(entry, self._sub_menu_actions) + [self._cancel_action]
133113

@@ -153,27 +133,6 @@ def _run_actions_on_entry(self, entry: ValueT) -> None:
153133
if value != self._cancel_action:
154134
self._data = self.handle_action(value, entry, self._data)
155135

156-
def reformat(self, data: list[Any]) -> dict[str, Any | None]:
157-
"""
158-
Default implementation of the table to be displayed.
159-
Override if any custom formatting is needed
160-
"""
161-
display_data: dict[str, Any | None] = {}
162-
163-
if data:
164-
table = FormattedOutput.as_table(data)
165-
rows = table.split('\n')
166-
167-
# these are the header rows of the table and do not map to any User obviously
168-
# we're adding 2 spaces as prefix because the menu selector '> ' will be put before
169-
# the selectable rows so the header has to be aligned
170-
display_data = {f'{rows[0]}': None, f'{rows[1]}': None}
171-
172-
for row, entry in zip(rows[2:], data):
173-
display_data[row] = entry
174-
175-
return display_data
176-
177136
def selected_action_display(self, selection: ValueT) -> str:
178137
"""
179138
this will return the value to be displayed in the

archinstall/lib/menu/menu_helper.py

Lines changed: 41 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,52 @@
55

66

77
class MenuHelper:
8-
@staticmethod
9-
def create_table(
10-
data: list[Any] | None = None,
11-
table_data: tuple[list[Any], str] | None = None,
12-
) -> tuple[MenuItemGroup, str]:
13-
if data is not None:
14-
table_text = FormattedOutput.as_table(data)
15-
rows = table_text.split('\n')
16-
table = MenuHelper._create_table(data, rows)
17-
elif table_data is not None:
18-
# we assume the table to be
19-
# h1 | h2
20-
# -----------
21-
# r1 | r2
22-
data = table_data[0]
23-
rows = table_data[1].split('\n')
24-
table = MenuHelper._create_table(data, rows)
25-
else:
26-
raise ValueError('Either "data" or "table_data" must be provided')
27-
28-
table, header = MenuHelper._prepare_selection(table)
29-
30-
items = [
31-
MenuItem(text, value=entry)
32-
for text, entry in table.items()
33-
]
8+
def __init__(
9+
self,
10+
data: list[Any],
11+
additional_options: list[str] = []
12+
) -> None:
13+
self._separator = ''
14+
self._data = data
15+
self._additional_options = additional_options
16+
17+
def create_menu_group(self) -> MenuItemGroup:
18+
table_data_mapping = self._table_to_data_mapping(self._data)
19+
20+
items = []
21+
for key, value in table_data_mapping.items():
22+
item = MenuItem(key, value=value)
23+
24+
if value is None:
25+
item.read_only = True
26+
27+
items.append(item)
28+
3429
group = MenuItemGroup(items, sort_items=False)
3530

36-
return group, header
31+
return group
3732

38-
@staticmethod
39-
def _create_table(data: list[Any], rows: list[str], header_padding: int = 2) -> dict[str, Any]:
40-
# these are the header rows of the table and do not map to any data obviously
41-
# we're adding 2 spaces as prefix because the menu selector '> ' will be put before
42-
# the selectable rows so the header has to be aligned
43-
padding = ' ' * header_padding
44-
display_data = {f'{padding}{rows[0]}': None, f'{padding}{rows[1]}': None}
33+
def _get_table_header(self, data_formatted: dict[str, Any]) -> list[str]:
34+
table_header = [key for key, val in data_formatted.items() if val is None]
35+
return table_header
4536

46-
for row, entry in zip(rows[2:], data):
47-
display_data[row] = entry
37+
def _table_to_data_mapping(self, data: list[Any]) -> dict[str, Any | None]:
38+
display_data: dict[str, Any | None] = {}
4839

49-
return display_data
40+
if data:
41+
table = FormattedOutput.as_table(data)
42+
rows = table.split('\n')
43+
44+
# these are the header rows of the table
45+
display_data = {f'{rows[0]}': None, f'{rows[1]}': None}
5046

51-
@staticmethod
52-
def _prepare_selection(table: dict[str, Any]) -> tuple[dict[str, Any], str]:
53-
# header rows are mapped to None so make sure to exclude those from the selectable data
54-
options = {key: val for key, val in table.items() if val is not None}
55-
header = ''
47+
for row, entry in zip(rows[2:], data):
48+
display_data[row] = entry
5649

57-
if len(options) > 0:
58-
table_header = [key for key, val in table.items() if val is None]
59-
header = '\n'.join(table_header)
50+
if self._additional_options:
51+
display_data[self._separator] = None
6052

61-
return options, header
53+
for option in self._additional_options:
54+
display_data[option] = option
55+
56+
return display_data

0 commit comments

Comments
 (0)