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 {STATIC_ASSETS} = require("#utils/statics");
|
||||||
|
|
||||||
const {Context} = require("detritus-client/lib/command");
|
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 {Message} = require("detritus-client/lib/structures");
|
||||||
const {ComponentContext, Components, ComponentActionRow, ComponentButton} = require("detritus-client/lib/utils");
|
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.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 {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 {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) {
|
constructor(context, options) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
this.cards = options.cards || [];
|
this.cards = options.cards || [];
|
||||||
this.buttons = options.buttons || ["previous", "next"]
|
this.buttons = options.buttons || ["previous", "next"]
|
||||||
|
@ -56,6 +59,8 @@ class DynamicCardStack {
|
||||||
this.expires = options.expires || 5 * 60 * 1000;
|
this.expires = options.expires || 5 * 60 * 1000;
|
||||||
this.pageNumbers = options.pageNumbers || true;
|
this.pageNumbers = options.pageNumbers || true;
|
||||||
this.pageNumberGenerator = options.pageNumberGenerator || ((pg) => `Page ${pg.index + 1}/${pg.activeCardStack.length}`);
|
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;
|
this.rootIndex = this.index;
|
||||||
|
|
||||||
|
@ -137,7 +142,7 @@ class DynamicCardStack {
|
||||||
* Creates a new cardstack in the given channel
|
* Creates a new cardstack in the given channel
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_spawn() {
|
_spawn(createMessage = true) {
|
||||||
this._createDynamicCardStack(this.context.client);
|
this._createDynamicCardStack(this.context.client);
|
||||||
|
|
||||||
this.activeCardStack = [...this.cards];
|
this.activeCardStack = [...this.cards];
|
||||||
|
@ -158,10 +163,12 @@ class DynamicCardStack {
|
||||||
|
|
||||||
//this.spawned = Date.now()
|
//this.spawned = Date.now()
|
||||||
|
|
||||||
return this._edit({
|
if (createMessage) return this._edit({
|
||||||
...this.getCurrentCard(),
|
...this.getCurrentCard(),
|
||||||
components: this.listener
|
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;
|
return card;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Card rendering failed:")
|
console.error("Card rendering failed:")
|
||||||
|
@ -246,7 +256,7 @@ class DynamicCardStack {
|
||||||
* Decreases the index and returns the next card from the stack.
|
* Decreases the index and returns the next card from the stack.
|
||||||
* @returns {Message} Card
|
* @returns {Message} Card
|
||||||
*/
|
*/
|
||||||
previousPage() {
|
previousCard() {
|
||||||
this.index = this.index - 1;
|
this.index = this.index - 1;
|
||||||
if (this.index < 0) {
|
if (this.index < 0) {
|
||||||
if (this.loopPages) this.index = this.activeCardStack.length - 1;
|
if (this.loopPages) this.index = this.activeCardStack.length - 1;
|
||||||
|
@ -267,7 +277,7 @@ class DynamicCardStack {
|
||||||
let message = Object.assign({}, cardContent);
|
let message = Object.assign({}, cardContent);
|
||||||
|
|
||||||
if (!components) {
|
if (!components) {
|
||||||
this.listener.components = this._renderComponents();
|
this._renderComponents();
|
||||||
message.components = this.listener;
|
message.components = this.listener;
|
||||||
} else {
|
} else {
|
||||||
message.components = components;
|
message.components = components;
|
||||||
|
@ -279,7 +289,9 @@ class DynamicCardStack {
|
||||||
return editOrReply(this.context, {
|
return editOrReply(this.context, {
|
||||||
...message,
|
...message,
|
||||||
reference: true,
|
reference: true,
|
||||||
allowedMentions: {parse: [], repliedUser: false}
|
allowedMentions: {parse: [], repliedUser: false},
|
||||||
|
// TODO: allow supplying flags
|
||||||
|
flags: this.ephemeral ? MessageFlags.EPHEMERAL : 0
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Message editing failed:")
|
console.error("Message editing failed:")
|
||||||
|
@ -378,7 +390,7 @@ class DynamicCardStack {
|
||||||
// We currently support up to 5 "slots" (action rows),
|
// We currently support up to 5 "slots" (action rows),
|
||||||
// although the amount you can actually use depends
|
// although the amount you can actually use depends
|
||||||
// on how many components are added to each slot.
|
// on how many components are added to each slot.
|
||||||
let componentSlots = [[],[],[],[],[]];
|
let componentSlots = [[], [], [], [], []];
|
||||||
|
|
||||||
// First Row always starts with built-in components
|
// First Row always starts with built-in components
|
||||||
for (const b of this.buttons) {
|
for (const b of this.buttons) {
|
||||||
|
@ -419,28 +431,30 @@ class DynamicCardStack {
|
||||||
let renderedSlots = [];
|
let renderedSlots = [];
|
||||||
|
|
||||||
// Render slots
|
// Render slots
|
||||||
for(const components of componentSlots) {
|
for (const components of componentSlots) {
|
||||||
if(components.length === 0) continue;
|
if (components.length === 0) continue;
|
||||||
|
|
||||||
let row = new ComponentActionRow({});
|
let row = new ComponentActionRow({});
|
||||||
|
|
||||||
// Slot all components into their respective rows.
|
// Slot all components into their respective rows.
|
||||||
while(components.length > 0) {
|
while (components.length > 0) {
|
||||||
row.addButton(components.shift());
|
row.addButton(components.shift());
|
||||||
|
|
||||||
// Create a new row for content to overflow in.
|
// Create a new row for content to overflow in.
|
||||||
if(row.isFull){
|
if (row.isFull) {
|
||||||
renderedSlots.push(row)
|
renderedSlots.push(row)
|
||||||
row = new ComponentActionRow({});
|
row = new ComponentActionRow({});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push rendered row to stack if there are components in it.
|
// 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.")
|
if (renderedSlots.length > 5) console.warn("Component Overflow - Limiting to 5.")
|
||||||
return renderedSlots.splice(0, 5);
|
let compListener = this.listener;
|
||||||
|
compListener.components = renderedSlots.splice(0, 5)
|
||||||
|
return compListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -500,9 +514,44 @@ class DynamicCardStack {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _handleInteraction(ctx) {
|
async _handleInteraction(ctx) {
|
||||||
if (ctx.user.id !== this.context.user.id) return ctx.respond({
|
if (ctx.user.id !== this.context.user.id) {
|
||||||
type: InteractionCallbackTypes.DEFERRED_UPDATE_MESSAGE
|
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();
|
//this.lastInteraction = Date.now();
|
||||||
|
|
||||||
|
@ -510,15 +559,9 @@ class DynamicCardStack {
|
||||||
if (Object.values(BuiltInButtonTypes).includes(ctx.data.customId)) {
|
if (Object.values(BuiltInButtonTypes).includes(ctx.data.customId)) {
|
||||||
switch (ctx.data.customId) {
|
switch (ctx.data.customId) {
|
||||||
case "next":
|
case "next":
|
||||||
return ctx.respond({
|
return ctx.editOrRespond(this.nextCard())
|
||||||
type: InteractionCallbackTypes.UPDATE_MESSAGE,
|
|
||||||
data: this.nextCard()
|
|
||||||
})
|
|
||||||
case "previous":
|
case "previous":
|
||||||
return ctx.respond({
|
return ctx.editOrRespond(this.previousCard())
|
||||||
type: InteractionCallbackTypes.UPDATE_MESSAGE,
|
|
||||||
data: this.previousPage()
|
|
||||||
})
|
|
||||||
default:
|
default:
|
||||||
console.error("unknown button??")
|
console.error("unknown button??")
|
||||||
}
|
}
|
||||||
|
@ -537,10 +580,7 @@ class DynamicCardStack {
|
||||||
this.index = this.rootIndex;
|
this.index = this.rootIndex;
|
||||||
this.currentSelectedSubcategory = null;
|
this.currentSelectedSubcategory = null;
|
||||||
|
|
||||||
return await ctx.respond({
|
return await ctx.editOrRespond(Object.assign(this.getCurrentCard(), {components: this._renderComponents()}));
|
||||||
type: InteractionCallbackTypes.UPDATE_MESSAGE,
|
|
||||||
data: Object.assign(this.getCurrentCard(), {components: this._renderComponents(false)})
|
|
||||||
})
|
|
||||||
} else this.currentSelectedSubcategory = cid;
|
} else this.currentSelectedSubcategory = cid;
|
||||||
|
|
||||||
let resolveTime = Date.now();
|
let resolveTime = Date.now();
|
||||||
|
@ -549,10 +589,7 @@ class DynamicCardStack {
|
||||||
// If we have a cached result, retrieve it
|
// If we have a cached result, retrieve it
|
||||||
if (this._getCachedValue(this.rootIndex, cid, STACK_CACHE_KEYS.RESULT_CARDS) !== null) {
|
if (this._getCachedValue(this.rootIndex, cid, STACK_CACHE_KEYS.RESULT_CARDS) !== null) {
|
||||||
this.activeCardStack = [...this._getCachedValue(this.rootIndex, cid, STACK_CACHE_KEYS.RESULT_CARDS)];
|
this.activeCardStack = [...this._getCachedValue(this.rootIndex, cid, STACK_CACHE_KEYS.RESULT_CARDS)];
|
||||||
await ctx.respond({
|
await ctx.editOrRespond(Object.assign(this.getCurrentCard(), {components: this._renderComponents()}));
|
||||||
type: InteractionCallbackTypes.UPDATE_MESSAGE,
|
|
||||||
data: Object.assign(this.getCurrentCard(), {components: this._renderComponents(false)})
|
|
||||||
})
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// Controls if we should display a loading (skeleton) embed while the
|
// Controls if we should display a loading (skeleton) embed while the
|
||||||
|
@ -574,10 +611,7 @@ class DynamicCardStack {
|
||||||
if (component.renderLoadingState)
|
if (component.renderLoadingState)
|
||||||
processingEmbed = page(component.renderLoadingState(this, component));
|
processingEmbed = page(component.renderLoadingState(this, component));
|
||||||
|
|
||||||
await ctx.respond({
|
await ctx.editOrRespond(Object.assign(processingEmbed, {components: this._renderComponents(true)}))
|
||||||
type: InteractionCallbackTypes.UPDATE_MESSAGE,
|
|
||||||
data: Object.assign(processingEmbed, {components: this._renderComponents(true)})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the active cardstack.
|
// Compute the active cardstack.
|
||||||
|
@ -661,10 +695,7 @@ class DynamicCardStack {
|
||||||
|
|
||||||
// Update the card stack with a card from the new stack.
|
// Update the card stack with a card from the new stack.
|
||||||
if (component.instantResult) {
|
if (component.instantResult) {
|
||||||
await ctx.respond({
|
await ctx.editOrRespond(Object.assign(this.getCurrentCard(), {components: this._renderComponents()}))
|
||||||
type: InteractionCallbackTypes.UPDATE_MESSAGE,
|
|
||||||
data: Object.assign(this.getCurrentCard(), {components: this._renderComponents()})
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
// This timeout exists 1. for cosmetic reasons so people can
|
// This timeout exists 1. for cosmetic reasons so people can
|
||||||
// see the skeleton state and 2. in order to avoid a really
|
// 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.
|
// it *should* be safe to just edit the message now.
|
||||||
if ((Date.now() - resolveTime) < 2000) {
|
if ((Date.now() - resolveTime) < 2000) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
return this._edit(Object.assign(this.getCurrentCard(), {components: this._renderComponents()}))
|
return ctx.editOrRespond(Object.assign(this.getCurrentCard(), {components: this._renderComponents()}))
|
||||||
}, 1500)
|
}, 1500)
|
||||||
} else {
|
} else {
|
||||||
await this._edit(Object.assign(this.getCurrentCard(), {components: this._renderComponents()}))
|
await ctx.editOrRespond(Object.assign(this.getCurrentCard(), {components: this._renderComponents()}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue