-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dev/button rework2 #14
Conversation
This feature allows users to build complex discord UIs more easily by abstracting `customId` management into dispatch.
I'm not against merging this, however I would personally use the collector api in most use cases, as it offers a balance of abstraction and escape hatches. Some examples include:
|
Fair enough. This doesn't break any existing code so you can feel free to use the collector method if you prefer that. If nobody but me prefers this API, though, we probably should not add it. Filter predicates are a good point. I'm not sure I understand the function references; you can just do |
We should get input from @vijaystroup and @Armintron on what method they prefer |
Could I get a summary of what this PR does? I haven't worked with buttons or anything so I'm not sure what's good/bad. |
I’ll post two examples of both of the implementations later.
… On Jul 25, 2021, at 1:36 PM, Armin ***@***.***> wrote:
Could I get a summary of what this PR does? I haven't worked with buttons or anything so I'm not sure what's good/bad.
—
You are receiving this because your review was requested.
Reply to this email directly, view it on GitHub, or unsubscribe.
|
But TLDR; if you've used to emoji reaction buttons, then the discord button collectors act almost exactly the same. This PR differs by taking a more abstract approach. |
As long as it is easier for the end developer or anyone new that might join the team in creating additions, I am all for it. |
+1 |
run({ | ||
interaction, | ||
registerUI, | ||
}: { | ||
interaction: CommandInteraction; | ||
registerUI: ( | ||
ui: UIComponent | UIComponent[] | UIComponent[][] | ||
) => MessageActionRow[]; | ||
}): Promise<void> | void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know how I feel about registerUI
being a parameter on run
. registerUI doesn't seem like something that should be related to running the command, especially, if you don't use components.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's why I used the destructuring syntax so you don't need to put it in your parameter list unless you need it. This will also make it easy to inject other dependencies like storage later without introducing more parameters or a IOC container.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But why does this need to be passed in as a function parameter in the first place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't, I just thought a function injection was simpler than passing in the entire Client
since you really only need the function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like having a class-based command with a client constructor (like other frameworks) would be easier to reason about, it would also allow for adding other properties later.
src/UI.ts
Outdated
|
||
export type UIComponent = DispatchButton | DispatchLinkButton; | ||
|
||
export type ButtonOptions = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't this be an interface that extends MessageButtonOptions
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly, I've never used extends
with an interface. I'll look into it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I did a thing with Omit
, lmk what you think
src/UI.ts
Outdated
onClick: ButtonHandler; | ||
}; | ||
|
||
export type LinkButtonOptions = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same with this but use the Omit
utility to not inherit all properties from MessageButtonOptions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above
This comment has been minimized.
This comment has been minimized.
Really indifferent on the structure; I can do whatever as long as documentation is maintained. So in that sense, whatever is easier to document is my choice. |
In all honesty, the documentation is basically "look at another command similar to what you want to do" or ask someone who knows what they're doing either way. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I think you can make a master's thesis out of all this. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Ok I've decided that we'll use @rob-3's method of passing data in and we'll take it from there. And we'll use a "wait and see" approach to this. If we run into issues we can look into classes. |
Don't merge this or bother reviewing it yet. After our discussion I have a few ideas about how it could be improved. |
I'm not planning on merging anything yet, but you can merge whenever you're satisfied with your implementation. |
export type ButtonOptions = Omit< | ||
MessageButtonOptions, | ||
'customId' | 'style' | 'url' | ||
> & { | ||
style: Exclude<MessageButtonStyleResolvable, 'LINK'>; | ||
onClick: ButtonHandler; | ||
}; | ||
|
||
export type LinkButtonOptions = Omit< | ||
MessageButtonOptions, | ||
'customId' | 'style' | 'url' | ||
> & { | ||
url: string; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These can be simplified now that discordjs/discord.js#6212 is merged and deployed. From now on to check for differences between the two buttons you can use type narrowing:
if (button.options.type === 'LINK') {
... Skip listeners
return;
}
// Otherwise we know for sure customID has to exist.
button.customID
export class DispatchButton { | ||
constructor(readonly options: ButtonOptions) {} | ||
|
||
toDiscordComponent( | ||
buttonListeners: Map<string, ButtonHandler> | ||
): MessageButtonOptions { | ||
const { onClick, ...options } = this.options; | ||
// nonlink buttons must have a customId | ||
const id = getID(this.options.label ?? '<unlabeled>', 'button'); | ||
buttonListeners.set(id, onClick); | ||
return { ...options, type: 'BUTTON', customId: id }; | ||
} | ||
} | ||
|
||
export class DispatchLinkButton { | ||
constructor(readonly options: LinkButtonOptions) {} | ||
|
||
toDiscordComponent(): MessageButtonOptions { | ||
// we override style in case the user omitted it | ||
return { | ||
...this.options, | ||
type: 'BUTTON', | ||
style: 'LINK', | ||
}; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corollary to the comment above, these two classes can be merged together.
It's questionable if classes are even needed in this context. You could achieve polymorphism with just interfaces.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about doing that. Before I thought this was a better solution because it makes the type errors better when the user doesn't include the correct properties.
Closed in favor of newer PR |
See this for an example of how this works.