import React, { FormEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Box, Text, Icon, Icons, IconButton, Chip, Scroll, config, TextArea as TextAreaComponent, color, Spinner, Button, } from 'folds'; import { MatrixError } from 'matrix-js-sdk'; import { Page, PageHeader } from '../../../components/page'; import { SequenceCard } from '../../../components/sequence-card'; import { TextViewerContent } from '../../../components/text-viewer'; import { useStateEvent } from '../../../hooks/useStateEvent'; import { useRoom } from '../../../hooks/useRoom'; import { StateEvent } from '../../../../types/matrix/room'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useAlive } from '../../../hooks/useAlive'; import { Cursor } from '../../../plugins/text-area'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { syntaxErrorPosition } from '../../../utils/dom'; import { SettingTile } from '../../../components/setting-tile'; import { SequenceCardStyle } from '../styles.css'; import { usePowerLevels, usePowerLevelsAPI } from '../../../hooks/usePowerLevels'; import { useTextAreaCodeEditor } from '../../../hooks/useTextAreaCodeEditor'; const EDITOR_INTENT_SPACE_COUNT = 2; type StateEventEditProps = { type: string; stateKey: string; content: object; requestClose: () => void; }; function StateEventEdit({ type, stateKey, content, requestClose }: StateEventEditProps) { const mx = useMatrixClient(); const room = useRoom(); const alive = useAlive(); const defaultContentStr = useMemo( () => JSON.stringify(content, undefined, EDITOR_INTENT_SPACE_COUNT), [content] ); const textAreaRef = useRef(null); const [jsonError, setJSONError] = useState(); const { handleKeyDown, operations, getTarget } = useTextAreaCodeEditor( textAreaRef, EDITOR_INTENT_SPACE_COUNT ); const [submitState, submit] = useAsyncCallback( useCallback( (c) => mx.sendStateEvent(room.roomId, type as any, c, stateKey), [mx, room, type, stateKey] ) ); const submitting = submitState.status === AsyncStatus.Loading; const handleSubmit: FormEventHandler = (evt) => { evt.preventDefault(); if (submitting) return; const target = evt.target as HTMLFormElement | undefined; const contentTextArea = target?.contentTextArea as HTMLTextAreaElement | undefined; if (!contentTextArea) return; const contentStr = contentTextArea.value.trim(); let parsedContent: object; try { parsedContent = JSON.parse(contentStr); } catch (e) { setJSONError(e as SyntaxError); return; } setJSONError(undefined); if ( parsedContent === null || defaultContentStr === JSON.stringify(parsedContent, null, EDITOR_INTENT_SPACE_COUNT) ) { return; } submit(parsedContent).then(() => { if (alive()) { requestClose(); } }); }; useEffect(() => { if (jsonError) { const errorPosition = syntaxErrorPosition(jsonError) ?? 0; const cursor = new Cursor(errorPosition, errorPosition, 'none'); operations.select(cursor); getTarget()?.focus(); } }, [jsonError, operations, getTarget]); return ( State Event } /> {submitState.status === AsyncStatus.Error && ( {submitState.error.message} )} JSON Content {jsonError && ( {jsonError.name}: {jsonError.message} )} ); } type StateEventViewProps = { content: object; eventJSONStr: string; onEditContent?: (content: object) => void; }; function StateEventView({ content, eventJSONStr, onEditContent }: StateEventViewProps) { return ( State Event {onEditContent && ( onEditContent(content)} > Edit )} ); } export type StateEventInfo = { type: string; stateKey: string; }; export type StateEventEditorProps = StateEventInfo & { requestClose: () => void; }; export function StateEventEditor({ type, stateKey, requestClose }: StateEventEditorProps) { const mx = useMatrixClient(); const room = useRoom(); const stateEvent = useStateEvent(room, type as unknown as StateEvent, stateKey); const [editContent, setEditContent] = useState(); const powerLevels = usePowerLevels(room); const { getPowerLevel, canSendStateEvent } = usePowerLevelsAPI(powerLevels); const canEdit = canSendStateEvent(type, getPowerLevel(mx.getSafeUserId())); const eventJSONStr = useMemo(() => { if (!stateEvent) return ''; return JSON.stringify(stateEvent.event, null, EDITOR_INTENT_SPACE_COUNT); }, [stateEvent]); const handleCloseEdit = useCallback(() => { setEditContent(undefined); }, []); return ( } > Developer Tools {editContent ? ( ) : ( )} ); }