Skip to content
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

Closed
wants to merge 21 commits into from
Closed

Dev/button rework2 #14

wants to merge 21 commits into from

Conversation

rob-3
Copy link
Contributor

@rob-3 rob-3 commented Jul 24, 2021

See this for an example of how this works.

rob-3 added 2 commits July 24, 2021 12:56
This feature allows users to build complex discord UIs more easily by
abstracting `customId` management into dispatch.
@suneettipirneni
Copy link
Member

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:

  • A built-in filter predicates in the collector.
  • Using function references for the collector handlers.
  • Uses raw components so it's as customizable as possible.

@rob-3
Copy link
Contributor Author

rob-3 commented Jul 25, 2021

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 onClick: myref with my API.

@suneettipirneni
Copy link
Member

We should get input from @vijaystroup and @Armintron on what method they prefer

@Armintron
Copy link

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.

@suneettipirneni
Copy link
Member

suneettipirneni commented Jul 25, 2021 via email

@suneettipirneni
Copy link
Member

suneettipirneni commented Jul 25, 2021

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.

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.

@vijaystroup
Copy link

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.

@Armintron
Copy link

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

@rob-3 rob-3 requested a review from suneettipirneni July 28, 2021 21:51
Comment on lines +39 to +47
run({
interaction,
registerUI,
}: {
interaction: CommandInteraction;
registerUI: (
ui: UIComponent | UIComponent[] | UIComponent[][]
) => MessageActionRow[];
}): Promise<void> | void;
Copy link
Member

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 registerUIbeing 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.

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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 = {
Copy link
Member

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?

Copy link
Contributor Author

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

Copy link
Contributor Author

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 = {
Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

@rob-3

This comment has been minimized.

@Armintron
Copy link

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.

@rob-3
Copy link
Contributor Author

rob-3 commented Jul 29, 2021

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.

@rob-3

This comment has been minimized.

@suneettipirneni

This comment has been minimized.

@rob-3

This comment has been minimized.

@suneettipirneni

This comment has been minimized.

@suneettipirneni

This comment has been minimized.

@rob-3

This comment has been minimized.

@rob-3

This comment has been minimized.

@suneettipirneni

This comment has been minimized.

@rob-3

This comment has been minimized.

@rob-3

This comment has been minimized.

@suneettipirneni

This comment has been minimized.

@rob-3

This comment has been minimized.

@Armintron
Copy link

I think you can make a master's thesis out of all this.

@suneettipirneni

This comment has been minimized.

@rob-3

This comment has been minimized.

@suneettipirneni
Copy link
Member

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.

@rob-3
Copy link
Contributor Author

rob-3 commented Jul 31, 2021

Don't merge this or bother reviewing it yet. After our discussion I have a few ideas about how it could be improved.

@suneettipirneni
Copy link
Member

I'm not planning on merging anything yet, but you can merge whenever you're satisfied with your implementation.

Comment on lines +11 to +24
export type ButtonOptions = Omit<
MessageButtonOptions,
'customId' | 'style' | 'url'
> & {
style: Exclude<MessageButtonStyleResolvable, 'LINK'>;
onClick: ButtonHandler;
};

export type LinkButtonOptions = Omit<
MessageButtonOptions,
'customId' | 'style' | 'url'
> & {
url: string;
};
Copy link
Member

@suneettipirneni suneettipirneni Jul 31, 2021

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 

Comment on lines +26 to +51
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',
};
}
}
Copy link
Member

@suneettipirneni suneettipirneni Jul 31, 2021

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.

Copy link
Contributor Author

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.

@suneettipirneni
Copy link
Member

Closed in favor of newer PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants