diff --git a/src/core/backup_reader.py b/src/core/backup_reader.py index 8a0569d..1098f46 100644 --- a/src/core/backup_reader.py +++ b/src/core/backup_reader.py @@ -617,6 +617,11 @@ class BackupGuild: def categories(self) -> List[BackupCategory]: return self._reader._categories if self._reader else [] + def get_member(self, user_id: int) -> "BackupMember | None": + if self._reader: + return self._reader._member_map.get(int(user_id)) + return None + def __repr__(self) -> str: return f"BackupGuild(id={self.id}, name='{self.name}')" diff --git a/src/ui/backup_ops.py b/src/ui/backup_ops.py index 4a02255..f65eb93 100644 --- a/src/ui/backup_ops.py +++ b/src/ui/backup_ops.py @@ -158,14 +158,14 @@ class BackupPane(Container): await self.exporter.setup() # Gather and print summary - server = getattr(self.engine.discord_reader, 'current_server', None) + server = getattr(self.engine.discord_reader, 'guild', None) if server: modal.write(f"[bold cyan]Server Profile to Backup:[/bold cyan]") modal.write(f" Name: [green]{server.name}[/green]") modal.write(f" Icon: [green]{'Present' if server.icon else 'None'}[/green]") - modal.write(f" Roles: [green]{len(server.roles)}[/green]") - modal.write(f" Emojis: [green]{len(server.emojis)}[/green]") - modal.write(f" Channels: [green]{len(server.channels)}[/green]") + modal.write(f" Roles: [green]{len(getattr(server, 'roles', []))}[/green]") + modal.write(f" Emojis: [green]{len(getattr(server, 'emojis', []))}[/green]") + modal.write(f" Channels: [green]{len(getattr(server, 'channels', []))}[/green]") modal.write("") modal.show_info("[bold cyan]Profile Backup Ready[/bold cyan]", f"Overview: {len(server.channels) if server else '?'} Channels, {len(server.roles) if server else '?'} Roles") @@ -280,7 +280,7 @@ class BackupPane(Container): new_channels = [c for c in selected_channels if c.id not in backed_up_ids] existing_channels = [c for c in selected_channels if c.id in backed_up_ids] - server = getattr(self.engine.discord_reader, 'current_server', None) + server = getattr(self.engine.discord_reader, 'guild', None) if server: modal_prog.write(f"[bold cyan]Server Profile:[/bold cyan]") modal_prog.write(f" Name: [green]{server.name}[/green]") @@ -391,14 +391,14 @@ class BackupPane(Container): await self.exporter.setup() # Gather and print summary - server = getattr(self.engine.discord_reader, 'current_server', None) + server = getattr(self.engine.discord_reader, 'guild', None) if server: modal_prog.write(f"[bold cyan]Server Profile to Sync:[/bold cyan]") modal_prog.write(f" Name: [green]{server.name}[/green]") modal_prog.write(f" Icon: [green]{'Present' if server.icon else 'None'}[/green]") - modal_prog.write(f" Roles: [green]{len(server.roles)}[/green]") - modal_prog.write(f" Emojis: [green]{len(server.emojis)}[/green]") - modal_prog.write(f" Channels: [green]{len(server.channels)}[/green]") + modal_prog.write(f" Roles: [green]{len(getattr(server, 'roles', []))}[/green]") + modal_prog.write(f" Emojis: [green]{len(getattr(server, 'emojis', []))}[/green]") + modal_prog.write(f" Channels: [green]{len(getattr(server, 'channels', []))}[/green]") modal_prog.write("\n[dim]This operation will update the profile and scan existing baked-up channels for new messages.[/dim]") modal_prog.write("") diff --git a/src/ui/modals.py b/src/ui/modals.py index 0f5c486..e1e04e6 100644 --- a/src/ui/modals.py +++ b/src/ui/modals.py @@ -63,8 +63,6 @@ class ProgressScreen(Screen[None]): #prog_log { height: 1fr; margin-bottom: 1; border: solid $primary; } #live_log { height: 10; margin-bottom: 1; border: solid yellow; } - #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 cyan; padding: 1; margin-bottom: 1; display: none; } @@ -73,6 +71,7 @@ class ProgressScreen(Screen[None]): #prog_actions { height: auto; margin-top: 1; 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; } """ def compose(self) -> ComposeResult: @@ -93,13 +92,7 @@ class ProgressScreen(Screen[None]): with Vertical(id="info_container"): yield Label("", id="info_migration_status", classes="info_label") yield Label("", id="info_new_items", classes="info_label") - - # Progress bar moved inside info container - with Center(id="prog_bar_container"): - yield Label("", id="prog_item_status") - pb = ProgressBar(total=None, show_eta=False, show_percentage=False, id="prog_bar") - pb.display = False - yield pb + yield Label("", id="prog_item_status") yield RichLog(id="prog_log", highlight=True, markup=True) yield RichLog(id="live_log", highlight=True, markup=True) @@ -124,14 +117,6 @@ class ProgressScreen(Screen[None]): self.start_time = time.time() self.timer_event = self.set_interval(1.0, self.update_timer) - # Hide all action rows by default (fetch phase = no buttons) - try: self.query_one("#prog_actions_row1", Horizontal).display = False - except Exception: pass - try: self.query_one("#prog_actions_row2", Horizontal).display = False - except Exception: pass - try: self.query_one("#prog_actions_cancel", Horizontal).display = False - except Exception: pass - # Intercept Python logs and pipe to the #live_log self.log_handler = UILogHandler(self.write_live) @@ -213,11 +198,7 @@ class ProgressScreen(Screen[None]): # 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 + # Ensure the container is visible self.query_one("#info_container", Vertical).display = True except Exception: pass @@ -254,9 +235,6 @@ class ProgressScreen(Screen[None]): """Phase 2: Wait for user confirmation after analysis.""" try: self.query_one("#prog_loader", LoadingIndicator).display = False except Exception: pass - - try: self.query_one("#prog_bar", ProgressBar).display = False - except Exception: pass try: self.query_one("#prog_timer", Label).display = False except Exception: pass @@ -353,8 +331,6 @@ class ProgressScreen(Screen[None]): except Exception: pass # Hide progress bar (no need to show 100% bar) - try: self.query_one("#prog_bar", ProgressBar).display = False - except Exception: pass # Hide Cancel, show Back + Main Menu try: self.query_one("#prog_actions_cancel", Horizontal).display = False @@ -615,7 +591,9 @@ class ChannelSelectScreen(Screen[dict]): margin: 2 0; background: $surface; } - #chan_title { text-style: bold; margin-bottom: 1; } + #cs_header { height: auto; margin-bottom: 1; } + #chan_title { text-style: bold; } + #chan_warning { padding-left: 1; color: yellow; text-style: bold; } #channel_list_scroll { height: 1fr; border: solid $primary; @@ -657,7 +635,10 @@ class ChannelSelectScreen(Screen[dict]): yield Header(show_clock=True) with Container(id="cs_outer"): with Container(id="channel_dialog"): - yield Label("Select Channels to Backup", id="chan_title") + with Horizontal(id="cs_header"): + yield Label("Select Channels to Backup", id="chan_title") + if self.any_found: + yield Label(" (Existing backups found)", id="chan_warning") with VerticalScroll(id="channel_list_scroll"): cat_ids = sorted( @@ -679,6 +660,10 @@ class ChannelSelectScreen(Screen[dict]): label = f"{c.name}" color = "green" if c.id in self.backed_up_ids else "white" yield RadioButton(f"[{color}]{label}[/]", value=False, id=f"chan_{c.id}") + + if self.any_found: + yield Label("", classes="label_warning") + yield Label("Note: Channels shown in green have existing backups", classes="label_warning") with Horizontal(id="select_all_buttons"): yield Button("Select All", id="btn_all") @@ -687,7 +672,6 @@ class ChannelSelectScreen(Screen[dict]): yield Rule() with Horizontal(id="confirm_buttons"): if self.any_found: - yield Label("Existing backups found:", classes="label_warning") yield Button("Sync", variant="success", id="btn_sync") yield Button("Force Overwrite", variant="warning", id="btn_force") else: diff --git a/src/ui/shuttle_ops.py b/src/ui/shuttle_ops.py index ae2444d..3cd0c81 100644 --- a/src/ui/shuttle_ops.py +++ b/src/ui/shuttle_ops.py @@ -83,7 +83,7 @@ class ShuttlePane(Container): DEFAULT_CSS = """ ShuttlePane { height: auto; width: 100%; } ShuttlePane #sp_info { - height: auto; border: tall cyan; padding: 1; margin-bottom: 1; layout: vertical; + height: auto; border: tall yellow; padding: 1; margin-bottom: 1; layout: vertical; } #sp_info_split { height: auto; layout: horizontal; width: 100%; margin-bottom: 1; } .info_pane { width: 1fr; height: auto; } @@ -368,7 +368,7 @@ class ShuttlePane(Container): preview = await self._fetch_clone_preview(selections) if connections_started else {} if connections_started: - src_server = getattr(self.engine.discord_reader, 'current_server', None) + src_server = getattr(self.engine.discord_reader, 'guild', None) tgt_server_info = await self.engine.writer.validate() tgt_server_name = tgt_server_info.get("community_name", "target community") @@ -376,9 +376,9 @@ class ShuttlePane(Container): modal.write(f"[bold cyan]Source Server Profile:[/bold cyan]") modal.write(f" Name: [green]{src_server.name}[/green]") modal.write(f" Icon: [green]{'Present' if src_server.icon else 'None'}[/green]") - modal.write(f" Roles: [green]{len(src_server.roles)}[/green]") - modal.write(f" Emojis: [green]{len(src_server.emojis)}[/green]") - modal.write(f" Channels: [green]{len(src_server.channels)}[/green]") + modal.write(f" Roles: [green]{len(getattr(src_server, 'roles', []))}[/green]") + modal.write(f" Emojis: [green]{len(getattr(src_server, 'emojis', []))}[/green]") + modal.write(f" Channels: [green]{len(getattr(src_server, 'channels', []))}[/green]") modal.write("") modal.write(f"[bold cyan]Target Community:[/bold cyan] [green]{tgt_server_name}[/green]\n") @@ -489,7 +489,7 @@ class ShuttlePane(Container): logger.warning(f"Could not pre-connect for Sync preview: {e}") if connections_started: - src_server = getattr(self.engine.discord_reader, 'current_server', None) + src_server = getattr(self.engine.discord_reader, 'guild', None) tgt_server_info = await self.engine.writer.validate() tgt_server_name = tgt_server_info.get("community_name", "target community") @@ -497,9 +497,9 @@ class ShuttlePane(Container): modal.write(f"[bold cyan]Source Server Profile:[/bold cyan]") modal.write(f" Name: [green]{src_server.name}[/green]") modal.write(f" Icon: [green]{'Present' if src_server.icon else 'None'}[/green]") - modal.write(f" Roles: [green]{len(src_server.roles)}[/green]") - modal.write(f" Emojis: [green]{len(src_server.emojis)}[/green]") - modal.write(f" Channels: [green]{len(src_server.channels)}[/green]") + modal.write(f" Roles: [green]{len(getattr(src_server, 'roles', []))}[/green]") + modal.write(f" Emojis: [green]{len(getattr(src_server, 'emojis', []))}[/green]") + modal.write(f" Channels: [green]{len(getattr(src_server, 'channels', []))}[/green]") modal.write("") modal.write(f"[bold cyan]Target Community:[/bold cyan] [green]{tgt_server_name}[/green]\n") @@ -804,7 +804,7 @@ class ShuttlePane(Container): self.app.push_screen(modal) await asyncio.sleep(0.1) - src_server = getattr(self.engine.discord_reader, 'current_server', None) + src_server = getattr(self.engine.discord_reader, 'guild', None) tgt_server_info = await self.engine.writer.validate() tgt_server_name = tgt_server_info.get("community_name", "target community")