back to immutable mode
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import type { ReactNode } from "react"
|
||||
import React from "react"
|
||||
|
||||
export type TextProps = {
|
||||
children?: string
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
export function Text(props: TextProps) {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { Message, TextBasedChannels } from "discord.js"
|
||||
|
||||
export class ReacordInstance {
|
||||
message?: Message
|
||||
content: string
|
||||
|
||||
constructor(content: string) {
|
||||
this.content = content
|
||||
}
|
||||
|
||||
render(channel: TextBasedChannels) {
|
||||
if (this.message) {
|
||||
this.message.edit(this.content).catch(console.error)
|
||||
} else {
|
||||
channel.send(this.content).then((message) => {
|
||||
this.message = message
|
||||
}, console.error)
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.message?.delete().catch(console.error)
|
||||
this.message?.channel.messages.cache.delete(this.message?.id)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
/* eslint-disable unicorn/no-null */
|
||||
|
||||
import { raise } from "reacord-helpers/raise.js"
|
||||
import ReactReconciler from "react-reconciler"
|
||||
import type { ReacordElementMap } from "./elements.js"
|
||||
@@ -7,25 +6,47 @@ import type { ReacordContainer } from "./renderer/container.js"
|
||||
import { TextElementInstance } from "./renderer/text-element-instance.js"
|
||||
import { TextInstance } from "./renderer/text-instance.js"
|
||||
|
||||
// instances that represent an element
|
||||
type ElementInstance = TextElementInstance
|
||||
|
||||
// any instance
|
||||
type Instance = ElementInstance | TextInstance
|
||||
|
||||
type ElementTag =
|
||||
| keyof ReacordElementMap
|
||||
| (string & { __autocompleteHack__?: never })
|
||||
|
||||
type Props = Record<string, unknown>
|
||||
|
||||
const createInstance = (
|
||||
type: ElementTag,
|
||||
props: Props,
|
||||
): TextElementInstance => {
|
||||
if (type === "reacord-text") {
|
||||
return new TextElementInstance()
|
||||
}
|
||||
raise(`Unknown element type "${type}"`)
|
||||
}
|
||||
|
||||
export const reconciler = ReactReconciler<
|
||||
keyof ReacordElementMap | (string & { __autocompleteHack__?: never }), // Type,
|
||||
Record<string, unknown>, // Props,
|
||||
ElementTag, // Type,
|
||||
Props, // Props,
|
||||
ReacordContainer, // Container,
|
||||
TextElementInstance, // Instance,
|
||||
ElementInstance, // Instance,
|
||||
TextInstance, // TextInstance,
|
||||
never, // SuspenseInstance,
|
||||
never, // HydratableInstance,
|
||||
never, // PublicInstance,
|
||||
null, // HostContext,
|
||||
never, // UpdatePayload,
|
||||
never, // ChildSet,
|
||||
Instance[], // ChildSet,
|
||||
unknown, // TimeoutHandle,
|
||||
unknown // NoTimeout
|
||||
>({
|
||||
now: Date.now,
|
||||
isPrimaryRenderer: true,
|
||||
supportsMutation: true,
|
||||
supportsPersistence: false,
|
||||
supportsMutation: false,
|
||||
supportsPersistence: true,
|
||||
supportsHydration: false,
|
||||
scheduleTimeout: setTimeout,
|
||||
cancelTimeout: clearTimeout,
|
||||
@@ -35,42 +56,44 @@ export const reconciler = ReactReconciler<
|
||||
getChildHostContext: (parentContext) => parentContext,
|
||||
shouldSetTextContent: () => false,
|
||||
|
||||
createInstance: (type, props) => {
|
||||
if (type === "reacord-text") {
|
||||
return new TextElementInstance()
|
||||
}
|
||||
raise(`Unknown element type "${type}"`)
|
||||
createInstance,
|
||||
|
||||
createTextInstance: (text) => new TextInstance(text),
|
||||
|
||||
createContainerChildSet: () => [],
|
||||
|
||||
appendChildToContainerChildSet: (childSet: Instance[], child: Instance) => {
|
||||
childSet.push(child)
|
||||
},
|
||||
|
||||
createTextInstance: (text) => {
|
||||
return new TextInstance(text)
|
||||
},
|
||||
finalizeContainerChildren: (
|
||||
container: ReacordContainer,
|
||||
children: Instance[],
|
||||
) => false,
|
||||
|
||||
clearContainer: (container) => {
|
||||
container.clear()
|
||||
},
|
||||
|
||||
appendChildToContainer: (container, child) => {
|
||||
container.add(child)
|
||||
},
|
||||
|
||||
removeChildFromContainer: (container, child) => {
|
||||
container.remove(child)
|
||||
replaceContainerChildren: (
|
||||
container: ReacordContainer,
|
||||
children: Instance[],
|
||||
) => {
|
||||
container.render(children)
|
||||
},
|
||||
|
||||
appendInitialChild: (parent, child) => {
|
||||
parent.add(child)
|
||||
},
|
||||
|
||||
removeChild: (parent, child) => {
|
||||
parent.remove(child)
|
||||
},
|
||||
cloneInstance: (
|
||||
instance: Instance,
|
||||
_: unknown,
|
||||
type: ElementTag,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
) => createInstance(type, newProps),
|
||||
|
||||
finalizeInitialChildren: () => false,
|
||||
prepareForCommit: (container) => null,
|
||||
resetAfterCommit: () => null,
|
||||
prepareUpdate: () => null,
|
||||
|
||||
getPublicInstance: () => raise("Not implemented"),
|
||||
preparePortalMount: () => raise("Not implemented"),
|
||||
})
|
||||
|
||||
@@ -6,46 +6,35 @@ type Action =
|
||||
| { type: "updateMessage"; options: MessageOptions }
|
||||
| { type: "deleteMessage" }
|
||||
|
||||
type ReacordContainerChild = TextElementInstance | TextInstance
|
||||
type ContainerChild = TextInstance | TextElementInstance
|
||||
|
||||
export class ReacordContainer {
|
||||
private channel: TextBasedChannels
|
||||
private message?: Message
|
||||
private actions: Action[] = []
|
||||
private runningPromise?: Promise<void>
|
||||
private instances = new Set<ReacordContainerChild>()
|
||||
|
||||
constructor(channel: TextBasedChannels) {
|
||||
this.channel = channel
|
||||
}
|
||||
|
||||
add(instance: ReacordContainerChild) {
|
||||
this.instances.add(instance)
|
||||
this.render()
|
||||
}
|
||||
|
||||
remove(instance: ReacordContainerChild) {
|
||||
this.instances.delete(instance)
|
||||
this.render()
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.instances.clear()
|
||||
this.render()
|
||||
}
|
||||
|
||||
render() {
|
||||
render(children: ContainerChild[]) {
|
||||
const messageOptions: MessageOptions = {}
|
||||
for (const instance of this.instances) {
|
||||
instance.render(messageOptions)
|
||||
for (const child of children) {
|
||||
child.renderToMessage(messageOptions)
|
||||
}
|
||||
|
||||
// can't render an empty message
|
||||
if (!messageOptions.content) {
|
||||
this.addAction({ type: "deleteMessage" })
|
||||
} else {
|
||||
this.addAction({ type: "updateMessage", options: messageOptions })
|
||||
if (!messageOptions?.content) {
|
||||
messageOptions.content = "_ _"
|
||||
}
|
||||
|
||||
this.addAction({ type: "updateMessage", options: messageOptions })
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.actions = []
|
||||
this.addAction({ type: "deleteMessage" })
|
||||
}
|
||||
|
||||
completion() {
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
import type { MessageOptions } from "discord.js"
|
||||
import type { TextInstance } from "./text-instance.js"
|
||||
|
||||
type TextElementInstanceChild = TextElementInstance | TextInstance
|
||||
type TextElementChild = TextElementInstance | TextInstance
|
||||
|
||||
export class TextElementInstance {
|
||||
children = new Set<TextElementInstanceChild>()
|
||||
children = new Set<TextElementChild>()
|
||||
|
||||
add(child: TextElementInstanceChild) {
|
||||
add(child: TextElementChild) {
|
||||
this.children.add(child)
|
||||
}
|
||||
|
||||
remove(child: TextElementInstanceChild) {
|
||||
this.children.delete(child)
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.children.clear()
|
||||
}
|
||||
|
||||
render(options: MessageOptions) {
|
||||
renderToMessage(options: MessageOptions) {
|
||||
for (const child of this.children) {
|
||||
child.render(options)
|
||||
options.content = `${options.content ?? ""}${child.text}`
|
||||
}
|
||||
}
|
||||
|
||||
get text(): string {
|
||||
return [...this.children].map((child) => child.text).join("")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import type { MessageOptions } from "discord.js"
|
||||
|
||||
export class TextInstance {
|
||||
text: string
|
||||
constructor(readonly text: string) {}
|
||||
|
||||
constructor(text: string) {
|
||||
this.text = text
|
||||
}
|
||||
|
||||
render(options: MessageOptions) {
|
||||
renderToMessage(options: MessageOptions) {
|
||||
options.content = `${options.content ?? ""}${this.text}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable unicorn/no-null */
|
||||
import type { TextBasedChannels } from "discord.js"
|
||||
import type { ReactNode } from "react"
|
||||
import { reconciler } from "./reconciler"
|
||||
@@ -7,12 +8,16 @@ export type ReacordRenderTarget = TextBasedChannels
|
||||
|
||||
export function createRoot(target: ReacordRenderTarget) {
|
||||
const container = new ReacordContainer(target)
|
||||
// eslint-disable-next-line unicorn/no-null
|
||||
const containerId = reconciler.createContainer(container, 0, false, null)
|
||||
return {
|
||||
render: (content: ReactNode) => {
|
||||
reconciler.updateContainer(content, containerId)
|
||||
return container.completion()
|
||||
},
|
||||
destroy: () => {
|
||||
reconciler.updateContainer(null, containerId)
|
||||
container.destroy()
|
||||
return container.completion()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user