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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 467 - (hide annotations)
Mon Jul 29 17:30:23 2024 UTC (8 months, 1 week ago) by rakin
File MIME type: application/typescript
File size: 7133 byte(s)
perf(pagination): better response time
1 rakin 446 import DiscordClient from "../client/Client";
2     import MessageEmbed from "../client/MessageEmbed";
3     import { v4 as uuid } from 'uuid';
4 rakin 447 import { ButtonInteraction, InteractionCollector, InteractionReplyOptions, Message, MessageActionRow, MessageButton, MessageEditOptions, MessageOptions, ReplyMessageOptions } from "discord.js";
5 rakin 446
6     export interface EmbedBuilderOptions<T> {
7     data: Array<T>;
8     currentPage: number;
9     maxPages: number;
10     }
11    
12     export interface PaginationOptions<T> {
13     limit: number;
14     guild_id: string;
15     channel_id: string;
16     user_id?: string;
17 rakin 467 timeout?: number;
18 rakin 446 embedBuilder: (options: EmbedBuilderOptions<T>) => MessageEmbed;
19     actionRowBuilder?: (options: { first: boolean, last: boolean, next: boolean, back: boolean }) => MessageActionRow<MessageButton>;
20     }
21    
22     export default class Pagination<T> {
23     protected readonly client = DiscordClient.client;
24     protected readonly id: string;
25     protected currentPage: number = 1;
26    
27     constructor(protected readonly data: Array<T> = [], protected readonly options: PaginationOptions<T>) {
28     this.id = uuid();
29     }
30    
31     getOffset(page: number = 1) {
32     return (page - 1) * this.options.limit;
33     }
34    
35     getPaginatedData(page: number = 1) {
36     console.log(page, this.getOffset(page));
37     return this.data.slice(this.getOffset(page), this.getOffset(page) + this.options.limit);
38     }
39    
40     getEmbed(page: number = 1): MessageEmbed {
41     return this.options.embedBuilder({
42     data: this.getPaginatedData(page),
43     currentPage: this.currentPage,
44     maxPages: Math.ceil(this.data.length / this.options.limit),
45     });
46     }
47    
48     getMessageOptions(page: number = 1, actionRowOptions: { first: boolean, last: boolean, next: boolean, back: boolean } | undefined = undefined, optionsToMerge: ReplyMessageOptions & MessageOptions & InteractionReplyOptions & MessageEditOptions = {}) {
49     const options = {...optionsToMerge};
50     const actionRowOptionsDup = actionRowOptions ? {...actionRowOptions} : { first: true, last: true, next: true, back: true };
51    
52     if (actionRowOptionsDup && page <= 1) {
53     actionRowOptionsDup.back = false;
54     actionRowOptionsDup.first = false;
55     }
56    
57     if (actionRowOptionsDup && page >= Math.ceil(this.data.length / this.options.limit)) {
58     actionRowOptionsDup.last = false
59     actionRowOptionsDup.next = false;
60     }
61    
62     options.embeds ??= [];
63     options.embeds.push(this.getEmbed(page));
64 rakin 467
65 rakin 446 options.components ??= [];
66     options.components.push(this.getActionRow(actionRowOptionsDup));
67    
68     return options;
69     }
70    
71     getActionRow({ first, last, next, back }: { first: boolean, last: boolean, next: boolean, back: boolean } = { first: true, last: true, next: true, back: true }) {
72     if (this.options.actionRowBuilder) {
73     return this.options.actionRowBuilder({ first, last, next, back });
74     }
75    
76 rakin 467 const actionRow = new MessageActionRow<MessageButton>();
77 rakin 446
78     actionRow.addComponents(
79     new MessageButton()
80     .setCustomId(`pagination_first_${this.id}`)
81     .setStyle("PRIMARY")
82     .setLabel('⏪')
83     .setDisabled(!first),
84     new MessageButton()
85     .setCustomId(`pagination_back_${this.id}`)
86     .setStyle("PRIMARY")
87     .setLabel('◀')
88     .setDisabled(!back),
89     new MessageButton()
90     .setCustomId(`pagination_next_${this.id}`)
91     .setStyle("PRIMARY")
92     .setLabel('▶')
93     .setDisabled(!next),
94     new MessageButton()
95     .setCustomId(`pagination_last_${this.id}`)
96     .setStyle("PRIMARY")
97     .setLabel('⏩')
98     .setDisabled(!last)
99     );
100    
101     return actionRow;
102     }
103    
104     async start(message: Message) {
105     const collector = new InteractionCollector(this.client, {
106     guild: this.options.guild_id,
107     channel: this.options.channel_id,
108     interactionType: 'MESSAGE_COMPONENT',
109     componentType: 'BUTTON',
110     message,
111 rakin 467 time: this.options.timeout ?? 60_000,
112 rakin 446 filter: interaction => {
113     if (interaction.inGuild() && (!this.options.user_id || interaction.user.id === this.options.user_id)) {
114     return true;
115     }
116    
117     if (interaction.isRepliable()) {
118     interaction.reply({ content: 'That\'t not under your control or the button controls are expired', ephemeral: true });
119     }
120    
121     return false;
122     },
123     });
124    
125     collector.on("collect", async (interaction: ButtonInteraction) => {
126     if (!interaction.customId.endsWith(this.id)) {
127     return;
128     }
129    
130 rakin 467 // await interaction.deferUpdate();
131    
132 rakin 446 const maxPage = Math.ceil(this.data.length / this.options.limit);
133     const componentOptions = { first: true, last: true, next: true, back: true };
134    
135     if ([`pagination_next_${this.id}`, `pagination_back_${this.id}`].includes(interaction.customId)) {
136     console.log('here');
137    
138     if (this.currentPage >= maxPage && interaction.customId === `pagination_next_${this.id}`) {
139     console.log('here');
140     await interaction.reply({ content: maxPage === 1 ? "This is the only page!" : "You've reached the last page!", ephemeral: true });
141     return;
142     }
143    
144     if (this.currentPage <= 1 && interaction.customId === `pagination_back_${this.id}`) {
145     console.log('here');
146     await interaction.reply({ content: maxPage === 1 ? "This is the only page!" : "You're in the very first page!", ephemeral: true });
147     return;
148     }
149     }
150    
151     if (interaction.customId === `pagination_first_${this.id}`)
152     this.currentPage = 1;
153     else if (interaction.customId === `pagination_last_${this.id}`)
154     this.currentPage = maxPage;
155    
156     await interaction.update(this.getMessageOptions(
157     interaction.customId === `pagination_first_${this.id}` ? 1 :
158     interaction.customId === `pagination_last_${this.id}` ? maxPage :
159     (interaction.customId === `pagination_next_${this.id}` ? (this.currentPage >= maxPage ? this.currentPage : ++this.currentPage) : --this.currentPage),
160     componentOptions,
161     {
162     embeds: []
163     }
164     ));
165     });
166    
167     collector.on("end", async () => {
168 rakin 467 const component = message.components[0]; // this.getActionRow({ first: false, last: false, next: false, back: false })
169    
170     for (const i in component.components) {
171     component.components[i].disabled = true;
172     }
173    
174     await message.edit({ components: [component] });
175 rakin 446 });
176     }
177     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26