forked from Mirrors/gomuks
web/devtools: add send message event button
This commit is contained in:
parent
e6242a9c37
commit
d093ea2f90
7 changed files with 112 additions and 20 deletions
|
@ -101,7 +101,7 @@ func main() {
|
||||||
resp, err := cli.Send(ctx, id.RoomID(fields[1]), event.EventMessage, &event.MessageEventContent{
|
resp, err := cli.Send(ctx, id.RoomID(fields[1]), event.EventMessage, &event.MessageEventContent{
|
||||||
Body: strings.Join(fields[2:], " "),
|
Body: strings.Join(fields[2:], " "),
|
||||||
MsgType: event.MsgText,
|
MsgType: event.MsgText,
|
||||||
})
|
}, false)
|
||||||
_, _ = fmt.Fprintln(rl, err)
|
_, _ = fmt.Fprintln(rl, err)
|
||||||
_, _ = fmt.Fprintf(rl, "%+v\n", resp)
|
_, _ = fmt.Fprintf(rl, "%+v\n", resp)
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ func (h *HiClient) handleJSONCommand(ctx context.Context, req *JSONCommand) (any
|
||||||
})
|
})
|
||||||
case "send_event":
|
case "send_event":
|
||||||
return unmarshalAndCall(req.Data, func(params *sendEventParams) (*database.Event, error) {
|
return unmarshalAndCall(req.Data, func(params *sendEventParams) (*database.Event, error) {
|
||||||
return h.Send(ctx, params.RoomID, params.EventType, params.Content)
|
return h.Send(ctx, params.RoomID, params.EventType, params.Content, params.DisableEncryption)
|
||||||
})
|
})
|
||||||
case "resend_event":
|
case "resend_event":
|
||||||
return unmarshalAndCall(req.Data, func(params *resendEventParams) (*database.Event, error) {
|
return unmarshalAndCall(req.Data, func(params *resendEventParams) (*database.Event, error) {
|
||||||
|
@ -267,9 +267,10 @@ type sendMessageParams struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type sendEventParams struct {
|
type sendEventParams struct {
|
||||||
RoomID id.RoomID `json:"room_id"`
|
RoomID id.RoomID `json:"room_id"`
|
||||||
EventType event.Type `json:"type"`
|
EventType event.Type `json:"type"`
|
||||||
Content json.RawMessage `json:"content"`
|
Content json.RawMessage `json:"content"`
|
||||||
|
DisableEncryption bool `json:"disable_encryption"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type resendEventParams struct {
|
type resendEventParams struct {
|
||||||
|
|
|
@ -245,8 +245,9 @@ func (h *HiClient) Send(
|
||||||
roomID id.RoomID,
|
roomID id.RoomID,
|
||||||
evtType event.Type,
|
evtType event.Type,
|
||||||
content any,
|
content any,
|
||||||
|
disableEncryption bool,
|
||||||
) (*database.Event, error) {
|
) (*database.Event, error) {
|
||||||
return h.send(ctx, roomID, evtType, content, "", false)
|
return h.send(ctx, roomID, evtType, content, "", disableEncryption)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HiClient) Resend(ctx context.Context, txnID string) (*database.Event, error) {
|
func (h *HiClient) Resend(ctx context.Context, txnID string) (*database.Event, error) {
|
||||||
|
|
|
@ -292,12 +292,14 @@ export default class Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendEvent(roomID: RoomID, type: EventType, content: unknown): Promise<void> {
|
async sendEvent(
|
||||||
|
roomID: RoomID, type: EventType, content: unknown, disableEncryption: boolean = false,
|
||||||
|
): Promise<void> {
|
||||||
const room = this.store.rooms.get(roomID)
|
const room = this.store.rooms.get(roomID)
|
||||||
if (!room) {
|
if (!room) {
|
||||||
throw new Error("Room not found")
|
throw new Error("Room not found")
|
||||||
}
|
}
|
||||||
const dbEvent = await this.rpc.sendEvent(roomID, type, content)
|
const dbEvent = await this.rpc.sendEvent(roomID, type, content, disableEncryption)
|
||||||
this.#handleOutgoingEvent(dbEvent, room)
|
this.#handleOutgoingEvent(dbEvent, room)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -148,8 +148,10 @@ export default abstract class RPCClient {
|
||||||
return this.request("send_message", params)
|
return this.request("send_message", params)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEvent(room_id: RoomID, type: EventType, content: unknown): Promise<RawDBEvent> {
|
sendEvent(
|
||||||
return this.request("send_event", { room_id, type, content })
|
room_id: RoomID, type: EventType, content: unknown, disable_encryption: boolean = false,
|
||||||
|
): Promise<RawDBEvent> {
|
||||||
|
return this.request("send_event", { room_id, type, content, disable_encryption })
|
||||||
}
|
}
|
||||||
|
|
||||||
resendEvent(transaction_id: string): Promise<RawDBEvent> {
|
resendEvent(transaction_id: string): Promise<RawDBEvent> {
|
||||||
|
|
|
@ -8,6 +8,10 @@ div.state-explorer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
div.state-button-list {
|
div.state-button-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -26,7 +30,15 @@ div.state-explorer {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: .5rem;
|
gap: .5rem;
|
||||||
margin-top: .5rem;
|
margin-top: .5rem;
|
||||||
justify-content: space-between;
|
|
||||||
|
> div.spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
> button {
|
> button {
|
||||||
padding: .5rem 1rem;
|
padding: .5rem 1rem;
|
||||||
|
|
|
@ -31,6 +31,11 @@ interface StateEventViewProps {
|
||||||
onDone?: (type: string, stateKey: string) => void
|
onDone?: (type: string, stateKey: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface NewMessageEventViewProps {
|
||||||
|
room: RoomStateStore
|
||||||
|
onBack: () => void
|
||||||
|
}
|
||||||
|
|
||||||
interface StateKeyListProps {
|
interface StateKeyListProps {
|
||||||
room: RoomStateStore
|
room: RoomStateStore
|
||||||
type: string
|
type: string
|
||||||
|
@ -84,6 +89,7 @@ const StateEventView = ({ room, type, stateKey, onBack, onDone }: StateEventView
|
||||||
<h3>New state event</h3>
|
<h3>New state event</h3>
|
||||||
<div className="new-event-type">
|
<div className="new-event-type">
|
||||||
<input
|
<input
|
||||||
|
autoFocus
|
||||||
type="text"
|
type="text"
|
||||||
value={newType}
|
value={newType}
|
||||||
onChange={evt => setNewType(evt.target.value)}
|
onChange={evt => setNewType(evt.target.value)}
|
||||||
|
@ -100,7 +106,7 @@ const StateEventView = ({ room, type, stateKey, onBack, onDone }: StateEventView
|
||||||
: <h3><code>{type}</code> ({stateKey ? <code>{stateKey}</code> : "no state key"})</h3>
|
: <h3><code>{type}</code> ({stateKey ? <code>{stateKey}</code> : "no state key"})</h3>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className={`state-event-content`}>
|
<div className="state-event-content">
|
||||||
{editingContent !== null
|
{editingContent !== null
|
||||||
? <textarea rows={10} value={editingContent} onChange={evt => setEditingContent(evt.target.value)}/>
|
? <textarea rows={10} value={editingContent} onChange={evt => setEditingContent(evt.target.value)}/>
|
||||||
: <JSONView data={event}/>
|
: <JSONView data={event}/>
|
||||||
|
@ -119,6 +125,65 @@ const StateEventView = ({ room, type, stateKey, onBack, onDone }: StateEventView
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NewMessageEventView = ({ room, onBack }: NewMessageEventViewProps) => {
|
||||||
|
const [content, setContent] = useState<string>("{\n\n}")
|
||||||
|
const [type, setType] = useState<string>("")
|
||||||
|
const [disableEncryption, setDisableEncryption] = useState<boolean>(false)
|
||||||
|
const client = use(ClientContext)!
|
||||||
|
|
||||||
|
const sendEvent = () => {
|
||||||
|
let parsedContent
|
||||||
|
try {
|
||||||
|
parsedContent = JSON.parse(content || "{}")
|
||||||
|
} catch (err) {
|
||||||
|
window.alert(`Failed to parse JSON: ${err}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client.sendEvent(room.roomID, type, parsedContent, disableEncryption).then(
|
||||||
|
() => {
|
||||||
|
console.log("Successfully sent message event", room.roomID, type)
|
||||||
|
onBack()
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
console.error("Failed to send message event", err)
|
||||||
|
window.alert(`Failed to send message event: ${err}`)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="state-explorer state-event-view">
|
||||||
|
<div className="state-header">
|
||||||
|
<h3>New message event</h3>
|
||||||
|
<div className="new-event-type">
|
||||||
|
<input
|
||||||
|
autoFocus
|
||||||
|
type="text"
|
||||||
|
value={type}
|
||||||
|
onChange={evt => setType(evt.target.value)}
|
||||||
|
placeholder="Event type"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="state-event-content">
|
||||||
|
<textarea rows={10} value={content} onChange={evt => setContent(evt.target.value)}/>
|
||||||
|
</div>
|
||||||
|
<div className="nav-buttons">
|
||||||
|
<button onClick={onBack}>Back</button>
|
||||||
|
<button onClick={sendEvent}>Send</button>
|
||||||
|
{room.meta.current.encryption_event ? <label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={disableEncryption}
|
||||||
|
onChange={evt => setDisableEncryption(evt.target.checked)}
|
||||||
|
/>
|
||||||
|
Disable encryption
|
||||||
|
</label> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const StateKeyList = ({ room, type, onSelectStateKey, onBack }: StateKeyListProps) => {
|
const StateKeyList = ({ room, type, onSelectStateKey, onBack }: StateKeyListProps) => {
|
||||||
const stateMap = room.state.get(type)
|
const stateMap = room.state.get(type)
|
||||||
return (
|
return (
|
||||||
|
@ -141,7 +206,7 @@ const StateKeyList = ({ room, type, onSelectStateKey, onBack }: StateKeyListProp
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StateExplorer = ({ room }: StateExplorerProps) => {
|
export const StateExplorer = ({ room }: StateExplorerProps) => {
|
||||||
const [creatingNew, setCreatingNew] = useState(false)
|
const [creatingNew, setCreatingNew] = useState<"message" | "state" | null>(null)
|
||||||
const [selectedType, setSelectedType] = useState<string | null>(null)
|
const [selectedType, setSelectedType] = useState<string | null>(null)
|
||||||
const [selectedStateKey, setSelectedStateKey] = useState<string | null>(null)
|
const [selectedStateKey, setSelectedStateKey] = useState<string | null>(null)
|
||||||
const [loadingState, setLoadingState] = useState(false)
|
const [loadingState, setLoadingState] = useState(false)
|
||||||
|
@ -167,7 +232,7 @@ export const StateExplorer = ({ room }: StateExplorerProps) => {
|
||||||
|
|
||||||
const handleBack = useCallback(() => {
|
const handleBack = useCallback(() => {
|
||||||
if (creatingNew) {
|
if (creatingNew) {
|
||||||
setCreatingNew(false)
|
setCreatingNew(null)
|
||||||
} else if (selectedStateKey !== null && selectedType !== null) {
|
} else if (selectedStateKey !== null && selectedType !== null) {
|
||||||
setSelectedStateKey(null)
|
setSelectedStateKey(null)
|
||||||
const stateKeysMap = room.state.get(selectedType)
|
const stateKeysMap = room.state.get(selectedType)
|
||||||
|
@ -178,18 +243,25 @@ export const StateExplorer = ({ room }: StateExplorerProps) => {
|
||||||
setSelectedType(null)
|
setSelectedType(null)
|
||||||
}
|
}
|
||||||
}, [selectedType, selectedStateKey, creatingNew, room])
|
}, [selectedType, selectedStateKey, creatingNew, room])
|
||||||
const handleNewEventDone = useCallback((type: string, stateKey: string) => {
|
const handleNewEventDone = useCallback((type: string, stateKey?: string) => {
|
||||||
setCreatingNew(false)
|
setCreatingNew(null)
|
||||||
setSelectedType(type)
|
if (stateKey !== undefined) {
|
||||||
setSelectedStateKey(stateKey)
|
setSelectedType(type)
|
||||||
|
setSelectedStateKey(stateKey)
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (creatingNew) {
|
if (creatingNew === "state") {
|
||||||
return <StateEventView
|
return <StateEventView
|
||||||
room={room}
|
room={room}
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
onDone={handleNewEventDone}
|
onDone={handleNewEventDone}
|
||||||
/>
|
/>
|
||||||
|
} else if (creatingNew === "message") {
|
||||||
|
return <NewMessageEventView
|
||||||
|
room={room}
|
||||||
|
onBack={handleBack}
|
||||||
|
/>
|
||||||
} else if (selectedType !== null && selectedStateKey !== null) {
|
} else if (selectedType !== null && selectedStateKey !== null) {
|
||||||
return <StateEventView
|
return <StateEventView
|
||||||
room={room}
|
room={room}
|
||||||
|
@ -237,7 +309,9 @@ export const StateExplorer = ({ room }: StateExplorerProps) => {
|
||||||
: "Load room members"
|
: "Load room members"
|
||||||
: "Load room state and members"}
|
: "Load room state and members"}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setCreatingNew(true)}>Send new state event</button>
|
<div className="spacer"/>
|
||||||
|
<button onClick={() => setCreatingNew("message")}>Send new message event</button>
|
||||||
|
<button onClick={() => setCreatingNew("state")}>Send new state event</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue