diff --git a/src/components/Component.ts b/src/components/Component.ts new file mode 100644 index 0000000..1d0cb24 --- /dev/null +++ b/src/components/Component.ts @@ -0,0 +1,77 @@ +import StaticComponent, { ComponentState } from './StaticComponent'; +import InteractiveComponent from './InteractiveComponent'; + +import { + ActionRowComponent, + ButtonComponent, + ContainerComponent, + EmbedStructure, + MediaComponent, + SectionComponent, + TextDisplayComponent, +} from "../types"; + +export type SupportedComponents = + | EmbedStructure + | ButtonComponent + | ActionRowComponent + | TextDisplayComponent + | SectionComponent + | MediaComponent + | ContainerComponent; + +export enum ComponentType { + Static = 'Static', + Interactive = 'Interactive', +} + +export type StateListener = (newState: T) => void; +export type StateSetter = (newState: Partial | ((prevState: T) => T)) => void; + +export default class Component { + private static componentRegistry = new Map | InteractiveComponent>(); + private static stateRegistry = new Map>(); + + public static registerComponent(id: symbol, component: StaticComponent | InteractiveComponent) { + Component.componentRegistry.set(id, component); + } + + public static initializeState(id: symbol, data: T) { + Component.stateRegistry.set(id, { data, listeners: new Set>() }); + } + + public static getCurrentData(id: symbol): T { + const state = Component.stateRegistry.get(id); + if (!state) throw new Error('Component state not found'); + + return state.data as T; + } + + public static useState(component: InteractiveComponent): [T, StateListener] { + const state = Component.stateRegistry.get(component.id); + + if (!state) throw new Error('Component state not found. Make sure the component was created properly.'); + + const setter: StateSetter = (newStateOrUpdater) => { + const currentState = Component.stateRegistry.get(component.id)!; + + const newData = typeof newStateOrUpdater === 'function' + ? newStateOrUpdater(currentState.data as T) + : { ...currentState.data as T, ...newStateOrUpdater } as T; + + currentState.data = newData as SupportedComponents; + currentState.listeners.forEach(listener => listener(newData as SupportedComponents)); + }; + + return [state.data as T, setter]; + } + + public static createStaticComponent(data: T): StaticComponent { + return new StaticComponent(data); + } + + public static createInteractiveComponent(data: T): InteractiveComponent { + return new InteractiveComponent(data); + } + +} \ No newline at end of file diff --git a/src/components/InteractiveComponent.ts b/src/components/InteractiveComponent.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/components/StaticComponent.ts b/src/components/StaticComponent.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/types/ActionRow.ts b/src/types/ActionRow.ts index 6200586..59dbb1e 100644 --- a/src/types/ActionRow.ts +++ b/src/types/ActionRow.ts @@ -22,6 +22,7 @@ import { MessageComponentType } from "./types"; import { ButtonComponent } from './Button'; import { TextInputComponent } from './TextInputField'; +import { LabelComponent } from './LabelComponent'; import { ChannelSelectMenuComponent, @@ -42,7 +43,8 @@ export type ActionRowComponents = | RoleSelectMenuComponent | StringSelectMenuComponent | MentionableSelectMenuComponent - | ChannelSelectMenuComponent; + | ChannelSelectMenuComponent + | LabelComponent; /** * @type ActionRowComponent diff --git a/src/types/LabelComponent.ts b/src/types/LabelComponent.ts new file mode 100644 index 0000000..a7fdf83 --- /dev/null +++ b/src/types/LabelComponent.ts @@ -0,0 +1,21 @@ +import { BaseComponent } from './Component'; +import { MessageComponentType } from './types'; +import { TextInputComponent } from "./TextInputField"; + +import { + ChannelSelectMenuComponent, + MentionableSelectMenuComponent, + RoleSelectMenuComponent, + UserSelectMenuComponent, +} from "./SelectMenu"; + +/** + * @type LabelComponent + * @description Label component container for select menus in modals + * @see https://discord.com/developers/docs/components/using-modal-components + * + **/ +export type LabelComponent = BaseComponent & { + type: MessageComponentType.LABEL + components: Array +}; \ No newline at end of file diff --git a/src/types/Modal.ts b/src/types/Modal.ts index 7337898..75a237a 100644 --- a/src/types/Modal.ts +++ b/src/types/Modal.ts @@ -17,18 +17,18 @@ * along with this program. If not, see . */ -import { TextInputComponent } from './TextInputField'; - -/** - * @type RawModal - * @description The raw modal data as described by the Discord API - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-modal - */ -export type RawModal = { - custom_id: string - title: string - components: Array<{ - type: 1 - components: Array - }> +import { LabelComponent } from "./LabelComponent"; + +/** + * @type RawModal + * @description The raw modal data as described by the Discord API + * @see https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-modal + */ +export type RawModal = { + custom_id: string + title: string + components: Array<{ + type: 1 + components: Array + }> }; \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index f478b2b..c21fa45 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -24,6 +24,7 @@ export * from './Container'; export * from './Embed'; export * from './File'; export * from './InteractibleComponent'; +export * from './LabelComponent'; export * from './Media'; export * from './Modal'; export * from './Section'; diff --git a/src/types/types.ts b/src/types/types.ts index c38ffc0..e12c3a6 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,22 +1,22 @@ -/** - * This file is part of Builders (https://github.com/FCAgreatgoals/builders). - * - * Copyright (C) 2025 SAS French Community Agency - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - +/** + * This file is part of Builders (https://github.com/FCAgreatgoals/builders). + * + * Copyright (C) 2025 SAS French Community Agency + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + export enum MessageComponentType { ACTION_ROW = 1, BUTTON = 2, @@ -32,7 +32,8 @@ export enum MessageComponentType { MEDIA_GALERY = 12, FILE = 13, SEPARATOR = 14, - CONTAINER = 17 + CONTAINER = 17, + LABEL = 18 } /**