pycascadeui 3.1.0__tar.gz → 3.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/PKG-INFO +54 -44
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/README.md +53 -41
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/__init__.py +18 -3
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/components/__init__.py +3 -0
- pycascadeui-3.2.0/cascadeui/components/base.py +402 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/components/inputs.py +114 -57
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/components/patterns/v2.py +9 -8
- pycascadeui-3.2.0/cascadeui/components/selects.py +270 -0
- pycascadeui-3.2.0/cascadeui/components/types.py +25 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/components/wrappers.py +3 -1
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/devtools.py +13 -2
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/persistence/manager.py +17 -4
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/state/middleware/persistence.py +22 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/state/middleware/undo.py +34 -1
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/utils/coercion.py +41 -1
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/_navigation.py +199 -93
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/base.py +20 -4
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/layout.py +16 -16
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/__init__.py +3 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/form.py +4 -1
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/leaderboard.py +49 -10
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/menu.py +6 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/paginated.py +106 -25
- pycascadeui-3.2.0/cascadeui/views/patterns/roles.py +538 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/tabs.py +3 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/types.py +85 -5
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/wizard.py +9 -7
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/persistent.py +27 -36
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/PKG-INFO +54 -44
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/SOURCES.txt +6 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/requires.txt +0 -3
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/pyproject.toml +1 -4
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_components.py +116 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_devtools.py +26 -2
- pycascadeui-3.2.0/tests/test_dynamic_persistent_button.py +330 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_form_patterns.py +23 -8
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_input_coercion.py +70 -1
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_inputs.py +100 -15
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_layout_view.py +38 -10
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_leaderboard_patterns.py +164 -10
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_navigation.py +171 -3
- pycascadeui-3.2.0/tests/test_navigation_artifact_restore.py +232 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_paginated_patterns.py +210 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_persistence.py +101 -6
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_persistence_middleware.py +2 -2
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_persistent_views.py +46 -4
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_rebuild_callback.py +29 -14
- pycascadeui-3.2.0/tests/test_roles_patterns.py +505 -0
- pycascadeui-3.2.0/tests/test_selects.py +227 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_undo.py +98 -0
- pycascadeui-3.1.0/cascadeui/components/base.py +0 -199
- pycascadeui-3.1.0/cascadeui/components/selects.py +0 -106
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/LICENSE +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/__main__.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/components/buttons.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/components/patterns/__init__.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/components/patterns/v1.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/components/v1_composition.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/exceptions.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/persistence/__init__.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/persistence/backends/__init__.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/persistence/backends/memory.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/persistence/backends/sqlite.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/persistence/config.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/persistence/migrations.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/persistence/protocols.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/persistence/schema.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/py.typed +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/setup.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/state/__init__.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/state/actions.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/state/computed.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/state/middleware/__init__.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/state/middleware/logging.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/state/reducers.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/state/singleton.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/state/slots.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/state/store.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/state/types.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/theming/__init__.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/theming/context.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/theming/core.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/theming/themes.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/tracing.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/utils/__init__.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/utils/decorators.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/utils/errors.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/utils/helpers.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/utils/logging.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/utils/tasks.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/validation.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/__init__.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/_interaction.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/cascadeui/views/view.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/dependency_links.txt +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/top_level.txt +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/setup.cfg +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_auto_defer.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_backends.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_batching.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_button_grid.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_computed.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_emoji_grid.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_ephemeral_refresh.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_five_pillars.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_hooks.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_instance_limit.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_layout_pagination.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_layout_patterns.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_layout_persistent.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_menu_patterns.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_message_cleanup.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_middleware.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_nav_version.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_owner_only.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_pagination.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_pillar_isolation.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_reducers.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_scoping.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_state_slots.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_state_store.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_tab_patterns.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_theming.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_update_coalescing.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_v2_helpers.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_validation.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_view_init.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_wizard_patterns.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.2.0}/tests/test_wrappers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycascadeui
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Redux-inspired UI framework for discord.py
|
|
5
5
|
Author-email: HollowTheSilver <hollow@users.noreply.github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -36,8 +36,6 @@ Requires-Dist: mkdocs-material>=9.0; extra == "docs"
|
|
|
36
36
|
Requires-Dist: pymdown-extensions>=10.0; extra == "docs"
|
|
37
37
|
Provides-Extra: sqlite
|
|
38
38
|
Requires-Dist: aiosqlite>=0.19; extra == "sqlite"
|
|
39
|
-
Provides-Extra: redis
|
|
40
|
-
Requires-Dist: redis>=5.0; extra == "redis"
|
|
41
39
|
Dynamic: license-file
|
|
42
40
|
|
|
43
41
|
<p align="center">
|
|
@@ -226,22 +224,25 @@ class NotificationPanel(StatefulLayoutView):
|
|
|
226
224
|
> Define `build_ui()` once. The library calls it on every relevant state change and ships the edit for you. No `on_state_changed()` override, no manual `refresh()`, no `message.edit()` plumbing.
|
|
227
225
|
|
|
228
226
|
```python
|
|
229
|
-
class
|
|
227
|
+
class MyFleetView(StatefulLayoutView):
|
|
230
228
|
state_scope = "user"
|
|
231
|
-
subscribed_actions = {"
|
|
229
|
+
subscribed_actions = {"FLEET_REROLLED"}
|
|
232
230
|
|
|
233
231
|
def build_ui(self):
|
|
234
232
|
self.clear_items()
|
|
235
|
-
|
|
236
|
-
self.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
233
|
+
grid = emoji_grid(10, 10, fill="\U0001f7e6", row_labels="alpha", col_labels="numeric")
|
|
234
|
+
for row, col in self.ship_cells():
|
|
235
|
+
grid[row, col] = "\U0001f6a2"
|
|
236
|
+
|
|
237
|
+
self.add_item(card("## My Fleet", divider(), grid, color=discord.Color.blue()))
|
|
238
|
+
self.add_item(ActionRow(
|
|
239
|
+
StatefulButton(label="Regenerate", emoji="\U0001f3b2", callback=self._reroll),
|
|
242
240
|
))
|
|
243
241
|
|
|
244
|
-
|
|
242
|
+
async def _reroll(self, interaction):
|
|
243
|
+
await self.dispatch("FLEET_REROLLED", {"cells": random_placement()})
|
|
244
|
+
|
|
245
|
+
# build_ui() runs automatically on every FLEET_REROLLED dispatch --
|
|
245
246
|
# no manual refresh() or on_state_changed() override needed.
|
|
246
247
|
```
|
|
247
248
|
|
|
@@ -300,18 +301,14 @@ class BattleshipView(StatefulLayoutView):
|
|
|
300
301
|
|
|
301
302
|
### Lifecycle Control
|
|
302
303
|
|
|
303
|
-
>
|
|
304
|
+
> Cap active sessions per user, guild, or globally. Pick how a collision resolves and how the old view cleans up when a new one opens.
|
|
304
305
|
|
|
305
306
|
```python
|
|
306
|
-
class
|
|
307
|
+
class SettingsHubView(MenuLayoutView):
|
|
307
308
|
instance_limit = 1 # Only one open at a time
|
|
308
309
|
instance_scope = "user_guild" # Per user per guild
|
|
309
310
|
instance_policy = "replace" # Exit the old one, open the new one
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
class GameView(StatefulLayoutView):
|
|
313
|
-
participant_limit = 8 # Owner + 7 joiners maximum
|
|
314
|
-
auto_register_participants = True # Claim slots from allowed_users on send()
|
|
311
|
+
exit_policy = "disable" # Old view's buttons grey out, message stays
|
|
315
312
|
```
|
|
316
313
|
|
|
317
314
|

|
|
@@ -332,12 +329,18 @@ async def setup_hook(self):
|
|
|
332
329
|
)
|
|
333
330
|
```
|
|
334
331
|
|
|
335
|
-
|
|
332
|
+
Subclass `PersistentRolesLayoutView` and declare your categories. The pattern handles button rendering, cardinality enforcement, and restart re-attachment. A stable `persistence_key` is the match identity the middleware uses to find this panel after restart:
|
|
336
333
|
|
|
337
334
|
```python
|
|
338
|
-
class
|
|
339
|
-
|
|
340
|
-
|
|
335
|
+
class GuildRoles(PersistentRolesLayoutView):
|
|
336
|
+
categories = [
|
|
337
|
+
RoleCategory(name="Color Roles", exclusive=True, roles={"Red": 123, "Blue": 456}),
|
|
338
|
+
RoleCategory(name="Pronouns", required=True, roles={"He/Him": 789, "She/Her": 12}),
|
|
339
|
+
]
|
|
340
|
+
|
|
341
|
+
# Same key in, same panel out -- across bot restarts.
|
|
342
|
+
panel = GuildRoles(context=ctx, persistence_key=f"roles:{ctx.guild.id}")
|
|
343
|
+
await panel.send()
|
|
341
344
|
```
|
|
342
345
|
|
|
343
346
|

|
|
@@ -349,8 +352,9 @@ class BattleshipView(StatefulLayoutView):
|
|
|
349
352
|
> Snapshot-based state history per session with built-in undo and redo support.
|
|
350
353
|
|
|
351
354
|
```python
|
|
352
|
-
class
|
|
355
|
+
class NotificationsView(StatefulLayoutView):
|
|
353
356
|
enable_undo = True # Every dispatch captures a snapshot
|
|
357
|
+
undo_limit = 10 # Stack depth cap (self.undo_depth / self.redo_depth read live)
|
|
354
358
|
|
|
355
359
|
async def _undo(self, interaction):
|
|
356
360
|
await self.undo() # Restore previous snapshot
|
|
@@ -490,7 +494,13 @@ await view.send()
|
|
|
490
494
|
# Persistent variant -- admin-posted panel that survives bot restarts
|
|
491
495
|
# and re-fetches live data on every restore.
|
|
492
496
|
class PersistentBoard(PersistentLeaderboardLayoutView):
|
|
493
|
-
|
|
497
|
+
pass
|
|
498
|
+
|
|
499
|
+
panel = PersistentBoard(
|
|
500
|
+
context=ctx,
|
|
501
|
+
persistence_key="battleship-leaderboard-main",
|
|
502
|
+
)
|
|
503
|
+
await panel.send()
|
|
494
504
|
```
|
|
495
505
|
|
|
496
506
|

|
|
@@ -602,8 +612,6 @@ for row in rows:
|
|
|
602
612
|
view.add_item(row)
|
|
603
613
|
```
|
|
604
614
|
|
|
605
|
-

|
|
606
|
-
|
|
607
615
|
<div align="center">
|
|
608
616
|
<img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/pngs/v2-emoji-grid.PNG" width="30%" alt="Emoji Grid" style="border-radius: 8px; margin: 5px;" />
|
|
609
617
|
<img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/pngs/v2-button-grid.PNG" width="30%" alt="Button Grid" style="border-radius: 8px; margin: 5px;" />
|
|
@@ -630,7 +638,7 @@ for row in rows:
|
|
|
630
638
|
### Views
|
|
631
639
|
- V2 layout-based system for structured, container-driven interfaces
|
|
632
640
|
- Full support for traditional discord.py Views (V1)
|
|
633
|
-
- Pre-built patterns: menus, tabs, wizards, forms, pagination, leaderboards
|
|
641
|
+
- Pre-built patterns: menus, tabs, wizards, forms, pagination, leaderboards, roles
|
|
634
642
|
- `PaginatedView.from_cursor()` for lazy cursor-driven pagination with LRU page cache
|
|
635
643
|
- `DisplayLayoutView` for one-shot V2 sends from a pre-built container
|
|
636
644
|
- Automatic state-driven rebuilds: define `build_ui()`, the library handles the edit (≈ React `render()`)
|
|
@@ -677,20 +685,6 @@ for row in rows:
|
|
|
677
685
|
|
|
678
686
|
---
|
|
679
687
|
|
|
680
|
-
## V1 Components
|
|
681
|
-
|
|
682
|
-
> CascadeUI supports traditional discord.py Views and embeds.
|
|
683
|
-
|
|
684
|
-
Use V1 when you need:
|
|
685
|
-
- Embed-specific features such as fields or timestamps
|
|
686
|
-
- Simpler layouts without containers
|
|
687
|
-
|
|
688
|
-
All core features such as navigation, persistence, and undo/redo are supported.
|
|
689
|
-
|
|
690
|
-

|
|
691
|
-
|
|
692
|
-
---
|
|
693
|
-
|
|
694
688
|
## Examples
|
|
695
689
|
|
|
696
690
|
> The <a href="https://hollowthesilver.github.io/CascadeUI/examples/"><strong>documentation</strong></a> includes full implementations demonstrating practical usage:
|
|
@@ -703,6 +697,22 @@ All core features such as navigation, persistence, and undo/redo are supported.
|
|
|
703
697
|
- Multi-user games with shared state, hidden information, and challenge flows (TicTacToe, Battleship)
|
|
704
698
|
- Open-join lobbies with capacity caps and host-vs-participant authority (Werewolf-style)
|
|
705
699
|
|
|
700
|
+

|
|
701
|
+
|
|
702
|
+
---
|
|
703
|
+
|
|
704
|
+
## V1 Components
|
|
705
|
+
|
|
706
|
+
> CascadeUI supports traditional discord.py Views and embeds.
|
|
707
|
+
|
|
708
|
+
Use V1 when you need:
|
|
709
|
+
- Embed-specific features such as fields or timestamps
|
|
710
|
+
- Simpler layouts without containers
|
|
711
|
+
|
|
712
|
+
All core features such as navigation, persistence, and undo/redo are supported.
|
|
713
|
+
|
|
714
|
+

|
|
715
|
+
|
|
706
716
|
---
|
|
707
717
|
|
|
708
718
|
## Documentation
|
|
@@ -734,7 +744,7 @@ isort cascadeui/
|
|
|
734
744
|
|
|
735
745
|
## Developer's Note
|
|
736
746
|
|
|
737
|
-
> I built CascadeUI with over **ten years** of Python, and roughly fifteen years of
|
|
747
|
+
> I built CascadeUI with over **ten years** of Python, and roughly fifteen years of development experience. All documentation, docstrings and test modules are written and designed using custom **Anthropic Opus** sub-agents. I do not attempt to conceal this fact. I'm a proponent of efficient and responsible agent application in software design. That experience is what makes these tools effective. They're amplifiers, not substitutes.
|
|
738
748
|
>
|
|
739
749
|
> *-- Hollow*
|
|
740
750
|
|
|
@@ -184,22 +184,25 @@ class NotificationPanel(StatefulLayoutView):
|
|
|
184
184
|
> Define `build_ui()` once. The library calls it on every relevant state change and ships the edit for you. No `on_state_changed()` override, no manual `refresh()`, no `message.edit()` plumbing.
|
|
185
185
|
|
|
186
186
|
```python
|
|
187
|
-
class
|
|
187
|
+
class MyFleetView(StatefulLayoutView):
|
|
188
188
|
state_scope = "user"
|
|
189
|
-
subscribed_actions = {"
|
|
189
|
+
subscribed_actions = {"FLEET_REROLLED"}
|
|
190
190
|
|
|
191
191
|
def build_ui(self):
|
|
192
192
|
self.clear_items()
|
|
193
|
-
|
|
194
|
-
self.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
193
|
+
grid = emoji_grid(10, 10, fill="\U0001f7e6", row_labels="alpha", col_labels="numeric")
|
|
194
|
+
for row, col in self.ship_cells():
|
|
195
|
+
grid[row, col] = "\U0001f6a2"
|
|
196
|
+
|
|
197
|
+
self.add_item(card("## My Fleet", divider(), grid, color=discord.Color.blue()))
|
|
198
|
+
self.add_item(ActionRow(
|
|
199
|
+
StatefulButton(label="Regenerate", emoji="\U0001f3b2", callback=self._reroll),
|
|
200
200
|
))
|
|
201
201
|
|
|
202
|
-
|
|
202
|
+
async def _reroll(self, interaction):
|
|
203
|
+
await self.dispatch("FLEET_REROLLED", {"cells": random_placement()})
|
|
204
|
+
|
|
205
|
+
# build_ui() runs automatically on every FLEET_REROLLED dispatch --
|
|
203
206
|
# no manual refresh() or on_state_changed() override needed.
|
|
204
207
|
```
|
|
205
208
|
|
|
@@ -258,18 +261,14 @@ class BattleshipView(StatefulLayoutView):
|
|
|
258
261
|
|
|
259
262
|
### Lifecycle Control
|
|
260
263
|
|
|
261
|
-
>
|
|
264
|
+
> Cap active sessions per user, guild, or globally. Pick how a collision resolves and how the old view cleans up when a new one opens.
|
|
262
265
|
|
|
263
266
|
```python
|
|
264
|
-
class
|
|
267
|
+
class SettingsHubView(MenuLayoutView):
|
|
265
268
|
instance_limit = 1 # Only one open at a time
|
|
266
269
|
instance_scope = "user_guild" # Per user per guild
|
|
267
270
|
instance_policy = "replace" # Exit the old one, open the new one
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
class GameView(StatefulLayoutView):
|
|
271
|
-
participant_limit = 8 # Owner + 7 joiners maximum
|
|
272
|
-
auto_register_participants = True # Claim slots from allowed_users on send()
|
|
271
|
+
exit_policy = "disable" # Old view's buttons grey out, message stays
|
|
273
272
|
```
|
|
274
273
|
|
|
275
274
|

|
|
@@ -290,12 +289,18 @@ async def setup_hook(self):
|
|
|
290
289
|
)
|
|
291
290
|
```
|
|
292
291
|
|
|
293
|
-
|
|
292
|
+
Subclass `PersistentRolesLayoutView` and declare your categories. The pattern handles button rendering, cardinality enforcement, and restart re-attachment. A stable `persistence_key` is the match identity the middleware uses to find this panel after restart:
|
|
294
293
|
|
|
295
294
|
```python
|
|
296
|
-
class
|
|
297
|
-
|
|
298
|
-
|
|
295
|
+
class GuildRoles(PersistentRolesLayoutView):
|
|
296
|
+
categories = [
|
|
297
|
+
RoleCategory(name="Color Roles", exclusive=True, roles={"Red": 123, "Blue": 456}),
|
|
298
|
+
RoleCategory(name="Pronouns", required=True, roles={"He/Him": 789, "She/Her": 12}),
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
# Same key in, same panel out -- across bot restarts.
|
|
302
|
+
panel = GuildRoles(context=ctx, persistence_key=f"roles:{ctx.guild.id}")
|
|
303
|
+
await panel.send()
|
|
299
304
|
```
|
|
300
305
|
|
|
301
306
|

|
|
@@ -307,8 +312,9 @@ class BattleshipView(StatefulLayoutView):
|
|
|
307
312
|
> Snapshot-based state history per session with built-in undo and redo support.
|
|
308
313
|
|
|
309
314
|
```python
|
|
310
|
-
class
|
|
315
|
+
class NotificationsView(StatefulLayoutView):
|
|
311
316
|
enable_undo = True # Every dispatch captures a snapshot
|
|
317
|
+
undo_limit = 10 # Stack depth cap (self.undo_depth / self.redo_depth read live)
|
|
312
318
|
|
|
313
319
|
async def _undo(self, interaction):
|
|
314
320
|
await self.undo() # Restore previous snapshot
|
|
@@ -448,7 +454,13 @@ await view.send()
|
|
|
448
454
|
# Persistent variant -- admin-posted panel that survives bot restarts
|
|
449
455
|
# and re-fetches live data on every restore.
|
|
450
456
|
class PersistentBoard(PersistentLeaderboardLayoutView):
|
|
451
|
-
|
|
457
|
+
pass
|
|
458
|
+
|
|
459
|
+
panel = PersistentBoard(
|
|
460
|
+
context=ctx,
|
|
461
|
+
persistence_key="battleship-leaderboard-main",
|
|
462
|
+
)
|
|
463
|
+
await panel.send()
|
|
452
464
|
```
|
|
453
465
|
|
|
454
466
|

|
|
@@ -560,8 +572,6 @@ for row in rows:
|
|
|
560
572
|
view.add_item(row)
|
|
561
573
|
```
|
|
562
574
|
|
|
563
|
-

|
|
564
|
-
|
|
565
575
|
<div align="center">
|
|
566
576
|
<img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/pngs/v2-emoji-grid.PNG" width="30%" alt="Emoji Grid" style="border-radius: 8px; margin: 5px;" />
|
|
567
577
|
<img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/pngs/v2-button-grid.PNG" width="30%" alt="Button Grid" style="border-radius: 8px; margin: 5px;" />
|
|
@@ -588,7 +598,7 @@ for row in rows:
|
|
|
588
598
|
### Views
|
|
589
599
|
- V2 layout-based system for structured, container-driven interfaces
|
|
590
600
|
- Full support for traditional discord.py Views (V1)
|
|
591
|
-
- Pre-built patterns: menus, tabs, wizards, forms, pagination, leaderboards
|
|
601
|
+
- Pre-built patterns: menus, tabs, wizards, forms, pagination, leaderboards, roles
|
|
592
602
|
- `PaginatedView.from_cursor()` for lazy cursor-driven pagination with LRU page cache
|
|
593
603
|
- `DisplayLayoutView` for one-shot V2 sends from a pre-built container
|
|
594
604
|
- Automatic state-driven rebuilds: define `build_ui()`, the library handles the edit (≈ React `render()`)
|
|
@@ -635,20 +645,6 @@ for row in rows:
|
|
|
635
645
|
|
|
636
646
|
---
|
|
637
647
|
|
|
638
|
-
## V1 Components
|
|
639
|
-
|
|
640
|
-
> CascadeUI supports traditional discord.py Views and embeds.
|
|
641
|
-
|
|
642
|
-
Use V1 when you need:
|
|
643
|
-
- Embed-specific features such as fields or timestamps
|
|
644
|
-
- Simpler layouts without containers
|
|
645
|
-
|
|
646
|
-
All core features such as navigation, persistence, and undo/redo are supported.
|
|
647
|
-
|
|
648
|
-

|
|
649
|
-
|
|
650
|
-
---
|
|
651
|
-
|
|
652
648
|
## Examples
|
|
653
649
|
|
|
654
650
|
> The <a href="https://hollowthesilver.github.io/CascadeUI/examples/"><strong>documentation</strong></a> includes full implementations demonstrating practical usage:
|
|
@@ -661,6 +657,22 @@ All core features such as navigation, persistence, and undo/redo are supported.
|
|
|
661
657
|
- Multi-user games with shared state, hidden information, and challenge flows (TicTacToe, Battleship)
|
|
662
658
|
- Open-join lobbies with capacity caps and host-vs-participant authority (Werewolf-style)
|
|
663
659
|
|
|
660
|
+

|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## V1 Components
|
|
665
|
+
|
|
666
|
+
> CascadeUI supports traditional discord.py Views and embeds.
|
|
667
|
+
|
|
668
|
+
Use V1 when you need:
|
|
669
|
+
- Embed-specific features such as fields or timestamps
|
|
670
|
+
- Simpler layouts without containers
|
|
671
|
+
|
|
672
|
+
All core features such as navigation, persistence, and undo/redo are supported.
|
|
673
|
+
|
|
674
|
+

|
|
675
|
+
|
|
664
676
|
---
|
|
665
677
|
|
|
666
678
|
## Documentation
|
|
@@ -692,7 +704,7 @@ isort cascadeui/
|
|
|
692
704
|
|
|
693
705
|
## Developer's Note
|
|
694
706
|
|
|
695
|
-
> I built CascadeUI with over **ten years** of Python, and roughly fifteen years of
|
|
707
|
+
> I built CascadeUI with over **ten years** of Python, and roughly fifteen years of development experience. All documentation, docstrings and test modules are written and designed using custom **Anthropic Opus** sub-agents. I do not attempt to conceal this fact. I'm a proponent of efficient and responsible agent application in software design. That experience is what makes these tools effective. They're amplifiers, not substitutes.
|
|
696
708
|
>
|
|
697
709
|
> *-- Hollow*
|
|
698
710
|
|
|
@@ -11,7 +11,7 @@ import logging as _logging
|
|
|
11
11
|
|
|
12
12
|
_logging.getLogger(__name__).addHandler(_logging.NullHandler())
|
|
13
13
|
|
|
14
|
-
from .components.base import StatefulButton, StatefulSelect
|
|
14
|
+
from .components.base import DynamicPersistentButton, StatefulButton, StatefulSelect
|
|
15
15
|
from .components.inputs import Checkbox, CheckboxGroup, FileUpload, Modal, RadioGroup, TextInput
|
|
16
16
|
from .components.patterns import (
|
|
17
17
|
ConfirmationButtons,
|
|
@@ -39,6 +39,7 @@ from .components.patterns import (
|
|
|
39
39
|
toggle_button,
|
|
40
40
|
toggle_section,
|
|
41
41
|
)
|
|
42
|
+
from .components.types import EmojiInput
|
|
42
43
|
from .components.v1_composition import CompositeComponent, get_component, register_component
|
|
43
44
|
from .components.wrappers import with_confirmation, with_cooldown, with_loading_state
|
|
44
45
|
from .devtools import DevToolsCog, InspectorView
|
|
@@ -105,12 +106,20 @@ from .views.patterns import (
|
|
|
105
106
|
PaginatedLayoutView,
|
|
106
107
|
PaginatedView,
|
|
107
108
|
PersistentLeaderboardLayoutView,
|
|
109
|
+
PersistentRolesLayoutView,
|
|
110
|
+
RolesLayoutView,
|
|
108
111
|
TabLayoutView,
|
|
109
112
|
TabView,
|
|
110
113
|
WizardLayoutView,
|
|
111
114
|
WizardView,
|
|
112
115
|
)
|
|
113
|
-
from .views.patterns.types import
|
|
116
|
+
from .views.patterns.types import (
|
|
117
|
+
FormField,
|
|
118
|
+
FormSchema,
|
|
119
|
+
RoleCategory,
|
|
120
|
+
WizardSchema,
|
|
121
|
+
WizardStep,
|
|
122
|
+
)
|
|
114
123
|
from .views.persistent import PersistentLayoutView, PersistentView
|
|
115
124
|
from .views.view import StatefulView
|
|
116
125
|
|
|
@@ -121,7 +130,7 @@ if "SQLiteBackend" in _persistence_all:
|
|
|
121
130
|
# // ========================================( Script )======================================== // #
|
|
122
131
|
|
|
123
132
|
|
|
124
|
-
__version__ = "3.
|
|
133
|
+
__version__ = "3.2.0"
|
|
125
134
|
|
|
126
135
|
# Export public API
|
|
127
136
|
__all__ = [
|
|
@@ -160,11 +169,14 @@ __all__ = [
|
|
|
160
169
|
"MenuLayoutView",
|
|
161
170
|
"PaginatedLayoutView",
|
|
162
171
|
"PersistentLeaderboardLayoutView",
|
|
172
|
+
"PersistentRolesLayoutView",
|
|
173
|
+
"RolesLayoutView",
|
|
163
174
|
"TabLayoutView",
|
|
164
175
|
"WizardLayoutView",
|
|
165
176
|
# Components
|
|
166
177
|
"StatefulButton",
|
|
167
178
|
"StatefulSelect",
|
|
179
|
+
"DynamicPersistentButton",
|
|
168
180
|
"CompositeComponent",
|
|
169
181
|
"ConfirmationButtons",
|
|
170
182
|
"PaginationControls",
|
|
@@ -183,6 +195,8 @@ __all__ = [
|
|
|
183
195
|
"with_loading_state",
|
|
184
196
|
"with_confirmation",
|
|
185
197
|
"with_cooldown",
|
|
198
|
+
# Type aliases
|
|
199
|
+
"EmojiInput",
|
|
186
200
|
# V2 Cards & Sections
|
|
187
201
|
"card",
|
|
188
202
|
"action_section",
|
|
@@ -228,6 +242,7 @@ __all__ = [
|
|
|
228
242
|
# Typed schemas
|
|
229
243
|
"FormField",
|
|
230
244
|
"FormSchema",
|
|
245
|
+
"RoleCategory",
|
|
231
246
|
"WizardStep",
|
|
232
247
|
"WizardSchema",
|
|
233
248
|
# Validation
|
|
@@ -35,6 +35,7 @@ from .patterns import (
|
|
|
35
35
|
toggle_section,
|
|
36
36
|
)
|
|
37
37
|
from .selects import ChannelSelect, Dropdown, MentionableSelect, RoleSelect, UserSelect
|
|
38
|
+
from .types import EmojiInput
|
|
38
39
|
from .v1_composition import CompositeComponent, get_component, register_component
|
|
39
40
|
from .wrappers import with_confirmation, with_cooldown, with_loading_state
|
|
40
41
|
|
|
@@ -101,4 +102,6 @@ __all__ = [
|
|
|
101
102
|
"tab_nav",
|
|
102
103
|
# V2 media
|
|
103
104
|
"gallery",
|
|
105
|
+
# Type aliases
|
|
106
|
+
"EmojiInput",
|
|
104
107
|
]
|