mirror of
https://gitlab.com/bignutty/labscore.git
synced 2025-06-08 14:13:02 -04:00
[nextgen/cardstack] add cardstack cloning
This commit is contained in:
parent
2e523b1dad
commit
e2075b8306
1 changed files with 74 additions and 44 deletions
|
@ -6,7 +6,7 @@ const {editOrReply} = require("#utils/message");
|
|||
const {STATIC_ASSETS} = require("#utils/statics");
|
||||
|
||||
const {Context} = require("detritus-client/lib/command");
|
||||
const {MessageComponentTypes, InteractionCallbackTypes} = require("detritus-client/lib/constants");
|
||||
const {MessageComponentTypes, InteractionCallbackTypes, MessageFlags} = require("detritus-client/lib/constants");
|
||||
const {Message} = require("detritus-client/lib/structures");
|
||||
const {ComponentContext, Components, ComponentActionRow, ComponentButton} = require("detritus-client/lib/utils");
|
||||
|
||||
|
@ -44,9 +44,12 @@ class DynamicCardStack {
|
|||
* @param {boolean} options.disableStackCache Allows disabling the stack result cache, meaning that every trigger will reevaluate a stack
|
||||
* @param {boolean} options.pageNumbers Renders Page Numbers in the footer of all embeds in cards.
|
||||
* @param {Function} options.pageNumberGenerator Function that renders a page number. Default style is `Page <index>/<total>`
|
||||
* @param {boolean} options.disableCloning Disables cloning a card stack when someone other than the author interacts with it.
|
||||
* @param {boolean} options.ephemeral Makes the response ephemeral (primarily used by cloning)
|
||||
*/
|
||||
constructor(context, options) {
|
||||
this.context = context;
|
||||
this.options = options;
|
||||
|
||||
this.cards = options.cards || [];
|
||||
this.buttons = options.buttons || ["previous", "next"]
|
||||
|
@ -56,6 +59,8 @@ class DynamicCardStack {
|
|||
this.expires = options.expires || 5 * 60 * 1000;
|
||||
this.pageNumbers = options.pageNumbers || true;
|
||||
this.pageNumberGenerator = options.pageNumberGenerator || ((pg) => `Page ${pg.index + 1}/${pg.activeCardStack.length}`);
|
||||
this.disableCloning = options.disableCloning || false;
|
||||
this.ephemeral = options.ephemeral || false;
|
||||
|
||||
this.rootIndex = this.index;
|
||||
|
||||
|
@ -137,7 +142,7 @@ class DynamicCardStack {
|
|||
* Creates a new cardstack in the given channel
|
||||
* @private
|
||||
*/
|
||||
_spawn() {
|
||||
_spawn(createMessage = true) {
|
||||
this._createDynamicCardStack(this.context.client);
|
||||
|
||||
this.activeCardStack = [...this.cards];
|
||||
|
@ -158,10 +163,12 @@ class DynamicCardStack {
|
|||
|
||||
//this.spawned = Date.now()
|
||||
|
||||
return this._edit({
|
||||
if (createMessage) return this._edit({
|
||||
...this.getCurrentCard(),
|
||||
components: this.listener
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -217,6 +224,9 @@ class DynamicCardStack {
|
|||
})
|
||||
}
|
||||
|
||||
// TODO: ensure flags don't get overwritten/allow supplying custom flags
|
||||
if (this.ephemeral) card.flags = MessageFlags.EPHEMERAL;
|
||||
|
||||
return card;
|
||||
} catch (e) {
|
||||
console.error("Card rendering failed:")
|
||||
|
@ -246,7 +256,7 @@ class DynamicCardStack {
|
|||
* Decreases the index and returns the next card from the stack.
|
||||
* @returns {Message} Card
|
||||
*/
|
||||
previousPage() {
|
||||
previousCard() {
|
||||
this.index = this.index - 1;
|
||||
if (this.index < 0) {
|
||||
if (this.loopPages) this.index = this.activeCardStack.length - 1;
|
||||
|
@ -267,7 +277,7 @@ class DynamicCardStack {
|
|||
let message = Object.assign({}, cardContent);
|
||||
|
||||
if (!components) {
|
||||
this.listener.components = this._renderComponents();
|
||||
this._renderComponents();
|
||||
message.components = this.listener;
|
||||
} else {
|
||||
message.components = components;
|
||||
|
@ -279,7 +289,9 @@ class DynamicCardStack {
|
|||
return editOrReply(this.context, {
|
||||
...message,
|
||||
reference: true,
|
||||
allowedMentions: {parse: [], repliedUser: false}
|
||||
allowedMentions: {parse: [], repliedUser: false},
|
||||
// TODO: allow supplying flags
|
||||
flags: this.ephemeral ? MessageFlags.EPHEMERAL : 0
|
||||
})
|
||||
} catch (e) {
|
||||
console.error("Message editing failed:")
|
||||
|
@ -378,7 +390,7 @@ class DynamicCardStack {
|
|||
// We currently support up to 5 "slots" (action rows),
|
||||
// although the amount you can actually use depends
|
||||
// on how many components are added to each slot.
|
||||
let componentSlots = [[],[],[],[],[]];
|
||||
let componentSlots = [[], [], [], [], []];
|
||||
|
||||
// First Row always starts with built-in components
|
||||
for (const b of this.buttons) {
|
||||
|
@ -419,28 +431,30 @@ class DynamicCardStack {
|
|||
let renderedSlots = [];
|
||||
|
||||
// Render slots
|
||||
for(const components of componentSlots) {
|
||||
if(components.length === 0) continue;
|
||||
for (const components of componentSlots) {
|
||||
if (components.length === 0) continue;
|
||||
|
||||
let row = new ComponentActionRow({});
|
||||
|
||||
// Slot all components into their respective rows.
|
||||
while(components.length > 0) {
|
||||
while (components.length > 0) {
|
||||
row.addButton(components.shift());
|
||||
|
||||
// Create a new row for content to overflow in.
|
||||
if(row.isFull){
|
||||
if (row.isFull) {
|
||||
renderedSlots.push(row)
|
||||
row = new ComponentActionRow({});
|
||||
}
|
||||
}
|
||||
|
||||
// Push rendered row to stack if there are components in it.
|
||||
if(!row.isEmpty) renderedSlots.push(row);
|
||||
if (!row.isEmpty) renderedSlots.push(row);
|
||||
}
|
||||
|
||||
if(renderedSlots.length > 5) console.warn("Component Overflow - Limiting to 5.")
|
||||
return renderedSlots.splice(0, 5);
|
||||
if (renderedSlots.length > 5) console.warn("Component Overflow - Limiting to 5.")
|
||||
let compListener = this.listener;
|
||||
compListener.components = renderedSlots.splice(0, 5)
|
||||
return compListener;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -500,9 +514,44 @@ class DynamicCardStack {
|
|||
* @private
|
||||
*/
|
||||
async _handleInteraction(ctx) {
|
||||
if (ctx.user.id !== this.context.user.id) return ctx.respond({
|
||||
type: InteractionCallbackTypes.DEFERRED_UPDATE_MESSAGE
|
||||
})
|
||||
if (ctx.user.id !== this.context.user.id) {
|
||||
if (this.disableCloning) return ctx.respond({type: InteractionCallbackTypes.DEFERRED_UPDATE_MESSAGE});
|
||||
|
||||
/**
|
||||
* Card Stack Cloning
|
||||
*
|
||||
* This clones the card stack in its current state, calls
|
||||
* the internal spawn function to "respawn" it under a new
|
||||
* context, then executes the triggered interaction via
|
||||
* the new "cloned" cardstack.
|
||||
*
|
||||
* This is (maybe?) kind of jank, but I can't think of any
|
||||
* better ways to ensure state and content consistency
|
||||
* without it affecting the parent cardstack somehow.
|
||||
*/
|
||||
|
||||
// New message that the new cardstack will attach to.
|
||||
await ctx.respond(InteractionCallbackTypes.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE, {flags: MessageFlags.EPHEMERAL});
|
||||
|
||||
let newStack = Object.assign(Object.create(Object.getPrototypeOf(this)), this)
|
||||
|
||||
// Reassign the context
|
||||
newStack.context = ctx;
|
||||
|
||||
// Ensure all state is properly cloned to the new stack
|
||||
newStack.index = this.index;
|
||||
newStack.rootIndex = this.rootIndex;
|
||||
newStack.currentSelectedSubcategory = this.currentSelectedSubcategory;
|
||||
|
||||
newStack.cards = structuredClone(this.cards);
|
||||
newStack.activeCardStack = structuredClone(this.activeCardStack);
|
||||
newStack.currentComponentsBatch = structuredClone(this.currentComponentsBatch);
|
||||
|
||||
// Respawn and re-run interaction.
|
||||
await newStack._spawn(false);
|
||||
await newStack._handleInteraction(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
//this.lastInteraction = Date.now();
|
||||
|
||||
|
@ -510,15 +559,9 @@ class DynamicCardStack {
|
|||
if (Object.values(BuiltInButtonTypes).includes(ctx.data.customId)) {
|
||||
switch (ctx.data.customId) {
|
||||
case "next":
|
||||
return ctx.respond({
|
||||
type: InteractionCallbackTypes.UPDATE_MESSAGE,
|
||||
data: this.nextCard()
|
||||
})
|
||||
return ctx.editOrRespond(this.nextCard())
|
||||
case "previous":
|
||||
return ctx.respond({
|
||||
type: InteractionCallbackTypes.UPDATE_MESSAGE,
|
||||
data: this.previousPage()
|
||||
})
|
||||
return ctx.editOrRespond(this.previousCard())
|
||||
default:
|
||||
console.error("unknown button??")
|
||||
}
|
||||
|
@ -537,10 +580,7 @@ class DynamicCardStack {
|
|||
this.index = this.rootIndex;
|
||||
this.currentSelectedSubcategory = null;
|
||||
|
||||
return await ctx.respond({
|
||||
type: InteractionCallbackTypes.UPDATE_MESSAGE,
|
||||
data: Object.assign(this.getCurrentCard(), {components: this._renderComponents(false)})
|
||||
})
|
||||
return await ctx.editOrRespond(Object.assign(this.getCurrentCard(), {components: this._renderComponents()}));
|
||||
} else this.currentSelectedSubcategory = cid;
|
||||
|
||||
let resolveTime = Date.now();
|
||||
|
@ -549,10 +589,7 @@ class DynamicCardStack {
|
|||
// If we have a cached result, retrieve it
|
||||
if (this._getCachedValue(this.rootIndex, cid, STACK_CACHE_KEYS.RESULT_CARDS) !== null) {
|
||||
this.activeCardStack = [...this._getCachedValue(this.rootIndex, cid, STACK_CACHE_KEYS.RESULT_CARDS)];
|
||||
await ctx.respond({
|
||||
type: InteractionCallbackTypes.UPDATE_MESSAGE,
|
||||
data: Object.assign(this.getCurrentCard(), {components: this._renderComponents(false)})
|
||||
})
|
||||
await ctx.editOrRespond(Object.assign(this.getCurrentCard(), {components: this._renderComponents()}));
|
||||
return;
|
||||
} else {
|
||||
// Controls if we should display a loading (skeleton) embed while the
|
||||
|
@ -574,10 +611,7 @@ class DynamicCardStack {
|
|||
if (component.renderLoadingState)
|
||||
processingEmbed = page(component.renderLoadingState(this, component));
|
||||
|
||||
await ctx.respond({
|
||||
type: InteractionCallbackTypes.UPDATE_MESSAGE,
|
||||
data: Object.assign(processingEmbed, {components: this._renderComponents(true)})
|
||||
})
|
||||
await ctx.editOrRespond(Object.assign(processingEmbed, {components: this._renderComponents(true)}))
|
||||
}
|
||||
|
||||
// Compute the active cardstack.
|
||||
|
@ -661,10 +695,7 @@ class DynamicCardStack {
|
|||
|
||||
// Update the card stack with a card from the new stack.
|
||||
if (component.instantResult) {
|
||||
await ctx.respond({
|
||||
type: InteractionCallbackTypes.UPDATE_MESSAGE,
|
||||
data: Object.assign(this.getCurrentCard(), {components: this._renderComponents()})
|
||||
})
|
||||
await ctx.editOrRespond(Object.assign(this.getCurrentCard(), {components: this._renderComponents()}))
|
||||
} else {
|
||||
// This timeout exists 1. for cosmetic reasons so people can
|
||||
// see the skeleton state and 2. in order to avoid a really
|
||||
|
@ -675,13 +706,12 @@ class DynamicCardStack {
|
|||
// it *should* be safe to just edit the message now.
|
||||
if ((Date.now() - resolveTime) < 2000) {
|
||||
setTimeout(() => {
|
||||
return this._edit(Object.assign(this.getCurrentCard(), {components: this._renderComponents()}))
|
||||
return ctx.editOrRespond(Object.assign(this.getCurrentCard(), {components: this._renderComponents()}))
|
||||
}, 1500)
|
||||
} else {
|
||||
await this._edit(Object.assign(this.getCurrentCard(), {components: this._renderComponents()}))
|
||||
await ctx.editOrRespond(Object.assign(this.getCurrentCard(), {components: this._renderComponents()}))
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue