add tooltips

This commit is contained in:
rambros 2026-03-08 01:32:13 +05:30
parent b2fbac4622
commit a32334eaa3
6 changed files with 90 additions and 55 deletions

View file

@ -15,7 +15,7 @@ class AppConfig(BaseModel):
target_platform: str = Field(default="fluxer") # fluxer | stoat | none
target_bot_token: Optional[str] = Field(default=None)
target_server_id: Optional[str] = Field(default=None)
target_api_url: Optional[str] = Field(default="default")
target_api_url: Optional[str] = Field(default=None)
migration: MigrationSettings = Field(default_factory=MigrationSettings)
# ── backwardcompat shims (readonly) ────────────────────────────────
@ -77,12 +77,12 @@ def load_config(config_path: Union[str, Path] = "config.yaml", create_if_missing
data.setdefault("target_platform", "fluxer")
data.setdefault("target_bot_token", data["fluxer_bot_token"])
data.setdefault("target_server_id", data.get("fluxer_community_id"))
data.setdefault("target_api_url", data.get("fluxer_api_url", "default"))
data.setdefault("target_api_url", data.get("fluxer_api_url") or "default")
elif data.get("stoat_bot_token") and data["stoat_bot_token"] not in ("STOAT_BOT_TOKEN", None):
data.setdefault("target_platform", "stoat")
data.setdefault("target_bot_token", data["stoat_bot_token"])
data.setdefault("target_server_id", data.get("stoat_server_id"))
data.setdefault("target_api_url", data.get("stoat_api_url", "default"))
data.setdefault("target_api_url", data.get("stoat_api_url") or "default")
# Remove legacy keys so they don't conflict with the model
for key in ("fluxer_bot_token", "fluxer_community_id", "fluxer_api_url",
"stoat_bot_token", "stoat_server_id", "stoat_api_url",

View file

@ -73,9 +73,9 @@ class BackupPane(Container):
yield Label("", id="bp_lbl_backup")
with Vertical(id="bp_actions"):
yield Button("Backup Server Profile", id="bp_backup_profile", disabled=True)
yield Button("Backup Channel Messages", id="bp_backup_msgs", disabled=True, variant="primary")
yield Button("Update Existing Backup", id="bp_backup_sync", disabled=True, variant="success")
yield Button("Backup Server Profile", id="bp_backup_profile", disabled=True, tooltip="Backup Discord server roles, emojis, and channel structure")
yield Button("Backup Channel Messages", id="bp_backup_msgs", disabled=True, variant="primary", tooltip="Select and backup message history from text channels")
yield Button("Update Existing Backup", id="bp_backup_sync", disabled=True, variant="success", tooltip="Scan for new messages\n& Update existing backup")
def on_mount(self) -> None:
self._validate()

View file

@ -39,9 +39,9 @@ class NewConfigModal(ModalScreen[str]):
def compose(self) -> ComposeResult:
with Vertical(id="new_config_dialog"):
yield Label("Enter new configuration name:", id="new_config_title")
yield Input(placeholder="e.g. MyServer", id="new_config_input")
yield Input(placeholder="e.g. MyServer", id="new_config_input", tooltip="Enter a unique name for this config")
with Horizontal(id="new_config_buttons"):
yield Button("Create", variant="success", id="btn_create")
yield Button("Create", variant="success", id="btn_create", tooltip="Create config and launch setup")
yield Button("Cancel", variant="primary", id="btn_cancel")
def _get_sanitized_name(self) -> str:
@ -97,7 +97,7 @@ class ConfigSelectionScreen(Screen):
with VerticalScroll(id="config_list_container"):
yield ListView(id="config_list")
with Horizontal(id="config_sel_actions"):
yield Button("New Config", id="btn_new_config", variant="success")
yield Button("New Config", id="btn_new_config", variant="success", tooltip="Create a new configuration folder")
yield Button("Exit", id="btn_exit", variant="error")
yield Footer()
@ -219,9 +219,10 @@ class ConfigScreen(Screen):
value=self.config.discord_bot_token or "",
id="inp_discord_token",
password=True,
placeholder="Paste Bot Token here"
placeholder="Paste Bot Token here",
tooltip="Enter your Discord BOT token from the Developer Portal"
)
yield Button("Validate", id="btn_fetch_guilds", variant="primary")
yield Button("Validate", id="btn_fetch_guilds", variant="primary", tooltip="Verify token and fetch available Discord servers")
yield Label("Server ID:", classes="field_label")
yield Select(
@ -237,17 +238,17 @@ class ConfigScreen(Screen):
yield RadioButton(
"Shuttle Transfer (direct migration)",
id="radio_direct",
value=(cur_mode == "direct_transfer"),
value=(cur_mode == "direct_transfer")
)
yield RadioButton(
"Backup & Migrate (backup first, then migrate)",
id="radio_backup",
value=(cur_mode == "backup_transfer"),
value=(cur_mode == "backup_transfer")
)
yield RadioButton(
"Backup Only (local backup, no migration)",
id="radio_bkonly",
value=(cur_mode == "backup_only"),
value=(cur_mode == "backup_only")
)
# ── Target Platform (hidden for backup_only) ─────────────
@ -258,12 +259,12 @@ class ConfigScreen(Screen):
yield RadioButton(
"Fluxer",
id="radio_fluxer",
value=(cur_plat == "fluxer"),
value=(cur_plat == "fluxer")
)
yield RadioButton(
"Stoat",
id="radio_stoat",
value=(cur_plat == "stoat"),
value=(cur_plat == "stoat")
)
yield Label("Bot Token:", classes="field_label")
with Horizontal(classes="fetch_row"):
@ -271,9 +272,10 @@ class ConfigScreen(Screen):
value=self.config.target_bot_token or "",
id="inp_target_token",
password=True,
placeholder="Paste Target Bot Token"
placeholder="Paste Target Bot Token",
tooltip="Enter the Bot token for the target platform"
)
yield Button("Validate", id="btn_fetch_target_servers", variant="primary")
yield Button("Validate", id="btn_fetch_target_servers", variant="primary", tooltip="Verify token and fetch available communities")
yield Label("Community / Server ID:", classes="field_label")
yield Select(
@ -284,13 +286,15 @@ class ConfigScreen(Screen):
yield Label("Target API URL:", classes="field_label")
yield Input(
value=self.config.target_api_url or "default",
value=self.config.target_api_url if (self.config.target_api_url and self.config.target_api_url != "default") else "",
id="inp_target_api",
placeholder="Leave this Empty for official instance",
tooltip="Enter the custom API url\nfor self hosted instances"
)
yield Rule()
with Horizontal(id="cfg_actions"):
yield Button("Save Configuration", variant="success", id="btn_save")
yield Button("Save Configuration", variant="success", id="btn_save", tooltip="Save all changes to config.yaml")
yield Button("Back", id="btn_back")
yield Footer()
@ -455,7 +459,7 @@ class ConfigScreen(Screen):
self.config.target_server_id = None
target_api = self.query_one("#inp_target_api", Input).value.strip()
self.config.target_api_url = target_api or "default"
self.config.target_api_url = target_api or None
else:
self.config.target_platform = "none"

View file

@ -101,14 +101,14 @@ class ProgressScreen(Screen[None]):
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")
yield Button("Continue Migration", id="btn_continue", disabled=True, variant="success")
yield Button("Start from ID", id="btn_start_id", disabled=True, variant="warning")
yield Button("Start from First", id="btn_start_first", disabled=True, variant="primary", tooltip="Start the operation from the beginning")
yield Button("Continue Migration", id="btn_continue", disabled=True, variant="success", tooltip="Resume the operation from the last saved state")
yield Button("Start from ID", id="btn_start_id", disabled=True, variant="warning", tooltip="Start or resume from a specific Discord Message ID")
with Horizontal(classes="action_row", id="prog_actions_row2"):
yield Button("Back", id="btn_back", disabled=False)
yield Button("Main Menu", id="btn_main_menu", disabled=False)
with Horizontal(classes="action_row", id="prog_actions_cancel"):
yield Button("Cancel", id="btn_cancel", variant="error")
yield Button("Cancel", id="btn_cancel", variant="error", tooltip="Stop current operation")
yield Footer()
def __init__(self, log_level: str = "INFO", *args, **kwargs):
@ -232,7 +232,11 @@ class ProgressScreen(Screen[None]):
show_id: bool = True,
btn_start_label: str = "Start from First",
btn_continue_label: str = "Continue Migration",
btn_id_label: str = "Start from ID"
btn_id_label: str = "Start from ID",
btn_start_variant: str = "primary",
btn_start_tooltip: str | None = None,
btn_continue_tooltip: str | None = None,
btn_id_tooltip: str | None = None
):
"""Phase 2: Wait for user confirmation after analysis."""
try: self.query_one("#prog_loader", LoadingIndicator).display = False
@ -241,12 +245,27 @@ class ProgressScreen(Screen[None]):
try: self.query_one("#prog_timer", Label).display = False
except Exception: pass
# Update button labels
try: self.query_one("#btn_start_first", Button).label = btn_start_label
# Update button labels, variants and tooltips
try:
btn_start = self.query_one("#btn_start_first", Button)
btn_start.label = btn_start_label
btn_start.variant = btn_start_variant
if btn_start_tooltip:
btn_start.tooltip = btn_start_tooltip
except Exception: pass
try: self.query_one("#btn_continue", Button).label = btn_continue_label
try:
btn_cont = self.query_one("#btn_continue", Button)
btn_cont.label = btn_continue_label
if btn_continue_tooltip:
btn_cont.tooltip = btn_continue_tooltip
except Exception: pass
try: self.query_one("#btn_start_id", Button).label = btn_id_label
try:
btn_id = self.query_one("#btn_start_id", Button)
btn_id.label = btn_id_label
if btn_id_tooltip:
btn_id.tooltip = btn_id_tooltip
except Exception: pass
# Show confirmation buttons
@ -426,8 +445,8 @@ class OptionSelectModal(ModalScreen[list[str]]):
with Vertical(id="opt_dialog"):
yield Label(self._title, id="opt_title")
with Horizontal(id="opt_batch_buttons"):
yield Button("Select All", id="btn_opt_all", flat=True)
yield Button("Deselect All", id="btn_opt_none", flat=True)
yield Button("Select All", id="btn_opt_all", flat=True, tooltip="Select all available options")
yield Button("Deselect All", id="btn_opt_none", flat=True, tooltip="Deselect all options")
with Vertical(id="opt_scroll"):
for opt_id, label in self._options:
@ -435,8 +454,8 @@ class OptionSelectModal(ModalScreen[list[str]]):
yield Rule()
with Horizontal(id="opt_buttons"):
yield Button("Proceed", variant="success", id="btn_opt_ok")
yield Button("Back", id="btn_opt_back")
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")
def on_button_pressed(self, event: Button.Pressed):
if event.button.id == "btn_opt_back":
@ -568,8 +587,8 @@ class ChannelPickerScreen(Screen[tuple]):
yield Rule()
with Horizontal(id="chanpick_buttons"):
yield Button("Select", variant="success", id="btn_pick_ok")
yield Button("Back", id="btn_pick_back")
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")
yield Footer()
def on_mount(self) -> None:
@ -719,17 +738,17 @@ class ChannelSelectScreen(Screen[dict]):
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")
yield Button("Deselect All", id="btn_none")
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()
with Horizontal(id="confirm_buttons"):
if self.any_found:
yield Button("Sync", variant="success", id="btn_sync")
yield Button("Force Overwrite", variant="warning", id="btn_force")
yield Button("Sync", variant="success", id="btn_sync", tooltip="Backup new channels\n& update existing backups")
yield Button("Force Overwrite", variant="warning", id="btn_force", tooltip="Overwrite existing backups\nwith fresh data")
else:
yield Button("Backup", variant="success", id="btn_backup")
yield Button("Back", id="btn_cancel_chan")
yield Button("Backup", variant="success", id="btn_backup", tooltip="Start backing up selected channels")
yield Button("Back", id="btn_cancel_chan", tooltip="Cancel and go back")
yield Footer()
def on_button_pressed(self, event: Button.Pressed) -> None:
@ -798,8 +817,8 @@ class MessageIDInputModal(ModalScreen[int | None]):
yield Label("Enter an ID and click Verify to preview.", id="lbl_msg_preview")
with Horizontal(id="msg_id_buttons"):
yield Button("Verify", variant="primary", id="btn_verify_start", disabled=True)
yield Button("Back", variant="warning", id="btn_cancel_msg_id")
yield Button("Verify", variant="primary", id="btn_verify_start", disabled=True, tooltip="Check if this message ID exists in the channel")
yield Button("Back", variant="warning", id="btn_cancel_msg_id", tooltip="Cancel and go back")
def on_input_changed(self, event: Input.Changed) -> None:
if event.input.id == "input_msg_id":

View file

@ -129,8 +129,8 @@ class ModeScreen(Screen):
yield Rule()
with Horizontal(id="bottom_actions"):
if mode == "backup_transfer":
yield Button("Switch to Migrate ⇄", id="btn_switch", variant="primary")
yield Button("Configuration", id="btn_config", variant="success")
yield Button("Switch to Migrate ⇄", id="btn_switch", variant="primary", tooltip="Switch between\nBackup & Migrate operations")
yield Button("Configuration", id="btn_config", variant="success", tooltip="Change Bot tokens\nand Reaper Mode")
yield Button("Exit", id="btn_exit", variant="error")
yield Footer()

View file

@ -129,11 +129,11 @@ class ShuttlePane(Container):
yield Label("", id="sp_lbl_status")
with Vertical(id="sp_actions"):
yield Button("Clone Server Template", id="sp_clone", disabled=True)
yield Button("Sync Server Settings", id="sp_sync", disabled=True)
yield Button("Migrate Message History", id="sp_messages", disabled=True, variant="primary")
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 Button("Danger Zone ⚠", id="sp_danger", variant="error", disabled=True, flat=True)
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:
self._rebuild_engine()
@ -459,8 +459,10 @@ class ShuttlePane(Container):
choice = await modal.phase_wait_confirm(
btn_start_label="Start Cloning",
btn_id_label="Force Clone (may create duplicates)",
show_id=True
btn_id_label="Force Clone",
show_id=True,
btn_start_tooltip="Clone without creating duplicates",
btn_id_tooltip="Force clone everything\n(may create duplicates)"
)
if choice == "btn_back":
modal.dismiss()
@ -539,7 +541,9 @@ class ShuttlePane(Container):
choice = await modal.phase_wait_confirm(
btn_start_label="Start Syncing",
btn_id_label="Force Sync",
show_id=True
show_id=True,
btn_start_tooltip="Sync new assets only",
btn_id_tooltip="Force sync assets\n(may create duplicates)"
)
if choice == "btn_back":
modal.dismiss()
@ -905,7 +909,10 @@ class ShuttlePane(Container):
show_id=True,
btn_start_label="Start from\nFirst Message",
btn_continue_label="Continue\nMigration",
btn_id_label="Start from\nmessage ID"
btn_id_label="Start from\nmessage ID",
btn_start_tooltip="Start migrating from the earliest available message",
btn_continue_tooltip="Resume from the last successfully migrated message",
btn_id_tooltip="Start migrating from a specific Discord message ID"
)
logger.info(f"User confirmation choice: {choice}")
if choice == "btn_back":
@ -1084,7 +1091,12 @@ class ShuttlePane(Container):
modal.write(f" [dim](could not fetch list)[/dim]")
modal.write("")
choice = await modal.phase_wait_confirm(btn_start_label="WIPE ALL DATA", show_id=False)
choice = await modal.phase_wait_confirm(
btn_start_label="WIPE ALL DATA",
show_id=False,
btn_start_variant="error",
btn_start_tooltip="WARNING\nIrreversible Operation!\nProceed with Caution"
)
if choice == "btn_back":
modal.dismiss()
self._open_danger_menu()