diff --git a/apps/core/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/index.tsx b/apps/core/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/index.tsx index 645945fefc..48f9dbe003 100644 --- a/apps/core/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/index.tsx +++ b/apps/core/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/index.tsx @@ -6,7 +6,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { ArrowRightSmallIcon } from '@blocksuite/icons'; import { useCallback, useState } from 'react'; -import type { WorkspaceSettingDetailProps } from '../index'; +import type { WorkspaceSettingDetailProps } from '../types'; import { WorkspaceDeleteModal } from './delete'; export interface DeleteLeaveWorkspaceProps extends WorkspaceSettingDetailProps { diff --git a/apps/core/src/components/affine/new-workspace-setting-detail/index.tsx b/apps/core/src/components/affine/new-workspace-setting-detail/index.tsx index ae19c0ff99..6732ce7b0e 100644 --- a/apps/core/src/components/affine/new-workspace-setting-detail/index.tsx +++ b/apps/core/src/components/affine/new-workspace-setting-detail/index.tsx @@ -3,10 +3,6 @@ import { SettingRow, SettingWrapper, } from '@affine/component/setting-components'; -import type { - WorkspaceFlavour, - WorkspaceRegistry, -} from '@affine/env/workspace'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name'; import { useMemo } from 'react'; @@ -19,22 +15,7 @@ import { MembersPanel } from './members'; import { ProfilePanel } from './profile'; import { PublishPanel } from './publish'; import { StoragePanel } from './storage'; - -export interface WorkspaceSettingDetailProps { - workspaceId: string; - isOwner: boolean; - onDeleteLocalWorkspace: () => void; - onDeleteCloudWorkspace: () => void; - onLeaveWorkspace: () => void; - onTransferWorkspace: < - From extends WorkspaceFlavour, - To extends WorkspaceFlavour, - >( - from: From, - to: To, - workspace: WorkspaceRegistry[From] - ) => void; -} +import type { WorkspaceSettingDetailProps } from './types'; export const WorkspaceSettingDetail = (props: WorkspaceSettingDetailProps) => { const { workspaceId } = props; diff --git a/apps/core/src/components/affine/new-workspace-setting-detail/labels.tsx b/apps/core/src/components/affine/new-workspace-setting-detail/labels.tsx index f6005c3f23..285b525c61 100644 --- a/apps/core/src/components/affine/new-workspace-setting-detail/labels.tsx +++ b/apps/core/src/components/affine/new-workspace-setting-detail/labels.tsx @@ -1,8 +1,8 @@ import type { AffineOfficialWorkspace } from '@affine/env/workspace'; import { useMemo } from 'react'; -import { type WorkspaceSettingDetailProps } from './index'; import * as style from './style.css'; +import type { WorkspaceSettingDetailProps } from './types'; export interface LabelsPanelProps extends WorkspaceSettingDetailProps { workspace: AffineOfficialWorkspace; diff --git a/apps/core/src/components/affine/new-workspace-setting-detail/members.tsx b/apps/core/src/components/affine/new-workspace-setting-detail/members.tsx index 06fa051845..3f105aeb6b 100644 --- a/apps/core/src/components/affine/new-workspace-setting-detail/members.tsx +++ b/apps/core/src/components/affine/new-workspace-setting-detail/members.tsx @@ -25,8 +25,8 @@ import { useInviteMember } from '../../../hooks/affine/use-invite-member'; import { type Member, useMembers } from '../../../hooks/affine/use-members'; import { useRevokeMemberPermission } from '../../../hooks/affine/use-revoke-member-permission'; import { AnyErrorBoundary } from '../any-error-boundary'; -import { type WorkspaceSettingDetailProps } from './index'; import * as style from './style.css'; +import type { WorkspaceSettingDetailProps } from './types'; export interface MembersPanelProps extends WorkspaceSettingDetailProps { workspace: AffineOfficialWorkspace; diff --git a/apps/core/src/components/affine/new-workspace-setting-detail/profile.tsx b/apps/core/src/components/affine/new-workspace-setting-detail/profile.tsx index 700ffc35fe..a27934e385 100644 --- a/apps/core/src/components/affine/new-workspace-setting-detail/profile.tsx +++ b/apps/core/src/components/affine/new-workspace-setting-detail/profile.tsx @@ -15,8 +15,8 @@ import { } from 'react'; import { Upload } from '../../pure/file-upload'; -import { type WorkspaceSettingDetailProps } from './index'; import * as style from './style.css'; +import type { WorkspaceSettingDetailProps } from './types'; export interface ProfilePanelProps extends WorkspaceSettingDetailProps { workspace: AffineOfficialWorkspace; diff --git a/apps/core/src/components/affine/new-workspace-setting-detail/publish.tsx b/apps/core/src/components/affine/new-workspace-setting-detail/publish.tsx index 4742be7706..4b632e4232 100644 --- a/apps/core/src/components/affine/new-workspace-setting-detail/publish.tsx +++ b/apps/core/src/components/affine/new-workspace-setting-detail/publish.tsx @@ -17,8 +17,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { toast } from '../../../utils'; import { EnableAffineCloudModal } from '../enable-affine-cloud-modal'; import { TmpDisableAffineCloudModal } from '../tmp-disable-affine-cloud-modal'; -import type { WorkspaceSettingDetailProps } from './index'; import * as style from './style.css'; +import type { WorkspaceSettingDetailProps } from './types'; export interface PublishPanelProps extends Omit { diff --git a/apps/core/src/components/affine/new-workspace-setting-detail/types.ts b/apps/core/src/components/affine/new-workspace-setting-detail/types.ts new file mode 100644 index 0000000000..a090bc756a --- /dev/null +++ b/apps/core/src/components/affine/new-workspace-setting-detail/types.ts @@ -0,0 +1,20 @@ +import type { + WorkspaceFlavour, + WorkspaceRegistry, +} from '@affine/env/workspace'; + +export interface WorkspaceSettingDetailProps { + workspaceId: string; + isOwner: boolean; + onDeleteLocalWorkspace: () => void; + onDeleteCloudWorkspace: () => void; + onLeaveWorkspace: () => void; + onTransferWorkspace: < + From extends WorkspaceFlavour, + To extends WorkspaceFlavour, + >( + from: From, + to: To, + workspace: WorkspaceRegistry[From] + ) => void; +} diff --git a/apps/electron/src/helper/db/ensure-db.ts b/apps/electron/src/helper/db/ensure-db.ts index dcd099d9ab..ae8ee92a23 100644 --- a/apps/electron/src/helper/db/ensure-db.ts +++ b/apps/electron/src/helper/db/ensure-db.ts @@ -25,7 +25,8 @@ import { } from 'rxjs/operators'; import { logger } from '../logger'; -import { getWorkspaceMeta, workspaceSubjects } from '../workspace'; +import { getWorkspaceMeta } from '../workspace/meta'; +import { workspaceSubjects } from '../workspace/subjects'; import { SecondaryWorkspaceSQLiteDB } from './secondary-db'; import type { WorkspaceSQLiteDB } from './workspace-db-adapter'; import { openWorkspaceDatabase } from './workspace-db-adapter'; diff --git a/apps/electron/src/helper/db/secondary-db.ts b/apps/electron/src/helper/db/secondary-db.ts index 265aca105f..42686d0db8 100644 --- a/apps/electron/src/helper/db/secondary-db.ts +++ b/apps/electron/src/helper/db/secondary-db.ts @@ -6,7 +6,7 @@ import { applyUpdate, Doc as YDoc } from 'yjs'; import { logger } from '../logger'; import type { YOrigin } from '../type'; -import { getWorkspaceMeta } from '../workspace'; +import { getWorkspaceMeta } from '../workspace/meta'; import { BaseSQLiteAdapter } from './base-db-adapter'; import type { WorkspaceSQLiteDB } from './workspace-db-adapter'; diff --git a/apps/electron/src/helper/db/workspace-db-adapter.ts b/apps/electron/src/helper/db/workspace-db-adapter.ts index 7ec8edc4af..ae404fffac 100644 --- a/apps/electron/src/helper/db/workspace-db-adapter.ts +++ b/apps/electron/src/helper/db/workspace-db-adapter.ts @@ -5,7 +5,7 @@ import { applyUpdate, Doc as YDoc, encodeStateAsUpdate } from 'yjs'; import { logger } from '../logger'; import type { YOrigin } from '../type'; -import { getWorkspaceMeta } from '../workspace'; +import { getWorkspaceMeta } from '../workspace/meta'; import { BaseSQLiteAdapter } from './base-db-adapter'; import { dbSubjects } from './subjects'; diff --git a/apps/electron/src/helper/dialog/dialog.ts b/apps/electron/src/helper/dialog/dialog.ts index f65df39d5a..3c92d41183 100644 --- a/apps/electron/src/helper/dialog/dialog.ts +++ b/apps/electron/src/helper/dialog/dialog.ts @@ -21,13 +21,12 @@ import { import type { WorkspaceSQLiteDB } from '../db/workspace-db-adapter'; import { logger } from '../logger'; import { mainRPC } from '../main-rpc'; +import { listWorkspaces, storeWorkspaceMeta } from '../workspace'; import { getWorkspaceDBPath, getWorkspaceMeta, getWorkspacesBasePath, - listWorkspaces, - storeWorkspaceMeta, -} from '../workspace'; +} from '../workspace/meta'; // NOTE: // we are using native dialogs because HTML dialogs do not give full file paths diff --git a/apps/electron/src/helper/exposed.ts b/apps/electron/src/helper/exposed.ts index a6f6da3ec7..63ac85e39f 100644 --- a/apps/electron/src/helper/exposed.ts +++ b/apps/electron/src/helper/exposed.ts @@ -6,6 +6,7 @@ import type { import { dbEvents, dbHandlers } from './db'; import { dialogHandlers } from './dialog'; +import { provideExposed } from './provide'; import { workspaceEvents, workspaceHandlers } from './workspace'; type AllHandlers = { @@ -25,7 +26,7 @@ export const events = { workspace: workspaceEvents, }; -export const getExposedMeta = () => { +const getExposedMeta = () => { const handlersMeta = Object.entries(handlers).map( ([namespace, namespaceHandlers]) => { return [namespace, Object.keys(namespaceHandlers)] as [string, string[]]; @@ -43,3 +44,5 @@ export const getExposedMeta = () => { events: eventsMeta, }; }; + +provideExposed(getExposedMeta()); diff --git a/apps/electron/src/helper/main-rpc.ts b/apps/electron/src/helper/main-rpc.ts index 3ffde2aea5..12e3623360 100644 --- a/apps/electron/src/helper/main-rpc.ts +++ b/apps/electron/src/helper/main-rpc.ts @@ -1,13 +1,17 @@ +import { assertExists } from '@blocksuite/global/utils'; import type { HelperToMain, MainToHelper, } from '@toeverything/infra/preload/electron'; import { AsyncCall } from 'async-call-rpc'; -import { getExposedMeta } from './exposed'; +import { exposed } from './provide'; const helperToMainServer: HelperToMain = { - getMeta: () => getExposedMeta(), + getMeta: () => { + assertExists(exposed); + return exposed; + }, }; export const mainRPC = AsyncCall(helperToMainServer, { diff --git a/apps/electron/src/helper/provide.ts b/apps/electron/src/helper/provide.ts new file mode 100644 index 0000000000..6c7b4e9b30 --- /dev/null +++ b/apps/electron/src/helper/provide.ts @@ -0,0 +1,11 @@ +import type { ExposedMeta } from '@toeverything/infra/preload/electron'; + +/** + * A naive DI implementation to get rid of circular dependency. + */ + +export let exposed: ExposedMeta | undefined; + +export const provideExposed = (exposedMeta: ExposedMeta) => { + exposed = exposedMeta; +}; diff --git a/apps/electron/src/helper/workspace/__tests__/handlers.spec.ts b/apps/electron/src/helper/workspace/__tests__/handlers.spec.ts index c1ad11525a..94251dd6c4 100644 --- a/apps/electron/src/helper/workspace/__tests__/handlers.spec.ts +++ b/apps/electron/src/helper/workspace/__tests__/handlers.spec.ts @@ -74,7 +74,7 @@ describe('delete workspace', () => { describe('getWorkspaceMeta', () => { test('can get meta', async () => { - const { getWorkspaceMeta } = await import('../handlers'); + const { getWorkspaceMeta } = await import('../meta'); const workspaceId = v4(); const workspacePath = path.join(appDataPath, 'workspaces', workspaceId); const meta = { @@ -86,7 +86,7 @@ describe('getWorkspaceMeta', () => { }); test('can create meta if not exists', async () => { - const { getWorkspaceMeta } = await import('../handlers'); + const { getWorkspaceMeta } = await import('../meta'); const workspaceId = v4(); const workspacePath = path.join(appDataPath, 'workspaces', workspaceId); await fs.ensureDir(workspacePath); @@ -100,7 +100,7 @@ describe('getWorkspaceMeta', () => { }); test('can migrate meta if db file is a link', async () => { - const { getWorkspaceMeta } = await import('../handlers'); + const { getWorkspaceMeta } = await import('../meta'); const workspaceId = v4(); const workspacePath = path.join(appDataPath, 'workspaces', workspaceId); await fs.ensureDir(workspacePath); diff --git a/apps/electron/src/helper/workspace/handlers.ts b/apps/electron/src/helper/workspace/handlers.ts index 44a6ffaf0f..52c4a0f400 100644 --- a/apps/electron/src/helper/workspace/handlers.ts +++ b/apps/electron/src/helper/workspace/handlers.ts @@ -4,20 +4,15 @@ import fs from 'fs-extra'; import { ensureSQLiteDB } from '../db/ensure-db'; import { logger } from '../logger'; -import { mainRPC } from '../main-rpc'; import type { WorkspaceMeta } from '../type'; +import { + getDeletedWorkspacesBasePath, + getWorkspaceBasePath, + getWorkspaceMeta, + getWorkspacesBasePath, +} from './meta'; import { workspaceSubjects } from './subjects'; -let _appDataPath = ''; - -async function getAppDataPath() { - if (_appDataPath) { - return _appDataPath; - } - _appDataPath = await mainRPC.getPath('sessionData'); - return _appDataPath; -} - export async function listWorkspaces(): Promise< [workspaceId: string, meta: WorkspaceMeta][] > { @@ -58,67 +53,6 @@ export async function deleteWorkspace(id: string) { } } -export async function getWorkspacesBasePath() { - return path.join(await getAppDataPath(), 'workspaces'); -} - -export async function getWorkspaceBasePath(workspaceId: string) { - return path.join(await getAppDataPath(), 'workspaces', workspaceId); -} - -async function getDeletedWorkspacesBasePath() { - return path.join(await getAppDataPath(), 'deleted-workspaces'); -} - -export async function getWorkspaceDBPath(workspaceId: string) { - return path.join(await getWorkspaceBasePath(workspaceId), 'storage.db'); -} - -export async function getWorkspaceMetaPath(workspaceId: string) { - return path.join(await getWorkspaceBasePath(workspaceId), 'meta.json'); -} - -/** - * Get workspace meta, create one if not exists - * This function will also migrate the workspace if needed - */ -export async function getWorkspaceMeta( - workspaceId: string -): Promise { - try { - const basePath = await getWorkspaceBasePath(workspaceId); - const metaPath = await getWorkspaceMetaPath(workspaceId); - if (!(await fs.exists(metaPath))) { - // since not meta is found, we will migrate symlinked db file if needed - await fs.ensureDir(basePath); - const dbPath = await getWorkspaceDBPath(workspaceId); - - // todo: remove this after migration (in stable version) - const realDBPath = (await fs.exists(dbPath)) - ? await fs.realpath(dbPath) - : dbPath; - const isLink = realDBPath !== dbPath; - if (isLink) { - await fs.copy(realDBPath, dbPath); - } - // create one if not exists - const meta = { - id: workspaceId, - mainDBPath: dbPath, - secondaryDBPath: isLink ? realDBPath : undefined, - }; - await fs.writeJSON(metaPath, meta); - return meta; - } else { - const meta = await fs.readJSON(metaPath); - return meta; - } - } catch (err) { - logger.error('getWorkspaceMeta failed', err); - throw err; - } -} - export async function storeWorkspaceMeta( workspaceId: string, meta: Partial diff --git a/apps/electron/src/helper/workspace/index.ts b/apps/electron/src/helper/workspace/index.ts index c36e86a6d6..6acf6944f6 100644 --- a/apps/electron/src/helper/workspace/index.ts +++ b/apps/electron/src/helper/workspace/index.ts @@ -1,5 +1,6 @@ import type { MainEventRegister, WorkspaceMeta } from '../type'; -import { deleteWorkspace, getWorkspaceMeta, listWorkspaces } from './handlers'; +import { deleteWorkspace, listWorkspaces } from './handlers'; +import { getWorkspaceMeta } from './meta'; import { workspaceSubjects } from './subjects'; export * from './handlers'; diff --git a/apps/electron/src/helper/workspace/meta.ts b/apps/electron/src/helper/workspace/meta.ts new file mode 100644 index 0000000000..ddf13c0088 --- /dev/null +++ b/apps/electron/src/helper/workspace/meta.ts @@ -0,0 +1,78 @@ +import path from 'node:path'; + +import fs from 'fs-extra'; + +import { logger } from '../logger'; +import { mainRPC } from '../main-rpc'; +import type { WorkspaceMeta } from '../type'; + +let _appDataPath = ''; + +export async function getAppDataPath() { + if (_appDataPath) { + return _appDataPath; + } + _appDataPath = await mainRPC.getPath('sessionData'); + return _appDataPath; +} + +export async function getWorkspacesBasePath() { + return path.join(await getAppDataPath(), 'workspaces'); +} + +export async function getWorkspaceBasePath(workspaceId: string) { + return path.join(await getAppDataPath(), 'workspaces', workspaceId); +} + +export async function getDeletedWorkspacesBasePath() { + return path.join(await getAppDataPath(), 'deleted-workspaces'); +} + +export async function getWorkspaceDBPath(workspaceId: string) { + return path.join(await getWorkspaceBasePath(workspaceId), 'storage.db'); +} + +export async function getWorkspaceMetaPath(workspaceId: string) { + return path.join(await getWorkspaceBasePath(workspaceId), 'meta.json'); +} + +/** + * Get workspace meta, create one if not exists + * This function will also migrate the workspace if needed + */ +export async function getWorkspaceMeta( + workspaceId: string +): Promise { + try { + const basePath = await getWorkspaceBasePath(workspaceId); + const metaPath = await getWorkspaceMetaPath(workspaceId); + if (!(await fs.exists(metaPath))) { + // since not meta is found, we will migrate symlinked db file if needed + await fs.ensureDir(basePath); + const dbPath = await getWorkspaceDBPath(workspaceId); + + // todo: remove this after migration (in stable version) + const realDBPath = (await fs.exists(dbPath)) + ? await fs.realpath(dbPath) + : dbPath; + const isLink = realDBPath !== dbPath; + if (isLink) { + await fs.copy(realDBPath, dbPath); + } + // create one if not exists + const meta = { + id: workspaceId, + mainDBPath: dbPath, + secondaryDBPath: isLink ? realDBPath : undefined, + }; + await fs.writeJSON(metaPath, meta); + return meta; + } else { + const meta = await fs.readJSON(metaPath); + return meta; + } + } catch (err) { + logger.error('getWorkspaceMeta failed', err); + throw err; + } +} diff --git a/packages/component/src/components/page-list/all-page.tsx b/packages/component/src/components/page-list/all-page.tsx index 6b2aee3935..0910dc2042 100644 --- a/packages/component/src/components/page-list/all-page.tsx +++ b/packages/component/src/components/page-list/all-page.tsx @@ -1,7 +1,3 @@ -import { - CollectionBar, - type CollectionsAtom, -} from '@affine/component/page-list'; import { DEFAULT_SORT_KEY } from '@affine/env/constant'; import type { PropertiesMeta } from '@affine/env/filter'; import type { GetPageInfoById } from '@affine/env/page-info'; @@ -28,8 +24,10 @@ import { AllPageListMobileView, TrashListMobileView } from './mobile'; import { TrashOperationCell } from './operation-cell'; import { StyledTableContainer } from './styles'; import type { ListData, PageListProps, TrashListData } from './type'; +import type { CollectionsAtom } from './use-collection-manager'; import { useSorter } from './use-sorter'; import { formatDate, useIsSmallDevices } from './utils'; +import { CollectionBar } from './view/collection-bar'; interface AllPagesHeadProps { isPublicWorkspace: boolean; diff --git a/packages/component/src/components/page-list/view/collection-bar.tsx b/packages/component/src/components/page-list/view/collection-bar.tsx index b2c8257102..98870d2a59 100644 --- a/packages/component/src/components/page-list/view/collection-bar.tsx +++ b/packages/component/src/components/page-list/view/collection-bar.tsx @@ -1,7 +1,3 @@ -import { - type CollectionsAtom, - EditCollectionModel, -} from '@affine/component/page-list'; import type { PropertiesMeta } from '@affine/env/filter'; import type { GetPageInfoById } from '@affine/env/page-info'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; @@ -11,8 +7,12 @@ import { Tooltip } from '@toeverything/components/tooltip'; import clsx from 'clsx'; import { useCallback, useState } from 'react'; -import { useCollectionManager } from '../use-collection-manager'; +import { + type CollectionsAtom, + useCollectionManager, +} from '../use-collection-manager'; import * as styles from './collection-bar.css'; +import { EditCollectionModel } from './create-collection'; import { useActions } from './use-action'; interface CollectionBarProps { diff --git a/packages/component/src/components/page-list/view/collection-list.tsx b/packages/component/src/components/page-list/view/collection-list.tsx index c04bf1c44c..c3afc036e2 100644 --- a/packages/component/src/components/page-list/view/collection-list.tsx +++ b/packages/component/src/components/page-list/view/collection-list.tsx @@ -1,5 +1,4 @@ import { FlexWrapper } from '@affine/component'; -import { EditCollectionModel } from '@affine/component/page-list'; import type { Collection, Filter } from '@affine/env/filter'; import type { PropertiesMeta } from '@affine/env/filter'; import type { GetPageInfoById } from '@affine/env/page-info'; @@ -15,6 +14,7 @@ import { useCallback, useRef, useState } from 'react'; import { CreateFilterMenu } from '../filter/vars'; import type { useCollectionManager } from '../use-collection-manager'; import * as styles from './collection-list.css'; +import { EditCollectionModel } from './create-collection'; import { useActions } from './use-action'; const CollectionOption = ({