refactor: rendering button

This commit is contained in:
MapleLeaf
2021-12-25 01:24:52 -06:00
parent e799e71f1a
commit 99430e0edc
9 changed files with 107 additions and 31 deletions

View File

@@ -1,12 +1,12 @@
import * as React from "react" import * as React from "react"
import { Button } from "../src/main.js" import { Button } from "../src.new/components/button.js"
export function Counter() { export function Counter() {
const [count, setCount] = React.useState(0) const [count, setCount] = React.useState(0)
return ( return (
<> <>
this button was clicked {count} times this button was clicked {count} times
<Button onClick={() => setCount(count + 1)}>clicc</Button> <Button label="clicc" onClick={() => setCount(count + 1)} />
</> </>
) )
} }

View File

@@ -1,7 +1,9 @@
import { Client } from "discord.js" import { Client } from "discord.js"
import "dotenv/config" import "dotenv/config"
import React from "react"
import { InstanceManager } from "../src.new/main.js" import { InstanceManager } from "../src.new/main.js"
import { createCommandHandler } from "./command-handler.js" import { createCommandHandler } from "./command-handler.js"
import { Counter } from "./counter.js"
const client = new Client({ const client = new Client({
intents: ["GUILDS"], intents: ["GUILDS"],
@@ -14,7 +16,7 @@ createCommandHandler(client, [
name: "counter", name: "counter",
description: "shows a counter button", description: "shows a counter button",
run: (interaction) => { run: (interaction) => {
manager.create(interaction).render("hi world") manager.create(interaction).render(<Counter />)
}, },
}, },
]) ])

View File

@@ -0,0 +1,28 @@
import type {
EmojiResolvable,
MessageButtonStyle,
MessageComponentInteraction,
} from "discord.js"
import React from "react"
import { Node } from "../node.js"
export type ButtonProps = {
label?: string
style?: Exclude<Lowercase<MessageButtonStyle>, "link">
disabled?: boolean
emoji?: EmojiResolvable
onClick?: (interaction: MessageComponentInteraction) => void
}
export const ButtonTag = "reacord-button"
export function Button(props: ButtonProps) {
return React.createElement(ButtonTag, props)
}
export class ButtonNode extends Node {
readonly name = "button"
constructor(public props: ButtonProps) {
super()
}
}

View File

@@ -1,14 +0,0 @@
import type { ReactNode } from "react"
import React from "react"
export type TextProps = {
children?: ReactNode
}
export const TextTag = "reacord-text"
export function Text(props: TextProps) {
return React.createElement(TextTag, props)
}
export class TextElementNode {}

14
src.new/jsx.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
import type { ReactNode } from "react"
import type { Node } from "./node"
declare global {
namespace JSX {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface IntrinsicElements {
"reacord-element": {
createNode: () => Node
children?: ReactNode
}
}
}
}

3
src.new/node.ts Normal file
View File

@@ -0,0 +1,3 @@
export abstract class Node {
abstract get name(): string
}

View File

@@ -1,6 +1,8 @@
import type { HostConfig } from "react-reconciler" import type { HostConfig } from "react-reconciler"
import ReactReconciler from "react-reconciler" import ReactReconciler from "react-reconciler"
import { raise } from "../src/helpers/raise.js" import { raise } from "../src/helpers/raise.js"
import { ButtonNode } from "./components/button.js"
import type { Node } from "./node.js"
import type { RootNode } from "./root-node.js" import type { RootNode } from "./root-node.js"
import { TextNode } from "./text-node.js" import { TextNode } from "./text-node.js"
@@ -8,7 +10,7 @@ const config: HostConfig<
string, // Type, string, // Type,
Record<string, unknown>, // Props, Record<string, unknown>, // Props,
RootNode, // Container, RootNode, // Container,
never, // Instance, Node, // Instance,
TextNode, // TextInstance, TextNode, // TextInstance,
never, // SuspenseInstance, never, // SuspenseInstance,
never, // HydratableInstance, never, // HydratableInstance,
@@ -32,7 +34,10 @@ const config: HostConfig<
getRootHostContext: () => ({}), getRootHostContext: () => ({}),
getChildHostContext: () => ({}), getChildHostContext: () => ({}),
createInstance: () => raise("not implemented"), createInstance: (type, props) => {
if (type === "reacord-button") return new ButtonNode(props)
raise(`Unknown type: ${type}`)
},
createTextInstance: (text) => new TextNode(text), createTextInstance: (text) => new TextNode(text),
shouldSetTextContent: () => false, shouldSetTextContent: () => false,

View File

@@ -1,12 +1,21 @@
import type { CommandInteraction, MessageOptions } from "discord.js" import type { CommandInteraction, MessageOptions } from "discord.js"
import type { TextNode } from "./text-node.js" import { MessageActionRow } from "discord.js"
import { nanoid } from "nanoid"
import { last } from "../src/helpers/last.js"
import { toUpper } from "../src/helpers/to-upper.js"
import { ButtonNode } from "./components/button.js"
import { Node } from "./node.js"
import { TextNode } from "./text-node.js"
export class RootNode { export class RootNode extends Node {
private children = new Set<TextNode>() readonly name = "root"
private children = new Set<Node>()
constructor(private interaction: CommandInteraction) {} constructor(private interaction: CommandInteraction) {
super()
}
add(child: TextNode) { add(child: Node) {
this.children.add(child) this.children.add(child)
} }
@@ -14,7 +23,7 @@ export class RootNode {
this.children.clear() this.children.clear()
} }
remove(child: TextNode) { remove(child: Node) {
this.children.delete(child) this.children.delete(child)
} }
@@ -22,13 +31,37 @@ export class RootNode {
this.interaction.reply(this.getMessageOptions()).catch(console.error) this.interaction.reply(this.getMessageOptions()).catch(console.error)
} }
getMessageOptions() { private getMessageOptions(): MessageOptions {
const options: MessageOptions = {} let content = ""
let components: MessageActionRow[] = []
for (const child of this.children) { for (const child of this.children) {
options.content = (options.content ?? "") + child.text if (child instanceof TextNode) {
content += child.text
} }
return options if (child instanceof ButtonNode) {
let actionRow = last(components)
if (
!actionRow ||
actionRow.components.length >= 5 ||
actionRow.components[0]?.type === "SELECT_MENU"
) {
actionRow = new MessageActionRow()
components.push(actionRow)
}
actionRow.addComponents({
type: "BUTTON",
customId: nanoid(),
style: toUpper(child.props.style ?? "secondary"),
disabled: child.props.disabled,
emoji: child.props.emoji,
label: child.props.label,
})
}
}
return { content, components }
} }
} }

View File

@@ -1,3 +1,8 @@
export class TextNode { import { Node } from "./node.js"
constructor(public text: string) {}
export class TextNode extends Node {
readonly name = "text"
constructor(public text: string) {
super()
}
} }