@azurioh/discord-kernel 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +77 -0
- package/dist/authz/authorizer.d.ts +45 -0
- package/dist/authz/authorizer.d.ts.map +1 -0
- package/dist/authz/authorizer.js +31 -0
- package/dist/authz/authorizer.js.map +1 -0
- package/dist/authz/require-level-guard.d.ts +23 -0
- package/dist/authz/require-level-guard.d.ts.map +1 -0
- package/dist/authz/require-level-guard.js +37 -0
- package/dist/authz/require-level-guard.js.map +1 -0
- package/dist/clock.d.ts +13 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +13 -0
- package/dist/clock.js.map +1 -0
- package/dist/concurrency/keyed-queue.d.ts +18 -0
- package/dist/concurrency/keyed-queue.d.ts.map +1 -0
- package/dist/concurrency/keyed-queue.js +37 -0
- package/dist/concurrency/keyed-queue.js.map +1 -0
- package/dist/concurrency/run-with-concurrency.d.ts +15 -0
- package/dist/concurrency/run-with-concurrency.d.ts.map +1 -0
- package/dist/concurrency/run-with-concurrency.js +40 -0
- package/dist/concurrency/run-with-concurrency.js.map +1 -0
- package/dist/config/env.d.ts +17 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +49 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/errors.d.ts +14 -0
- package/dist/config/errors.d.ts.map +1 -0
- package/dist/config/errors.js +25 -0
- package/dist/config/errors.js.map +1 -0
- package/dist/datetime.d.ts +34 -0
- package/dist/datetime.d.ts.map +1 -0
- package/dist/datetime.js +188 -0
- package/dist/datetime.js.map +1 -0
- package/dist/discord/api-errors.d.ts +20 -0
- package/dist/discord/api-errors.d.ts.map +1 -0
- package/dist/discord/api-errors.js +90 -0
- package/dist/discord/api-errors.js.map +1 -0
- package/dist/discord/channel-exporter.d.ts +42 -0
- package/dist/discord/channel-exporter.d.ts.map +1 -0
- package/dist/discord/channel-exporter.js +3 -0
- package/dist/discord/channel-exporter.js.map +1 -0
- package/dist/discord/command/autocomplete-dispatcher.d.ts +26 -0
- package/dist/discord/command/autocomplete-dispatcher.d.ts.map +1 -0
- package/dist/discord/command/autocomplete-dispatcher.js +41 -0
- package/dist/discord/command/autocomplete-dispatcher.js.map +1 -0
- package/dist/discord/command/autocomplete.d.ts +18 -0
- package/dist/discord/command/autocomplete.d.ts.map +1 -0
- package/dist/discord/command/autocomplete.js +65 -0
- package/dist/discord/command/autocomplete.js.map +1 -0
- package/dist/discord/command/context.d.ts +33 -0
- package/dist/discord/command/context.d.ts.map +1 -0
- package/dist/discord/command/context.js +48 -0
- package/dist/discord/command/context.js.map +1 -0
- package/dist/discord/command/cooldown-guard.d.ts +25 -0
- package/dist/discord/command/cooldown-guard.d.ts.map +1 -0
- package/dist/discord/command/cooldown-guard.js +60 -0
- package/dist/discord/command/cooldown-guard.js.map +1 -0
- package/dist/discord/command/create-command.d.ts +100 -0
- package/dist/discord/command/create-command.d.ts.map +1 -0
- package/dist/discord/command/create-command.js +245 -0
- package/dist/discord/command/create-command.js.map +1 -0
- package/dist/discord/command/create-context-menu-command.d.ts +51 -0
- package/dist/discord/command/create-context-menu-command.d.ts.map +1 -0
- package/dist/discord/command/create-context-menu-command.js +128 -0
- package/dist/discord/command/create-context-menu-command.js.map +1 -0
- package/dist/discord/command/errors.d.ts +19 -0
- package/dist/discord/command/errors.d.ts.map +1 -0
- package/dist/discord/command/errors.js +27 -0
- package/dist/discord/command/errors.js.map +1 -0
- package/dist/discord/command/guard.d.ts +30 -0
- package/dist/discord/command/guard.d.ts.map +1 -0
- package/dist/discord/command/guard.js +34 -0
- package/dist/discord/command/guard.js.map +1 -0
- package/dist/discord/command/localization.d.ts +9 -0
- package/dist/discord/command/localization.d.ts.map +1 -0
- package/dist/discord/command/localization.js +13 -0
- package/dist/discord/command/localization.js.map +1 -0
- package/dist/discord/command/options.d.ts +113 -0
- package/dist/discord/command/options.d.ts.map +1 -0
- package/dist/discord/command/options.js +509 -0
- package/dist/discord/command/options.js.map +1 -0
- package/dist/discord/command/permission-guard.d.ts +21 -0
- package/dist/discord/command/permission-guard.d.ts.map +1 -0
- package/dist/discord/command/permission-guard.js +69 -0
- package/dist/discord/command/permission-guard.js.map +1 -0
- package/dist/discord/command/router.d.ts +48 -0
- package/dist/discord/command/router.d.ts.map +1 -0
- package/dist/discord/command/router.js +118 -0
- package/dist/discord/command/router.js.map +1 -0
- package/dist/discord/command/types.d.ts +51 -0
- package/dist/discord/command/types.d.ts.map +1 -0
- package/dist/discord/command/types.js +3 -0
- package/dist/discord/command/types.js.map +1 -0
- package/dist/discord/components/component-router.d.ts +116 -0
- package/dist/discord/components/component-router.d.ts.map +1 -0
- package/dist/discord/components/component-router.js +123 -0
- package/dist/discord/components/component-router.js.map +1 -0
- package/dist/discord/components/interactive-message/index.d.ts +3 -0
- package/dist/discord/components/interactive-message/index.d.ts.map +1 -0
- package/dist/discord/components/interactive-message/index.js +11 -0
- package/dist/discord/components/interactive-message/index.js.map +1 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.d.ts +195 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.d.ts.map +1 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.js +151 -0
- package/dist/discord/components/interactive-message/interactive-message-collector.js.map +1 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.d.ts +30 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.d.ts.map +1 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.js +44 -0
- package/dist/discord/components/interactive-message/mount-interactive-message.js.map +1 -0
- package/dist/discord/components/paginator/index.d.ts +5 -0
- package/dist/discord/components/paginator/index.d.ts.map +1 -0
- package/dist/discord/components/paginator/index.js +15 -0
- package/dist/discord/components/paginator/index.js.map +1 -0
- package/dist/discord/components/paginator/mount-paginator.d.ts +25 -0
- package/dist/discord/components/paginator/mount-paginator.d.ts.map +1 -0
- package/dist/discord/components/paginator/mount-paginator.js +41 -0
- package/dist/discord/components/paginator/mount-paginator.js.map +1 -0
- package/dist/discord/components/paginator/page-state.d.ts +21 -0
- package/dist/discord/components/paginator/page-state.d.ts.map +1 -0
- package/dist/discord/components/paginator/page-state.js +44 -0
- package/dist/discord/components/paginator/page-state.js.map +1 -0
- package/dist/discord/components/paginator/paginator-buttons.d.ts +17 -0
- package/dist/discord/components/paginator/paginator-buttons.d.ts.map +1 -0
- package/dist/discord/components/paginator/paginator-buttons.js +45 -0
- package/dist/discord/components/paginator/paginator-buttons.js.map +1 -0
- package/dist/discord/components/paginator/paginator-view.d.ts +26 -0
- package/dist/discord/components/paginator/paginator-view.d.ts.map +1 -0
- package/dist/discord/components/paginator/paginator-view.js +28 -0
- package/dist/discord/components/paginator/paginator-view.js.map +1 -0
- package/dist/discord/components/persistent-paginator.d.ts +47 -0
- package/dist/discord/components/persistent-paginator.d.ts.map +1 -0
- package/dist/discord/components/persistent-paginator.js +137 -0
- package/dist/discord/components/persistent-paginator.js.map +1 -0
- package/dist/discord/components/settings-editor/index.d.ts +7 -0
- package/dist/discord/components/settings-editor/index.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/index.js +22 -0
- package/dist/discord/components/settings-editor/index.js.map +1 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.d.ts +140 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.js +344 -0
- package/dist/discord/components/settings-editor/mount-settings-editor.js.map +1 -0
- package/dist/discord/components/settings-editor/navigation.d.ts +49 -0
- package/dist/discord/components/settings-editor/navigation.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/navigation.js +39 -0
- package/dist/discord/components/settings-editor/navigation.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts +56 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.js +284 -0
- package/dist/discord/components/settings-editor/settings-editor-card.view.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts +69 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.js +353 -0
- package/dist/discord/components/settings-editor/settings-editor-field-modal.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.d.ts +216 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.js +66 -0
- package/dist/discord/components/settings-editor/settings-editor-fields.js.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor.view.d.ts +303 -0
- package/dist/discord/components/settings-editor/settings-editor.view.d.ts.map +1 -0
- package/dist/discord/components/settings-editor/settings-editor.view.js +316 -0
- package/dist/discord/components/settings-editor/settings-editor.view.js.map +1 -0
- package/dist/discord/events/create-event.d.ts +5 -0
- package/dist/discord/events/create-event.d.ts.map +1 -0
- package/dist/discord/events/create-event.js +8 -0
- package/dist/discord/events/create-event.js.map +1 -0
- package/dist/discord/events/event-router.d.ts +16 -0
- package/dist/discord/events/event-router.d.ts.map +1 -0
- package/dist/discord/events/event-router.js +51 -0
- package/dist/discord/events/event-router.js.map +1 -0
- package/dist/discord/events/types.d.ts +16 -0
- package/dist/discord/events/types.d.ts.map +1 -0
- package/dist/discord/events/types.js +3 -0
- package/dist/discord/events/types.js.map +1 -0
- package/dist/discord/i18n.d.ts +58 -0
- package/dist/discord/i18n.d.ts.map +1 -0
- package/dist/discord/i18n.js +173 -0
- package/dist/discord/i18n.js.map +1 -0
- package/dist/discord/interaction/button.d.ts +39 -0
- package/dist/discord/interaction/button.d.ts.map +1 -0
- package/dist/discord/interaction/button.js +36 -0
- package/dist/discord/interaction/button.js.map +1 -0
- package/dist/discord/interaction/interaction-router.d.ts +21 -0
- package/dist/discord/interaction/interaction-router.d.ts.map +1 -0
- package/dist/discord/interaction/interaction-router.js +24 -0
- package/dist/discord/interaction/interaction-router.js.map +1 -0
- package/dist/discord/interaction/modal.d.ts +171 -0
- package/dist/discord/interaction/modal.d.ts.map +1 -0
- package/dist/discord/interaction/modal.js +264 -0
- package/dist/discord/interaction/modal.js.map +1 -0
- package/dist/discord/interaction/select-menu.d.ts +104 -0
- package/dist/discord/interaction/select-menu.d.ts.map +1 -0
- package/dist/discord/interaction/select-menu.js +126 -0
- package/dist/discord/interaction/select-menu.js.map +1 -0
- package/dist/discord/permissions.d.ts +32 -0
- package/dist/discord/permissions.d.ts.map +1 -0
- package/dist/discord/permissions.js +40 -0
- package/dist/discord/permissions.js.map +1 -0
- package/dist/discord/placeholder-registry-errors.d.ts +10 -0
- package/dist/discord/placeholder-registry-errors.d.ts.map +1 -0
- package/dist/discord/placeholder-registry-errors.js +18 -0
- package/dist/discord/placeholder-registry-errors.js.map +1 -0
- package/dist/discord/placeholder-registry.d.ts +53 -0
- package/dist/discord/placeholder-registry.d.ts.map +1 -0
- package/dist/discord/placeholder-registry.js +52 -0
- package/dist/discord/placeholder-registry.js.map +1 -0
- package/dist/discord/presenter.d.ts +26 -0
- package/dist/discord/presenter.d.ts.map +1 -0
- package/dist/discord/presenter.js +3 -0
- package/dist/discord/presenter.js.map +1 -0
- package/dist/discord/ui/card.d.ts +114 -0
- package/dist/discord/ui/card.d.ts.map +1 -0
- package/dist/discord/ui/card.js +162 -0
- package/dist/discord/ui/card.js.map +1 -0
- package/dist/discord/ui/color-input-errors.d.ts +12 -0
- package/dist/discord/ui/color-input-errors.d.ts.map +1 -0
- package/dist/discord/ui/color-input-errors.js +21 -0
- package/dist/discord/ui/color-input-errors.js.map +1 -0
- package/dist/discord/ui/color-input.d.ts +37 -0
- package/dist/discord/ui/color-input.d.ts.map +1 -0
- package/dist/discord/ui/color-input.js +166 -0
- package/dist/discord/ui/color-input.js.map +1 -0
- package/dist/discord/ui/colors.d.ts +44 -0
- package/dist/discord/ui/colors.d.ts.map +1 -0
- package/dist/discord/ui/colors.js +45 -0
- package/dist/discord/ui/colors.js.map +1 -0
- package/dist/discord/ui/embed.d.ts +16 -0
- package/dist/discord/ui/embed.d.ts.map +1 -0
- package/dist/discord/ui/embed.js +53 -0
- package/dist/discord/ui/embed.js.map +1 -0
- package/dist/discord/ui/message-attachments.d.ts +40 -0
- package/dist/discord/ui/message-attachments.d.ts.map +1 -0
- package/dist/discord/ui/message-attachments.js +27 -0
- package/dist/discord/ui/message-attachments.js.map +1 -0
- package/dist/errors/business-error.d.ts +41 -0
- package/dist/errors/business-error.d.ts.map +1 -0
- package/dist/errors/business-error.js +39 -0
- package/dist/errors/business-error.js.map +1 -0
- package/dist/errors/describe-error.d.ts +29 -0
- package/dist/errors/describe-error.d.ts.map +1 -0
- package/dist/errors/describe-error.js +49 -0
- package/dist/errors/describe-error.js.map +1 -0
- package/dist/errors/incident-ref.d.ts +7 -0
- package/dist/errors/incident-ref.d.ts.map +1 -0
- package/dist/errors/incident-ref.js +13 -0
- package/dist/errors/incident-ref.js.map +1 -0
- package/dist/errors/index.d.ts +4 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +15 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/i18n/business-message.d.ts +14 -0
- package/dist/i18n/business-message.d.ts.map +1 -0
- package/dist/i18n/business-message.js +19 -0
- package/dist/i18n/business-message.js.map +1 -0
- package/dist/i18n/catalog.d.ts +27 -0
- package/dist/i18n/catalog.d.ts.map +1 -0
- package/dist/i18n/catalog.js +25 -0
- package/dist/i18n/catalog.js.map +1 -0
- package/dist/i18n/errors.d.ts +10 -0
- package/dist/i18n/errors.d.ts.map +1 -0
- package/dist/i18n/errors.js +18 -0
- package/dist/i18n/errors.js.map +1 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +16 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/locale.d.ts +22 -0
- package/dist/i18n/locale.d.ts.map +1 -0
- package/dist/i18n/locale.js +40 -0
- package/dist/i18n/locale.js.map +1 -0
- package/dist/i18n/translator.d.ts +35 -0
- package/dist/i18n/translator.d.ts.map +1 -0
- package/dist/i18n/translator.js +40 -0
- package/dist/i18n/translator.js.map +1 -0
- package/dist/logger.d.ts +28 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +3 -0
- package/dist/logger.js.map +1 -0
- package/dist/module/module.d.ts +42 -0
- package/dist/module/module.d.ts.map +1 -0
- package/dist/module/module.js +3 -0
- package/dist/module/module.js.map +1 -0
- package/dist/persistence/database.d.ts +24 -0
- package/dist/persistence/database.d.ts.map +1 -0
- package/dist/persistence/database.js +3 -0
- package/dist/persistence/database.js.map +1 -0
- package/dist/resilience/retry.d.ts +36 -0
- package/dist/resilience/retry.d.ts.map +1 -0
- package/dist/resilience/retry.js +78 -0
- package/dist/resilience/retry.js.map +1 -0
- package/dist/scheduler/errors.d.ts +25 -0
- package/dist/scheduler/errors.d.ts.map +1 -0
- package/dist/scheduler/errors.js +45 -0
- package/dist/scheduler/errors.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +68 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -0
- package/dist/scheduler/scheduler.js +162 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/package.json +89 -0
- package/src/authz/authorizer.ts +76 -0
- package/src/authz/require-level-guard.ts +42 -0
- package/src/clock.ts +18 -0
- package/src/concurrency/keyed-queue.ts +50 -0
- package/src/concurrency/run-with-concurrency.ts +44 -0
- package/src/config/env.ts +58 -0
- package/src/config/errors.ts +21 -0
- package/src/datetime.ts +221 -0
- package/src/discord/api-errors.ts +91 -0
- package/src/discord/channel-exporter.ts +48 -0
- package/src/discord/command/autocomplete-dispatcher.ts +54 -0
- package/src/discord/command/autocomplete.ts +76 -0
- package/src/discord/command/context.ts +85 -0
- package/src/discord/command/cooldown-guard.ts +81 -0
- package/src/discord/command/create-command.ts +431 -0
- package/src/discord/command/create-context-menu-command.ts +227 -0
- package/src/discord/command/errors.ts +22 -0
- package/src/discord/command/guard.ts +54 -0
- package/src/discord/command/localization.ts +11 -0
- package/src/discord/command/options.ts +847 -0
- package/src/discord/command/permission-guard.ts +74 -0
- package/src/discord/command/router.ts +142 -0
- package/src/discord/command/types.ts +52 -0
- package/src/discord/components/component-router.ts +213 -0
- package/src/discord/components/interactive-message/index.ts +18 -0
- package/src/discord/components/interactive-message/interactive-message-collector.ts +374 -0
- package/src/discord/components/interactive-message/mount-interactive-message.ts +71 -0
- package/src/discord/components/paginator/index.ts +17 -0
- package/src/discord/components/paginator/mount-paginator.ts +69 -0
- package/src/discord/components/paginator/page-state.ts +45 -0
- package/src/discord/components/paginator/paginator-buttons.ts +60 -0
- package/src/discord/components/paginator/paginator-view.ts +61 -0
- package/src/discord/components/persistent-paginator.ts +197 -0
- package/src/discord/components/settings-editor/index.ts +41 -0
- package/src/discord/components/settings-editor/mount-settings-editor.ts +646 -0
- package/src/discord/components/settings-editor/navigation.ts +67 -0
- package/src/discord/components/settings-editor/settings-editor-card.view.ts +344 -0
- package/src/discord/components/settings-editor/settings-editor-field-modal.ts +538 -0
- package/src/discord/components/settings-editor/settings-editor-fields.ts +290 -0
- package/src/discord/components/settings-editor/settings-editor.view.ts +654 -0
- package/src/discord/events/create-event.ts +7 -0
- package/src/discord/events/event-router.ts +53 -0
- package/src/discord/events/types.ts +17 -0
- package/src/discord/i18n.ts +173 -0
- package/src/discord/interaction/button.ts +75 -0
- package/src/discord/interaction/interaction-router.ts +28 -0
- package/src/discord/interaction/modal.ts +505 -0
- package/src/discord/interaction/select-menu.ts +246 -0
- package/src/discord/permissions.ts +57 -0
- package/src/discord/placeholder-registry-errors.ts +11 -0
- package/src/discord/placeholder-registry.ts +77 -0
- package/src/discord/presenter.ts +26 -0
- package/src/discord/ui/card.ts +303 -0
- package/src/discord/ui/color-input-errors.ts +17 -0
- package/src/discord/ui/color-input.ts +179 -0
- package/src/discord/ui/colors.ts +58 -0
- package/src/discord/ui/embed.ts +64 -0
- package/src/discord/ui/message-attachments.ts +57 -0
- package/src/errors/business-error.ts +49 -0
- package/src/errors/describe-error.ts +73 -0
- package/src/errors/incident-ref.ts +10 -0
- package/src/errors/index.ts +12 -0
- package/src/i18n/business-message.ts +23 -0
- package/src/i18n/catalog.ts +41 -0
- package/src/i18n/errors.ts +11 -0
- package/src/i18n/index.ts +11 -0
- package/src/i18n/locale.ts +41 -0
- package/src/i18n/translator.ts +78 -0
- package/src/logger.ts +28 -0
- package/src/module/module.ts +42 -0
- package/src/persistence/database.ts +23 -0
- package/src/resilience/retry.ts +102 -0
- package/src/scheduler/errors.ts +40 -0
- package/src/scheduler/scheduler.ts +248 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { SettingsEditorField } from "@/discord/components/settings-editor/settings-editor-fields";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The level every screen opens on. Its fields are the ones handed to the editor
|
|
5
|
+
* directly, so a screen with no depth at all never names a level.
|
|
6
|
+
*/
|
|
7
|
+
export const ROOT_LEVEL_KEY = "root";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* One level of a screen: the settings it offers and what it is called.
|
|
11
|
+
*
|
|
12
|
+
* A screen declares its deeper levels the same way it declares its fields — as
|
|
13
|
+
* pure data — so the menu, the heading and the return control are all built from
|
|
14
|
+
* one source rather than three that have to agree.
|
|
15
|
+
*/
|
|
16
|
+
export interface SettingsEditorLevel<F extends string> {
|
|
17
|
+
/** Identifies the level. A field of kind `level` names one to descend into. */
|
|
18
|
+
readonly key: string;
|
|
19
|
+
/** What this level is called, shown in the heading so the reader knows where they are. */
|
|
20
|
+
readonly titleKey: string;
|
|
21
|
+
readonly fields: readonly SettingsEditorField<F>[];
|
|
22
|
+
/**
|
|
23
|
+
* How this level offers its entries. `"sections"`, the default, gives each
|
|
24
|
+
* its own row with its hint and button; `"buttons"` folds them into a
|
|
25
|
+
* single row, for a level whose entries act on what the picker above has
|
|
26
|
+
* already named and whose hint would only repeat the row.
|
|
27
|
+
*/
|
|
28
|
+
readonly entryLayout?: "sections" | "buttons";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Where the screen currently is, outermost first.
|
|
33
|
+
*
|
|
34
|
+
* Never empty: the root is always its first entry, which is what makes
|
|
35
|
+
* "is there anywhere to go back to" a length check rather than a special case.
|
|
36
|
+
*/
|
|
37
|
+
export type SettingsEditorPath = readonly string[];
|
|
38
|
+
|
|
39
|
+
/** The level being rendered — the last one entered. */
|
|
40
|
+
export function currentLevelKey(path: SettingsEditorPath): string {
|
|
41
|
+
return path[path.length - 1] ?? ROOT_LEVEL_KEY;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Whether there is a level above the current one to return to. */
|
|
45
|
+
export function canReturn(path: SettingsEditorPath): boolean {
|
|
46
|
+
return path.length > 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Descend into `key`.
|
|
51
|
+
*
|
|
52
|
+
* Re-entering a level already on the path unwinds to it rather than stacking a
|
|
53
|
+
* second copy: a screen that let the same level appear twice would need two
|
|
54
|
+
* clicks of the return control to leave one place.
|
|
55
|
+
*/
|
|
56
|
+
export function pushLevel(path: SettingsEditorPath, key: string): SettingsEditorPath {
|
|
57
|
+
const existing = path.indexOf(key);
|
|
58
|
+
if (existing !== -1) {
|
|
59
|
+
return path.slice(0, existing + 1);
|
|
60
|
+
}
|
|
61
|
+
return [...path, key];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Go back one level. At the root there is nothing to pop, so the path stands. */
|
|
65
|
+
export function popLevel(path: SettingsEditorPath): SettingsEditorPath {
|
|
66
|
+
return canReturn(path) ? path.slice(0, -1) : path;
|
|
67
|
+
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import type { InteractiveView } from "@/discord/components/interactive-message";
|
|
2
|
+
import {
|
|
3
|
+
canReturn,
|
|
4
|
+
currentLevelKey,
|
|
5
|
+
ROOT_LEVEL_KEY,
|
|
6
|
+
type SettingsEditorLevel,
|
|
7
|
+
} from "@/discord/components/settings-editor/navigation";
|
|
8
|
+
import {
|
|
9
|
+
resetButtons,
|
|
10
|
+
SETTINGS_EDITOR_RESET_CONFIRMING,
|
|
11
|
+
type SettingsEditorButtons,
|
|
12
|
+
type SettingsEditorCardChrome,
|
|
13
|
+
type SettingsEditorDressing,
|
|
14
|
+
type SettingsEditorIds,
|
|
15
|
+
type SettingsEditorRow,
|
|
16
|
+
type SettingsEditorState,
|
|
17
|
+
} from "@/discord/components/settings-editor/settings-editor.view";
|
|
18
|
+
import type { SettingsEditorField } from "@/discord/components/settings-editor/settings-editor-fields";
|
|
19
|
+
import { SETTINGS_EDITOR_MESSAGES } from "@/discord/i18n";
|
|
20
|
+
import { type Button, createActionRow } from "@/discord/interaction/button";
|
|
21
|
+
import { type CardBlock, type CardDef, createCard } from "@/discord/ui/card";
|
|
22
|
+
import { EMBED_COLORS } from "@/discord/ui/colors";
|
|
23
|
+
import type { Locale, Translator } from "@/i18n";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Discord refuses a card carrying more than {@link import("@/discord/ui/card").MAX_CARD_COMPONENTS}
|
|
27
|
+
* components; every menu entry a level declares becomes a `Section` — its
|
|
28
|
+
* text plus a button accessory, three components — so this is what a level's
|
|
29
|
+
* own field list may not exceed *before* the chrome fixed around it
|
|
30
|
+
* (breadcrumb, preview, extra blocks, separators, the bottom controls, the
|
|
31
|
+
* signature) is even counted. Generous rather than exact: the fixed chrome
|
|
32
|
+
* costs roughly a dozen components on its own, and no level this repository
|
|
33
|
+
* declares comes within three of this cap.
|
|
34
|
+
*/
|
|
35
|
+
export const MAX_CARD_LEVEL_ENTRIES = 10;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Discord refuses an `ActionRow` carrying more than five buttons, so a level
|
|
39
|
+
* declaring `entryLayout: "buttons"` folds its entries into as many rows of
|
|
40
|
+
* this size as it needs, rather than the single row it used to require.
|
|
41
|
+
*/
|
|
42
|
+
const BUTTONS_PER_ROW = 5;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* How many entries a level declaring `entryLayout: "buttons"` may carry
|
|
46
|
+
* before the rows they compile to — one `ActionRow` per five, however many
|
|
47
|
+
* that takes — would overrun what a container can actually hold.
|
|
48
|
+
*
|
|
49
|
+
* Sits higher than {@link MAX_CARD_LEVEL_ENTRIES} on purpose: a `"buttons"`
|
|
50
|
+
* row costs its `ActionRow` plus the buttons in it, cheaper per entry than a
|
|
51
|
+
* `"sections"` level's `Section` (text, accessory, the `Section` itself —
|
|
52
|
+
* three components apiece). Generous rather than exact, for the same reason
|
|
53
|
+
* {@link MAX_CARD_LEVEL_ENTRIES} is: the fixed chrome (breadcrumb, preview,
|
|
54
|
+
* separators, the bottom controls, the signature) costs roughly a dozen
|
|
55
|
+
* components on its own, and no level this repository declares comes within
|
|
56
|
+
* half of this cap.
|
|
57
|
+
*/
|
|
58
|
+
export const MAX_CARD_BUTTON_LEVEL_ENTRIES = 20;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The most buttons {@link SettingsEditorCardChrome.entryActions} ever adds to
|
|
62
|
+
* one render of a level's own row: the confirm/cancel pair a screen's own
|
|
63
|
+
* removal swaps to once armed — the single ask button it swaps *from* costs
|
|
64
|
+
* less. Reserved against every `"buttons"` level's own cap whenever the
|
|
65
|
+
* chrome declares the hook at all: the guard below runs once at construction,
|
|
66
|
+
* before any state — and so before `entryActions` has anything to measure —
|
|
67
|
+
* exists.
|
|
68
|
+
*/
|
|
69
|
+
const ENTRY_ACTIONS_RESERVE = 2;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The button a menu entry's own `Section` carries, derived from the field's
|
|
73
|
+
* key rather than declared alongside it — the same relationship `ids.field`
|
|
74
|
+
* already has to the select it used to name, one level of indirection lower.
|
|
75
|
+
*/
|
|
76
|
+
export function fieldButtonId(ids: SettingsEditorIds, key: string): string {
|
|
77
|
+
return `${ids.field}:${key}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The card editor screen: one Components V2 card carrying the breadcrumb, the
|
|
82
|
+
* last notice, whatever the screen adds above the preview, the preview
|
|
83
|
+
* itself, a `Section` per entry of the current level — each with the button
|
|
84
|
+
* `fieldButtonId` names — and the bottom controls, closing with the bot's own
|
|
85
|
+
* signature.
|
|
86
|
+
*
|
|
87
|
+
* The counterpart of `createEditorView` for a screen declaring
|
|
88
|
+
* `chrome.layout === "card"`; the two never mix; {@link mountSettingsEditor}
|
|
89
|
+
* picks whichever this screen's chrome names.
|
|
90
|
+
*
|
|
91
|
+
* Takes no `SettingsEditorIds` of its own, unlike `createEditorView`: every
|
|
92
|
+
* button it renders — reset, back, and one per menu entry — already exists,
|
|
93
|
+
* built by `mountSettingsEditor` against those very ids, so nothing here has
|
|
94
|
+
* to name them again.
|
|
95
|
+
*/
|
|
96
|
+
export function createCardEditorView<S, A, F extends string>(
|
|
97
|
+
fields: readonly SettingsEditorField<F>[],
|
|
98
|
+
levels: readonly SettingsEditorLevel<F>[] | undefined,
|
|
99
|
+
buttons: SettingsEditorButtons<S, A>,
|
|
100
|
+
fieldButtons: ReadonlyMap<string, Button<SettingsEditorState<S, A>>>,
|
|
101
|
+
chrome: SettingsEditorCardChrome<S, A>,
|
|
102
|
+
translator: Translator,
|
|
103
|
+
locale: Locale,
|
|
104
|
+
dressing: SettingsEditorDressing,
|
|
105
|
+
): InteractiveView<SettingsEditorState<S, A>> {
|
|
106
|
+
const screenTitle = translator.translate(locale, chrome.titleKey);
|
|
107
|
+
// Separates the levels in the breadcrumb trail.
|
|
108
|
+
const BREADCRUMB_SEPARATOR = " › ";
|
|
109
|
+
|
|
110
|
+
// The root is a level like any other, named after the screen itself, so
|
|
111
|
+
// rendering never special-cases "no depth declared" — the same bargain
|
|
112
|
+
// `createEditorView` strikes for the embeds layout. Its own `entryLayout`
|
|
113
|
+
// is the chrome's, mirroring `SettingsEditorLevel.entryLayout` for the one
|
|
114
|
+
// level that is never itself a `SettingsEditorLevel`.
|
|
115
|
+
const root: SettingsEditorLevel<F> = {
|
|
116
|
+
key: ROOT_LEVEL_KEY,
|
|
117
|
+
titleKey: chrome.titleKey,
|
|
118
|
+
fields,
|
|
119
|
+
entryLayout: chrome.entryLayout,
|
|
120
|
+
};
|
|
121
|
+
const allLevels: readonly SettingsEditorLevel<F>[] = [root, ...(levels ?? [])];
|
|
122
|
+
|
|
123
|
+
// A level's fields are authored, not accumulated: passing more entries than a
|
|
124
|
+
// card can carry is a mistake in the declaration, and Discord would reject
|
|
125
|
+
// the whole message rather than show the first ones that fit. Failing here
|
|
126
|
+
// names the level; failing at render time would only say the message was
|
|
127
|
+
// invalid.
|
|
128
|
+
for (const level of allLevels) {
|
|
129
|
+
// A `"buttons"` level spends its budget on rows of `ActionRow`s rather
|
|
130
|
+
// than `Section`s, so it is checked against its own, higher cap instead
|
|
131
|
+
// of the `"sections"` one — failing here names the level rather than the
|
|
132
|
+
// invalid message Discord would otherwise reject. The chrome's own
|
|
133
|
+
// `entryActions`, when declared, reserves its worst case on top of the
|
|
134
|
+
// level's declared fields: those buttons exist only once a state renders
|
|
135
|
+
// them, which this construction-time guard has none of to count instead.
|
|
136
|
+
if (level.entryLayout === "buttons") {
|
|
137
|
+
const reserve = chrome.entryActions === undefined ? 0 : ENTRY_ACTIONS_RESERVE;
|
|
138
|
+
if (level.fields.length + reserve > MAX_CARD_BUTTON_LEVEL_ENTRIES) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Settings editor level "${level.key}" declares ${level.fields.length} entries${
|
|
141
|
+
reserve === 0 ? "" : ` (+${reserve} reserved for the chrome's own entryActions)`
|
|
142
|
+
} with entryLayout "buttons"; a card holds ${MAX_CARD_BUTTON_LEVEL_ENTRIES} before its own controls. Split it into a deeper level.`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
} else if (level.fields.length > MAX_CARD_LEVEL_ENTRIES) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Settings editor level "${level.key}" declares ${level.fields.length} entries; a card section holds ${MAX_CARD_LEVEL_ENTRIES} before its own controls. Split it into a deeper level.`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function levelAt(key: string): SettingsEditorLevel<F> {
|
|
153
|
+
return allLevels.find((level) => level.key === key) ?? root;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Where the reader is, as a trail rather than only the current name: a level
|
|
158
|
+
* called "Roles" says nothing on its own about which type it belongs to.
|
|
159
|
+
*/
|
|
160
|
+
function heading(state: SettingsEditorState<S, A>): string {
|
|
161
|
+
return state.path
|
|
162
|
+
.map((key) => translator.translate(locale, levelAt(key).titleKey))
|
|
163
|
+
.join(BREADCRUMB_SEPARATOR);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The bot's own signature, once at the bottom of the card rather than on
|
|
168
|
+
* every embed the way two embeds each carried their own footer. Discord's
|
|
169
|
+
* own timestamp markdown, so it reads in the viewer's own locale exactly as
|
|
170
|
+
* an embed's timestamp already did.
|
|
171
|
+
*/
|
|
172
|
+
function signature(): string {
|
|
173
|
+
const unixSeconds = Math.floor(dressing.clock.now().getTime() / 1000);
|
|
174
|
+
return `-# ${screenTitle} • <t:${unixSeconds}:t>`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* One menu entry, as the `Section` that replaces it: its label as the title,
|
|
179
|
+
* its hint as the line under it, and the button `mountSettingsEditor`
|
|
180
|
+
* registered for it as the accessory — the very button that opens its modal
|
|
181
|
+
* or descends into it, reached by a click instead of a pick.
|
|
182
|
+
*/
|
|
183
|
+
function entrySection(field: SettingsEditorField<F>): CardBlock<SettingsEditorState<S, A>> {
|
|
184
|
+
const title = translator.translate(locale, field.labelKey);
|
|
185
|
+
const lines = [translator.translate(locale, field.hintKey)];
|
|
186
|
+
const action = fieldButtons.get(field.key);
|
|
187
|
+
return action === undefined
|
|
188
|
+
? { kind: "entry", title, lines }
|
|
189
|
+
: { kind: "entry", title, lines, action };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* A `"buttons"` level's entries, as the rows that replace their `Section`s:
|
|
194
|
+
* each field's own button — labelled with the entry's own name, not the
|
|
195
|
+
* generic "Open"/"Edit" a `"sections"` level wears, which is the whole
|
|
196
|
+
* point of folding them into rows of buttons instead of stacking them.
|
|
197
|
+
* `mountSettingsEditor` is what actually words the button that way — see
|
|
198
|
+
* its own `buildFieldButton` — this only collects the ones this level owns.
|
|
199
|
+
*/
|
|
200
|
+
function entryButtons(level: SettingsEditorLevel<F>): Button<SettingsEditorState<S, A>>[] {
|
|
201
|
+
return level.fields.map((field) => {
|
|
202
|
+
const button = fieldButtons.get(field.key);
|
|
203
|
+
if (button === undefined) {
|
|
204
|
+
// `fieldButtons` is built by `mountSettingsEditor` over every field of
|
|
205
|
+
// every level up front, this level's included — unreachable outside a
|
|
206
|
+
// wiring mistake in that map, not something an administrator can cause.
|
|
207
|
+
throw new Error(
|
|
208
|
+
`Settings editor level "${level.key}" has no button registered for field "${field.key}".`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return button;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* A `"buttons"` level's entries row for one render: its own field buttons,
|
|
217
|
+
* plus whatever {@link SettingsEditorCardChrome.entryActions} adds — the
|
|
218
|
+
* create/edit/remove row a module's own removal control now lives in,
|
|
219
|
+
* rather than floating beside a picker or hiding at the bottom of the card.
|
|
220
|
+
*
|
|
221
|
+
* A screen's own action waiting on its second click takes the row over
|
|
222
|
+
* entirely: while `state.confirming` names anything, the level's own
|
|
223
|
+
* buttons step aside and the row is exactly what `entryActions` returns —
|
|
224
|
+
* the confirm/cancel pair, once it recognises its own key in `confirming`.
|
|
225
|
+
* The same "the pending question replaces what is here" rule the editor's
|
|
226
|
+
* own reset already follows for the row below the card, transposed to a
|
|
227
|
+
* level's own entries instead of the screen's outer one.
|
|
228
|
+
*/
|
|
229
|
+
function levelButtons(
|
|
230
|
+
level: SettingsEditorLevel<F>,
|
|
231
|
+
state: SettingsEditorState<S, A>,
|
|
232
|
+
): readonly Button<SettingsEditorState<S, A>>[] {
|
|
233
|
+
const actions = chrome.entryActions?.(state) ?? [];
|
|
234
|
+
return state.confirming === null ? [...entryButtons(level), ...actions] : actions;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Split a `"buttons"` level's entries into as many rows of
|
|
239
|
+
* {@link BUTTONS_PER_ROW} as they need — Discord's own cap on one
|
|
240
|
+
* `ActionRow`, not a limit on how many entries the level may declare in
|
|
241
|
+
* total (that is {@link MAX_CARD_BUTTON_LEVEL_ENTRIES}, checked above).
|
|
242
|
+
*/
|
|
243
|
+
function buttonRows(
|
|
244
|
+
buttons: readonly Button<SettingsEditorState<S, A>>[],
|
|
245
|
+
): CardBlock<SettingsEditorState<S, A>>[] {
|
|
246
|
+
const rows: CardBlock<SettingsEditorState<S, A>>[] = [];
|
|
247
|
+
for (let start = 0; start < buttons.length; start += BUTTONS_PER_ROW) {
|
|
248
|
+
rows.push({ kind: "controls", buttons: buttons.slice(start, start + BUTTONS_PER_ROW) });
|
|
249
|
+
}
|
|
250
|
+
return rows;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* The screen's own controls for the current state, outside the card
|
|
255
|
+
* entirely — see the module-level note on {@link SettingsEditorRow}. Empty
|
|
256
|
+
* for a screen with nothing to offer here at all — a flat screen with
|
|
257
|
+
* nothing to reset, with dismissing the message the only way out.
|
|
258
|
+
*/
|
|
259
|
+
function outerButtons(state: SettingsEditorState<S, A>): Button<SettingsEditorState<S, A>>[] {
|
|
260
|
+
if (state.confirming === SETTINGS_EDITOR_RESET_CONFIRMING) {
|
|
261
|
+
return buttons.confirming;
|
|
262
|
+
}
|
|
263
|
+
// Reset, then the return control — the order the outer row reads left to
|
|
264
|
+
// right. Each is absent rather than disabled wherever there is nothing for
|
|
265
|
+
// it to do, same principle both already followed on their own.
|
|
266
|
+
return [
|
|
267
|
+
...resetButtons(buttons, chrome, state),
|
|
268
|
+
...(canReturn(state.path) ? buttons.returning : []),
|
|
269
|
+
];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** {@link outerButtons}, compiled into the one row it renders as — or none at all. */
|
|
273
|
+
function outerRow(state: SettingsEditorState<S, A>, forceDisabled: boolean): SettingsEditorRow[] {
|
|
274
|
+
const row = outerButtons(state);
|
|
275
|
+
return row.length === 0 ? [] : [createActionRow(row, state, { disableAll: forceDisabled })];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The card's blocks, top to bottom — but for the screen's own controls,
|
|
280
|
+
* which render beside the card rather than inside it — see {@link outerRow}.
|
|
281
|
+
* `disabled` here means only "the editor's own reset is waiting for its
|
|
282
|
+
* second click", not the outer neutralising pass: that one is `Card.apply`'s
|
|
283
|
+
* own `forceDisabled`, applied uniformly across every block a second time —
|
|
284
|
+
* see {@link import("@/discord/ui/card").Card}.
|
|
285
|
+
*/
|
|
286
|
+
function openBlocks(
|
|
287
|
+
state: SettingsEditorState<S, A>,
|
|
288
|
+
): readonly (CardBlock<SettingsEditorState<S, A>> | null)[] {
|
|
289
|
+
const confirmingReset = state.confirming === SETTINGS_EDITOR_RESET_CONFIRMING;
|
|
290
|
+
const level = levelAt(currentLevelKey(state.path));
|
|
291
|
+
|
|
292
|
+
const notice: CardBlock<SettingsEditorState<S, A>> | null = confirmingReset
|
|
293
|
+
? {
|
|
294
|
+
kind: "entry",
|
|
295
|
+
title: translator.translate(locale, SETTINGS_EDITOR_MESSAGES.resetConfirmTitle),
|
|
296
|
+
lines: [translator.translate(locale, SETTINGS_EDITOR_MESSAGES.resetConfirmDescription)],
|
|
297
|
+
}
|
|
298
|
+
: state.notice === null
|
|
299
|
+
? null
|
|
300
|
+
: { kind: "text", content: state.notice.text };
|
|
301
|
+
|
|
302
|
+
const entries: CardBlock<SettingsEditorState<S, A>>[] =
|
|
303
|
+
level.entryLayout === "buttons"
|
|
304
|
+
? buttonRows(levelButtons(level, state))
|
|
305
|
+
: level.fields.map((field) => entrySection(field));
|
|
306
|
+
|
|
307
|
+
return [
|
|
308
|
+
{ kind: "text", content: `-# ${heading(state)}` },
|
|
309
|
+
notice,
|
|
310
|
+
...(chrome.extraBlocks?.(state, confirmingReset) ?? []),
|
|
311
|
+
{ kind: "separator" },
|
|
312
|
+
...chrome.preview(state),
|
|
313
|
+
{ kind: "separator" },
|
|
314
|
+
...entries,
|
|
315
|
+
{ kind: "text", content: signature() },
|
|
316
|
+
];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function cardDef(state: SettingsEditorState<S, A>): CardDef<SettingsEditorState<S, A>> {
|
|
320
|
+
return { accent: EMBED_COLORS.brand, blocks: openBlocks(state) };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const mentions =
|
|
324
|
+
chrome.allowedMentions === undefined ? {} : { allowedMentions: chrome.allowedMentions };
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
render: (state) => ({
|
|
328
|
+
layout: "card",
|
|
329
|
+
card: createCard(cardDef(state)),
|
|
330
|
+
// The screen's own controls, beside the card rather than inside it —
|
|
331
|
+
// empty for a screen with nothing to offer here, which is what
|
|
332
|
+
// `outerRow` already returns.
|
|
333
|
+
controls: outerRow(state, false),
|
|
334
|
+
// Always stated, empty included: this is the screen's whole set of
|
|
335
|
+
// attachments, and an update that said nothing would leave the image of
|
|
336
|
+
// an override that has just been cleared sitting on the message.
|
|
337
|
+
files: [...chrome.filesOf(state)],
|
|
338
|
+
...mentions,
|
|
339
|
+
}),
|
|
340
|
+
disabledControls: (state) => ({
|
|
341
|
+
components: [createCard(cardDef(state)).apply(state, true), ...outerRow(state, true)],
|
|
342
|
+
}),
|
|
343
|
+
};
|
|
344
|
+
}
|