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 |
484 |
import { emoji } from "./Emoji"; |
6 |
rakin |
446 |
|
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 |
rakin |
467 |
timeout?: number; |
19 |
rakin |
446 |
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 |
rakin |
467 |
|
66 |
rakin |
446 |
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 |
rakin |
467 |
const actionRow = new MessageActionRow<MessageButton>(); |
78 |
rakin |
446 |
|
79 |
|
|
actionRow.addComponents( |
80 |
|
|
new MessageButton() |
81 |
|
|
.setCustomId(`pagination_first_${this.id}`) |
82 |
|
|
.setStyle("PRIMARY") |
83 |
rakin |
484 |
.setDisabled(!first) |
84 |
|
|
.setEmoji(emoji('ChevronLeft')!), |
85 |
rakin |
446 |
new MessageButton() |
86 |
|
|
.setCustomId(`pagination_back_${this.id}`) |
87 |
|
|
.setStyle("PRIMARY") |
88 |
rakin |
484 |
.setDisabled(!back) |
89 |
|
|
.setEmoji(emoji('ArrowLeft')!), |
90 |
rakin |
446 |
new MessageButton() |
91 |
|
|
.setCustomId(`pagination_next_${this.id}`) |
92 |
|
|
.setStyle("PRIMARY") |
93 |
rakin |
484 |
.setDisabled(!next) |
94 |
|
|
.setEmoji(emoji('ArrowRight')!), |
95 |
rakin |
446 |
new MessageButton() |
96 |
|
|
.setCustomId(`pagination_last_${this.id}`) |
97 |
|
|
.setStyle("PRIMARY") |
98 |
|
|
.setDisabled(!last) |
99 |
rakin |
484 |
.setEmoji(emoji('ChevronRight')!) |
100 |
rakin |
446 |
); |
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 |
rakin |
467 |
time: this.options.timeout ?? 60_000, |
113 |
rakin |
446 |
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 |
rakin |
485 |
interaction.reply({ content: 'That\'s not under your control or the button controls are expired', ephemeral: true }); |
120 |
rakin |
446 |
} |
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 |
rakin |
467 |
// await interaction.deferUpdate(); |
132 |
|
|
|
133 |
rakin |
446 |
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 |
rakin |
467 |
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 |
rakin |
446 |
}); |
177 |
|
|
} |
178 |
|
|
} |