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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26