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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 467 - (show 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 import DiscordClient from "../client/Client";
2 import MessageEmbed from "../client/MessageEmbed";
3 import { v4 as uuid } from 'uuid';
4 import { ButtonInteraction, InteractionCollector, InteractionReplyOptions, Message, MessageActionRow, MessageButton, MessageEditOptions, MessageOptions, ReplyMessageOptions } from "discord.js";
5
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 timeout?: number;
18 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
65 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 const actionRow = new MessageActionRow<MessageButton>();
77
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 time: this.options.timeout ?? 60_000,
112 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 // await interaction.deferUpdate();
131
132 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 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 });
176 }
177 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26