Skip to content

Commit 70979ca

Browse files
fix: modularize views
1 parent fdcc08e commit 70979ca

File tree

5 files changed

+437
-120
lines changed

5 files changed

+437
-120
lines changed

VIEWS_MODULARIZATION.md

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# UI Components Modularization
2+
3+
This document outlines the modularization of UI components from the `general.py` file into a dedicated `views` module.
4+
5+
## Overview
6+
7+
The UI components have been extracted from the monolithic `general.py` file and organized into a modular structure for better maintainability, reusability, and code organization.
8+
9+
## New Structure
10+
11+
### Views Module (`src/gitcord/views/`)
12+
13+
```
14+
views/
15+
├── __init__.py # Module exports
16+
├── base_views.py # Base UI components and utilities
17+
└── channel_views.py # Channel management UI components
18+
```
19+
20+
### Components
21+
22+
#### Base Views (`base_views.py`)
23+
24+
1. **`BaseView`** - Base view class with common functionality
25+
- Timeout handling
26+
- Button management utilities
27+
- Common view lifecycle methods
28+
29+
2. **`ConfirmationView`** - Generic confirmation dialog
30+
- Yes/No buttons
31+
- Result tracking
32+
- Standard confirmation flow
33+
34+
3. **`ErrorView`** - Error display with close button
35+
- Error message display
36+
- Close functionality
37+
- Ephemeral error handling
38+
39+
4. **`LoadingView`** - Loading state indicator
40+
- Disabled button to show loading
41+
- Configurable loading message
42+
- Timeout handling
43+
44+
#### Channel Views (`channel_views.py`)
45+
46+
1. **`DeleteExtraChannelsView`** - Channel deletion confirmation
47+
- Lists extra channels to be deleted
48+
- Permission checking
49+
- Confirmation flow initiation
50+
51+
2. **`ConfirmDeleteView`** - Final deletion confirmation
52+
- Actual channel deletion logic
53+
- Success/failure reporting
54+
- Result display
55+
56+
## Benefits
57+
58+
### 1. **Maintainability**
59+
- Each view has a single responsibility
60+
- Easier to locate and modify specific UI components
61+
- Clear separation of concerns
62+
63+
### 2. **Reusability**
64+
- Base views can be extended for new UI components
65+
- Common patterns are abstracted into base classes
66+
- Consistent UI behavior across the application
67+
68+
### 3. **Testability**
69+
- Individual views can be unit tested
70+
- Mock interactions for testing
71+
- Isolated component testing
72+
73+
### 4. **Code Organization**
74+
- Logical grouping of related UI components
75+
- Clear module structure
76+
- Easy to find and understand
77+
78+
### 5. **Extensibility**
79+
- Easy to add new view types
80+
- Consistent patterns for new UI components
81+
- Base classes provide common functionality
82+
83+
## Usage
84+
85+
### Importing Views
86+
87+
```python
88+
from gitcord.views import (
89+
DeleteExtraChannelsView,
90+
ConfirmDeleteView,
91+
BaseView,
92+
ConfirmationView,
93+
ErrorView,
94+
LoadingView
95+
)
96+
```
97+
98+
### Using Channel Views
99+
100+
```python
101+
# Create a view for deleting extra channels
102+
delete_view = DeleteExtraChannelsView(extra_channels, category_name)
103+
104+
# Send with embed
105+
await interaction.followup.send(embed=embed, view=delete_view)
106+
```
107+
108+
### Extending Base Views
109+
110+
```python
111+
class CustomView(BaseView):
112+
def __init__(self, custom_data, timeout=60):
113+
super().__init__(timeout=timeout)
114+
self.custom_data = custom_data
115+
116+
# Add custom buttons
117+
custom_button = Button(
118+
label="Custom Action",
119+
style=discord.ButtonStyle.primary,
120+
custom_id="custom_action"
121+
)
122+
custom_button.callback = self.custom_callback
123+
self.add_item(custom_button)
124+
125+
async def custom_callback(self, interaction: discord.Interaction):
126+
# Handle custom action
127+
pass
128+
```
129+
130+
## Migration from general.py
131+
132+
The following components were moved from `general.py`:
133+
134+
- `DeleteExtraChannelsView``views/channel_views.py`
135+
- `ConfirmDeleteView``views/channel_views.py`
136+
137+
The `general.py` file now imports these views:
138+
139+
```python
140+
from ..views import DeleteExtraChannelsView
141+
```
142+
143+
## Future Enhancements
144+
145+
### Potential New View Types
146+
147+
1. **`PaginatedView`** - For paginated content
148+
2. **`SelectionView`** - For multi-select options
149+
3. **`FormView`** - For user input forms
150+
4. **`ProgressView`** - For long-running operations
151+
152+
### Additional Base Classes
153+
154+
1. **`ModalView`** - Base for modal interactions
155+
2. **`SelectView`** - Base for select menus
156+
3. **`ContextMenuView`** - Base for context menus
157+
158+
## Testing
159+
160+
To test the views module:
161+
162+
```bash
163+
cd gitcord
164+
python -c "from src.gitcord.views import DeleteExtraChannelsView, ConfirmDeleteView, BaseView, ConfirmationView; print('✅ Views module imported successfully')"
165+
```
166+
167+
## Contributing
168+
169+
When adding new UI components:
170+
171+
1. Determine if it should extend a base view
172+
2. Place it in the appropriate module
173+
3. Add it to the `__init__.py` exports
174+
4. Update this documentation
175+
5. Add tests for the new component
176+
177+
## Dependencies
178+
179+
The views module depends on:
180+
- `discord.py` - For UI components
181+
- `gitcord.utils.helpers` - For embed creation
182+
- `gitcord.utils.logger` - For logging

src/gitcord/cogs/general.py

Lines changed: 1 addition & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -11,128 +11,10 @@
1111
from bs4 import BeautifulSoup
1212
from discord import app_commands
1313
from discord.ext import commands
14-
from discord.ui import Button, View
1514

1615
from ..utils.helpers import format_latency, create_embed, parse_channel_config
1716
from ..utils.logger import main_logger as logger
18-
19-
20-
class DeleteExtraChannelsView(View):
21-
"""View for confirming deletion of extra channels."""
22-
23-
def __init__(self, extra_channels, category_name, timeout=60):
24-
super().__init__(timeout=timeout)
25-
self.extra_channels = extra_channels
26-
self.category_name = category_name
27-
28-
# Add delete button
29-
delete_button = Button(
30-
label="🗑️ Delete Extra Channels",
31-
style=discord.ButtonStyle.danger,
32-
custom_id="delete_extra_channels"
33-
)
34-
delete_button.callback = self.delete_callback
35-
self.add_item(delete_button)
36-
37-
async def delete_callback(self, interaction: discord.Interaction):
38-
"""Handle delete button click."""
39-
# Check if user has manage channels permission
40-
if not interaction.user.guild_permissions.manage_channels:
41-
embed = create_embed(
42-
title="❌ Permission Denied",
43-
description="You need the 'Manage Channels' permission to delete channels.",
44-
color=discord.Color.red()
45-
)
46-
await interaction.response.send_message(embed=embed, ephemeral=True)
47-
return
48-
49-
# Create confirmation embed
50-
channel_list = "\n".join([f"• {channel.mention}" for channel in self.extra_channels])
51-
embed = create_embed(
52-
title="⚠️ Confirm Deletion",
53-
description=f"Are you sure you want to delete the following channels from **{self.category_name}**?\n\n{channel_list}\n\n**This action is irreversible!**",
54-
color=discord.Color.orange()
55-
)
56-
57-
# Create confirmation view
58-
confirm_view = ConfirmDeleteView(self.extra_channels, self.category_name)
59-
await interaction.response.send_message(embed=embed, view=confirm_view, ephemeral=True)
60-
61-
62-
class ConfirmDeleteView(View):
63-
"""View for final confirmation of channel deletion."""
64-
65-
def __init__(self, extra_channels, category_name, timeout=60):
66-
super().__init__(timeout=timeout)
67-
self.extra_channels = extra_channels
68-
self.category_name = category_name
69-
70-
# Add confirm and cancel buttons
71-
confirm_button = Button(
72-
label="✅ Yes, Delete All",
73-
style=discord.ButtonStyle.danger,
74-
custom_id="confirm_delete"
75-
)
76-
confirm_button.callback = self.confirm_callback
77-
78-
cancel_button = Button(
79-
label="❌ Cancel",
80-
style=discord.ButtonStyle.secondary,
81-
custom_id="cancel_delete"
82-
)
83-
cancel_button.callback = self.cancel_callback
84-
85-
self.add_item(confirm_button)
86-
self.add_item(cancel_button)
87-
88-
async def confirm_callback(self, interaction: discord.Interaction):
89-
"""Handle confirm button click."""
90-
deleted_channels = []
91-
failed_channels = []
92-
93-
# Delete each channel
94-
for channel in self.extra_channels:
95-
try:
96-
channel_name = channel.name
97-
await channel.delete()
98-
deleted_channels.append(channel_name)
99-
logger.info("Deleted extra channel '%s' from category '%s'", channel_name, self.category_name)
100-
except Exception as e:
101-
failed_channels.append(channel.name)
102-
logger.error("Failed to delete channel '%s': %s", channel.name, e)
103-
104-
# Create result embed
105-
if deleted_channels:
106-
embed = create_embed(
107-
title="✅ Channels Deleted",
108-
description=f"Successfully deleted {len(deleted_channels)} extra channels from **{self.category_name}**",
109-
color=discord.Color.green()
110-
)
111-
112-
if deleted_channels:
113-
deleted_list = "\n".join([f"• #{name}" for name in deleted_channels])
114-
embed.add_field(name="Deleted Channels", value=deleted_list, inline=False)
115-
116-
if failed_channels:
117-
failed_list = "\n".join([f"• #{name}" for name in failed_channels])
118-
embed.add_field(name="Failed to Delete", value=failed_list, inline=False)
119-
else:
120-
embed = create_embed(
121-
title="❌ Deletion Failed",
122-
description="Failed to delete any channels. Please check permissions and try again.",
123-
color=discord.Color.red()
124-
)
125-
126-
await interaction.response.edit_message(embed=embed, view=None)
127-
128-
async def cancel_callback(self, interaction: discord.Interaction):
129-
"""Handle cancel button click."""
130-
embed = create_embed(
131-
title="❌ Deletion Cancelled",
132-
description="Channel deletion was cancelled.",
133-
color=discord.Color.blue()
134-
)
135-
await interaction.response.edit_message(embed=embed, view=None)
17+
from ..views import DeleteExtraChannelsView
13618

13719

13820
class General(commands.Cog):
@@ -1308,7 +1190,6 @@ async def help_slash(self, interaction: discord.Interaction) -> None:
13081190

13091191
await interaction.response.send_message(embed=embed)
13101192

1311-
13121193
async def setup(bot: commands.Bot) -> None:
13131194
"""Set up the General cog."""
13141195
await bot.add_cog(General(bot))

src/gitcord/views/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""
2+
Views module for GitCord bot UI components.
3+
"""
4+
5+
from .base_views import BaseView, ConfirmationView, ErrorView, LoadingView
6+
from .channel_views import DeleteExtraChannelsView, ConfirmDeleteView
7+
8+
__all__ = [
9+
'BaseView',
10+
'ConfirmationView',
11+
'ErrorView',
12+
'LoadingView',
13+
'DeleteExtraChannelsView',
14+
'ConfirmDeleteView'
15+
]

0 commit comments

Comments
 (0)