diff --git a/packages/maskbook/src/_locales/en/messages.json b/packages/maskbook/src/_locales/en/messages.json index 436ecfe2a556..be12feda5c29 100644 --- a/packages/maskbook/src/_locales/en/messages.json +++ b/packages/maskbook/src/_locales/en/messages.json @@ -190,6 +190,7 @@ "search_box_placeholder": "Type here to search", "select_all": "Select All", "select_none": "Select None", + "all_friends": "All Friends", "select_specific_friends_dialog__button": "Done", "select_specific_friends_dialog__title": "Select Specific Contacts", "service_decryption_failed": "Decryption failed.", diff --git a/packages/maskbook/src/_locales/ja/messages.json b/packages/maskbook/src/_locales/ja/messages.json index 037aca2c4972..2fa2bdb457b0 100644 --- a/packages/maskbook/src/_locales/ja/messages.json +++ b/packages/maskbook/src/_locales/ja/messages.json @@ -190,6 +190,7 @@ "search_box_placeholder": "ここに打って検索", "select_all": "全て選ぶ", "select_none": "何も選んでいません。", + "all_friends": "", "select_specific_friends_dialog__button": "完了", "select_specific_friends_dialog__title": "特定の連絡先を選ぶ", "service_decryption_failed": "復号に失敗", diff --git a/packages/maskbook/src/_locales/ko/messages.json b/packages/maskbook/src/_locales/ko/messages.json index c0aa18c2c06b..d1a9a50ea825 100644 --- a/packages/maskbook/src/_locales/ko/messages.json +++ b/packages/maskbook/src/_locales/ko/messages.json @@ -190,6 +190,7 @@ "search_box_placeholder": "여기서 검색하기", "select_all": "전체 선택", "select_none": "전체 선택 취소", + "all_friends": "", "select_specific_friends_dialog__button": "닫기", "select_specific_friends_dialog__title": "지정 연락처 선택", "service_decryption_failed": "해독 실패", diff --git a/packages/maskbook/src/_locales/zh/messages.json b/packages/maskbook/src/_locales/zh/messages.json index 788d00752c80..9049c3ff57d1 100644 --- a/packages/maskbook/src/_locales/zh/messages.json +++ b/packages/maskbook/src/_locales/zh/messages.json @@ -190,6 +190,7 @@ "search_box_placeholder": "在這裡輸入以搜尋", "select_all": "選擇全部", "select_none": "取消選擇全部", + "all_friends": "", "select_specific_friends_dialog__button": "完成", "select_specific_friends_dialog__title": "選擇特定聯絡人", "service_decryption_failed": "解密失敗。", diff --git a/packages/maskbook/src/components/shared/SelectPeopleAndGroups/PersonOrGroupInList.tsx b/packages/maskbook/src/components/shared/SelectPeopleAndGroups/PersonOrGroupInList.tsx index 90b35cbd31a8..84a9b92344e5 100644 --- a/packages/maskbook/src/components/shared/SelectPeopleAndGroups/PersonOrGroupInList.tsx +++ b/packages/maskbook/src/components/shared/SelectPeopleAndGroups/PersonOrGroupInList.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react' +import { memo, useMemo } from 'react' import { ListItem, Theme, ListItemAvatar, ListItemText } from '@material-ui/core' import type { DefaultComponentProps } from '@material-ui/core/OverridableComponent' import type { ListItemTypeMap } from '@material-ui/core/ListItem' @@ -37,62 +37,95 @@ const useStyle = makeStyles((theme: Theme) => ({ /** * Item in the list */ -export function ProfileOrGroupInList(props: ProfileOrGroupInListProps) { - const { t } = useI18N() +export const ProfileOrGroupInList = memo((props) => { const classes = useStylesExtends(useStyle(), props) - const nicknamePreviewsForGroup = useNickNamesFromList(isGroup(props.item) ? props.item.members : []) - const listFormat = useIntlListFormat() const { disabled, ListItemProps: listItemProps, onClick, showAtNetwork } = props - let name = '' - let avatar: ReturnType - let secondaryText: string | undefined = undefined - const groupName = useResolveSpecialGroupName(props.item) - if (isGroup(props.item)) { - const group = props.item - name = groupName - avatar = ( - - - + + if (isGroup(props.item)) + return ( + + + ) - const joined = listFormat(nicknamePreviewsForGroup) - const groupSize = group.members.length - const data = { people: joined, count: groupSize } - if (groupSize === 0) { - secondaryText = t('person_or_group_in_list_0') - } else if (nicknamePreviewsForGroup.length === 0) { - secondaryText = t('person_or_group_in_list_many_no_preview', data) - } else if (groupSize > nicknamePreviewsForGroup.length) { - secondaryText = t('person_or_group_in_list_many', data) - } else { - secondaryText = joined - } + + const name = props.item.nickname || props.item.identifier.userId + + return ( + + + + + + {name} + @ {props.item.identifier.network} + + ) : ( + name + ) + } + secondary={props.item.linkedPersona?.fingerprint.toLowerCase()} + /> + + ) +}) + +function GroupListItemContent({ group, showAtNetwork }: { group: Group; showAtNetwork?: boolean }) { + const { t } = useI18N() + const classes = useStyle() + const nicknamePreviewsForGroup = useNickNamesFromList(group.members) + const listFormat = useIntlListFormat() + const groupName = useResolveSpecialGroupName(group) + + const joined = listFormat(nicknamePreviewsForGroup) + const groupSize = group.members.length + const data = { people: joined, count: groupSize } + + let secondaryText: string | undefined = undefined + if (groupSize === 0) { + secondaryText = t('person_or_group_in_list_0') + } else if (nicknamePreviewsForGroup.length === 0) { + secondaryText = t('person_or_group_in_list_many_no_preview', data) + } else if (groupSize > nicknamePreviewsForGroup.length) { + secondaryText = t('person_or_group_in_list_many', data) } else { - const person = props.item - name = person.nickname || person.identifier.userId - avatar = - secondaryText = person.linkedPersona?.fingerprint.toLowerCase() + secondaryText = joined } - const withNetwork = ( - <> - {name} - @ {props.item.identifier.network} - - ) + return ( - - {avatar} + <> + + + + + + {groupName} + @ {group.identifier.network} + + ) : ( + groupName + ) + } secondary={secondaryText} /> - + ) } diff --git a/packages/maskbook/src/components/shared/SelectPeopleAndGroups/SelectPeopleAndGroupsUI.tsx b/packages/maskbook/src/components/shared/SelectPeopleAndGroups/SelectPeopleAndGroupsUI.tsx index e6216ab41316..a6a6ceedb17d 100644 --- a/packages/maskbook/src/components/shared/SelectPeopleAndGroups/SelectPeopleAndGroupsUI.tsx +++ b/packages/maskbook/src/components/shared/SelectPeopleAndGroups/SelectPeopleAndGroupsUI.tsx @@ -1,5 +1,5 @@ -import { useEffect, useState, useCallback } from 'react' -import { makeStyles, ListItem, ListItemText, InputBase, Button, List, Box } from '@material-ui/core' +import { useEffect, useState, useCallback, CSSProperties } from 'react' +import { makeStyles, ListItem, ListItemText, InputBase, Button, List, Box, Chip } from '@material-ui/core' import { useI18N } from '../../../utils' import type { Profile, Group } from '../../../database' import { useCurrentIdentity } from '../../DataSource/useActivatedUI' @@ -7,6 +7,7 @@ import { ProfileOrGroupInList, ProfileOrGroupInListProps } from './PersonOrGroup import { ProfileOrGroupInChip, ProfileOrGroupInChipProps } from './PersonOrGroupInChip' import { ProfileIdentifier, GroupIdentifier } from '../../../database/type' import { useStylesExtends } from '../../custom-ui-helper' +import { FixedSizeList } from 'react-window' type ProfileOrGroup = Group | Profile export interface SelectProfileAndGroupsUIProps @@ -87,6 +88,7 @@ export function SelectProfileAndGroupsUI 0 && typeof maxSelection === 'undefined' const showSelectNone = !hideSelectNone && selected.length > 0 + return (
- {frozenSelected.map((x) => FrozenChip(x, props.ProfileOrGroupInChipProps))} - {selected - .filter((item) => !frozenSelected.includes(item as ServeType)) - .map((item) => ( - - onSetSelected( - selected.filter((x) => !x.identifier.equals(item.identifier)) as ServeType[], - ) - } - {...props.ProfileOrGroupInChipProps} - /> - ))} + {frozenSelected.length === items.length || !listBeforeSearch.length ? ( + onSetSelected([]) : undefined} + label={t('all_friends')} + /> + ) : ( + <> + {frozenSelected.map((x) => FrozenChip(x, props.ProfileOrGroupInChipProps))} + {selected + .filter((item) => !frozenSelected.includes(item as ServeType)) + .map((item) => ( + + onSetSelected( + selected.filter( + (x) => !x.identifier.equals(item.identifier), + ) as ServeType[], + ) + } + {...props.ProfileOrGroupInChipProps} + /> + ))} + + )} )} - {listAfterSearch.map(PeopleListItem)} + + {({ index, style }) => + listAfterSearch[index] ? PeopleListItem(listAfterSearch[index], style) : null + } +
) - function PeopleListItem(item: ProfileOrGroup) { + function PeopleListItem(item: ProfileOrGroup, style: CSSProperties) { if (ignoreMyself && myself && item.identifier.equals(myself.identifier)) return null return ( ) diff --git a/packages/maskbook/src/database/index.ts b/packages/maskbook/src/database/index.ts index b0e312dca414..26904b50ae78 100644 --- a/packages/maskbook/src/database/index.ts +++ b/packages/maskbook/src/database/index.ts @@ -1,5 +1,3 @@ -import './migrate/index' -import './tasks/index' export * from './helpers/avatar' export * from './helpers/group' export * from './Persona/helpers' diff --git a/packages/maskbook/src/database/tasks/index.ts b/packages/maskbook/src/database/tasks/index.ts deleted file mode 100644 index 0bf50cabf659..000000000000 --- a/packages/maskbook/src/database/tasks/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { cleanProfileWithNoLinkedPersona } from './clean-avatar-profile' -import { untilDocumentReady } from '../../utils/dom' -import { sideEffect } from '../../utils/side-effects' - -sideEffect.then(untilDocumentReady).then(() => { - cleanProfileWithNoLinkedPersona() -}) diff --git a/packages/maskbook/src/database/tasks/clean-avatar-profile.ts b/packages/maskbook/src/extension/background-script/Jobs/DatabaseCleanup/CleanProfileAndAvatar.ts similarity index 64% rename from packages/maskbook/src/database/tasks/clean-avatar-profile.ts rename to packages/maskbook/src/extension/background-script/Jobs/DatabaseCleanup/CleanProfileAndAvatar.ts index 80cef0f62fda..14d69b4b2749 100644 --- a/packages/maskbook/src/database/tasks/clean-avatar-profile.ts +++ b/packages/maskbook/src/extension/background-script/Jobs/DatabaseCleanup/CleanProfileAndAvatar.ts @@ -1,8 +1,9 @@ -import { createAvatarDBAccess, queryAvatarOutdatedDB, deleteAvatarsDB } from '../avatar' -import { createTransaction } from '../helpers/openDB' -import { consistentPersonaDBWriteAccess } from '../Persona/Persona.db' -import { Identifier, ProfileIdentifier, GroupIdentifier } from '../type' -import { IdentifierMap } from '../IdentifierMap' +import { createAvatarDBAccess, queryAvatarOutdatedDB, deleteAvatarsDB } from '../../../../database/avatar' +import { createTransaction } from '../../../../database/helpers/openDB' +import { consistentPersonaDBWriteAccess } from '../../../../database/Persona/Persona.db' +import { Identifier, ProfileIdentifier, GroupIdentifier } from '../../../../database/type' +import { IdentifierMap } from '../../../../database/IdentifierMap' +import { untilDocumentReady } from '../../../../utils' async function cleanAvatarDB(anotherList: IdentifierMap) { const t = createTransaction(await createAvatarDBAccess(), 'readwrite')('avatars', 'metadata') @@ -13,8 +14,10 @@ async function cleanAvatarDB(anotherList: IdentifierMap clearTimeout(timeout)) const cleanedList = new IdentifierMap( new Map(), @@ -23,6 +26,7 @@ export async function cleanProfileWithNoLinkedPersona() { ) const expired = new Date(Date.now() - 1000 * 60 * 60 * 24 * 14 /** days */) await consistentPersonaDBWriteAccess(async (t) => { + if (signal.aborted) throw new Error('Abort') for await (const x of t.objectStore('profiles')) { if (x.value.linkedPersona) continue if (expired < x.value.updatedAt) continue diff --git a/packages/maskbook/src/database/migrate/_deprecated_people_db.ts b/packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/_deprecated_people_db.ts similarity index 94% rename from packages/maskbook/src/database/migrate/_deprecated_people_db.ts rename to packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/_deprecated_people_db.ts index 0d6c009cb81f..d5ddd50b4fb2 100644 --- a/packages/maskbook/src/database/migrate/_deprecated_people_db.ts +++ b/packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/_deprecated_people_db.ts @@ -1,5 +1,4 @@ /* eslint-disable import/no-deprecated */ -/// /** * @deprecated * This database is deprecated since Mask 1.8.10 @@ -24,12 +23,12 @@ * @type {Record} Record of * @keys outline, string, which means network. */ -import { GroupIdentifier, Identifier, ProfileIdentifier } from '../type' +import { GroupIdentifier, Identifier, ProfileIdentifier } from '../../../../database/type' import { DBSchema, openDB } from 'idb/with-async-ittr-cjs' -import { JsonWebKeyToCryptoKey, getKeyParameter } from '../../utils/type-transform/CryptoKey-JsonWebKey' +import { JsonWebKeyToCryptoKey, getKeyParameter } from '../../../../utils/type-transform/CryptoKey-JsonWebKey' import { assertEnvironment, Environment } from '@dimensiondev/holoflows-kit' -import { createDBAccess } from '../helpers/openDB' -import type { AESJsonWebKey } from '../../modules/CryptoAlgorithm/interfaces/utils' +import { createDBAccess } from '../../../../database/helpers/openDB' +import type { AESJsonWebKey } from '../../../../modules/CryptoAlgorithm/interfaces/utils' assertEnvironment(Environment.ManifestBackground) //#region Type and utils diff --git a/packages/maskbook/src/database/migrate/create.user.group.for.old.users.ts b/packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/create.user.group.for.old.users.ts similarity index 60% rename from packages/maskbook/src/database/migrate/create.user.group.for.old.users.ts rename to packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/create.user.group.for.old.users.ts index c8302302affe..3cabb44abfc2 100644 --- a/packages/maskbook/src/database/migrate/create.user.group.for.old.users.ts +++ b/packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/create.user.group.for.old.users.ts @@ -1,7 +1,7 @@ -import { queryUserGroupDatabase } from '../group' -import { GroupIdentifier } from '../type' -import { createDefaultFriendsGroup } from '../helpers/group' -import { queryMyProfiles } from '../../extension/background-script/IdentityService' +import { queryUserGroupDatabase } from '../../../../database/group' +import { GroupIdentifier } from '@masknet/shared' +import { createDefaultFriendsGroup } from '../../UserGroupService' +import { queryMyProfiles } from '../../IdentityService' /** * If an identity has no default user group, create one diff --git a/packages/maskbook/src/database/migrate/fix.qr.private.key.bug.ts b/packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/fix.qr.private.key.bug.ts similarity index 77% rename from packages/maskbook/src/database/migrate/fix.qr.private.key.bug.ts rename to packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/fix.qr.private.key.bug.ts index c5f2531576a1..f5d24a82981e 100644 --- a/packages/maskbook/src/database/migrate/fix.qr.private.key.bug.ts +++ b/packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/fix.qr.private.key.bug.ts @@ -1,6 +1,6 @@ -import { setStorage } from '../../utils/browser.storage' -import { queryPersonasWithPrivateKey } from '../Persona/Persona.db' -import { deletePersona } from '../Persona/helpers' +import { setStorage } from '../../../../utils/browser.storage' +import { queryPersonasWithPrivateKey } from '../../../../database/Persona/Persona.db' +import { deletePersona } from '../../../../database/Persona/helpers' /** * There is a bug that when use QR to import key, the private ket lost its secret. diff --git a/packages/maskbook/src/database/migrate/index.ts b/packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/index.ts similarity index 76% rename from packages/maskbook/src/database/migrate/index.ts rename to packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/index.ts index 4bb2a78ed884..8903fef71ee2 100644 --- a/packages/maskbook/src/database/migrate/index.ts +++ b/packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/index.ts @@ -1,8 +1,7 @@ import createUserGroupForOldUsers from './create.user.group.for.old.users' import fixQrPrivateKeyBug from './fix.qr.private.key.bug' import migratePeopleToPersona from './people.to.persona' -import { untilDocumentReady } from '../../utils/dom' -import { sideEffect } from '../../utils/side-effects' +import { sideEffect, untilDocumentReady } from '../../../../utils' sideEffect.then(untilDocumentReady).then(run) function run() { diff --git a/packages/maskbook/src/database/migrate/people.to.persona.ts b/packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/people.to.persona.ts similarity index 93% rename from packages/maskbook/src/database/migrate/people.to.persona.ts rename to packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/people.to.persona.ts index 0f0060b442f7..25500c59b6fb 100644 --- a/packages/maskbook/src/database/migrate/people.to.persona.ts +++ b/packages/maskbook/src/extension/background-script/Jobs/MigrateDatabase/people.to.persona.ts @@ -7,11 +7,10 @@ import { PersonRecordPublic, PersonRecordPublicPrivate, } from './_deprecated_people_db' -import * as persona from '../Persona/Persona.db' -import { ECKeyIdentifier, ECKeyIdentifierFromCryptoKey, ProfileIdentifier } from '../type' -import { IdentifierMap } from '../IdentifierMap' -import { CryptoKeyToJsonWebKey } from '../../utils/type-transform/CryptoKey-JsonWebKey' -import type { AESJsonWebKey } from '../../modules/CryptoAlgorithm/interfaces/utils' +import * as persona from '../../../../database/Persona/Persona.db' +import { ECKeyIdentifier, ProfileIdentifier, IdentifierMap, AESJsonWebKey } from '@masknet/shared' +import { ECKeyIdentifierFromCryptoKey } from '../../../../database/type' +import { CryptoKeyToJsonWebKey } from '../../../../utils/type-transform/CryptoKey-JsonWebKey' export default async function migratePeopleToPersona() { const myIDs = await getMyIdentitiesDB() diff --git a/packages/maskbook/src/extension/background-script/Jobs/index.ts b/packages/maskbook/src/extension/background-script/Jobs/index.ts index 73c4370087ba..5e6964583136 100644 --- a/packages/maskbook/src/extension/background-script/Jobs/index.ts +++ b/packages/maskbook/src/extension/background-script/Jobs/index.ts @@ -1,3 +1,4 @@ import './index_hmr' // Add and execute your non-cancelable jobs here. It won't compatible to HMR. import './PrintBuildFlags' +import './MigrateDatabase' diff --git a/packages/maskbook/src/extension/background-script/Jobs/index_hmr.ts b/packages/maskbook/src/extension/background-script/Jobs/index_hmr.ts index 00e601fd8c3b..1f39957cb2b7 100644 --- a/packages/maskbook/src/extension/background-script/Jobs/index_hmr.ts +++ b/packages/maskbook/src/extension/background-script/Jobs/index_hmr.ts @@ -4,6 +4,7 @@ import * as InjectContentScripts from './InjectContentScripts' import * as NewInstalled from './NewInstalled' import * as PluginWorker from './StartPluginWorker' import * as SettingListeners from './SettingListeners' +import * as CleanupProfileDatabase from './DatabaseCleanup/CleanProfileAndAvatar' type CancelableJob = { default: (signal: AbortSignal) => void } const CancelableJobs: CancelableJob[] = [ @@ -13,6 +14,7 @@ const CancelableJobs: CancelableJob[] = [ IsolatedDashboardBridge, PluginWorker, SettingListeners, + CleanupProfileDatabase, ] const abort = new AbortController()