mirror of
				https://github.com/cinnyapp/cinny.git
				synced 2025-11-04 06:20:28 +03:00 
			
		
		
		
	open account data in same window instead of popup (#2234)
* refactor TextViewer Content component * open account data inside setting window * close account data edit window on cancel when adding new
This commit is contained in:
		
							parent
							
								
									b7e5e0db3e
								
							
						
					
					
						commit
						2b8b0dcffd
					
				
					 5 changed files with 429 additions and 383 deletions
				
			
		| 
						 | 
				
			
			@ -31,8 +31,11 @@ export const TextViewerContent = style([
 | 
			
		|||
export const TextViewerPre = style([
 | 
			
		||||
  DefaultReset,
 | 
			
		||||
  {
 | 
			
		||||
    padding: config.space.S600,
 | 
			
		||||
    whiteSpace: 'pre-wrap',
 | 
			
		||||
    wordBreak: 'break-word',
 | 
			
		||||
  },
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
export const TextViewerPrePadding = style({
 | 
			
		||||
  padding: config.space.S600,
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
 | 
			
		||||
import React, { Suspense, lazy } from 'react';
 | 
			
		||||
import React, { ComponentProps, HTMLAttributes, Suspense, forwardRef, lazy } from 'react';
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
import { Box, Chip, Header, Icon, IconButton, Icons, Scroll, Text, as } from 'folds';
 | 
			
		||||
import { ErrorBoundary } from 'react-error-boundary';
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +8,29 @@ import { copyToClipboard } from '../../utils/dom';
 | 
			
		|||
 | 
			
		||||
const ReactPrism = lazy(() => import('../../plugins/react-prism/ReactPrism'));
 | 
			
		||||
 | 
			
		||||
type TextViewerContentProps = {
 | 
			
		||||
  text: string;
 | 
			
		||||
  langName: string;
 | 
			
		||||
  size?: ComponentProps<typeof Text>['size'];
 | 
			
		||||
} & HTMLAttributes<HTMLPreElement>;
 | 
			
		||||
export const TextViewerContent = forwardRef<HTMLPreElement, TextViewerContentProps>(
 | 
			
		||||
  ({ text, langName, size, className, ...props }, ref) => (
 | 
			
		||||
    <Text
 | 
			
		||||
      as="pre"
 | 
			
		||||
      size={size}
 | 
			
		||||
      className={classNames(css.TextViewerPre, `language-${langName}`, className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
      ref={ref}
 | 
			
		||||
    >
 | 
			
		||||
      <ErrorBoundary fallback={<code>{text}</code>}>
 | 
			
		||||
        <Suspense fallback={<code>{text}</code>}>
 | 
			
		||||
          <ReactPrism>{(codeRef) => <code ref={codeRef}>{text}</code>}</ReactPrism>
 | 
			
		||||
        </Suspense>
 | 
			
		||||
      </ErrorBoundary>
 | 
			
		||||
    </Text>
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export type TextViewerProps = {
 | 
			
		||||
  name: string;
 | 
			
		||||
  text: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +66,7 @@ export const TextViewer = as<'div', TextViewerProps>(
 | 
			
		|||
            </Chip>
 | 
			
		||||
          </Box>
 | 
			
		||||
        </Header>
 | 
			
		||||
 | 
			
		||||
        <Box
 | 
			
		||||
          grow="Yes"
 | 
			
		||||
          className={css.TextViewerContent}
 | 
			
		||||
| 
						 | 
				
			
			@ -50,13 +74,11 @@ export const TextViewer = as<'div', TextViewerProps>(
 | 
			
		|||
          alignItems="Center"
 | 
			
		||||
        >
 | 
			
		||||
          <Scroll hideTrack variant="Background" visibility="Hover">
 | 
			
		||||
            <Text as="pre" className={classNames(css.TextViewerPre, `language-${langName}`)}>
 | 
			
		||||
              <ErrorBoundary fallback={<code>{text}</code>}>
 | 
			
		||||
                <Suspense fallback={<code>{text}</code>}>
 | 
			
		||||
                  <ReactPrism>{(codeRef) => <code ref={codeRef}>{text}</code>}</ReactPrism>
 | 
			
		||||
                </Suspense>
 | 
			
		||||
              </ErrorBoundary>
 | 
			
		||||
            </Text>
 | 
			
		||||
            <TextViewerContent
 | 
			
		||||
              className={css.TextViewerPrePadding}
 | 
			
		||||
              text={text}
 | 
			
		||||
              langName={langName}
 | 
			
		||||
            />
 | 
			
		||||
          </Scroll>
 | 
			
		||||
        </Box>
 | 
			
		||||
      </Box>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										90
									
								
								src/app/features/settings/developer-tools/AccountData.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/app/features/settings/developer-tools/AccountData.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,90 @@
 | 
			
		|||
import React, { useCallback, useState } from 'react';
 | 
			
		||||
import { Box, Text, Icon, Icons, Chip, Button } from 'folds';
 | 
			
		||||
import { SequenceCard } from '../../../components/sequence-card';
 | 
			
		||||
import { SequenceCardStyle } from '../styles.css';
 | 
			
		||||
import { SettingTile } from '../../../components/setting-tile';
 | 
			
		||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
 | 
			
		||||
import { useAccountDataCallback } from '../../../hooks/useAccountDataCallback';
 | 
			
		||||
 | 
			
		||||
type AccountDataProps = {
 | 
			
		||||
  expand: boolean;
 | 
			
		||||
  onExpandToggle: (expand: boolean) => void;
 | 
			
		||||
  onSelect: (type: string | null) => void;
 | 
			
		||||
};
 | 
			
		||||
export function AccountData({ expand, onExpandToggle, onSelect }: AccountDataProps) {
 | 
			
		||||
  const mx = useMatrixClient();
 | 
			
		||||
  const [accountData, setAccountData] = useState(() => Array.from(mx.store.accountData.values()));
 | 
			
		||||
 | 
			
		||||
  useAccountDataCallback(
 | 
			
		||||
    mx,
 | 
			
		||||
    useCallback(
 | 
			
		||||
      () => setAccountData(Array.from(mx.store.accountData.values())),
 | 
			
		||||
      [mx, setAccountData]
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Box direction="Column" gap="100">
 | 
			
		||||
      <Text size="L400">Account Data</Text>
 | 
			
		||||
      <SequenceCard
 | 
			
		||||
        className={SequenceCardStyle}
 | 
			
		||||
        variant="SurfaceVariant"
 | 
			
		||||
        direction="Column"
 | 
			
		||||
        gap="400"
 | 
			
		||||
      >
 | 
			
		||||
        <SettingTile
 | 
			
		||||
          title="Global"
 | 
			
		||||
          description="Data stored in your global account data."
 | 
			
		||||
          after={
 | 
			
		||||
            <Button
 | 
			
		||||
              onClick={() => onExpandToggle(!expand)}
 | 
			
		||||
              variant="Secondary"
 | 
			
		||||
              fill="Soft"
 | 
			
		||||
              size="300"
 | 
			
		||||
              radii="300"
 | 
			
		||||
              outlined
 | 
			
		||||
              before={
 | 
			
		||||
                <Icon src={expand ? Icons.ChevronTop : Icons.ChevronBottom} size="100" filled />
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <Text size="B300">{expand ? 'Collapse' : 'Expand'}</Text>
 | 
			
		||||
            </Button>
 | 
			
		||||
          }
 | 
			
		||||
        />
 | 
			
		||||
        {expand && (
 | 
			
		||||
          <SettingTile>
 | 
			
		||||
            <Box direction="Column" gap="200">
 | 
			
		||||
              <Text size="L400">Types</Text>
 | 
			
		||||
              <Box gap="200" wrap="Wrap">
 | 
			
		||||
                <Chip
 | 
			
		||||
                  variant="Secondary"
 | 
			
		||||
                  fill="Soft"
 | 
			
		||||
                  radii="Pill"
 | 
			
		||||
                  before={<Icon size="50" src={Icons.Plus} />}
 | 
			
		||||
                  onClick={() => onSelect(null)}
 | 
			
		||||
                >
 | 
			
		||||
                  <Text size="T200" truncate>
 | 
			
		||||
                    Add New
 | 
			
		||||
                  </Text>
 | 
			
		||||
                </Chip>
 | 
			
		||||
                {accountData.map((mEvent) => (
 | 
			
		||||
                  <Chip
 | 
			
		||||
                    key={mEvent.getType()}
 | 
			
		||||
                    variant="Secondary"
 | 
			
		||||
                    fill="Soft"
 | 
			
		||||
                    radii="Pill"
 | 
			
		||||
                    onClick={() => onSelect(mEvent.getType())}
 | 
			
		||||
                  >
 | 
			
		||||
                    <Text size="T200" truncate>
 | 
			
		||||
                      {mEvent.getType()}
 | 
			
		||||
                    </Text>
 | 
			
		||||
                  </Chip>
 | 
			
		||||
                ))}
 | 
			
		||||
              </Box>
 | 
			
		||||
            </Box>
 | 
			
		||||
          </SettingTile>
 | 
			
		||||
        )}
 | 
			
		||||
      </SequenceCard>
 | 
			
		||||
    </Box>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -8,9 +8,7 @@ import React, {
 | 
			
		|||
  useState,
 | 
			
		||||
} from 'react';
 | 
			
		||||
import {
 | 
			
		||||
  as,
 | 
			
		||||
  Box,
 | 
			
		||||
  Header,
 | 
			
		||||
  Text,
 | 
			
		||||
  Icon,
 | 
			
		||||
  Icons,
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +18,9 @@ import {
 | 
			
		|||
  TextArea as TextAreaComponent,
 | 
			
		||||
  color,
 | 
			
		||||
  Spinner,
 | 
			
		||||
  Chip,
 | 
			
		||||
  Scroll,
 | 
			
		||||
  config,
 | 
			
		||||
} from 'folds';
 | 
			
		||||
import { isKeyHotkey } from 'is-hotkey';
 | 
			
		||||
import { MatrixError } from 'matrix-js-sdk';
 | 
			
		||||
| 
						 | 
				
			
			@ -30,182 +31,302 @@ import { GetTarget } from '../../../plugins/text-area/type';
 | 
			
		|||
import { syntaxErrorPosition } from '../../../utils/dom';
 | 
			
		||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
 | 
			
		||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
 | 
			
		||||
import { Page, PageHeader } from '../../../components/page';
 | 
			
		||||
import { useAlive } from '../../../hooks/useAlive';
 | 
			
		||||
import { SequenceCard } from '../../../components/sequence-card';
 | 
			
		||||
import { TextViewerContent } from '../../../components/text-viewer';
 | 
			
		||||
 | 
			
		||||
const EDITOR_INTENT_SPACE_COUNT = 2;
 | 
			
		||||
 | 
			
		||||
type AccountDataInfo = {
 | 
			
		||||
  type: string;
 | 
			
		||||
  content: object;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type AccountDataEditProps = {
 | 
			
		||||
  type: string;
 | 
			
		||||
  defaultContent: string;
 | 
			
		||||
  onCancel: () => void;
 | 
			
		||||
  onSave: (info: AccountDataInfo) => void;
 | 
			
		||||
};
 | 
			
		||||
function AccountDataEdit({ type, defaultContent, onCancel, onSave }: AccountDataEditProps) {
 | 
			
		||||
  const mx = useMatrixClient();
 | 
			
		||||
  const alive = useAlive();
 | 
			
		||||
 | 
			
		||||
  const textAreaRef = useRef<HTMLTextAreaElement>(null);
 | 
			
		||||
  const [jsonError, setJSONError] = useState<SyntaxError>();
 | 
			
		||||
 | 
			
		||||
  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<HTMLTextAreaElement> = (evt) => {
 | 
			
		||||
    intentHandler(evt);
 | 
			
		||||
    if (isKeyHotkey('escape', evt)) {
 | 
			
		||||
      const cursor = Cursor.fromTextAreaElement(getTarget());
 | 
			
		||||
      operations.deselect(cursor);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const [submitState, submit] = useAsyncCallback<object, MatrixError, [string, object]>(
 | 
			
		||||
    useCallback((dataType, data) => mx.setAccountData(dataType, data), [mx])
 | 
			
		||||
  );
 | 
			
		||||
  const submitting = submitState.status === AsyncStatus.Loading;
 | 
			
		||||
 | 
			
		||||
  const handleSubmit: FormEventHandler<HTMLFormElement> = (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 (
 | 
			
		||||
    <Box
 | 
			
		||||
      as="form"
 | 
			
		||||
      onSubmit={handleSubmit}
 | 
			
		||||
      grow="Yes"
 | 
			
		||||
      className={css.EditorContent}
 | 
			
		||||
      direction="Column"
 | 
			
		||||
      gap="400"
 | 
			
		||||
      aria-disabled={submitting}
 | 
			
		||||
    >
 | 
			
		||||
      <Box shrink="No" direction="Column" gap="100">
 | 
			
		||||
        <Text size="L400">Account Data</Text>
 | 
			
		||||
        <Box gap="300">
 | 
			
		||||
          <Box grow="Yes" direction="Column">
 | 
			
		||||
            <Input
 | 
			
		||||
              variant={type.length > 0 || submitting ? 'SurfaceVariant' : 'Background'}
 | 
			
		||||
              name="typeInput"
 | 
			
		||||
              size="400"
 | 
			
		||||
              radii="300"
 | 
			
		||||
              readOnly={type.length > 0 || submitting}
 | 
			
		||||
              defaultValue={type}
 | 
			
		||||
              required
 | 
			
		||||
            />
 | 
			
		||||
          </Box>
 | 
			
		||||
          <Button
 | 
			
		||||
            variant="Success"
 | 
			
		||||
            size="400"
 | 
			
		||||
            radii="300"
 | 
			
		||||
            type="submit"
 | 
			
		||||
            disabled={submitting}
 | 
			
		||||
            before={submitting && <Spinner variant="Primary" fill="Solid" size="300" />}
 | 
			
		||||
          >
 | 
			
		||||
            <Text size="B400">Save</Text>
 | 
			
		||||
          </Button>
 | 
			
		||||
          <Button
 | 
			
		||||
            variant="Secondary"
 | 
			
		||||
            fill="Soft"
 | 
			
		||||
            size="400"
 | 
			
		||||
            radii="300"
 | 
			
		||||
            onClick={onCancel}
 | 
			
		||||
            disabled={submitting}
 | 
			
		||||
          >
 | 
			
		||||
            <Text size="B400">Cancel</Text>
 | 
			
		||||
          </Button>
 | 
			
		||||
        </Box>
 | 
			
		||||
 | 
			
		||||
        {submitState.status === AsyncStatus.Error && (
 | 
			
		||||
          <Text size="T200" style={{ color: color.Critical.Main }}>
 | 
			
		||||
            <b>{submitState.error.message}</b>
 | 
			
		||||
          </Text>
 | 
			
		||||
        )}
 | 
			
		||||
      </Box>
 | 
			
		||||
      <Box grow="Yes" direction="Column" gap="100">
 | 
			
		||||
        <Box shrink="No">
 | 
			
		||||
          <Text size="L400">JSON Content</Text>
 | 
			
		||||
        </Box>
 | 
			
		||||
        <TextAreaComponent
 | 
			
		||||
          ref={textAreaRef}
 | 
			
		||||
          name="contentTextArea"
 | 
			
		||||
          className={css.EditorTextArea}
 | 
			
		||||
          onKeyDown={handleKeyDown}
 | 
			
		||||
          defaultValue={defaultContent}
 | 
			
		||||
          resize="None"
 | 
			
		||||
          spellCheck="false"
 | 
			
		||||
          required
 | 
			
		||||
          readOnly={submitting}
 | 
			
		||||
        />
 | 
			
		||||
        {jsonError && (
 | 
			
		||||
          <Text size="T200" style={{ color: color.Critical.Main }}>
 | 
			
		||||
            <b>
 | 
			
		||||
              {jsonError.name}: {jsonError.message}
 | 
			
		||||
            </b>
 | 
			
		||||
          </Text>
 | 
			
		||||
        )}
 | 
			
		||||
      </Box>
 | 
			
		||||
    </Box>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AccountDataViewProps = {
 | 
			
		||||
  type: string;
 | 
			
		||||
  defaultContent: string;
 | 
			
		||||
  onEdit: () => void;
 | 
			
		||||
};
 | 
			
		||||
function AccountDataView({ type, defaultContent, onEdit }: AccountDataViewProps) {
 | 
			
		||||
  return (
 | 
			
		||||
    <Box direction="Column" className={css.EditorContent} gap="400">
 | 
			
		||||
      <Box shrink="No" gap="300" alignItems="End">
 | 
			
		||||
        <Box grow="Yes" direction="Column" gap="100">
 | 
			
		||||
          <Text size="L400">Account Data</Text>
 | 
			
		||||
          <Input
 | 
			
		||||
            variant="SurfaceVariant"
 | 
			
		||||
            size="400"
 | 
			
		||||
            radii="300"
 | 
			
		||||
            readOnly
 | 
			
		||||
            defaultValue={type}
 | 
			
		||||
            required
 | 
			
		||||
          />
 | 
			
		||||
        </Box>
 | 
			
		||||
        <Button variant="Secondary" size="400" radii="300" onClick={onEdit}>
 | 
			
		||||
          <Text size="B400">Edit</Text>
 | 
			
		||||
        </Button>
 | 
			
		||||
      </Box>
 | 
			
		||||
      <Box grow="Yes" direction="Column" gap="100">
 | 
			
		||||
        <Text size="L400">JSON Content</Text>
 | 
			
		||||
        <SequenceCard variant="SurfaceVariant">
 | 
			
		||||
          <Scroll visibility="Always" size="300" hideTrack>
 | 
			
		||||
            <TextViewerContent
 | 
			
		||||
              size="T300"
 | 
			
		||||
              style={{
 | 
			
		||||
                padding: `${config.space.S300} ${config.space.S100} ${config.space.S300} ${config.space.S300}`,
 | 
			
		||||
              }}
 | 
			
		||||
              text={defaultContent}
 | 
			
		||||
              langName="JSON"
 | 
			
		||||
            />
 | 
			
		||||
          </Scroll>
 | 
			
		||||
        </SequenceCard>
 | 
			
		||||
      </Box>
 | 
			
		||||
    </Box>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<HTMLTextAreaElement>(null);
 | 
			
		||||
    const [jsonError, setJSONError] = useState<SyntaxError>();
 | 
			
		||||
export function AccountDataEditor({ type, requestClose }: AccountDataEditorProps) {
 | 
			
		||||
  const mx = useMatrixClient();
 | 
			
		||||
 | 
			
		||||
    const getTarget: GetTarget = useCallback(() => {
 | 
			
		||||
      const target = textAreaRef.current;
 | 
			
		||||
      if (!target) throw new Error('TextArea element not found!');
 | 
			
		||||
      return target;
 | 
			
		||||
    }, []);
 | 
			
		||||
  const [data, setData] = useState<AccountDataInfo>({
 | 
			
		||||
    type: type ?? '',
 | 
			
		||||
    content: mx.getAccountData(type ?? '')?.getContent() ?? {},
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
    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 [edit, setEdit] = useState(!type);
 | 
			
		||||
 | 
			
		||||
    const intentHandler = useTextAreaIntentHandler(textArea, operations, intent);
 | 
			
		||||
  const closeEdit = useCallback(() => {
 | 
			
		||||
    if (!type) {
 | 
			
		||||
      requestClose();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    setEdit(false);
 | 
			
		||||
  }, [type, requestClose]);
 | 
			
		||||
 | 
			
		||||
    const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = (evt) => {
 | 
			
		||||
      intentHandler(evt);
 | 
			
		||||
      if (isKeyHotkey('escape', evt)) {
 | 
			
		||||
        const cursor = Cursor.fromTextAreaElement(getTarget());
 | 
			
		||||
        operations.deselect(cursor);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  const handleSave = useCallback((info: AccountDataInfo) => {
 | 
			
		||||
    setData(info);
 | 
			
		||||
    setEdit(false);
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
    const [submitState, submit] = useAsyncCallback<object, MatrixError, [string, object]>(
 | 
			
		||||
      useCallback((dataType, data) => mx.setAccountData(dataType, data), [mx])
 | 
			
		||||
    );
 | 
			
		||||
    const submitting = submitState.status === AsyncStatus.Loading;
 | 
			
		||||
  const contentJSONStr = useMemo(
 | 
			
		||||
    () => JSON.stringify(data.content, null, EDITOR_INTENT_SPACE_COUNT),
 | 
			
		||||
    [data.content]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    const handleSubmit: FormEventHandler<HTMLFormElement> = (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 (
 | 
			
		||||
      <Box grow="Yes" direction="Column" {...props} ref={ref}>
 | 
			
		||||
        <Header className={css.EditorHeader} size="600">
 | 
			
		||||
          <Box grow="Yes" gap="200">
 | 
			
		||||
            <Box grow="Yes" alignItems="Center" gap="200">
 | 
			
		||||
              <Text size="H3" truncate>
 | 
			
		||||
                Account Data
 | 
			
		||||
              </Text>
 | 
			
		||||
            </Box>
 | 
			
		||||
            <Box shrink="No">
 | 
			
		||||
              <IconButton onClick={requestClose} variant="Surface">
 | 
			
		||||
                <Icon src={Icons.Cross} />
 | 
			
		||||
              </IconButton>
 | 
			
		||||
            </Box>
 | 
			
		||||
  return (
 | 
			
		||||
    <Page>
 | 
			
		||||
      <PageHeader outlined={false} balance>
 | 
			
		||||
        <Box alignItems="Center" grow="Yes" gap="200">
 | 
			
		||||
          <Box alignItems="Inherit" grow="Yes" gap="200">
 | 
			
		||||
            <Chip
 | 
			
		||||
              size="500"
 | 
			
		||||
              radii="Pill"
 | 
			
		||||
              onClick={requestClose}
 | 
			
		||||
              before={<Icon size="100" src={Icons.ArrowLeft} />}
 | 
			
		||||
            >
 | 
			
		||||
              <Text size="T300">Developer Tools</Text>
 | 
			
		||||
            </Chip>
 | 
			
		||||
          </Box>
 | 
			
		||||
        </Header>
 | 
			
		||||
        <Box
 | 
			
		||||
          as="form"
 | 
			
		||||
          onSubmit={handleSubmit}
 | 
			
		||||
          grow="Yes"
 | 
			
		||||
          className={css.EditorContent}
 | 
			
		||||
          direction="Column"
 | 
			
		||||
          gap="400"
 | 
			
		||||
          aria-disabled={submitting}
 | 
			
		||||
        >
 | 
			
		||||
          <Box shrink="No" direction="Column" gap="100">
 | 
			
		||||
            <Text size="L400">Type</Text>
 | 
			
		||||
            <Box gap="300">
 | 
			
		||||
              <Box grow="Yes" direction="Column">
 | 
			
		||||
                <Input
 | 
			
		||||
                  name="typeInput"
 | 
			
		||||
                  size="400"
 | 
			
		||||
                  readOnly={!!type || submitting}
 | 
			
		||||
                  defaultValue={type}
 | 
			
		||||
                  required
 | 
			
		||||
                />
 | 
			
		||||
              </Box>
 | 
			
		||||
              <Button
 | 
			
		||||
                variant="Primary"
 | 
			
		||||
                size="400"
 | 
			
		||||
                type="submit"
 | 
			
		||||
                disabled={submitting}
 | 
			
		||||
                before={submitting && <Spinner variant="Primary" fill="Solid" size="300" />}
 | 
			
		||||
              >
 | 
			
		||||
                <Text size="B400">Save</Text>
 | 
			
		||||
              </Button>
 | 
			
		||||
            </Box>
 | 
			
		||||
 | 
			
		||||
            {submitState.status === AsyncStatus.Error && (
 | 
			
		||||
              <Text size="T200" style={{ color: color.Critical.Main }}>
 | 
			
		||||
                <b>{submitState.error.message}</b>
 | 
			
		||||
              </Text>
 | 
			
		||||
            )}
 | 
			
		||||
          </Box>
 | 
			
		||||
          <Box grow="Yes" direction="Column" gap="100">
 | 
			
		||||
            <Box shrink="No">
 | 
			
		||||
              <Text size="L400">JSON Content</Text>
 | 
			
		||||
            </Box>
 | 
			
		||||
            <TextAreaComponent
 | 
			
		||||
              ref={textAreaRef}
 | 
			
		||||
              name="contentTextArea"
 | 
			
		||||
              className={css.EditorTextArea}
 | 
			
		||||
              onKeyDown={handleKeyDown}
 | 
			
		||||
              defaultValue={defaultContent}
 | 
			
		||||
              resize="None"
 | 
			
		||||
              spellCheck="false"
 | 
			
		||||
              required
 | 
			
		||||
              readOnly={submitting}
 | 
			
		||||
            />
 | 
			
		||||
            {jsonError && (
 | 
			
		||||
              <Text size="T200" style={{ color: color.Critical.Main }}>
 | 
			
		||||
                <b>
 | 
			
		||||
                  {jsonError.name}: {jsonError.message}
 | 
			
		||||
                </b>
 | 
			
		||||
              </Text>
 | 
			
		||||
            )}
 | 
			
		||||
          <Box shrink="No">
 | 
			
		||||
            <IconButton onClick={requestClose} variant="Surface">
 | 
			
		||||
              <Icon src={Icons.Cross} />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
          </Box>
 | 
			
		||||
        </Box>
 | 
			
		||||
      </PageHeader>
 | 
			
		||||
      <Box grow="Yes" direction="Column">
 | 
			
		||||
        {edit ? (
 | 
			
		||||
          <AccountDataEdit
 | 
			
		||||
            type={data.type}
 | 
			
		||||
            defaultContent={contentJSONStr}
 | 
			
		||||
            onCancel={closeEdit}
 | 
			
		||||
            onSave={handleSave}
 | 
			
		||||
          />
 | 
			
		||||
        ) : (
 | 
			
		||||
          <AccountDataView
 | 
			
		||||
            type={data.type}
 | 
			
		||||
            defaultContent={contentJSONStr}
 | 
			
		||||
            onEdit={() => setEdit(true)}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
      </Box>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
    </Page>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,26 +1,5 @@
 | 
			
		|||
import React, { MouseEventHandler, useCallback, useState } from 'react';
 | 
			
		||||
import {
 | 
			
		||||
  Box,
 | 
			
		||||
  Text,
 | 
			
		||||
  IconButton,
 | 
			
		||||
  Icon,
 | 
			
		||||
  Icons,
 | 
			
		||||
  Scroll,
 | 
			
		||||
  Switch,
 | 
			
		||||
  Overlay,
 | 
			
		||||
  OverlayBackdrop,
 | 
			
		||||
  OverlayCenter,
 | 
			
		||||
  Modal,
 | 
			
		||||
  Chip,
 | 
			
		||||
  Button,
 | 
			
		||||
  PopOut,
 | 
			
		||||
  RectCords,
 | 
			
		||||
  Menu,
 | 
			
		||||
  config,
 | 
			
		||||
  MenuItem,
 | 
			
		||||
} from 'folds';
 | 
			
		||||
import { MatrixEvent } from 'matrix-js-sdk';
 | 
			
		||||
import FocusTrap from 'focus-trap-react';
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { Box, Text, IconButton, Icon, Icons, Scroll, Switch, Button } from 'folds';
 | 
			
		||||
import { Page, PageContent, PageHeader } from '../../../components/page';
 | 
			
		||||
import { SequenceCard } from '../../../components/sequence-card';
 | 
			
		||||
import { SequenceCardStyle } from '../styles.css';
 | 
			
		||||
| 
						 | 
				
			
			@ -28,195 +7,9 @@ import { SettingTile } from '../../../components/setting-tile';
 | 
			
		|||
import { useSetting } from '../../../state/hooks/settings';
 | 
			
		||||
import { settingsAtom } from '../../../state/settings';
 | 
			
		||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
 | 
			
		||||
import { useAccountDataCallback } from '../../../hooks/useAccountDataCallback';
 | 
			
		||||
import { TextViewer } from '../../../components/text-viewer';
 | 
			
		||||
import { stopPropagation } from '../../../utils/keyboard';
 | 
			
		||||
import { AccountDataEditor } from './AccountDataEditor';
 | 
			
		||||
import { copyToClipboard } from '../../../utils/dom';
 | 
			
		||||
 | 
			
		||||
function AccountData() {
 | 
			
		||||
  const mx = useMatrixClient();
 | 
			
		||||
  const [view, setView] = useState(false);
 | 
			
		||||
  const [accountData, setAccountData] = useState(() => Array.from(mx.store.accountData.values()));
 | 
			
		||||
  const [selectedEvent, selectEvent] = useState<MatrixEvent>();
 | 
			
		||||
  const [menuCords, setMenuCords] = useState<RectCords>();
 | 
			
		||||
  const [selectedOption, selectOption] = useState<'edit' | 'inspect'>();
 | 
			
		||||
 | 
			
		||||
  useAccountDataCallback(
 | 
			
		||||
    mx,
 | 
			
		||||
    useCallback(
 | 
			
		||||
      () => setAccountData(Array.from(mx.store.accountData.values())),
 | 
			
		||||
      [mx, setAccountData]
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const handleMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
 | 
			
		||||
    const target = evt.currentTarget;
 | 
			
		||||
    const eventType = target.getAttribute('data-event-type');
 | 
			
		||||
    if (eventType) {
 | 
			
		||||
      const mEvent = accountData.find((mEvt) => mEvt.getType() === eventType);
 | 
			
		||||
      setMenuCords(evt.currentTarget.getBoundingClientRect());
 | 
			
		||||
      selectEvent(mEvent);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleMenuClose = () => setMenuCords(undefined);
 | 
			
		||||
 | 
			
		||||
  const handleEdit = () => {
 | 
			
		||||
    selectOption('edit');
 | 
			
		||||
    setMenuCords(undefined);
 | 
			
		||||
  };
 | 
			
		||||
  const handleInspect = () => {
 | 
			
		||||
    selectOption('inspect');
 | 
			
		||||
    setMenuCords(undefined);
 | 
			
		||||
  };
 | 
			
		||||
  const handleClose = useCallback(() => {
 | 
			
		||||
    selectEvent(undefined);
 | 
			
		||||
    selectOption(undefined);
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Box direction="Column" gap="100">
 | 
			
		||||
      <Text size="L400">Account Data</Text>
 | 
			
		||||
      <SequenceCard
 | 
			
		||||
        className={SequenceCardStyle}
 | 
			
		||||
        variant="SurfaceVariant"
 | 
			
		||||
        direction="Column"
 | 
			
		||||
        gap="400"
 | 
			
		||||
      >
 | 
			
		||||
        <SettingTile
 | 
			
		||||
          title="Global"
 | 
			
		||||
          description="Data stored in your global account data."
 | 
			
		||||
          after={
 | 
			
		||||
            <Button
 | 
			
		||||
              onClick={() => setView(!view)}
 | 
			
		||||
              variant="Secondary"
 | 
			
		||||
              fill="Soft"
 | 
			
		||||
              size="300"
 | 
			
		||||
              radii="300"
 | 
			
		||||
              outlined
 | 
			
		||||
              before={
 | 
			
		||||
                <Icon src={view ? Icons.ChevronTop : Icons.ChevronBottom} size="100" filled />
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <Text size="B300">{view ? 'Collapse' : 'Expand'}</Text>
 | 
			
		||||
            </Button>
 | 
			
		||||
          }
 | 
			
		||||
        />
 | 
			
		||||
        {view && (
 | 
			
		||||
          <SettingTile>
 | 
			
		||||
            <Box direction="Column" gap="200">
 | 
			
		||||
              <Text size="L400">Types</Text>
 | 
			
		||||
              <Box gap="200" wrap="Wrap">
 | 
			
		||||
                <Chip
 | 
			
		||||
                  variant="Secondary"
 | 
			
		||||
                  fill="Soft"
 | 
			
		||||
                  radii="Pill"
 | 
			
		||||
                  onClick={handleEdit}
 | 
			
		||||
                  before={<Icon size="50" src={Icons.Plus} />}
 | 
			
		||||
                >
 | 
			
		||||
                  <Text size="T200" truncate>
 | 
			
		||||
                    Add New
 | 
			
		||||
                  </Text>
 | 
			
		||||
                </Chip>
 | 
			
		||||
                {accountData.map((mEvent) => (
 | 
			
		||||
                  <Chip
 | 
			
		||||
                    key={mEvent.getType()}
 | 
			
		||||
                    variant="Secondary"
 | 
			
		||||
                    fill="Soft"
 | 
			
		||||
                    radii="Pill"
 | 
			
		||||
                    aria-pressed={menuCords && selectedEvent?.getType() === mEvent.getType()}
 | 
			
		||||
                    onClick={handleMenu}
 | 
			
		||||
                    data-event-type={mEvent.getType()}
 | 
			
		||||
                  >
 | 
			
		||||
                    <Text size="T200" truncate>
 | 
			
		||||
                      {mEvent.getType()}
 | 
			
		||||
                    </Text>
 | 
			
		||||
                  </Chip>
 | 
			
		||||
                ))}
 | 
			
		||||
              </Box>
 | 
			
		||||
            </Box>
 | 
			
		||||
          </SettingTile>
 | 
			
		||||
        )}
 | 
			
		||||
        <PopOut
 | 
			
		||||
          anchor={menuCords}
 | 
			
		||||
          offset={5}
 | 
			
		||||
          position="Bottom"
 | 
			
		||||
          content={
 | 
			
		||||
            <FocusTrap
 | 
			
		||||
              focusTrapOptions={{
 | 
			
		||||
                initialFocus: false,
 | 
			
		||||
                onDeactivate: handleMenuClose,
 | 
			
		||||
                clickOutsideDeactivates: true,
 | 
			
		||||
                isKeyForward: (evt: KeyboardEvent) =>
 | 
			
		||||
                  evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
 | 
			
		||||
                isKeyBackward: (evt: KeyboardEvent) =>
 | 
			
		||||
                  evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',
 | 
			
		||||
                escapeDeactivates: stopPropagation,
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <Menu>
 | 
			
		||||
                <Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
 | 
			
		||||
                  <MenuItem size="300" variant="Surface" radii="300" onClick={handleInspect}>
 | 
			
		||||
                    <Text size="T300">Inspect</Text>
 | 
			
		||||
                  </MenuItem>
 | 
			
		||||
                  <MenuItem size="300" variant="Surface" radii="300" onClick={handleEdit}>
 | 
			
		||||
                    <Text size="T300">Edit</Text>
 | 
			
		||||
                  </MenuItem>
 | 
			
		||||
                </Box>
 | 
			
		||||
              </Menu>
 | 
			
		||||
            </FocusTrap>
 | 
			
		||||
          }
 | 
			
		||||
        />
 | 
			
		||||
      </SequenceCard>
 | 
			
		||||
      {selectedEvent && selectedOption === 'inspect' && (
 | 
			
		||||
        <Overlay open backdrop={<OverlayBackdrop />}>
 | 
			
		||||
          <OverlayCenter>
 | 
			
		||||
            <FocusTrap
 | 
			
		||||
              focusTrapOptions={{
 | 
			
		||||
                initialFocus: false,
 | 
			
		||||
                onDeactivate: handleClose,
 | 
			
		||||
                clickOutsideDeactivates: true,
 | 
			
		||||
                escapeDeactivates: stopPropagation,
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <Modal variant="Surface" size="500">
 | 
			
		||||
                <TextViewer
 | 
			
		||||
                  name={selectedEvent.getType() ?? 'Source Code'}
 | 
			
		||||
                  langName="json"
 | 
			
		||||
                  text={JSON.stringify(selectedEvent.getContent(), null, 2)}
 | 
			
		||||
                  requestClose={handleClose}
 | 
			
		||||
                />
 | 
			
		||||
              </Modal>
 | 
			
		||||
            </FocusTrap>
 | 
			
		||||
          </OverlayCenter>
 | 
			
		||||
        </Overlay>
 | 
			
		||||
      )}
 | 
			
		||||
      {selectedOption === 'edit' && (
 | 
			
		||||
        <Overlay open backdrop={<OverlayBackdrop />}>
 | 
			
		||||
          <OverlayCenter>
 | 
			
		||||
            <FocusTrap
 | 
			
		||||
              focusTrapOptions={{
 | 
			
		||||
                initialFocus: false,
 | 
			
		||||
                onDeactivate: handleClose,
 | 
			
		||||
                clickOutsideDeactivates: true,
 | 
			
		||||
                escapeDeactivates: stopPropagation,
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <Modal variant="Surface" size="500">
 | 
			
		||||
                <AccountDataEditor
 | 
			
		||||
                  type={selectedEvent?.getType()}
 | 
			
		||||
                  content={selectedEvent?.getContent()}
 | 
			
		||||
                  requestClose={handleClose}
 | 
			
		||||
                />
 | 
			
		||||
              </Modal>
 | 
			
		||||
            </FocusTrap>
 | 
			
		||||
          </OverlayCenter>
 | 
			
		||||
        </Overlay>
 | 
			
		||||
      )}
 | 
			
		||||
    </Box>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
import { AccountData } from './AccountData';
 | 
			
		||||
 | 
			
		||||
type DeveloperToolsProps = {
 | 
			
		||||
  requestClose: () => void;
 | 
			
		||||
| 
						 | 
				
			
			@ -224,6 +17,17 @@ type DeveloperToolsProps = {
 | 
			
		|||
export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
 | 
			
		||||
  const mx = useMatrixClient();
 | 
			
		||||
  const [developerTools, setDeveloperTools] = useSetting(settingsAtom, 'developerTools');
 | 
			
		||||
  const [expand, setExpend] = useState(false);
 | 
			
		||||
  const [accountDataType, setAccountDataType] = useState<string | null>();
 | 
			
		||||
 | 
			
		||||
  if (accountDataType !== undefined) {
 | 
			
		||||
    return (
 | 
			
		||||
      <AccountDataEditor
 | 
			
		||||
        type={accountDataType ?? undefined}
 | 
			
		||||
        requestClose={() => setAccountDataType(undefined)}
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Page>
 | 
			
		||||
| 
						 | 
				
			
			@ -292,7 +96,13 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
 | 
			
		|||
                  </SequenceCard>
 | 
			
		||||
                )}
 | 
			
		||||
              </Box>
 | 
			
		||||
              {developerTools && <AccountData />}
 | 
			
		||||
              {developerTools && (
 | 
			
		||||
                <AccountData
 | 
			
		||||
                  expand={expand}
 | 
			
		||||
                  onExpandToggle={setExpend}
 | 
			
		||||
                  onSelect={setAccountDataType}
 | 
			
		||||
                />
 | 
			
		||||
              )}
 | 
			
		||||
            </Box>
 | 
			
		||||
          </PageContent>
 | 
			
		||||
        </Scroll>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue