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

New select menus #8775

Closed
wants to merge 38 commits into from
Closed
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c31cb23
Select Menus Builders
jaw0r3k Oct 14, 2022
d9d9720
feat(builders): new select menus
RedGuy12 Oct 14, 2022
4f1e627
Handle resolved
jaw0r3k Oct 14, 2022
d87199d
feat: update re-exported builders in djs
RedGuy12 Oct 14, 2022
d2cbbf3
Merge branch 'new-select-menus' of https://github.com/jaw0r3k/discord…
jaw0r3k Oct 14, 2022
e0b0898
Some typings
jaw0r3k Oct 14, 2022
5a3ea6a
fix: these builders don't have options
RedGuy12 Oct 14, 2022
7cc5b2d
feat: `SelectMenuComponent#channelTypes`
RedGuy12 Oct 14, 2022
5965e09
fix: don't construct unnecessary `Collection`s
RedGuy12 Oct 14, 2022
87c38e4
Merge remote-tracking branch 'origin/main' into new-select-menus
RedGuy12 Oct 14, 2022
d9fec21
Add typwguards
jaw0r3k Oct 17, 2022
9c20d57
Typeguards v2
jaw0r3k Oct 18, 2022
6c79811
Forgotten type
jaw0r3k Oct 18, 2022
a7f63c2
fix: better typeguard names
RedGuy12 Oct 19, 2022
e94fe28
Split them into classes
jaw0r3k Oct 21, 2022
f6b240e
Jsdocs change
jaw0r3k Oct 21, 2022
b164851
Handle new interaction types
jaw0r3k Oct 21, 2022
6f446b9
Type changes
jaw0r3k Oct 21, 2022
89a0ff9
Add SelectMenuTypes
jaw0r3k Oct 21, 2022
f85f2ec
make it one class again
RedGuy12 Oct 21, 2022
f757dff
Merge branch 'new-select-menus' of https://github.com/jaw0r3k/discord…
RedGuy12 Oct 21, 2022
2de0322
type tests
RedGuy12 Oct 21, 2022
c4089c7
Revert "type tests"
jaw0r3k Oct 21, 2022
6cf241c
Revert "make it one class again"
jaw0r3k Oct 21, 2022
82952bf
Merge branch 'main' into new-select-menus
RedGuy12 Oct 22, 2022
49fe123
Update index.d.ts
jaw0r3k Oct 22, 2022
0e1d6b3
Update packages/discord.js/typings/index.test-d.ts
jaw0r3k Oct 23, 2022
64ce799
Format
jaw0r3k Oct 23, 2022
2a54e29
Merge branch 'new-select-menus' of https://github.com/jaw0r3k/discord…
jaw0r3k Oct 23, 2022
7758546
Remove missplaced [
jaw0r3k Oct 23, 2022
c988888
split SelectMenuComponentData
jaw0r3k Oct 24, 2022
d342d64
Apply suggestions from code review
jaw0r3k Oct 25, 2022
8a74365
Apply suggestions from code review
jaw0r3k Oct 25, 2022
98b92da
Merge branch 'new-select-menus' of https://github.com/jaw0r3k/discord…
jaw0r3k Oct 25, 2022
33698c9
feat: bump discord-api-types
jaw0r3k Oct 28, 2022
d9f801e
fix: some improvments
jaw0r3k Oct 28, 2022
f77c4bf
Hello World!
jaw0r3k Oct 28, 2022
600ae37
Add channel_types to json
jaw0r3k Oct 31, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 5 additions & 5 deletions packages/builders/__tests__/components/actionRow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
ActionRowBuilder,
ButtonBuilder,
createComponentBuilder,
SelectMenuBuilder,
StringSelectMenuBuilder,
SelectMenuOptionBuilder,
} from '../../src/index.js';

Expand All @@ -29,7 +29,7 @@ const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent>
type: ComponentType.ActionRow,
components: [
{
type: ComponentType.SelectMenu,
type: ComponentType.StringSelect,
custom_id: '1234',
options: [
{
Expand Down Expand Up @@ -73,7 +73,7 @@ describe('Action Row Components', () => {
url: 'https://google.com',
},
{
type: ComponentType.SelectMenu,
type: ComponentType.StringSelect,
placeholder: 'test',
custom_id: 'test',
options: [
Expand Down Expand Up @@ -108,7 +108,7 @@ describe('Action Row Components', () => {
type: ComponentType.ActionRow,
components: [
{
type: ComponentType.SelectMenu,
type: ComponentType.StringSelect,
custom_id: '1234',
options: [
{
Expand All @@ -134,7 +134,7 @@ describe('Action Row Components', () => {

test('GIVEN valid builder options THEN valid JSON output is given 2', () => {
const button = new ButtonBuilder().setLabel('test').setStyle(ButtonStyle.Primary).setCustomId('123');
const selectMenu = new SelectMenuBuilder()
const selectMenu = new StringSelectMenuBuilder()
.setCustomId('1234')
.setMaxValues(10)
.setMinValues(12)
Expand Down
10 changes: 5 additions & 5 deletions packages/builders/__tests__/components/components.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import {
ActionRowBuilder,
ButtonBuilder,
createComponentBuilder,
SelectMenuBuilder,
StringSelectMenuBuilder,
TextInputBuilder,
} from '../../src/index.js';

describe('createComponentBuilder', () => {
test.each([ButtonBuilder, SelectMenuBuilder, TextInputBuilder])(
test.each([ButtonBuilder, StringSelectMenuBuilder, TextInputBuilder])(
'passing an instance of %j should return itself',
(Builder) => {
const builder = new Builder();
Expand All @@ -45,14 +45,14 @@ describe('createComponentBuilder', () => {
expect(createComponentBuilder(button)).toBeInstanceOf(ButtonBuilder);
});

test('GIVEN a select menu component THEN returns a SelectMenuBuilder', () => {
test('GIVEN a select menu component THEN returns a StringSelectMenuBuilder', () => {
const selectMenu: APISelectMenuComponent = {
custom_id: 'abc',
options: [],
type: ComponentType.SelectMenu,
type: ComponentType.StringSelect,
};

expect(createComponentBuilder(selectMenu)).toBeInstanceOf(SelectMenuBuilder);
expect(createComponentBuilder(selectMenu)).toBeInstanceOf(StringSelectMenuBuilder);
});

test('GIVEN a text input component THEN returns a TextInputBuilder', () => {
Expand Down
10 changes: 5 additions & 5 deletions packages/builders/__tests__/components/selectMenu.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ComponentType, type APISelectMenuComponent, type APISelectMenuOption } from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import { SelectMenuBuilder, SelectMenuOptionBuilder } from '../../src/index.js';
import { StringSelectMenuBuilder, SelectMenuOptionBuilder } from '../../src/index.js';

const selectMenu = () => new SelectMenuBuilder();
const selectMenu = () => new StringSelectMenuBuilder();
const selectMenuOption = () => new SelectMenuOptionBuilder();

const longStr = 'a'.repeat(256);
Expand All @@ -16,7 +16,7 @@ const selectMenuOptionData: APISelectMenuOption = {
};

const selectMenuDataWithoutOptions = {
type: ComponentType.SelectMenu,
type: ComponentType.StringSelect,
custom_id: 'test',
max_values: 10,
min_values: 3,
Expand Down Expand Up @@ -165,12 +165,12 @@ describe('Select Menu Components', () => {

test('GIVEN valid JSON input THEN valid JSON history is correct', () => {
expect(
new SelectMenuBuilder(selectMenuDataWithoutOptions)
new StringSelectMenuBuilder(selectMenuDataWithoutOptions)
.addOptions(new SelectMenuOptionBuilder(selectMenuOptionData))
.toJSON(),
).toEqual(selectMenuData);
expect(
new SelectMenuBuilder(selectMenuDataWithoutOptions)
new StringSelectMenuBuilder(selectMenuDataWithoutOptions)
.addOptions([new SelectMenuOptionBuilder(selectMenuOptionData)])
.toJSON(),
).toEqual(selectMenuData);
Expand Down
14 changes: 12 additions & 2 deletions packages/builders/src/components/ActionRow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,24 @@ import { normalizeArray, type RestOrArray } from '../util/normalizeArray.js';
import { ComponentBuilder } from './Component.js';
import { createComponentBuilder } from './Components.js';
import type { ButtonBuilder } from './button/Button.js';
import type { SelectMenuBuilder } from './selectMenu/SelectMenu.js';
import type { ChannelSelectMenuBuilder } from './selectMenu/ChannelSelectMenu.js';
import type { MentionableSelectMenuBuilder } from './selectMenu/MentionableSelectMenu.js';
import type { RoleSelectMenuBuilder } from './selectMenu/RoleSelectMenu.js';
import type { StringSelectMenuBuilder } from './selectMenu/StringSelectMenu.js';
import type { UserSelectMenuBuilder } from './selectMenu/UserSelectMenu.js';
import type { TextInputBuilder } from './textInput/TextInput.js';

export type MessageComponentBuilder =
| ActionRowBuilder<MessageActionRowComponentBuilder>
| MessageActionRowComponentBuilder;
export type ModalComponentBuilder = ActionRowBuilder<ModalActionRowComponentBuilder> | ModalActionRowComponentBuilder;
export type MessageActionRowComponentBuilder = ButtonBuilder | SelectMenuBuilder;
export type MessageActionRowComponentBuilder =
| ButtonBuilder
| ChannelSelectMenuBuilder
| MentionableSelectMenuBuilder
| RoleSelectMenuBuilder
| StringSelectMenuBuilder
| UserSelectMenuBuilder;
export type ModalActionRowComponentBuilder = TextInputBuilder;
export type AnyComponentBuilder = MessageActionRowComponentBuilder | ModalActionRowComponentBuilder;

Expand Down
24 changes: 20 additions & 4 deletions packages/builders/src/components/Components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,22 @@ import {
} from './ActionRow.js';
import { ComponentBuilder } from './Component.js';
import { ButtonBuilder } from './button/Button.js';
import { SelectMenuBuilder } from './selectMenu/SelectMenu.js';
import { ChannelSelectMenuBuilder } from './selectMenu/ChannelSelectMenu.js';
import { MentionableSelectMenuBuilder } from './selectMenu/MentionableSelectMenu.js';
import { RoleSelectMenuBuilder } from './selectMenu/RoleSelectMenu.js';
import { StringSelectMenuBuilder } from './selectMenu/StringSelectMenu.js';
import { UserSelectMenuBuilder } from './selectMenu/UserSelectMenu.js';
import { TextInputBuilder } from './textInput/TextInput.js';

export interface MappedComponentTypes {
[ComponentType.ActionRow]: ActionRowBuilder<AnyComponentBuilder>;
[ComponentType.Button]: ButtonBuilder;
[ComponentType.SelectMenu]: SelectMenuBuilder;
[ComponentType.StringSelect]: StringSelectMenuBuilder;
[ComponentType.TextInput]: TextInputBuilder;
[ComponentType.UserSelect]: UserSelectMenuBuilder;
[ComponentType.RoleSelect]: RoleSelectMenuBuilder;
[ComponentType.MentionableSelect]: MentionableSelectMenuBuilder;
[ComponentType.ChannelSelect]: ChannelSelectMenuBuilder;
}

/**
Expand All @@ -39,10 +47,18 @@ export function createComponentBuilder(
return new ActionRowBuilder(data);
case ComponentType.Button:
return new ButtonBuilder(data);
case ComponentType.SelectMenu:
return new SelectMenuBuilder(data);
case ComponentType.StringSelect:
return new StringSelectMenuBuilder(data);
case ComponentType.TextInput:
return new TextInputBuilder(data);
case ComponentType.UserSelect:
return new UserSelectMenuBuilder(data);
case ComponentType.RoleSelect:
return new RoleSelectMenuBuilder(data);
case ComponentType.MentionableSelect:
return new MentionableSelectMenuBuilder(data);
case ComponentType.ChannelSelect:
return new ChannelSelectMenuBuilder(data);
default:
// @ts-expect-error: This case can still occur if we get a newer unsupported component type
throw new Error(`Cannot properly serialize component type: ${data.type}`);
Expand Down
62 changes: 62 additions & 0 deletions packages/builders/src/components/selectMenu/BaseSelectMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { APISelectMenuComponent } from 'discord-api-types/v10';
import { customIdValidator, disabledValidator, minMaxValidator, placeholderValidator } from '../Assertions.js';
import { ComponentBuilder } from '../Component.js';

export class BaseSelectMenu extends ComponentBuilder<APISelectMenuComponent> {
jaw0r3k marked this conversation as resolved.
Show resolved Hide resolved
/**
* Sets the placeholder for this select menu
*
* @param placeholder - The placeholder to use for this select menu
*/
public setPlaceholder(placeholder: string) {
this.data.placeholder = placeholderValidator.parse(placeholder);
return this;
}

/**
* Sets the minimum values that must be selected in the select menu
*
* @param minValues - The minimum values that must be selected
*/
public setMinValues(minValues: number) {
this.data.min_values = minMaxValidator.parse(minValues);
return this;
}

/**
* Sets the maximum values that must be selected in the select menu
*
* @param maxValues - The maximum values that must be selected
*/
public setMaxValues(maxValues: number) {
this.data.max_values = minMaxValidator.parse(maxValues);
return this;
}

/**
* Sets the custom id for this select menu
*
* @param customId - The custom id to use for this select menu
*/
public setCustomId(customId: string) {
this.data.custom_id = customIdValidator.parse(customId);
return this;
}

/**
* Sets whether this select menu is disabled
*
* @param disabled - Whether this select menu is disabled
*/
public setDisabled(disabled = true) {
this.data.disabled = disabledValidator.parse(disabled);
return this;
}

public toJSON(): APISelectMenuComponent {
customIdValidator.parse(this.data.custom_id);
return {
...this.data,
} as APISelectMenuComponent;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { ChannelType } from 'discord-api-types/v10';
import { ComponentType, type APISelectMenuComponent } from 'discord-api-types/v10';
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
import { customIdValidator } from '../Assertions.js';
import { BaseSelectMenu } from './BaseSelectMenu.js';
jaw0r3k marked this conversation as resolved.
Show resolved Hide resolved

export class ChannelSelectMenuBuilder extends BaseSelectMenu {
jaw0r3k marked this conversation as resolved.
Show resolved Hide resolved
public channel_types: ChannelType[];
jaw0r3k marked this conversation as resolved.
Show resolved Hide resolved

/**
* Creates a new select menu from API data
*
* @param data - The API data to create this select menu with
* @example
* Creating a select menu from an API data object
* ```ts
* const selectMenu = new ChannelSelectMenuBuilder({
* custom_id: 'a cool select menu',
* placeholder: 'select an option',
* max_values: 2,
* ],
* });
jaw0r3k marked this conversation as resolved.
Show resolved Hide resolved
* ```
* @example
* Creating a select menu using setters and API data
* ```ts
* const selectMenu = new ChannelSelectMenuBuilder({
* custom_id: 'a cool select menu',
* })
* .addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement)
* .setMinValues(2)
* ```
*/
public constructor(data?: Partial<APISelectMenuComponent>) {
const { channel_types, ...initData } = data ?? {};
super({ type: ComponentType.ChannelSelect, ...initData });
this.channel_types = channel_types ?? [];
}

public addChannelTypes(...types: RestOrArray<ChannelType>) {
// eslint-disable-next-line no-param-reassign
types = normalizeArray(types);

this.channel_types.push(...types);
return this;
}

public setChannelTypes(...types: RestOrArray<ChannelType>) {
// eslint-disable-next-line no-param-reassign
types = normalizeArray(types);

this.channel_types.splice(0, this.channel_types.length, ...types);
return this;
}

/**
* {@inheritDoc ComponentBuilder.toJSON}
*/
public override toJSON(): APISelectMenuComponent {
customIdValidator.parse(this.data.custom_id);

return {
...this.data,
channel_types: this.channel_types,
} as APISelectMenuComponent;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ComponentType, type APISelectMenuComponent } from 'discord-api-types/v10';
import { BaseSelectMenu } from './BaseSelectMenu.js';
jaw0r3k marked this conversation as resolved.
Show resolved Hide resolved

export class MentionableSelectMenuBuilder extends BaseSelectMenu {
jaw0r3k marked this conversation as resolved.
Show resolved Hide resolved
/**
* Creates a new select menu from API data
*
* @param data - The API data to create this select menu with
* @example
* Creating a select menu from an API data object
* ```ts
* const selectMenu = new MentionableSelectMenuBuilder({
* custom_id: 'a cool select menu',
* placeholder: 'select an option',
* max_values: 2,
* ],
* });
jaw0r3k marked this conversation as resolved.
Show resolved Hide resolved
* ```
* @example
* Creating a select menu using setters and API data
* ```ts
* const selectMenu = new MentionableSelectMenuBuilder({
* custom_id: 'a cool select menu',
* })
* .setMinValues(1)
* ```
*/
public constructor(data?: Partial<APISelectMenuComponent>) {
super({ type: ComponentType.MentionableSelect, ...data });
jaw0r3k marked this conversation as resolved.
Show resolved Hide resolved
}
}
31 changes: 31 additions & 0 deletions packages/builders/src/components/selectMenu/RoleSelectMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ComponentType, type APISelectMenuComponent } from 'discord-api-types/v10';
import { BaseSelectMenu } from './BaseSelectMenu.js';
jaw0r3k marked this conversation as resolved.
Show resolved Hide resolved

export class RoleSelectMenuBuilder extends BaseSelectMenu {
jaw0r3k marked this conversation as resolved.
Show resolved Hide resolved
/**
* Creates a new select menu from API data
*
* @param data - The API data to create this select menu with
* @example
* Creating a select menu from an API data object
* ```ts
* const selectMenu = new RoleSelectMenuBuilder({
* custom_id: 'a cool select menu',
* placeholder: 'select an option',
* max_values: 2,
* ],
* });
jaw0r3k marked this conversation as resolved.
Show resolved Hide resolved
* ```
* @example
* Creating a select menu using setters and API data
* ```ts
* const selectMenu = new RoleSelectMenuBuilder({
* custom_id: 'a cool select menu',
* })
* .setMinValues(1)
* ```
*/
public constructor(data?: Partial<APISelectMenuComponent>) {
super({ type: ComponentType.RoleSelect, ...data });
jaw0r3k marked this conversation as resolved.
Show resolved Hide resolved
}
}