From 74bada935150610539e2512a0cda789b7e85c003 Mon Sep 17 00:00:00 2001 From: itsMapleLeaf <19603573+itsMapleLeaf@users.noreply.github.com> Date: Sun, 7 Aug 2022 12:56:57 -0500 Subject: [PATCH] share more logic between renderers --- packages/reacord/src/renderer.ts | 139 +++++++++++++++++++------------ 1 file changed, 84 insertions(+), 55 deletions(-) diff --git a/packages/reacord/src/renderer.ts b/packages/reacord/src/renderer.ts index 1bab8ee..3023652 100644 --- a/packages/reacord/src/renderer.ts +++ b/packages/reacord/src/renderer.ts @@ -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 - deactivate(): Promise - destroy(): Promise +export abstract class Renderer { + private readonly queue = new AsyncQueue() + private active = true + private destroyPromise?: Promise + + protected abstract handleUpdate(payload: MessageUpdatePayload): Promise + protected abstract handleDestroy(): Promise + protected abstract handleDeactivate(): Promise + + 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>, - ) {} + private readonly clientPromise: Promise>, + ) { + super() + } + + override async handleUpdate(payload: MessageUpdatePayload): Promise { + if (this.message) { + await this.message.edit(payload) + } else { + const channel = await this.getChannel() + this.message = await channel.send(payload) + } + } + + override async handleDestroy(): Promise { + const message = this.message + this.message = undefined + await message?.delete() + } + + override async handleDeactivate(): Promise { + throw new Error("not implemented") + } private async getChannel(): Promise { 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 { + handleUpdate(payload: MessageUpdatePayload): Promise { throw new Error("Method not implemented.") } - deactivate(): Promise { + handleDestroy(): Promise { throw new Error("Method not implemented.") } - destroy(): Promise { + handleDeactivate(): Promise { 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 { + handleUpdate(payload: MessageUpdatePayload): Promise { throw new Error("Method not implemented.") } - deactivate(): Promise { + handleDestroy(): Promise { throw new Error("Method not implemented.") } - destroy(): Promise { + handleDeactivate(): Promise { throw new Error("Method not implemented.") } }