diff --git a/src/ui/modals.py b/src/ui/modals.py index 7f1c524..e68d69b 100644 --- a/src/ui/modals.py +++ b/src/ui/modals.py @@ -65,6 +65,7 @@ class ProgressScreen(Screen[None]): #prog_bar_container { height: auto; width: 100%; align: center middle; } #prog_bar { margin-bottom: 1; width: 80%; } + #prog_item_status { margin-bottom: 1; text-style: bold; color: cyan; width: 100%; text-align: center; } #info_container { height: auto; layout: vertical; border: solid $secondary; padding: 1; margin-bottom: 1; display: none; } .info_label { text-style: bold; content-align: center middle; width: 100%; color: $secondary-lighten-2; } @@ -95,7 +96,8 @@ class ProgressScreen(Screen[None]): # Progress bar moved inside info container with Center(id="prog_bar_container"): - pb = ProgressBar(total=None, show_eta=False, id="prog_bar") + yield Label("", id="prog_item_status") + pb = ProgressBar(total=None, show_eta=False, show_percentage=False, id="prog_bar") pb.display = False yield pb @@ -192,10 +194,22 @@ class ProgressScreen(Screen[None]): def set_progress(self, current: int, total: int): try: - self.query_one("#prog_loader", LoadingIndicator).display = False + # Keep loader visible during progress next to timer + self.query_one("#prog_loader", LoadingIndicator).display = True + bar = self.query_one("#prog_bar", ProgressBar) bar.display = True bar.update(total=total, progress=current) + + # Ensure the container is visible if we have a bar + self.query_one("#info_container", Vertical).display = True + except Exception: + pass + + def set_item_status(self, status: str): + try: + self.query_one("#prog_item_status", Label).update(status) + self.query_one("#info_container", Vertical).display = True except Exception: pass diff --git a/src/ui/shuttle_ops.py b/src/ui/shuttle_ops.py index 3c39e2e..d4625a1 100644 --- a/src/ui/shuttle_ops.py +++ b/src/ui/shuttle_ops.py @@ -395,6 +395,7 @@ class ShuttlePane(Container): force_mode = (choice == "btn_start_id") modal.cancel_callback = lambda: setattr(self.engine, "is_running", False) modal.phase_progress() + modal.set_status("Cloning Server Template") # Connections already started above if not connections_started: @@ -446,6 +447,7 @@ class ShuttlePane(Container): force_mode = (choice == "btn_start_id") modal.cancel_callback = lambda: setattr(self.engine, "is_running", False) modal.phase_progress() + modal.set_status("Syncing Server Settings") await self.engine.start_connections() self.engine.is_running = True @@ -483,14 +485,14 @@ class ShuttlePane(Container): else: from src.stoat.clone_server import sync_channel_state, migrate_channels - modal.set_status("Processing Server Structure...") + modal.set_item_status("Processing Server Structure...") await sync_channel_state(self.engine) categories = await self.engine.discord_reader.get_categories() channels = await self.engine.discord_reader.get_channels() async def update_progress(item_name, status, current, total): color = "cyan" if status == "Copying" else "yellow" - modal.set_status(f"[{color}]{status}: {item_name}[/{color}]") + modal.set_item_status(f"[{color}]{status}: {item_name}[/{color}]") modal.set_progress(current, total) cloned_info = await migrate_channels(self.engine, progress_callback=update_progress, force=force) @@ -507,11 +509,11 @@ class ShuttlePane(Container): async def _logic_clone_roles(self, modal: ProgressScreen, force: bool = False): roles_mod = fluxer_roles if self.target_platform == "fluxer" else stoat_roles - modal.set_status("Processing Roles...") + modal.set_item_status("Processing Roles...") await roles_mod.sync_roles_state(self.engine) async def update(name, current, total): - modal.set_status(f"[cyan]Copying Role: {name}[/cyan]") + modal.set_item_status(f"[cyan]Copying Role: {name}[/cyan]") modal.set_progress(current, total) cloned = await roles_mod.migrate_roles(self.engine, progress_callback=update, force=force) @@ -521,10 +523,10 @@ class ShuttlePane(Container): async def _logic_sync_permissions(self, modal: ProgressScreen): roles_mod = fluxer_roles if self.target_platform == "fluxer" else stoat_roles - modal.set_status("Syncing Permissions...") + modal.set_item_status("Syncing Permissions...") async def update(name, current, total): - modal.set_status(f"[cyan]Syncing Perms: {name}[/cyan]") + modal.set_item_status(f"[cyan]Syncing Perms: {name}[/cyan]") modal.set_progress(current, total) synced = await roles_mod.sync_permissions(self.engine, progress_callback=update) @@ -541,11 +543,11 @@ class ShuttlePane(Container): async def _logic_copy_assets(self, modal: ProgressScreen, types_to_include: list[str], force: bool = False): asset_mod = stoat_emoji_stickers if self.target_platform == "stoat" else fluxer_emoji_stickers - modal.set_status("Processing Assets...") + modal.set_item_status("Processing Assets...") await asset_mod.sync_assets_state(self.engine) async def update(name, item_type, current, total): - modal.set_status(f"[cyan]Copying {item_type}: {name}[/cyan]") + modal.set_item_status(f"[cyan]Copying {item_type}: {name}[/cyan]") modal.set_progress(current, total) cloned = await asset_mod.migrate_emojis(self.engine, progress_callback=update, types_to_include=types_to_include, force=force) @@ -560,7 +562,7 @@ class ShuttlePane(Container): async def _logic_sync_metadata(self, modal: ProgressScreen, components: list[str]): meta_mod = fluxer_metadata if self.target_platform == "fluxer" else stoat_metadata - modal.set_status("Syncing Server Profile...") + modal.set_item_status("Syncing Server Profile...") async def progress_cb(item, status): color = "green" if status == "DONE" else "red" if status == "ERROR" else "yellow" @@ -751,9 +753,9 @@ class ShuttlePane(Container): choice = await modal.phase_wait_confirm( show_continue=has_previous, show_id=True, - btn_start_label="Start Migration", - btn_continue_label="Continue Migration", - btn_id_label="Start from ID" + btn_start_label="Start from\nFirst Message", + btn_continue_label="Continue\nMigration", + btn_id_label="Start from\nmessage ID" ) logger.info(f"User confirmation choice: {choice}") if choice == "btn_back": @@ -802,7 +804,7 @@ class ShuttlePane(Container): c_threads = current_stats["threads"] c_files = current_stats["attachments"] - modal.set_status(f"[cyan]Migrated {c_msgs}/{total_messages} messages...") + modal.set_item_status(f"[cyan]Migrated {c_msgs}/{total_messages} messages...") modal.set_progress(c_msgs, total_messages) modal.update_stats( @@ -921,6 +923,7 @@ class ShuttlePane(Container): modal.cancel_callback = lambda: setattr(self.engine, "is_running", False) modal.phase_progress() + modal.set_status("Danger Zone: Destructive Operations") self.engine.is_running = True # Writer already started above, no need to reconnect if not target_started: @@ -1080,9 +1083,9 @@ class ShuttlePane(Container): else: from src.stoat.danger_zone import danger_delete_all_channels - modal.set_status("[red]Deleting channels...") + modal.set_item_status("[red]Deleting channels...") async def on_deleted(name, current, total): - modal.set_status(f"[red]Deleting: {name}") + modal.set_item_status(f"[red]Deleting: {name}") modal.set_progress(current, total) count = await danger_delete_all_channels(self.engine, progress_callback=on_deleted) @@ -1095,9 +1098,9 @@ class ShuttlePane(Container): else: from src.stoat.danger_zone import danger_reset_channel_permissions - modal.set_status("[red]Resetting permissions...") + modal.set_item_status("[red]Resetting permissions...") async def on_reset(name, current, total): - modal.set_status(f"[red]Resetting: {name}") + modal.set_item_status(f"[red]Resetting: {name}") modal.set_progress(current, total) count = await danger_reset_channel_permissions(self.engine, progress_callback=on_reset) @@ -1110,9 +1113,9 @@ class ShuttlePane(Container): else: from src.stoat.danger_zone import danger_delete_all_roles - modal.set_status("[red]Deleting roles...") + modal.set_item_status("[red]Deleting roles...") async def on_deleted(name, current, total): - modal.set_status(f"[red]Deleting role: {name}") + modal.set_item_status(f"[red]Deleting role: {name}") modal.set_progress(current, total) count = await danger_delete_all_roles(self.engine, progress_callback=on_deleted) @@ -1125,9 +1128,9 @@ class ShuttlePane(Container): else: from src.stoat.danger_zone import danger_delete_all_emojis_and_stickers - modal.set_status("[red]Deleting assets...") + modal.set_item_status("[red]Deleting assets...") async def on_deleted(name, asset_type, current, total): - modal.set_status(f"[red]Deleting {asset_type}: {name}") + modal.set_item_status(f"[red]Deleting {asset_type}: {name}") modal.set_progress(current, total) counts = await danger_delete_all_emojis_and_stickers(self.engine, progress_callback=on_deleted)