diff --git a/src/app/components/AccountDataEditor.tsx b/src/app/components/AccountDataEditor.tsx index 2dbaf1f1..3be3911c 100644 --- a/src/app/components/AccountDataEditor.tsx +++ b/src/app/components/AccountDataEditor.tsx @@ -27,6 +27,7 @@ import { useTextAreaCodeEditor } from '../hooks/useTextAreaCodeEditor'; const EDITOR_INTENT_SPACE_COUNT = 2; export type AccountDataSubmitCallback = (type: string, content: object) => Promise; +export type AccountDataDeleteCallback = (type: string) => Promise; type AccountDataInfo = { type: string; @@ -83,8 +84,7 @@ function AccountDataEdit({ if ( !typeStr || - parsedContent === null || - defaultContent === JSON.stringify(parsedContent, null, EDITOR_INTENT_SPACE_COUNT) + parsedContent === null ) { return; } @@ -121,7 +121,7 @@ function AccountDataEdit({ aria-disabled={submitting} > - Account Data + Field Name void; + requestClose: () => void; + submitDelete?: AccountDataDeleteCallback; }; -function AccountDataView({ type, defaultContent, onEdit }: AccountDataViewProps) { +function AccountDataView({ type, defaultContent, onEdit, requestClose, submitDelete }: AccountDataViewProps) { + const [deleteState, deleteCallback] = useAsyncCallback(useCallback( + async () => { + if (submitDelete !== undefined) { + await submitDelete(type); + requestClose(); + } + }, + [type, submitDelete, requestClose], + )); + const deleting = deleteState.status === AsyncStatus.Loading; + return ( - Account Data + Field Name Edit + {submitDelete && ( + + )} JSON Content @@ -243,8 +268,9 @@ function AccountDataView({ type, defaultContent, onEdit }: AccountDataViewProps) export type AccountDataEditorProps = { type?: string; - content?: object; + content?: unknown; submitChange: AccountDataSubmitCallback; + submitDelete?: AccountDataDeleteCallback; requestClose: () => void; }; @@ -252,6 +278,7 @@ export function AccountDataEditor({ type, content, submitChange, + submitDelete, requestClose, }: AccountDataEditorProps) { const [data, setData] = useState({ @@ -314,6 +341,8 @@ export function AccountDataEditor({ type={data.type} defaultContent={contentJSONStr} onEdit={() => setEdit(true)} + requestClose={requestClose} + submitDelete={submitDelete} /> )} diff --git a/src/app/features/settings/account/Profile.tsx b/src/app/features/settings/account/Profile.tsx index f3fcadbf..71f5773f 100644 --- a/src/app/features/settings/account/Profile.tsx +++ b/src/app/features/settings/account/Profile.tsx @@ -148,7 +148,7 @@ export function Profile() { // once the profile request completes await refreshExtendedProfile(); - // synthesise a profile update for ourselves to update our name and avatr in the rest + // synthesize a profile update for ourselves to update our name and avatar in the rest // of the UI. code copied from matrix-js-sdk const user = mx.getUser(userId); if (user) { diff --git a/src/app/features/settings/developer-tools/AccountData.tsx b/src/app/features/settings/developer-tools/AccountData.tsx deleted file mode 100644 index 8bccb62e..00000000 --- a/src/app/features/settings/developer-tools/AccountData.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import { Box, Text, Icon, Icons, Button, MenuItem } 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'; -import { CutoutCard } from '../../../components/cutout-card'; - -type AccountDataProps = { - expand: boolean; - onExpandToggle: (expand: boolean) => void; - onSelect: (type: string | null) => void; -}; -export function AccountData({ expand, onExpandToggle, onSelect }: AccountDataProps) { - const mx = useMatrixClient(); - const [accountDataTypes, setAccountDataKeys] = useState(() => - Array.from(mx.store.accountData.keys()) - ); - - useAccountDataCallback( - mx, - useCallback(() => { - setAccountDataKeys(Array.from(mx.store.accountData.keys())); - }, [mx]) - ); - - return ( - - Account Data - - onExpandToggle(!expand)} - variant="Secondary" - fill="Soft" - size="300" - radii="300" - outlined - before={ - - } - > - {expand ? 'Collapse' : 'Expand'} - - } - /> - {expand && ( - - - Events - Total: {accountDataTypes.length} - - - } - onClick={() => onSelect(null)} - > - - - Add New - - - - {accountDataTypes.sort().map((type) => ( - } - onClick={() => onSelect(type)} - > - - - {type} - - - - ))} - - - )} - - - ); -} diff --git a/src/app/features/settings/developer-tools/AccountDataList.tsx b/src/app/features/settings/developer-tools/AccountDataList.tsx new file mode 100644 index 00000000..71b1cf76 --- /dev/null +++ b/src/app/features/settings/developer-tools/AccountDataList.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { Box, Text, Icon, Icons, Button, MenuItem } from 'folds'; +import { SequenceCard } from '../../../components/sequence-card'; +import { SequenceCardStyle } from '../styles.css'; +import { SettingTile } from '../../../components/setting-tile'; +import { CutoutCard } from '../../../components/cutout-card'; + +type AccountDataListProps = { + title?: string; + description?: string; + expand: boolean; + setExpand: (expand: boolean) => void; + types: string[]; + onSelect: (type: string | null) => void; +}; +export function AccountDataList({ types, onSelect, expand, setExpand, title, description }: AccountDataListProps) { + return ( + + setExpand(!expand)} + variant="Secondary" + fill="Soft" + size="300" + radii="300" + outlined + before={ + + } + > + {expand ? 'Collapse' : 'Expand'} + + } + /> + {expand && ( + + + Fields + Total: {types.length} + + + } + onClick={() => onSelect(null)} + > + + + Add New + + + + {types.sort().map((type) => ( + } + onClick={() => onSelect(type)} + > + + + {type} + + + + ))} + + + )} + + ); +} diff --git a/src/app/features/settings/developer-tools/DevelopTools.tsx b/src/app/features/settings/developer-tools/DevelopTools.tsx index a3f04567..5ee86820 100644 --- a/src/app/features/settings/developer-tools/DevelopTools.tsx +++ b/src/app/features/settings/developer-tools/DevelopTools.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useState } from 'react'; import { Box, Text, IconButton, Icon, Icons, Scroll, Switch, Button } from 'folds'; +import { AccountDataEvents } from 'matrix-js-sdk'; import { Page, PageContent, PageHeader } from '../../../components/page'; import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCardStyle } from '../styles.css'; @@ -8,117 +9,187 @@ import { useSetting } from '../../../state/hooks/settings'; import { settingsAtom } from '../../../state/settings'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { + AccountDataDeleteCallback, AccountDataEditor, AccountDataSubmitCallback, } from '../../../components/AccountDataEditor'; import { copyToClipboard } from '../../../utils/dom'; -import { AccountData } from './AccountData'; +import { AccountDataList } from './AccountDataList'; +import { useExtendedProfile } from '../../../hooks/useExtendedProfile'; +import { useAccountDataCallback } from '../../../hooks/useAccountDataCallback'; + +type DeveloperToolsPage = + | { name: 'index' } + | { name: 'account-data'; type: string | null } + | { name: 'profile-field'; type: string | null }; type DeveloperToolsProps = { requestClose: () => void; }; export function DeveloperTools({ requestClose }: DeveloperToolsProps) { const mx = useMatrixClient(); + const userId = mx.getUserId() as string; + + const [accountDataTypes, setAccountDataKeys] = useState(() => + Array.from(mx.store.accountData.keys()) + ); + + useAccountDataCallback( + mx, + useCallback(() => { + setAccountDataKeys(Array.from(mx.store.accountData.keys())); + }, [mx]) + ); + const [extendedProfile, refreshExtendedProfile] = useExtendedProfile(userId); + const [developerTools, setDeveloperTools] = useSetting(settingsAtom, 'developerTools'); - const [expand, setExpend] = useState(false); - const [accountDataType, setAccountDataType] = useState(); + const [page, setPage] = useState({ name: 'index' }); + const [globalExpand, setGlobalExpand] = useState(false); + const [profileExpand, setProfileExpand] = useState(false); const submitAccountData: AccountDataSubmitCallback = useCallback( async (type, content) => { - await mx.setAccountData(type, content); + await mx.setAccountData(type as keyof AccountDataEvents, content); }, [mx] ); - if (accountDataType !== undefined) { - return ( - setAccountDataType(undefined)} - /> - ); - } - - return ( - - - - - - Developer Tools - - - - - - - - - - - - - - - Options - - - } - /> - - {developerTools && ( - - - copyToClipboard(mx.getAccessToken() ?? '') - } - variant="Secondary" - fill="Soft" - size="300" - radii="300" - outlined - > - Copy - - } - /> - - )} - - {developerTools && ( - - )} - - - - - + const submitProfileField: AccountDataSubmitCallback = useCallback( + async (type, content) => { + await mx.setExtendedProfileProperty(type, content); + await refreshExtendedProfile(); + }, + [mx, refreshExtendedProfile] ); + + const deleteProfileField: AccountDataDeleteCallback = useCallback( + async (type) => { + await mx.deleteExtendedProfileProperty(type); + await refreshExtendedProfile(); + }, + [mx, refreshExtendedProfile] + ); + + const handleClose = useCallback(() => setPage({ name: 'index' }), [setPage]); + + switch (page.name) { + case 'account-data': + return ( + + ); + + case 'profile-field': + return ( + + ); + + default: + return ( + + + + + + Developer Tools + + + + + + + + + + + + + + + Options + + + } + /> + + {developerTools && ( + + + copyToClipboard(mx.getAccessToken() ?? '') + } + variant="Secondary" + fill="Soft" + size="300" + radii="300" + outlined + > + Copy + + } + /> + + )} + + {developerTools && ( + + Account Data + setPage({ name: 'account-data', type })} + /> + {extendedProfile && ( + setPage({ name: 'profile-field', type })} + /> + )} + + )} + + + + + + ); + } }