share more logic between renderers

This commit is contained in:
itsMapleLeaf
2022-08-07 12:56:57 -05:00
parent bbf3c4ab17
commit 74bada9351

View File

@@ -1,31 +1,91 @@
import { AsyncQueue } from "@reacord/helpers/async-queue.js"
import type { Client, Message } from "discord.js"
import { TextChannel } from "discord.js"
import type { MessageUpdatePayload } from "./make-message-update-payload.js"
import { makeMessageUpdatePayload } from "./make-message-update-payload.js"
import type { Node } from "./node.js"
import type { InteractionInfo } from "./reacord-client.js"
export type Renderer = {
update(tree: Node): Promise<void>
deactivate(): Promise<void>
destroy(): Promise<void>
export abstract class Renderer {
private readonly queue = new AsyncQueue()
private active = true
private destroyPromise?: Promise<void>
protected abstract handleUpdate(payload: MessageUpdatePayload): Promise<void>
protected abstract handleDestroy(): Promise<void>
protected abstract handleDeactivate(): Promise<void>
update(tree: Node) {
const payload = makeMessageUpdatePayload(tree)
return this.queue.add(async () => {
if (!this.active) return
await this.handleUpdate(payload)
})
}
destroy() {
if (this.destroyPromise) return this.destroyPromise
this.active = false
const promise = this.queue.add(() => this.handleDestroy())
// if it failed, we'll want to try again
promise.catch((error) => {
console.error("Failed to destroy message:", error)
this.destroyPromise = undefined
})
return (this.destroyPromise = promise)
}
deactivate() {
return this.queue.add(async () => {
if (!this.active) return
await this.handleDeactivate()
// set active to false *after* running deactivation,
// so that other queued operations run first,
// and we can show the correct deactivated state
this.active = false
})
}
}
export class ChannelMessageRenderer implements Renderer {
private readonly queue = new AsyncQueue()
export class ChannelMessageRenderer extends Renderer {
private channel: TextChannel | undefined
private message: Message | undefined
private active = true
constructor(
private readonly channelId: string,
private readonly client: Promise<Client<true>>,
) {}
private readonly clientPromise: Promise<Client<true>>,
) {
super()
}
override async handleUpdate(payload: MessageUpdatePayload): Promise<void> {
if (this.message) {
await this.message.edit(payload)
} else {
const channel = await this.getChannel()
this.message = await channel.send(payload)
}
}
override async handleDestroy(): Promise<void> {
const message = this.message
this.message = undefined
await message?.delete()
}
override async handleDeactivate(): Promise<void> {
throw new Error("not implemented")
}
private async getChannel(): Promise<TextChannel> {
if (this.channel) return this.channel
const client = await this.client
const client = await this.clientPromise
const channel =
client.channels.cache.get(this.channelId) ??
@@ -38,71 +98,40 @@ export class ChannelMessageRenderer implements Renderer {
this.channel = channel
return channel
}
update(tree: Node) {
const payload = makeMessageUpdatePayload(tree)
return this.queue.add(async () => {
if (!this.active) return
if (this.message) {
await this.message.edit(payload)
} else {
const channel = await this.getChannel()
this.message = await channel.send(payload)
}
})
}
deactivate() {
return this.queue.add(async () => {
if (!this.active) return
// TODO: disable message components
// set active to false *after* running deactivation,
// so that other queued operations run first,
// and we can show the correct deactivated state
this.active = false
})
}
destroy() {
this.active = false
return this.queue.add(async () => {
const message = this.message
this.message = undefined
await message?.delete()
})
}
}
export class InteractionReplyRenderer implements Renderer {
constructor(private readonly interaction: InteractionInfo) {}
export class InteractionReplyRenderer extends Renderer {
constructor(private readonly interaction: InteractionInfo) {
super()
}
update(tree: Node): Promise<void> {
handleUpdate(payload: MessageUpdatePayload): Promise<void> {
throw new Error("Method not implemented.")
}
deactivate(): Promise<void> {
handleDestroy(): Promise<void> {
throw new Error("Method not implemented.")
}
destroy(): Promise<void> {
handleDeactivate(): Promise<void> {
throw new Error("Method not implemented.")
}
}
export class EphemeralInteractionReplyRenderer implements Renderer {
constructor(private readonly interaction: InteractionInfo) {}
export class EphemeralInteractionReplyRenderer extends Renderer {
constructor(private readonly interaction: InteractionInfo) {
super()
}
update(tree: Node): Promise<void> {
handleUpdate(payload: MessageUpdatePayload): Promise<void> {
throw new Error("Method not implemented.")
}
deactivate(): Promise<void> {
handleDestroy(): Promise<void> {
throw new Error("Method not implemented.")
}
destroy(): Promise<void> {
handleDeactivate(): Promise<void> {
throw new Error("Method not implemented.")
}
}