/[sudobot]/trunk/src/utils/Pagination.ts
ViewVC logotype

Annotation of /trunk/src/utils/Pagination.ts

Parent Directory Parent Directory | Revision Log Revision Log


Revision 543 - (hide annotations)
Mon Jul 29 17:30:44 2024 UTC (8 months, 1 week ago) by rakin
File MIME type: application/typescript
File size: 7953 byte(s)
fix(pagination): update button emoji orders (#114)
1 rakin 492 /**
2     * This file is part of SudoBot.
3     *
4     * Copyright (C) 2021-2022 OSN Inc.
5     *
6     * SudoBot is free software; you can redistribute it and/or modify it
7     * under the terms of the GNU Affero General Public License as published by
8     * the Free Software Foundation, either version 3 of the License, or
9     * (at your option) any later version.
10     *
11     * SudoBot is distributed in the hope that it will be useful, but
12     * WITHOUT ANY WARRANTY; without even the implied warranty of
13     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14     * GNU Affero General Public License for more details.
15     *
16     * You should have received a copy of the GNU Affero General Public License
17     * along with SudoBot. If not, see <https://www.gnu.org/licenses/>.
18     */
19    
20 rakin 446 import DiscordClient from "../client/Client";
21     import MessageEmbed from "../client/MessageEmbed";
22     import { v4 as uuid } from 'uuid';
23 rakin 447 import { ButtonInteraction, InteractionCollector, InteractionReplyOptions, Message, MessageActionRow, MessageButton, MessageEditOptions, MessageOptions, ReplyMessageOptions } from "discord.js";
24 rakin 484 import { emoji } from "./Emoji";
25 rakin 446
26     export interface EmbedBuilderOptions<T> {
27     data: Array<T>;
28     currentPage: number;
29     maxPages: number;
30     }
31    
32     export interface PaginationOptions<T> {
33     limit: number;
34     guild_id: string;
35     channel_id: string;
36     user_id?: string;
37 rakin 467 timeout?: number;
38 rakin 446 embedBuilder: (options: EmbedBuilderOptions<T>) => MessageEmbed;
39     actionRowBuilder?: (options: { first: boolean, last: boolean, next: boolean, back: boolean }) => MessageActionRow<MessageButton>;
40     }
41    
42     export default class Pagination<T> {
43     protected readonly client = DiscordClient.client;
44     protected readonly id: string;
45     protected currentPage: number = 1;
46    
47     constructor(protected readonly data: Array<T> = [], protected readonly options: PaginationOptions<T>) {
48     this.id = uuid();
49     }
50    
51     getOffset(page: number = 1) {
52     return (page - 1) * this.options.limit;
53     }
54    
55     getPaginatedData(page: number = 1) {
56     console.log(page, this.getOffset(page));
57     return this.data.slice(this.getOffset(page), this.getOffset(page) + this.options.limit);
58     }
59    
60     getEmbed(page: number = 1): MessageEmbed {
61     return this.options.embedBuilder({
62     data: this.getPaginatedData(page),
63     currentPage: this.currentPage,
64     maxPages: Math.ceil(this.data.length / this.options.limit),
65     });
66     }
67    
68     getMessageOptions(page: number = 1, actionRowOptions: { first: boolean, last: boolean, next: boolean, back: boolean } | undefined = undefined, optionsToMerge: ReplyMessageOptions & MessageOptions & InteractionReplyOptions & MessageEditOptions = {}) {
69     const options = {...optionsToMerge};
70     const actionRowOptionsDup = actionRowOptions ? {...actionRowOptions} : { first: true, last: true, next: true, back: true };
71    
72     if (actionRowOptionsDup && page <= 1) {
73     actionRowOptionsDup.back = false;
74     actionRowOptionsDup.first = false;
75     }
76    
77     if (actionRowOptionsDup && page >= Math.ceil(this.data.length / this.options.limit)) {
78     actionRowOptionsDup.last = false
79     actionRowOptionsDup.next = false;
80     }
81    
82     options.embeds ??= [];
83     options.embeds.push(this.getEmbed(page));
84 rakin 467
85 rakin 446 options.components ??= [];
86     options.components.push(this.getActionRow(actionRowOptionsDup));
87    
88     return options;
89     }
90    
91     getActionRow({ first, last, next, back }: { first: boolean, last: boolean, next: boolean, back: boolean } = { first: true, last: true, next: true, back: true }) {
92     if (this.options.actionRowBuilder) {
93     return this.options.actionRowBuilder({ first, last, next, back });
94     }
95    
96 rakin 467 const actionRow = new MessageActionRow<MessageButton>();
97 rakin 446
98     actionRow.addComponents(
99     new MessageButton()
100     .setCustomId(`pagination_first_${this.id}`)
101     .setStyle("PRIMARY")
102 rakin 484 .setDisabled(!first)
103 rakin 543 .setEmoji(emoji('ArrowLeft')!),
104 rakin 446 new MessageButton()
105     .setCustomId(`pagination_back_${this.id}`)
106     .setStyle("PRIMARY")
107 rakin 484 .setDisabled(!back)
108 rakin 543 .setEmoji(emoji('ChevronLeft')!),
109 rakin 446 new MessageButton()
110     .setCustomId(`pagination_next_${this.id}`)
111     .setStyle("PRIMARY")
112 rakin 484 .setDisabled(!next)
113 rakin 543 .setEmoji(emoji('ChevronRight')!),
114 rakin 446 new MessageButton()
115     .setCustomId(`pagination_last_${this.id}`)
116     .setStyle("PRIMARY")
117     .setDisabled(!last)
118 rakin 543 .setEmoji(emoji('ArrowRight')!)
119 rakin 446 );
120    
121     return actionRow;
122     }
123    
124     async start(message: Message) {
125     const collector = new InteractionCollector(this.client, {
126     guild: this.options.guild_id,
127     channel: this.options.channel_id,
128     interactionType: 'MESSAGE_COMPONENT',
129     componentType: 'BUTTON',
130     message,
131 rakin 467 time: this.options.timeout ?? 60_000,
132 rakin 446 filter: interaction => {
133     if (interaction.inGuild() && (!this.options.user_id || interaction.user.id === this.options.user_id)) {
134     return true;
135     }
136    
137     if (interaction.isRepliable()) {
138 rakin 485 interaction.reply({ content: 'That\'s not under your control or the button controls are expired', ephemeral: true });
139 rakin 446 }
140    
141     return false;
142     },
143     });
144    
145     collector.on("collect", async (interaction: ButtonInteraction) => {
146     if (!interaction.customId.endsWith(this.id)) {
147     return;
148     }
149    
150 rakin 467 // await interaction.deferUpdate();
151    
152 rakin 446 const maxPage = Math.ceil(this.data.length / this.options.limit);
153     const componentOptions = { first: true, last: true, next: true, back: true };
154    
155     if ([`pagination_next_${this.id}`, `pagination_back_${this.id}`].includes(interaction.customId)) {
156     console.log('here');
157    
158     if (this.currentPage >= maxPage && interaction.customId === `pagination_next_${this.id}`) {
159     console.log('here');
160     await interaction.reply({ content: maxPage === 1 ? "This is the only page!" : "You've reached the last page!", ephemeral: true });
161     return;
162     }
163    
164     if (this.currentPage <= 1 && interaction.customId === `pagination_back_${this.id}`) {
165     console.log('here');
166     await interaction.reply({ content: maxPage === 1 ? "This is the only page!" : "You're in the very first page!", ephemeral: true });
167     return;
168     }
169     }
170    
171     if (interaction.customId === `pagination_first_${this.id}`)
172     this.currentPage = 1;
173     else if (interaction.customId === `pagination_last_${this.id}`)
174     this.currentPage = maxPage;
175    
176     await interaction.update(this.getMessageOptions(
177     interaction.customId === `pagination_first_${this.id}` ? 1 :
178     interaction.customId === `pagination_last_${this.id}` ? maxPage :
179     (interaction.customId === `pagination_next_${this.id}` ? (this.currentPage >= maxPage ? this.currentPage : ++this.currentPage) : --this.currentPage),
180     componentOptions,
181     {
182     embeds: []
183     }
184     ));
185     });
186    
187     collector.on("end", async () => {
188 rakin 467 const component = message.components[0]; // this.getActionRow({ first: false, last: false, next: false, back: false })
189    
190     for (const i in component.components) {
191     component.components[i].disabled = true;
192     }
193    
194     await message.edit({ components: [component] });
195 rakin 446 });
196     }
197 rakin 543 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26