import React, { FormEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Box, Text, Icon, Icons, IconButton, Input, Button, TextArea as TextAreaComponent, color, Spinner, Chip, Scroll, config, } from 'folds'; import { MatrixError } from 'matrix-js-sdk'; import { Cursor } from '../plugins/text-area'; import { syntaxErrorPosition } from '../utils/dom'; import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback'; import { Page, PageHeader } from './page'; import { useAlive } from '../hooks/useAlive'; import { SequenceCard } from './sequence-card'; import { TextViewerContent } from './text-viewer'; import { useTextAreaCodeEditor } from '../hooks/useTextAreaCodeEditor'; const EDITOR_INTENT_SPACE_COUNT = 2; export type AccountDataSubmitCallback = (type: string, content: object) => Promise; type AccountDataInfo = { type: string; content: object; }; type AccountDataEditProps = { type: string; defaultContent: string; submitChange: AccountDataSubmitCallback; onCancel: () => void; onSave: (info: AccountDataInfo) => void; }; function AccountDataEdit({ type, defaultContent, submitChange, onCancel, onSave, }: AccountDataEditProps) { const alive = useAlive(); const textAreaRef = useRef(null); const [jsonError, setJSONError] = useState(); const { handleKeyDown, operations, getTarget } = useTextAreaCodeEditor( textAreaRef, EDITOR_INTENT_SPACE_COUNT ); const [submitState, submit] = useAsyncCallback(submitChange); 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).then(() => { if (alive()) { onSave({ type: typeStr, content: parsedContent, }); } }); }; useEffect(() => { if (jsonError) { const errorPosition = syntaxErrorPosition(jsonError) ?? 0; const cursor = new Cursor(errorPosition, errorPosition, 'none'); operations.select(cursor); getTarget()?.focus(); } }, [jsonError, operations, getTarget]); return ( Account Data 0 || submitting ? 'SurfaceVariant' : 'Background'} name="typeInput" size="400" radii="300" readOnly={type.length > 0 || submitting} defaultValue={type} required /> {submitState.status === AsyncStatus.Error && ( {submitState.error.message} )} JSON Content {jsonError && ( {jsonError.name}: {jsonError.message} )} ); } type AccountDataViewProps = { type: string; defaultContent: string; onEdit: () => void; }; function AccountDataView({ type, defaultContent, onEdit }: AccountDataViewProps) { return ( Account Data JSON Content ); } export type AccountDataEditorProps = { type?: string; content?: object; submitChange: AccountDataSubmitCallback; requestClose: () => void; }; export function AccountDataEditor({ type, content, submitChange, requestClose, }: AccountDataEditorProps) { const [data, setData] = useState({ type: type ?? '', content: content ?? {}, }); const [edit, setEdit] = useState(!type); const closeEdit = useCallback(() => { if (!type) { requestClose(); return; } setEdit(false); }, [type, requestClose]); const handleSave = useCallback((info: AccountDataInfo) => { setData(info); setEdit(false); }, []); const contentJSONStr = useMemo( () => JSON.stringify(data.content, null, EDITOR_INTENT_SPACE_COUNT), [data.content] ); return ( } > Developer Tools {edit ? ( ) : ( setEdit(true)} /> )} ); }