import React, { FormEventHandler, KeyboardEventHandler, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import { as, Box, Header, Text, Icon, Icons, IconButton, Input, Button, TextArea as TextAreaComponent, color, Spinner, } from 'folds'; import { isKeyHotkey } from 'is-hotkey'; import { MatrixError } from 'matrix-js-sdk'; import * as css from './styles.css'; import { useTextAreaIntentHandler } from '../../../hooks/useTextAreaIntent'; import { Cursor, Intent, TextArea, TextAreaOperations } from '../../../plugins/text-area'; import { GetTarget } from '../../../plugins/text-area/type'; import { syntaxErrorPosition } from '../../../utils/dom'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; const EDITOR_INTENT_SPACE_COUNT = 2; export type AccountDataEditorProps = { type?: string; content?: object; requestClose: () => void; }; export const AccountDataEditor = as<'div', AccountDataEditorProps>( ({ type, content, requestClose, ...props }, ref) => { const mx = useMatrixClient(); const defaultContent = useMemo( () => JSON.stringify(content, null, EDITOR_INTENT_SPACE_COUNT), [content] ); const textAreaRef = useRef(null); const [jsonError, setJSONError] = useState(); const getTarget: GetTarget = useCallback(() => { const target = textAreaRef.current; if (!target) throw new Error('TextArea element not found!'); return target; }, []); const { textArea, operations, intent } = useMemo(() => { const ta = new TextArea(getTarget); const op = new TextAreaOperations(getTarget); return { textArea: ta, operations: op, intent: new Intent(EDITOR_INTENT_SPACE_COUNT, ta, op), }; }, [getTarget]); const intentHandler = useTextAreaIntentHandler(textArea, operations, intent); const handleKeyDown: KeyboardEventHandler = (evt) => { intentHandler(evt); if (isKeyHotkey('escape', evt)) { const cursor = Cursor.fromTextAreaElement(getTarget()); operations.deselect(cursor); } }; const [submitState, submit] = useAsyncCallback( useCallback((dataType, data) => mx.setAccountData(dataType, data), [mx]) ); const submitting = submitState.status === AsyncStatus.Loading; const handleSubmit: FormEventHandler = (evt) => { evt.preventDefault(); if (submitting) return; const target = evt.target as HTMLFormElement | undefined; const typeInput = target?.typeInput as HTMLInputElement | undefined; const contentTextArea = target?.contentTextArea as HTMLTextAreaElement | undefined; if (!typeInput || !contentTextArea) return; const typeStr = typeInput.value.trim(); const contentStr = contentTextArea.value.trim(); let parsedContent: object; try { parsedContent = JSON.parse(contentStr); } catch (e) { setJSONError(e as SyntaxError); return; } setJSONError(undefined); if ( !typeStr || parsedContent === null || defaultContent === JSON.stringify(parsedContent, null, EDITOR_INTENT_SPACE_COUNT) ) { return; } submit(typeStr, parsedContent); }; useEffect(() => { if (jsonError) { const errorPosition = syntaxErrorPosition(jsonError) ?? 0; const cursor = new Cursor(errorPosition, errorPosition, 'none'); operations.select(cursor); getTarget()?.focus(); } }, [jsonError, operations, getTarget]); useEffect(() => { if (submitState.status === AsyncStatus.Success) { requestClose(); } }, [submitState, requestClose]); return (
Account Data
Type {submitState.status === AsyncStatus.Error && ( {submitState.error.message} )} JSON Content {jsonError && ( {jsonError.name}: {jsonError.message} )}
); } );