diff --git a/src/ui/main_app.py b/src/ui/main_app.py index 633acd1..94d5060 100644 --- a/src/ui/main_app.py +++ b/src/ui/main_app.py @@ -86,7 +86,7 @@ class ConfigSelectionScreen(Screen): height: 10; max-height: 20; border: solid $primary; margin-bottom: 1; } - #config_sel_actions { height: auto; margin-top: 1; } + #config_sel_actions { height: auto; margin-top: 0; } #config_sel_actions Button { width: 1fr; margin: 0 1; } """ @@ -189,8 +189,9 @@ class ConfigScreen(Screen): height: auto; margin: 0 0 0 2; } #target_section { height: auto; } - #cfg_actions { height: auto; margin-top: 1; margin-bottom: 0; dock: bottom; } + #cfg_actions { height: auto; margin-top: 0; margin-bottom: 0; dock: bottom; } #cfg_actions Button { width: 1fr; margin: 0 1; } + #footer_rule { margin: 0; } .fetch_row { height: auto; align: left middle; margin-bottom: 1; } .fetch_row Input { width: 1fr; } .fetch_row Button { width: auto; margin-left: 1; } @@ -292,7 +293,7 @@ class ConfigScreen(Screen): tooltip="Enter the custom API url\nfor self hosted instances" ) - yield Rule() + yield Rule(id="footer_rule") with Horizontal(id="cfg_actions"): yield Button("Save Configuration", variant="success", id="btn_save", tooltip="Save all changes to config.yaml") yield Button("Back", id="btn_back") diff --git a/src/ui/modals.py b/src/ui/modals.py index ab192fe..f453af0 100644 --- a/src/ui/modals.py +++ b/src/ui/modals.py @@ -70,10 +70,11 @@ class ProgressScreen(Screen[None]): #info_container { height: auto; layout: vertical; border: solid cyan; padding: 1; margin-bottom: 1; display: none; } .info_label { text-style: bold; content-align: center middle; width: 100%; color: cyan; } - #prog_actions { height: auto; margin-top: 1; dock: bottom; margin-bottom: 0; layout: vertical; } + #prog_actions { height: auto; margin-top: 0; dock: bottom; margin-bottom: 0; layout: vertical; } .action_row { height: auto; layout: horizontal; } .action_row Button { width: 1fr; margin: 0 1; } #prog_actions_row1, #prog_actions_row2 { display: none; } + #footer_rule { margin: 0; } """ def compose(self) -> ComposeResult: @@ -99,6 +100,7 @@ class ProgressScreen(Screen[None]): yield RichLog(id="prog_log", highlight=True, markup=True) yield RichLog(id="live_log", highlight=True, markup=True) + yield Rule(id="footer_rule") with Vertical(id="prog_actions"): with Horizontal(classes="action_row", id="prog_actions_row1"): yield Button("Start from First", id="btn_start_first", disabled=True, variant="primary", tooltip="Start the operation from the beginning") @@ -386,7 +388,10 @@ class ProgressScreen(Screen[None]): class SubMenuModal(ModalScreen[str]): """A generic sub-menu modal that presents a list of labelled buttons.""" - DEFAULT_CSS = "SubMenuModal { align: center middle; }" + DEFAULT_CSS = """ + SubMenuModal { align: center middle; } + #footer_rule { margin: 0; } + """ def __init__(self, title: str, options: list[tuple[str, str, str]]): """options: list of (button_id, label, variant)""" @@ -399,7 +404,7 @@ class SubMenuModal(ModalScreen[str]): yield Label(self._title, id="submenu_title") for btn_id, label, variant in self._options: yield Button(label, id=btn_id, variant=variant) - yield Rule() + yield Rule(id="footer_rule") yield Button("Cancel", id="btn_cancel_sub") def on_button_pressed(self, event: Button.Pressed): @@ -432,6 +437,7 @@ class OptionSelectModal(ModalScreen[list[str]]): #opt_batch_buttons Button { width: 1fr; margin: 0 1; } RadioButton { background: transparent; } RadioButton:focus { background: $accent 20%; } + #footer_rule { margin: 0; } """ @@ -452,7 +458,7 @@ class OptionSelectModal(ModalScreen[list[str]]): for opt_id, label in self._options: yield RadioButton(label, id=f"opt_{opt_id}") - yield Rule() + yield Rule(id="footer_rule") with Horizontal(id="opt_buttons"): yield Button("Proceed", variant="success", id="btn_opt_ok", tooltip="Proceed with the selected options") yield Button("Back", id="btn_opt_back", tooltip="Return to the previous menu") @@ -493,17 +499,18 @@ class ChannelPickerScreen(Screen[tuple]): layout: vertical; border: solid green; padding: 1 2; - margin: 2 0; + margin: 1 0; background: $surface; } #chanpick_title { text-style: bold; margin-bottom: 1; content-align: center middle; width: 100%; } #chanpick_split { height: 1fr; layout: horizontal; + margin-bottom: 1; } .split_pane { width: 1fr; - height: 100%; + height: 1fr; border: solid $primary; margin: 0 1; padding: 0 1; @@ -531,7 +538,8 @@ class ChannelPickerScreen(Screen[tuple]): padding-left: 1; color: cyan; } - #chanpick_buttons { height: auto; margin-top: 1; dock: bottom; margin-bottom: 0; } + #footer_rule { margin: 0; } + #chanpick_buttons { height: auto; margin-top: 0; margin-bottom: 0; } #chanpick_buttons Button { width: 1fr; margin: 0 1; } """ @@ -585,7 +593,7 @@ class ChannelPickerScreen(Screen[tuple]): yield Label(f"Target: {self.tgt_name}", classes="pane_title") yield from self._render_pane(self.tgt_channels, self.tgt_cat_map, "pane_tgt", "tgt") - yield Rule() + yield Rule(id="footer_rule") with Horizontal(id="chanpick_buttons"): yield Button("Select", variant="success", id="btn_pick_ok", tooltip="Confirm selection and start migration") yield Button("Back", id="btn_pick_back", tooltip="Cancel selection") @@ -685,8 +693,9 @@ class ChannelSelectScreen(Screen[dict]): } #select_all_buttons { height: auto; margin-bottom: 1; } #select_all_buttons Button { width: auto; margin-right: 1; } - #confirm_buttons { height: auto; margin-top: 1; dock: bottom; margin-bottom: 0; } + #confirm_buttons { height: auto; margin-top: 0; dock: bottom; margin-bottom: 0; } #confirm_buttons Button { width: 1fr; margin: 0 1; } + #footer_rule { margin: 0; } """ def __init__(self, channels: list, categories: dict, backed_up_ids: set, any_found: bool): @@ -741,7 +750,7 @@ class ChannelSelectScreen(Screen[dict]): yield Button("Select All", id="btn_all", tooltip="Select all channels for backup") yield Button("Deselect All", id="btn_none", tooltip="Deselect all channels") - yield Rule() + yield Rule(id="footer_rule") with Horizontal(id="confirm_buttons"): if self.any_found: yield Button("Sync", variant="success", id="btn_sync", tooltip="Backup new channels\n& update existing backups") @@ -798,7 +807,7 @@ class MessageIDInputModal(ModalScreen[int | None]): #msg_id_buttons { height: auto; dock: bottom; - margin-top: 1; + margin-top: 0; } #msg_id_buttons Button { width: 1fr; margin: 0 1; } """ diff --git a/src/ui/mode_screen.py b/src/ui/mode_screen.py index 8f051d8..a1eb2f9 100644 --- a/src/ui/mode_screen.py +++ b/src/ui/mode_screen.py @@ -50,6 +50,7 @@ class ModeScreen(Screen): width: 1fr; margin: 0 1; } + #footer_rule { margin: 0; } ModalScreen { align: center middle; @@ -83,7 +84,7 @@ class ModeScreen(Screen): text-style: bold; margin-bottom: 1; } #config_buttons, #confirm_buttons, #chanpick_buttons { - height: auto; margin-top: 1; + height: auto; margin-top: 0; } #config_buttons Button, #confirm_buttons Button, #chanpick_buttons Button { width: 1fr; margin: 0 1; @@ -130,7 +131,7 @@ class ModeScreen(Screen): yield ShuttlePane(self.cfg_name, self.config_path, id="pane_migrate") with Vertical(id="mode_footer"): - yield Rule() + yield Rule(id="footer_rule") with Horizontal(id="bottom_actions"): if mode == "backup_transfer": yield Button("Switch to Migrate ⇄", id="btn_switch", variant="primary", tooltip="Switch between\nBackup & Migrate operations") diff --git a/src/ui/shuttle_ops.py b/src/ui/shuttle_ops.py index 5e5b218..e0890ec 100644 --- a/src/ui/shuttle_ops.py +++ b/src/ui/shuttle_ops.py @@ -96,6 +96,7 @@ class ShuttlePane(Container): ShuttlePane #sp_actions { height: auto; } ShuttlePane #sp_actions Button { width: 100%; margin-bottom: 1; } + #footer_rule { margin: 0; } """ def __init__(self, cfg_name: str, cfg_path: Path, *args, **kwargs): @@ -132,7 +133,7 @@ class ShuttlePane(Container): yield Button("Clone Server Template", id="sp_clone", disabled=True, tooltip="Clone server roles, categories, and channels to the target community") yield Button("Sync Server Settings", id="sp_sync", disabled=True, tooltip="Sync emojis, stickers, server name, and icon to the target community") yield Button("Migrate Message History", id="sp_messages", disabled=True, variant="primary", tooltip="Migrate message history from Discord to the target platform") - yield Rule() + yield Rule(id="footer_rule") yield Button("Danger Zone ⚠", id="sp_danger", variant="error", disabled=True, flat=True, tooltip="Dangerous operations:\ndelete channels, roles, emojis on target\n(use with caution)") def on_mount(self) -> None: