mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
refactor(infra): migrate to new infra (#5565)
This commit is contained in:
@@ -24,7 +24,6 @@
|
||||
"@affine/graphql": "workspace:*",
|
||||
"@affine/i18n": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/workspace": "workspace:*",
|
||||
"@affine/workspace-impl": "workspace:*",
|
||||
"@blocksuite/block-std": "0.12.0-nightly-202401290223-b6302df",
|
||||
"@blocksuite/blocks": "0.12.0-nightly-202401290223-b6302df",
|
||||
|
||||
@@ -8,14 +8,17 @@ import { WorkspaceFallback } from '@affine/component/workspace';
|
||||
import { createI18n, setUpLanguage } from '@affine/i18n';
|
||||
import { CacheProvider } from '@emotion/react';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { ServiceCollection } from '@toeverything/infra/di';
|
||||
import type { PropsWithChildren, ReactElement } from 'react';
|
||||
import { lazy, memo, Suspense } from 'react';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
|
||||
import { GlobalScopeProvider } from './modules/infra-web/global-scope';
|
||||
import { CloudSessionProvider } from './providers/session-provider';
|
||||
import { router } from './router';
|
||||
import { performanceLogger, performanceRenderLogger } from './shared';
|
||||
import createEmotionCache from './utils/create-emotion-cache';
|
||||
import { configureWebServices } from './web';
|
||||
|
||||
const performanceI18nLogger = performanceLogger.namespace('i18n');
|
||||
const cache = createEmotionCache();
|
||||
@@ -52,6 +55,10 @@ async function loadLanguage() {
|
||||
|
||||
let languageLoadingPromise: Promise<void> | null = null;
|
||||
|
||||
const services = new ServiceCollection();
|
||||
configureWebServices(services);
|
||||
const serviceProvider = services.provider();
|
||||
|
||||
export const App = memo(function App() {
|
||||
performanceRenderLogger.info('App');
|
||||
|
||||
@@ -60,20 +67,26 @@ export const App = memo(function App() {
|
||||
}
|
||||
|
||||
return (
|
||||
<CacheProvider value={cache}>
|
||||
<AffineContext store={getCurrentStore()}>
|
||||
<CloudSessionProvider>
|
||||
<DebugProvider>
|
||||
<GlobalLoading />
|
||||
{runtimeConfig.enableNotificationCenter && <NotificationCenter />}
|
||||
<RouterProvider
|
||||
fallbackElement={<WorkspaceFallback key="RouterFallback" />}
|
||||
router={router}
|
||||
future={future}
|
||||
/>
|
||||
</DebugProvider>
|
||||
</CloudSessionProvider>
|
||||
</AffineContext>
|
||||
</CacheProvider>
|
||||
<Suspense>
|
||||
<GlobalScopeProvider provider={serviceProvider}>
|
||||
<CacheProvider value={cache}>
|
||||
<AffineContext store={getCurrentStore()}>
|
||||
<CloudSessionProvider>
|
||||
<DebugProvider>
|
||||
<GlobalLoading />
|
||||
{runtimeConfig.enableNotificationCenter && (
|
||||
<NotificationCenter />
|
||||
)}
|
||||
<RouterProvider
|
||||
fallbackElement={<WorkspaceFallback key="RouterFallback" />}
|
||||
router={router}
|
||||
future={future}
|
||||
/>
|
||||
</DebugProvider>
|
||||
</CloudSessionProvider>
|
||||
</AffineContext>
|
||||
</CacheProvider>
|
||||
</GlobalScopeProvider>
|
||||
</Suspense>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
import {
|
||||
currentWorkspaceAtom,
|
||||
waitForCurrentWorkspaceAtom,
|
||||
} from '@affine/core/modules/workspace';
|
||||
import type { Collection, DeprecatedCollection } from '@affine/env/filter';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import { type DBSchema, openDB } from 'idb';
|
||||
import { atom } from 'jotai';
|
||||
import { atomWithObservable } from 'jotai/utils';
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
import type {
|
||||
CollectionsCRUD,
|
||||
CollectionsCRUDAtom,
|
||||
} from '../components/page-list';
|
||||
import { getUserSetting } from '../utils/user-setting';
|
||||
import { getWorkspaceSetting } from '../utils/workspace-setting';
|
||||
import { sessionAtom } from './cloud-user';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export interface PageCollectionDBV1 extends DBSchema {
|
||||
view: {
|
||||
key: DeprecatedCollection['id'];
|
||||
value: DeprecatedCollection;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export interface StorageCRUD<Value> {
|
||||
get: (key: string) => Promise<Value | null>;
|
||||
set: (key: string, value: Value) => Promise<string>;
|
||||
delete: (key: string) => Promise<void>;
|
||||
list: () => Promise<string[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
const collectionDBAtom = atom(
|
||||
openDB<PageCollectionDBV1>('page-view', 1, {
|
||||
upgrade(database) {
|
||||
database.createObjectStore('view', {
|
||||
keyPath: 'id',
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
const localCollectionCRUDAtom = atom(get => ({
|
||||
get: async (key: string) => {
|
||||
const db = await get(collectionDBAtom);
|
||||
const t = db.transaction('view').objectStore('view');
|
||||
return (await t.get(key)) ?? null;
|
||||
},
|
||||
set: async (key: string, value: DeprecatedCollection) => {
|
||||
const db = await get(collectionDBAtom);
|
||||
const t = db.transaction('view', 'readwrite').objectStore('view');
|
||||
await t.put(value);
|
||||
return key;
|
||||
},
|
||||
delete: async (key: string) => {
|
||||
const db = await get(collectionDBAtom);
|
||||
const t = db.transaction('view', 'readwrite').objectStore('view');
|
||||
await t.delete(key);
|
||||
},
|
||||
list: async () => {
|
||||
const db = await get(collectionDBAtom);
|
||||
const t = db.transaction('view').objectStore('view');
|
||||
return t.getAllKeys();
|
||||
},
|
||||
}));
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
const getCollections = async (
|
||||
storage: StorageCRUD<DeprecatedCollection>
|
||||
): Promise<DeprecatedCollection[]> => {
|
||||
return storage
|
||||
.list()
|
||||
.then(async keys => {
|
||||
return await Promise.all(keys.map(key => storage.get(key))).then(v =>
|
||||
v.filter((v): v is DeprecatedCollection => v !== null)
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to load collections', error);
|
||||
return [];
|
||||
});
|
||||
};
|
||||
type BaseCollectionsDataType = {
|
||||
loading: boolean;
|
||||
collections: Collection[];
|
||||
};
|
||||
export const pageCollectionBaseAtom =
|
||||
atomWithObservable<BaseCollectionsDataType>(
|
||||
get => {
|
||||
const currentWorkspace = get(currentWorkspaceAtom);
|
||||
if (!currentWorkspace) {
|
||||
return of({ loading: true, collections: [] });
|
||||
}
|
||||
|
||||
const session = get(sessionAtom);
|
||||
const userId = session?.data?.user.id ?? null;
|
||||
const migrateCollectionsFromIdbData = async (
|
||||
workspace: Workspace
|
||||
): Promise<Collection[]> => {
|
||||
const localCRUD = get(localCollectionCRUDAtom);
|
||||
const collections = await getCollections(localCRUD);
|
||||
const result = collections.filter(v => v.workspaceId === workspace.id);
|
||||
Promise.all(
|
||||
result.map(collection => {
|
||||
return localCRUD.delete(collection.id);
|
||||
})
|
||||
).catch(error => {
|
||||
console.error('Failed to delete collections from indexeddb', error);
|
||||
});
|
||||
return result.map(v => {
|
||||
return {
|
||||
id: v.id,
|
||||
name: v.name,
|
||||
filterList: v.filterList,
|
||||
allowList: v.allowList ?? [],
|
||||
};
|
||||
});
|
||||
};
|
||||
const migrateCollectionsFromUserData = async (
|
||||
workspace: Workspace
|
||||
): Promise<Collection[]> => {
|
||||
if (userId == null) {
|
||||
return [];
|
||||
}
|
||||
const userSetting = getUserSetting(workspace, userId);
|
||||
await userSetting.loaded;
|
||||
const view = userSetting.view;
|
||||
if (view) {
|
||||
const collections: Omit<DeprecatedCollection, 'workspaceId'>[] = [
|
||||
...view.values(),
|
||||
];
|
||||
//delete collections
|
||||
view.clear();
|
||||
return collections.map(v => {
|
||||
return {
|
||||
id: v.id,
|
||||
name: v.name,
|
||||
filterList: v.filterList,
|
||||
allowList: v.allowList ?? [],
|
||||
};
|
||||
});
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
return new Observable<BaseCollectionsDataType>(subscriber => {
|
||||
const group = new DisposableGroup();
|
||||
const workspaceSetting = getWorkspaceSetting(
|
||||
currentWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
migrateCollectionsFromIdbData(currentWorkspace.blockSuiteWorkspace)
|
||||
.then(collections => {
|
||||
if (collections.length) {
|
||||
workspaceSetting.addCollection(...collections);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
migrateCollectionsFromUserData(currentWorkspace.blockSuiteWorkspace)
|
||||
.then(collections => {
|
||||
if (collections.length) {
|
||||
workspaceSetting.addCollection(...collections);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
subscriber.next({
|
||||
loading: false,
|
||||
collections: workspaceSetting.collections,
|
||||
});
|
||||
if (group.disposed) {
|
||||
return;
|
||||
}
|
||||
const fn = () => {
|
||||
subscriber.next({
|
||||
loading: false,
|
||||
collections: workspaceSetting.collections,
|
||||
});
|
||||
};
|
||||
workspaceSetting.setting.observeDeep(fn);
|
||||
group.add(() => {
|
||||
workspaceSetting.setting.unobserveDeep(fn);
|
||||
});
|
||||
|
||||
return () => {
|
||||
group.dispose();
|
||||
};
|
||||
});
|
||||
},
|
||||
{ initialValue: { loading: true, collections: [] } }
|
||||
);
|
||||
|
||||
export const collectionsCRUDAtom: CollectionsCRUDAtom = atom(async get => {
|
||||
const workspace = await get(waitForCurrentWorkspaceAtom);
|
||||
return {
|
||||
addCollection: (...collections) => {
|
||||
getWorkspaceSetting(workspace.blockSuiteWorkspace).addCollection(
|
||||
...collections
|
||||
);
|
||||
},
|
||||
collections: get(pageCollectionBaseAtom).collections,
|
||||
updateCollection: (id, updater) => {
|
||||
getWorkspaceSetting(workspace.blockSuiteWorkspace).updateCollection(
|
||||
id,
|
||||
updater
|
||||
);
|
||||
},
|
||||
deleteCollection: (info, ...ids) => {
|
||||
getWorkspaceSetting(workspace.blockSuiteWorkspace).deleteCollection(
|
||||
info,
|
||||
...ids
|
||||
);
|
||||
},
|
||||
} satisfies CollectionsCRUD;
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { DEFAULT_WORKSPACE_NAME } from '@affine/env/constant';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { workspaceManager } from '@affine/workspace-impl';
|
||||
import type { WorkspaceManager } from '@toeverything/infra';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import {
|
||||
buildShowcaseWorkspace,
|
||||
@@ -12,12 +12,12 @@ import { setPageModeAtom } from '../atoms';
|
||||
|
||||
const logger = new DebugLogger('affine:first-app-data');
|
||||
|
||||
export async function createFirstAppData() {
|
||||
export async function createFirstAppData(workspaceManager: WorkspaceManager) {
|
||||
if (localStorage.getItem('is-first-open') !== null) {
|
||||
return;
|
||||
}
|
||||
localStorage.setItem('is-first-open', 'false');
|
||||
const workspaceId = await workspaceManager.createWorkspace(
|
||||
const workspaceMetadata = await workspaceManager.createWorkspace(
|
||||
WorkspaceFlavour.LOCAL,
|
||||
async workspace => {
|
||||
workspace.meta.setName(DEFAULT_WORKSPACE_NAME);
|
||||
@@ -38,6 +38,6 @@ export async function createFirstAppData() {
|
||||
logger.debug('create first workspace');
|
||||
}
|
||||
);
|
||||
console.info('create first workspace', workspaceId);
|
||||
return workspaceId;
|
||||
console.info('create first workspace', workspaceMetadata);
|
||||
return workspaceMetadata;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
currentWorkspaceAtom,
|
||||
workspaceListAtom,
|
||||
} from '@affine/core/modules/workspace';
|
||||
import { WorkspaceListService } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useLiveData } from '@toeverything/infra/livedata';
|
||||
import { useAtomValue } from 'jotai/react';
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
|
||||
import { currentPageIdAtom } from '../../../../atoms/mode';
|
||||
import { CurrentWorkspaceService } from '../../../../modules/workspace/current-workspace';
|
||||
|
||||
export interface DumpInfoProps {
|
||||
error: any;
|
||||
@@ -14,8 +14,10 @@ export interface DumpInfoProps {
|
||||
|
||||
export const DumpInfo = (_props: DumpInfoProps) => {
|
||||
const location = useLocation();
|
||||
const workspaceList = useAtomValue(workspaceListAtom);
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom);
|
||||
const workspaceList = useService(WorkspaceListService);
|
||||
const currentWorkspace = useLiveData(
|
||||
useService(CurrentWorkspaceService).currentWorkspace
|
||||
);
|
||||
const currentPageId = useAtomValue(currentPageIdAtom);
|
||||
const path = location.pathname;
|
||||
const query = useParams();
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useLiveData } from '@toeverything/infra/livedata';
|
||||
import { Suspense, useEffect } from 'react';
|
||||
|
||||
import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status';
|
||||
import { useCurrentUser } from '../../../hooks/affine/use-current-user';
|
||||
import { CurrentWorkspaceService } from '../../../modules/workspace/current-workspace';
|
||||
|
||||
const SyncAwarenessInnerLoggedIn = () => {
|
||||
const currentUser = useCurrentUser();
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useLiveData(
|
||||
useService(CurrentWorkspaceService).currentWorkspace
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUser && currentWorkspace) {
|
||||
|
||||
@@ -5,18 +5,18 @@ import {
|
||||
Modal,
|
||||
} from '@affine/component/ui/modal';
|
||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||
import { workspaceManagerAtom } from '@affine/core/modules/workspace';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { apis } from '@affine/electron-api';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { _addLocalWorkspace } from '@affine/workspace-impl';
|
||||
import { WorkspaceManager } from '@toeverything/infra';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import {
|
||||
buildShowcaseWorkspace,
|
||||
initEmptyPage,
|
||||
} from '@toeverything/infra/blocksuite';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import type { KeyboardEvent } from 'react';
|
||||
import { useLayoutEffect } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
@@ -101,7 +101,7 @@ export const CreateWorkspaceModal = ({
|
||||
}: ModalProps) => {
|
||||
const [step, setStep] = useState<CreateWorkspaceStep>();
|
||||
const t = useAFFiNEI18N();
|
||||
const workspaceManager = useAtomValue(workspaceManagerAtom);
|
||||
const workspaceManager = useService(WorkspaceManager);
|
||||
|
||||
// todo: maybe refactor using xstate?
|
||||
useLayoutEffect(() => {
|
||||
@@ -148,7 +148,7 @@ export const CreateWorkspaceModal = ({
|
||||
async (name: string) => {
|
||||
// this will be the last step for web for now
|
||||
// fix me later
|
||||
const id = await workspaceManager.createWorkspace(
|
||||
const { id } = await workspaceManager.createWorkspace(
|
||||
WorkspaceFlavour.LOCAL,
|
||||
async workspace => {
|
||||
workspace.meta.setName(name);
|
||||
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
listHistoryQuery,
|
||||
recoverDocMutation,
|
||||
} from '@affine/graphql';
|
||||
import { globalBlockSuiteSchema } from '@affine/workspace';
|
||||
import { createAffineCloudBlobStorage } from '@affine/workspace-impl';
|
||||
import { AffineCloudBlobStorage } from '@affine/workspace-impl';
|
||||
import { assertEquals } from '@blocksuite/global/utils';
|
||||
import { Workspace } from '@blocksuite/store';
|
||||
import { globalBlockSuiteSchema } from '@toeverything/infra';
|
||||
import { revertUpdate } from '@toeverything/y-indexeddb';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import useSWRImmutable from 'swr/immutable';
|
||||
@@ -108,7 +108,7 @@ const workspaceMap = new Map<string, Workspace>();
|
||||
const getOrCreateShellWorkspace = (workspaceId: string) => {
|
||||
let workspace = workspaceMap.get(workspaceId);
|
||||
if (!workspace) {
|
||||
const blobStorage = createAffineCloudBlobStorage(workspaceId);
|
||||
const blobStorage = new AffineCloudBlobStorage(workspaceId);
|
||||
workspace = new Workspace({
|
||||
id: workspaceId,
|
||||
providerCreators: [],
|
||||
|
||||
@@ -7,14 +7,17 @@ import { useIsWorkspaceOwner } from '@affine/core/hooks/affine/use-is-workspace-
|
||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||
import { useBlockSuiteWorkspacePageTitle } from '@affine/core/hooks/use-block-suite-workspace-page-title';
|
||||
import { useWorkspaceQuota } from '@affine/core/hooks/use-workspace-quota';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { timestampToLocalTime } from '@affine/core/utils';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CloseIcon, ToggleCollapseIcon } from '@blocksuite/icons';
|
||||
import type { Page, Workspace } from '@blocksuite/store';
|
||||
import {
|
||||
type Page,
|
||||
type Workspace as BlockSuiteWorkspace,
|
||||
} from '@blocksuite/store';
|
||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||
import type { DialogContentProps } from '@radix-ui/react-dialog';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import {
|
||||
Fragment,
|
||||
@@ -29,6 +32,7 @@ import { encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
import { currentModeAtom } from '../../../atoms/mode';
|
||||
import { pageHistoryModalAtom } from '../../../atoms/page-history';
|
||||
import { timestampToLocalTime } from '../../../utils';
|
||||
import { BlockSuiteEditor } from '../../blocksuite/block-suite-editor';
|
||||
import { StyledEditorModeSwitch } from '../../blocksuite/block-suite-mode-switch/style';
|
||||
import {
|
||||
@@ -48,7 +52,7 @@ import * as styles from './styles.css';
|
||||
export interface PageHistoryModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
workspace: Workspace;
|
||||
workspace: BlockSuiteWorkspace;
|
||||
pageId: string;
|
||||
}
|
||||
|
||||
@@ -153,13 +157,12 @@ const HistoryEditorPreview = ({
|
||||
const planPromptClosedAtom = atom(false);
|
||||
|
||||
const PlanPrompt = () => {
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
|
||||
const isOwner = useIsWorkspaceOwner(currentWorkspace.meta);
|
||||
const workspaceQuota = useWorkspaceQuota(currentWorkspace.id);
|
||||
const workspace = useService(Workspace);
|
||||
const workspaceQuota = useWorkspaceQuota(workspace.id);
|
||||
const isProWorkspace = useMemo(() => {
|
||||
return workspaceQuota?.humanReadable.name.toLowerCase() !== 'free';
|
||||
}, [workspaceQuota]);
|
||||
const isOwner = useIsWorkspaceOwner(workspace.meta);
|
||||
|
||||
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
const [planPromptClosed, setPlanPromptClosed] = useAtom(planPromptClosedAtom);
|
||||
@@ -412,7 +415,7 @@ const PageHistoryManager = ({
|
||||
pageId,
|
||||
onClose,
|
||||
}: {
|
||||
workspace: Workspace;
|
||||
workspace: BlockSuiteWorkspace;
|
||||
pageId: string;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
@@ -536,8 +539,7 @@ export const PageHistoryModal = ({
|
||||
|
||||
export const GlobalPageHistoryModal = () => {
|
||||
const [{ open, pageId }, setState] = useAtom(pageHistoryModalAtom);
|
||||
const workspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
|
||||
const workspace = useService(Workspace);
|
||||
const handleOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
setState(prev => ({
|
||||
|
||||
@@ -3,15 +3,15 @@ import { openQuotaModalAtom, openSettingModalAtom } from '@affine/core/atoms';
|
||||
import { useIsWorkspaceOwner } from '@affine/core/hooks/affine/use-is-workspace-owner';
|
||||
import { useUserQuota } from '@affine/core/hooks/use-quota';
|
||||
import { useWorkspaceQuota } from '@affine/core/hooks/use-workspace-quota';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useService, Workspace } from '@toeverything/infra';
|
||||
import bytes from 'bytes';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useAtom, useSetAtom } from 'jotai';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
export const CloudQuotaModal = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const [open, setOpen] = useAtom(openQuotaModalAtom);
|
||||
const workspaceQuota = useWorkspaceQuota(currentWorkspace.id);
|
||||
const isOwner = useIsWorkspaceOwner(currentWorkspace.meta);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ConfirmModal } from '@affine/component/ui/modal';
|
||||
import { openQuotaModalAtom } from '@affine/core/atoms';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { useService, Workspace } from '@toeverything/infra';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
export const LocalQuotaModal = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const [open, setOpen] = useAtom(openQuotaModalAtom);
|
||||
|
||||
const onConfirm = useCallback(() => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { WorkspaceDetailSkeleton } from '@affine/component/setting-components';
|
||||
import { Modal, type ModalProps } from '@affine/component/ui/modal';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
|
||||
import { ContactWithUsIcon } from '@blocksuite/icons';
|
||||
import type { WorkspaceMetadata } from '@toeverything/infra';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { Suspense, useCallback, useLayoutEffect, useRef } from 'react';
|
||||
|
||||
|
||||
@@ -8,17 +8,19 @@ import { useIsWorkspaceOwner } from '@affine/core/hooks/affine/use-is-workspace-
|
||||
import { useWorkspaceBlobObjectUrl } from '@affine/core/hooks/use-workspace-blob';
|
||||
import { useWorkspaceAvailableFeatures } from '@affine/core/hooks/use-workspace-features';
|
||||
import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info';
|
||||
import {
|
||||
waitForCurrentWorkspaceAtom,
|
||||
workspaceListAtom,
|
||||
} from '@affine/core/modules/workspace';
|
||||
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { WorkspaceMetadata } from '@affine/workspace';
|
||||
import { Logo1Icon } from '@blocksuite/icons';
|
||||
import {
|
||||
Workspace,
|
||||
WorkspaceManager,
|
||||
type WorkspaceMetadata,
|
||||
} from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useLiveData } from '@toeverything/infra/livedata';
|
||||
import clsx from 'clsx';
|
||||
import { useAtom, useAtomValue } from 'jotai/react';
|
||||
import { useAtom } from 'jotai/react';
|
||||
import { type ReactElement, Suspense, useCallback, useMemo } from 'react';
|
||||
|
||||
import { authAtom } from '../../../../atoms';
|
||||
@@ -188,7 +190,9 @@ export const WorkspaceList = ({
|
||||
selectedWorkspaceId: string | null;
|
||||
activeSubTab: WorkspaceSubTab;
|
||||
}) => {
|
||||
const workspaces = useAtomValue(workspaceListAtom);
|
||||
const workspaces = useLiveData(
|
||||
useService(WorkspaceManager).list.workspaceList
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{workspaces.map(workspace => {
|
||||
@@ -236,7 +240,7 @@ const WorkspaceListItem = ({
|
||||
const information = useWorkspaceInfo(meta);
|
||||
const avatarUrl = useWorkspaceBlobObjectUrl(meta, information?.avatar);
|
||||
const name = information?.name ?? UNTITLED_WORKSPACE_NAME;
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const isCurrent = currentWorkspace.id === meta.id;
|
||||
const t = useAFFiNEI18N();
|
||||
const isOwner = useIsWorkspaceOwner(meta);
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '@affine/core/hooks/use-workspace-features';
|
||||
import { FeatureType } from '@affine/graphql';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
|
||||
import type { WorkspaceMetadata } from '@toeverything/infra';
|
||||
import { useAtom } from 'jotai';
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
import { Suspense, useCallback, useState } from 'react';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
|
||||
import type { WorkspaceMetadata } from '@toeverything/infra';
|
||||
|
||||
import { useIsWorkspaceOwner } from '../../../../hooks/affine/use-is-workspace-owner';
|
||||
import { ExperimentalFeatures } from './experimental-features';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
|
||||
import type { WorkspaceMetadata } from '@toeverything/infra';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import * as styles from './style.css';
|
||||
|
||||
@@ -2,15 +2,12 @@ import { pushNotificationAtom } from '@affine/component/notification-center';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { ConfirmModal } from '@affine/component/ui/modal';
|
||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||
import {
|
||||
currentWorkspaceAtom,
|
||||
workspaceListAtom,
|
||||
workspaceManagerAtom,
|
||||
} from '@affine/core/modules/workspace';
|
||||
import { WorkspaceSubPath } from '@affine/core/shared';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ArrowRightSmallIcon } from '@blocksuite/icons';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { Workspace, WorkspaceManager } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useLiveData } from '@toeverything/infra/livedata';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { openSettingModalAtom } from '../../../../../../atoms';
|
||||
@@ -18,6 +15,7 @@ import {
|
||||
RouteLogic,
|
||||
useNavigateHelper,
|
||||
} from '../../../../../../hooks/use-navigate-helper';
|
||||
import { WorkspaceSubPath } from '../../../../../../shared';
|
||||
import type { WorkspaceSettingDetailProps } from '../types';
|
||||
import { WorkspaceDeleteModal } from './delete';
|
||||
|
||||
@@ -35,9 +33,9 @@ export const DeleteLeaveWorkspace = ({
|
||||
const [showLeave, setShowLeave] = useState(false);
|
||||
const setSettingModal = useSetAtom(openSettingModalAtom);
|
||||
|
||||
const workspaceManager = useAtomValue(workspaceManagerAtom);
|
||||
const workspaceList = useAtomValue(workspaceListAtom);
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom);
|
||||
const workspaceManager = useService(WorkspaceManager);
|
||||
const workspaceList = useLiveData(workspaceManager.list.workspaceList);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const pushNotification = useSetAtom(pushNotificationAtom);
|
||||
|
||||
const onLeaveOrDelete = useCallback(() => {
|
||||
|
||||
@@ -2,17 +2,17 @@ import { SettingRow } from '@affine/component/setting-components';
|
||||
import { Button } from '@affine/component/ui/button';
|
||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||
import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info';
|
||||
import { workspaceManagerAtom } from '@affine/core/modules/workspace';
|
||||
import { WorkspaceSubPath } from '@affine/core/shared';
|
||||
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { Workspace } from '@affine/workspace';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { type Workspace, WorkspaceManager } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { openSettingModalAtom } from '../../../../../atoms';
|
||||
import { useNavigateHelper } from '../../../../../hooks/use-navigate-helper';
|
||||
import { WorkspaceSubPath } from '../../../../../shared';
|
||||
import { EnableAffineCloudModal } from '../../../enable-affine-cloud-modal';
|
||||
import { TmpDisableAffineCloudModal } from '../../../tmp-disable-affine-cloud-modal';
|
||||
import type { WorkspaceSettingDetailProps } from './types';
|
||||
@@ -29,7 +29,7 @@ export const EnableCloudPanel = ({
|
||||
|
||||
const { openPage } = useNavigateHelper();
|
||||
|
||||
const workspaceManager = useAtomValue(workspaceManagerAtom);
|
||||
const workspaceManager = useService(WorkspaceManager);
|
||||
const workspaceInfo = useWorkspaceInfo(workspaceMetadata);
|
||||
const setSettingModal = useSetAtom(openSettingModalAtom);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Button } from '@affine/component/ui/button';
|
||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||
import { apis } from '@affine/electron-api';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { Workspace, WorkspaceMetadata } from '@affine/workspace';
|
||||
import type { Workspace, WorkspaceMetadata } from '@toeverything/infra';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useState } from 'react';
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ import { useWorkspaceStatus } from '@affine/core/hooks/use-workspace-status';
|
||||
import { validateAndReduceImage } from '@affine/core/utils/reduce-image';
|
||||
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { Workspace } from '@affine/workspace';
|
||||
import { SyncPeerStep } from '@affine/workspace';
|
||||
import { CameraIcon } from '@blocksuite/icons';
|
||||
import type { Workspace } from '@toeverything/infra';
|
||||
import { SyncPeerStep } from '@toeverything/infra';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import {
|
||||
type KeyboardEvent,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Button } from '@affine/component/ui/button';
|
||||
import { Tooltip } from '@affine/component/ui/tooltip';
|
||||
import { apis, events } from '@affine/electron-api';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
|
||||
import type { WorkspaceMetadata } from '@toeverything/infra';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
|
||||
import type { WorkspaceMetadata } from '@toeverything/infra';
|
||||
|
||||
export interface WorkspaceSettingDetailProps {
|
||||
isOwner: boolean;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||
import { workspaceManagerAtom } from '@affine/core/modules/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import type { Workspace } from '@affine/workspace';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { type Workspace, WorkspaceManager } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
@@ -25,7 +24,7 @@ export const SharePageButton = ({
|
||||
|
||||
const { openPage } = useNavigateHelper();
|
||||
|
||||
const workspaceManager = useAtomValue(workspaceManagerAtom);
|
||||
const workspaceManager = useService(WorkspaceManager);
|
||||
|
||||
const handleConfirm = useAsyncCallback(async () => {
|
||||
if (workspace.flavour !== WorkspaceFlavour.LOCAL) {
|
||||
|
||||
@@ -3,9 +3,9 @@ import { Divider } from '@affine/component/ui/divider';
|
||||
import { Menu } from '@affine/component/ui/menu';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { WorkspaceMetadata } from '@affine/workspace';
|
||||
import { WebIcon } from '@blocksuite/icons';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import type { WorkspaceMetadata } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { useIsSharedPage } from '../../../../hooks/affine/use-is-shared-page';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { FavoriteTag } from '@affine/core/components/page-list';
|
||||
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper';
|
||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { toast } from '@affine/core/utils';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export interface FavoriteButtonProps {
|
||||
@@ -14,7 +14,7 @@ export interface FavoriteButtonProps {
|
||||
|
||||
export const useFavorite = (pageId: string) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const workspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const workspace = useService(Workspace);
|
||||
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||
const currentPage = blockSuiteWorkspace.getPage(pageId);
|
||||
assertExists(currentPage);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { toast } from '@affine/component';
|
||||
import {
|
||||
Menu,
|
||||
MenuIcon,
|
||||
@@ -11,8 +12,6 @@ import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-sui
|
||||
import { useExportPage } from '@affine/core/hooks/affine/use-export-page';
|
||||
import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper';
|
||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { toast } from '@affine/core/utils';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
@@ -26,6 +25,7 @@ import {
|
||||
ImportIcon,
|
||||
PageIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { useService, Workspace } from '@toeverything/infra';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
@@ -46,8 +46,7 @@ export const PageHeaderMenuButton = ({
|
||||
}: PageMenuProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
// fixme(himself65): remove these hooks ASAP
|
||||
const workspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const workspace = useService(Workspace);
|
||||
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||
const currentPage = blockSuiteWorkspace.getPage(pageId);
|
||||
assertExists(currentPage);
|
||||
|
||||
@@ -3,57 +3,46 @@
|
||||
*/
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
import type { CollectionService } from '@affine/core/modules/collection';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { atom } from 'jotai';
|
||||
import { atomWithObservable } from 'jotai/utils';
|
||||
import { LiveData } from '@toeverything/infra';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { expect, test } from 'vitest';
|
||||
|
||||
import { createDefaultFilter, vars } from '../filter/vars';
|
||||
import {
|
||||
type CollectionsCRUD,
|
||||
useCollectionManager,
|
||||
} from '../use-collection-manager';
|
||||
import { useCollectionManager } from '../use-collection-manager';
|
||||
|
||||
const defaultMeta = { tags: { options: [] } };
|
||||
const collectionsSubject = new BehaviorSubject<Collection[]>([]);
|
||||
const baseAtom = atomWithObservable<Collection[]>(
|
||||
() => {
|
||||
return collectionsSubject;
|
||||
},
|
||||
{
|
||||
initialValue: [],
|
||||
}
|
||||
);
|
||||
|
||||
const mockAtom = atom(get => {
|
||||
return {
|
||||
collections: get(baseAtom),
|
||||
addCollection: (...collections) => {
|
||||
const prev = collectionsSubject.value;
|
||||
collectionsSubject.next([...collections, ...prev]);
|
||||
},
|
||||
deleteCollection: (...ids) => {
|
||||
const prev = collectionsSubject.value;
|
||||
collectionsSubject.next(prev.filter(v => !ids.includes(v.id)));
|
||||
},
|
||||
updateCollection: (id, updater) => {
|
||||
const prev = collectionsSubject.value;
|
||||
collectionsSubject.next(
|
||||
prev.map(v => {
|
||||
if (v.id === id) {
|
||||
return updater(v);
|
||||
}
|
||||
return v;
|
||||
})
|
||||
);
|
||||
},
|
||||
} satisfies CollectionsCRUD;
|
||||
});
|
||||
const mockWorkspaceCollectionService = {
|
||||
collections: LiveData.from(collectionsSubject, []),
|
||||
addCollection: (...collections) => {
|
||||
const prev = collectionsSubject.value;
|
||||
collectionsSubject.next([...collections, ...prev]);
|
||||
},
|
||||
deleteCollection: (...ids) => {
|
||||
const prev = collectionsSubject.value;
|
||||
collectionsSubject.next(prev.filter(v => !ids.includes(v.id)));
|
||||
},
|
||||
updateCollection: (id, updater) => {
|
||||
const prev = collectionsSubject.value;
|
||||
collectionsSubject.next(
|
||||
prev.map(v => {
|
||||
if (v.id === id) {
|
||||
return updater(v);
|
||||
}
|
||||
return v;
|
||||
})
|
||||
);
|
||||
},
|
||||
} as CollectionService;
|
||||
|
||||
test('useAllPageSetting', async () => {
|
||||
const settingHook = renderHook(() => useCollectionManager(mockAtom));
|
||||
const settingHook = renderHook(() =>
|
||||
useCollectionManager(mockWorkspaceCollectionService)
|
||||
);
|
||||
const prevCollection = settingHook.result.current.currentCollection;
|
||||
expect(settingHook.result.current.savedCollections).toEqual([]);
|
||||
settingHook.result.current.updateCollection({
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { collectionsCRUDAtom } from '@affine/core/atoms/collections';
|
||||
import { useDeleteCollectionInfo } from '@affine/core/hooks/affine/use-delete-collection-info';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import {
|
||||
type ReactElement,
|
||||
useCallback,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { CollectionService } from '../../../modules/collection';
|
||||
import { ListFloatingToolbar } from '../components/list-floating-toolbar';
|
||||
import { collectionHeaderColsDef } from '../header-col-def';
|
||||
import { CollectionOperationCell } from '../operation-cell';
|
||||
@@ -69,8 +69,8 @@ export const VirtualizedCollectionList = ({
|
||||
const [selectedCollectionIds, setSelectedCollectionIds] = useState<string[]>(
|
||||
[]
|
||||
);
|
||||
const setting = useCollectionManager(collectionsCRUDAtom);
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const setting = useCollectionManager(useService(CollectionService));
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const info = useDeleteCollectionInfo();
|
||||
|
||||
const collectionOperations = useCollectionOperationsRenderer({
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Button } from '@affine/component';
|
||||
import { collectionsCRUDAtom } from '@affine/core/atoms/collections';
|
||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
|
||||
import type { Collection, Tag } from '@affine/env/filter';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ViewLayersIcon } from '@blocksuite/icons';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { CollectionService } from '../../../modules/collection';
|
||||
import { createTagFilter } from '../filter/utils';
|
||||
import {
|
||||
createEmptyCollection,
|
||||
@@ -24,7 +25,7 @@ import { PageListNewPageButton } from './page-list-new-page-button';
|
||||
|
||||
export const PageListHeader = ({ workspaceId }: { workspaceId: string }) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const setting = useCollectionManager(collectionsCRUDAtom);
|
||||
const setting = useCollectionManager(useService(CollectionService));
|
||||
const { jumpToCollections } = useNavigateHelper();
|
||||
|
||||
const handleJumpToCollections = useCallback(() => {
|
||||
@@ -74,14 +75,16 @@ export const CollectionPageListHeader = ({
|
||||
workspaceId: string;
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const setting = useCollectionManager(collectionsCRUDAtom);
|
||||
const setting = useCollectionManager(useService(CollectionService));
|
||||
const { jumpToCollections } = useNavigateHelper();
|
||||
|
||||
const handleJumpToCollections = useCallback(() => {
|
||||
jumpToCollections(workspaceId);
|
||||
}, [jumpToCollections, workspaceId]);
|
||||
|
||||
const { updateCollection } = useCollectionManager(collectionsCRUDAtom);
|
||||
const { updateCollection } = useCollectionManager(
|
||||
useService(CollectionService)
|
||||
);
|
||||
const { node, open } = useEditCollection(config);
|
||||
|
||||
const handleAddPage = useAsyncCallback(async () => {
|
||||
@@ -121,7 +124,7 @@ export const TagPageListHeader = ({
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const { jumpToTags, jumpToCollection } = useNavigateHelper();
|
||||
const setting = useCollectionManager(collectionsCRUDAtom);
|
||||
const setting = useCollectionManager(useService(CollectionService));
|
||||
const { open, node } = useEditCollectionName({
|
||||
title: t['com.affine.editCollection.saveCollection'](),
|
||||
showTips: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils';
|
||||
@@ -16,7 +16,7 @@ export const PageListNewPageButton = ({
|
||||
size?: 'small' | 'default';
|
||||
testId?: string;
|
||||
}>) => {
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const { importFile, createEdgeless, createPage } = usePageHelper(
|
||||
currentWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
|
||||
@@ -2,12 +2,12 @@ import { toast } from '@affine/component';
|
||||
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper';
|
||||
import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper';
|
||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { PageMeta, Tag } from '@blocksuite/store';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils';
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
} from './page-list-header';
|
||||
|
||||
const usePageOperationsRenderer = () => {
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const { setTrashModal } = useTrashModalHelper(
|
||||
currentWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
@@ -89,7 +89,7 @@ export const VirtualizedPageList = ({
|
||||
const listRef = useRef<ItemListHandle>(null);
|
||||
const [showFloatingToolbar, setShowFloatingToolbar] = useState(false);
|
||||
const [selectedPageIds, setSelectedPageIds] = useState<string[]>([]);
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const pageMetas = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace);
|
||||
const pageOperations = usePageOperationsRenderer();
|
||||
const { isPreferredEdgeless } = usePageHelper(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import type { Tag } from '@blocksuite/store';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { ListFloatingToolbar } from '../components/list-floating-toolbar';
|
||||
@@ -26,7 +26,7 @@ export const VirtualizedTagList = ({
|
||||
const listRef = useRef<ItemListHandle>(null);
|
||||
const [showFloatingToolbar, setShowFloatingToolbar] = useState(false);
|
||||
const [selectedTagIds, setSelectedTagIds] = useState<string[]>([]);
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
|
||||
const filteredSelectedTagIds = useMemo(() => {
|
||||
const ids = tags.map(tag => tag.id);
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import type {
|
||||
Collection,
|
||||
DeleteCollectionInfo,
|
||||
Filter,
|
||||
VariableMap,
|
||||
} from '@affine/env/filter';
|
||||
import type { CollectionService } from '@affine/core/modules/collection';
|
||||
import type { Collection, Filter, VariableMap } from '@affine/env/filter';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import { type Atom, useAtom, useAtomValue } from 'jotai';
|
||||
import { useLiveData } from '@toeverything/infra/livedata';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { atomWithReset } from 'jotai/utils';
|
||||
import { useCallback } from 'react';
|
||||
import { NIL } from 'uuid';
|
||||
@@ -32,47 +29,28 @@ export const currentCollectionAtom = atomWithReset<string>(NIL);
|
||||
|
||||
export type Updater<T> = (value: T) => T;
|
||||
export type CollectionUpdater = Updater<Collection>;
|
||||
export type CollectionsCRUD = {
|
||||
addCollection: (...collections: Collection[]) => void;
|
||||
collections: Collection[];
|
||||
updateCollection: (id: string, updater: CollectionUpdater) => void;
|
||||
deleteCollection: (info: DeleteCollectionInfo, ...ids: string[]) => void;
|
||||
};
|
||||
export type CollectionsCRUDAtom = Atom<
|
||||
Promise<CollectionsCRUD> | CollectionsCRUD
|
||||
>;
|
||||
|
||||
export const useSavedCollections = (collectionAtom: CollectionsCRUDAtom) => {
|
||||
const [{ collections, addCollection, deleteCollection, updateCollection }] =
|
||||
useAtom(collectionAtom);
|
||||
export const useSavedCollections = (collectionService: CollectionService) => {
|
||||
const addPage = useCallback(
|
||||
(collectionId: string, pageId: string) => {
|
||||
updateCollection(collectionId, old => {
|
||||
collectionService.updateCollection(collectionId, old => {
|
||||
return {
|
||||
...old,
|
||||
allowList: [pageId, ...(old.allowList ?? [])],
|
||||
};
|
||||
});
|
||||
},
|
||||
[updateCollection]
|
||||
[collectionService]
|
||||
);
|
||||
return {
|
||||
collections,
|
||||
addCollection,
|
||||
updateCollection,
|
||||
deleteCollection,
|
||||
collectionService,
|
||||
addPage,
|
||||
};
|
||||
};
|
||||
|
||||
export const useCollectionManager = (collectionsAtom: CollectionsCRUDAtom) => {
|
||||
const {
|
||||
collections,
|
||||
updateCollection,
|
||||
addCollection,
|
||||
deleteCollection,
|
||||
addPage,
|
||||
} = useSavedCollections(collectionsAtom);
|
||||
export const useCollectionManager = (collectionService: CollectionService) => {
|
||||
const collections = useLiveData(collectionService.collections);
|
||||
const { addPage } = useSavedCollections(collectionService);
|
||||
const currentCollectionId = useAtomValue(currentCollectionAtom);
|
||||
const [defaultCollection, updateDefaultCollection] = useAtom(
|
||||
defaultCollectionAtom
|
||||
@@ -82,10 +60,10 @@ export const useCollectionManager = (collectionsAtom: CollectionsCRUDAtom) => {
|
||||
if (collection.id === NIL) {
|
||||
updateDefaultCollection(collection);
|
||||
} else {
|
||||
updateCollection(collection.id, () => collection);
|
||||
collectionService.updateCollection(collection.id, () => collection);
|
||||
}
|
||||
},
|
||||
[updateDefaultCollection, updateCollection]
|
||||
[updateDefaultCollection, collectionService]
|
||||
);
|
||||
const setTemporaryFilter = useCallback(
|
||||
(filterList: Filter[]) => {
|
||||
@@ -108,9 +86,10 @@ export const useCollectionManager = (collectionsAtom: CollectionsCRUDAtom) => {
|
||||
isDefault: currentCollectionId === NIL,
|
||||
|
||||
// actions
|
||||
createCollection: addCollection,
|
||||
createCollection: collectionService.addCollection.bind(collectionService),
|
||||
updateCollection: update,
|
||||
deleteCollection,
|
||||
deleteCollection:
|
||||
collectionService.deleteCollection.bind(collectionService),
|
||||
addPage,
|
||||
setTemporaryFilter,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { allPageModeSelectAtom } from '@affine/core/atoms';
|
||||
import { collectionsCRUDAtom } from '@affine/core/atoms/collections';
|
||||
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import type { BlockSuiteWorkspace } from '@affine/core/shared';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
@@ -19,8 +20,9 @@ export const useFilteredPageMetas = (
|
||||
) => {
|
||||
const { isPreferredEdgeless } = usePageHelper(workspace);
|
||||
const pageMode = useAtomValue(allPageModeSelectAtom);
|
||||
const { currentCollection, isDefault } =
|
||||
useCollectionManager(collectionsCRUDAtom);
|
||||
const { currentCollection, isDefault } = useCollectionManager(
|
||||
useService(CollectionService)
|
||||
);
|
||||
|
||||
const filteredPageMetas = useMemo(
|
||||
() =>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Button, Tooltip } from '@affine/component';
|
||||
import type { CollectionService } from '@affine/core/modules/collection';
|
||||
import type { DeleteCollectionInfo, PropertiesMeta } from '@affine/env/filter';
|
||||
import type { GetPageInfoById } from '@affine/env/page-info';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
@@ -6,10 +7,7 @@ import { ViewLayersIcon } from '@blocksuite/icons';
|
||||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
|
||||
import {
|
||||
type CollectionsCRUDAtom,
|
||||
useCollectionManager,
|
||||
} from '../use-collection-manager';
|
||||
import { useCollectionManager } from '../use-collection-manager';
|
||||
import * as styles from './collection-bar.css';
|
||||
import {
|
||||
type AllPageListConfig,
|
||||
@@ -20,16 +18,16 @@ import { useActions } from './use-action';
|
||||
interface CollectionBarProps {
|
||||
getPageInfo: GetPageInfoById;
|
||||
propertiesMeta: PropertiesMeta;
|
||||
collectionsAtom: CollectionsCRUDAtom;
|
||||
collectionService: CollectionService;
|
||||
backToAll: () => void;
|
||||
allPageListConfig: AllPageListConfig;
|
||||
info: DeleteCollectionInfo;
|
||||
}
|
||||
|
||||
export const CollectionBar = (props: CollectionBarProps) => {
|
||||
const { collectionsAtom } = props;
|
||||
const { collectionService } = props;
|
||||
const t = useAFFiNEI18N();
|
||||
const setting = useCollectionManager(collectionsAtom);
|
||||
const setting = useCollectionManager(collectionService);
|
||||
const collection = setting.currentCollection;
|
||||
const [open, setOpen] = useState(false);
|
||||
const actions = useActions({
|
||||
|
||||
@@ -3,15 +3,11 @@ import {
|
||||
useBlockSuitePageMeta,
|
||||
usePageMetaHelper,
|
||||
} from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import {
|
||||
currentWorkspaceAtom,
|
||||
waitForCurrentWorkspaceAtom,
|
||||
} from '@affine/core/modules/workspace';
|
||||
import { WorkspaceSubPath } from '@affine/core/shared';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { EdgelessIcon, PageIcon, ViewLayersIcon } from '@blocksuite/icons';
|
||||
import type { Page, PageMeta } from '@blocksuite/store';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import {
|
||||
type AffineCommand,
|
||||
@@ -19,19 +15,17 @@ import {
|
||||
type CommandCategory,
|
||||
PreconditionStrategy,
|
||||
} from '@toeverything/infra/command';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { commandScore } from 'cmdk';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import { groupBy } from 'lodash-es';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
openQuickSearchModalAtom,
|
||||
pageSettingsAtom,
|
||||
recentPageIdsBaseAtom,
|
||||
} from '../../../atoms';
|
||||
import { collectionsCRUDAtom } from '../../../atoms/collections';
|
||||
import { pageSettingsAtom, recentPageIdsBaseAtom } from '../../../atoms';
|
||||
import { currentPageIdAtom } from '../../../atoms/mode';
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
import { CollectionService } from '../../../modules/collection';
|
||||
import { WorkspaceSubPath } from '../../../shared';
|
||||
import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils';
|
||||
import type { CMDKCommand, CommandContext } from './types';
|
||||
|
||||
@@ -47,41 +41,6 @@ export function removeDoubleQuotes(str?: string): string | undefined {
|
||||
export const cmdkQueryAtom = atom('');
|
||||
export const cmdkValueAtom = atom('');
|
||||
|
||||
// like currentWorkspaceAtom, but not throw error
|
||||
const safeCurrentPageAtom = atom<Promise<Page | undefined>>(async get => {
|
||||
const currentWorkspace = get(currentWorkspaceAtom);
|
||||
if (!currentWorkspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentPageId = get(currentPageIdAtom);
|
||||
|
||||
if (!currentPageId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const page = currentWorkspace.blockSuiteWorkspace.getPage(currentPageId);
|
||||
|
||||
if (!page) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!page.loaded) {
|
||||
await page.waitForLoaded();
|
||||
}
|
||||
return page;
|
||||
});
|
||||
|
||||
export const commandContextAtom = atom<Promise<CommandContext>>(async get => {
|
||||
const currentPage = await get(safeCurrentPageAtom);
|
||||
const pageSettings = get(pageSettingsAtom);
|
||||
|
||||
return {
|
||||
currentPage,
|
||||
pageMode: currentPage ? pageSettings[currentPage.id]?.mode : undefined,
|
||||
};
|
||||
});
|
||||
|
||||
function filterCommandByContext(
|
||||
command: AffineCommand,
|
||||
context: CommandContext
|
||||
@@ -96,7 +55,7 @@ function filterCommandByContext(
|
||||
return context.pageMode === 'page';
|
||||
}
|
||||
if (command.preconditionStrategy === PreconditionStrategy.InPaperOrEdgeless) {
|
||||
return !!context.currentPage;
|
||||
return !!context.pageMode;
|
||||
}
|
||||
if (command.preconditionStrategy === PreconditionStrategy.Never) {
|
||||
return false;
|
||||
@@ -107,27 +66,16 @@ function filterCommandByContext(
|
||||
return true;
|
||||
}
|
||||
|
||||
let quickSearchOpenCounter = 0;
|
||||
const openCountAtom = atom(get => {
|
||||
if (get(openQuickSearchModalAtom)) {
|
||||
quickSearchOpenCounter++;
|
||||
}
|
||||
return quickSearchOpenCounter;
|
||||
});
|
||||
|
||||
export const filteredAffineCommands = atom(async get => {
|
||||
const context = await get(commandContextAtom);
|
||||
// reset when modal open
|
||||
get(openCountAtom);
|
||||
function getAllCommand(context: CommandContext) {
|
||||
const commands = AffineCommandRegistry.getAll();
|
||||
return commands.filter(command => {
|
||||
return filterCommandByContext(command, context);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const useWorkspacePages = () => {
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const pages = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace);
|
||||
const workspace = useService(Workspace);
|
||||
const pages = useBlockSuitePageMeta(workspace.blockSuiteWorkspace);
|
||||
return pages;
|
||||
};
|
||||
|
||||
@@ -153,6 +101,7 @@ export const pageToCommand = (
|
||||
store: ReturnType<typeof getCurrentStore>,
|
||||
navigationHelper: ReturnType<typeof useNavigateHelper>,
|
||||
t: ReturnType<typeof useAFFiNEI18N>,
|
||||
workspace: Workspace,
|
||||
label?: {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
@@ -160,7 +109,6 @@ export const pageToCommand = (
|
||||
blockId?: string
|
||||
): CMDKCommand => {
|
||||
const pageMode = store.get(pageSettingsAtom)?.[page.id]?.mode;
|
||||
const currentWorkspace = store.get(currentWorkspaceAtom);
|
||||
|
||||
const title = page.title || t['Untitled']();
|
||||
const commandLabel = label || {
|
||||
@@ -186,18 +134,14 @@ export const pageToCommand = (
|
||||
originalValue: title,
|
||||
category: category,
|
||||
run: () => {
|
||||
if (!currentWorkspace) {
|
||||
if (!workspace) {
|
||||
console.error('current workspace not found');
|
||||
return;
|
||||
}
|
||||
if (blockId) {
|
||||
return navigationHelper.jumpToPageBlock(
|
||||
currentWorkspace.id,
|
||||
page.id,
|
||||
blockId
|
||||
);
|
||||
return navigationHelper.jumpToPageBlock(workspace.id, page.id, blockId);
|
||||
}
|
||||
return navigationHelper.jumpToPage(currentWorkspace.id, page.id);
|
||||
return navigationHelper.jumpToPage(workspace.id, page.id);
|
||||
},
|
||||
icon: pageMode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />,
|
||||
timestamp: page.updatedDate,
|
||||
@@ -212,7 +156,7 @@ export const usePageCommands = () => {
|
||||
const recentPages = useRecentPages();
|
||||
const pages = useWorkspacePages();
|
||||
const store = getCurrentStore();
|
||||
const workspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const workspace = useService(Workspace);
|
||||
const pageHelper = usePageHelper(workspace.blockSuiteWorkspace);
|
||||
const pageMetaHelper = usePageMetaHelper(workspace.blockSuiteWorkspace);
|
||||
const query = useAtomValue(cmdkQueryAtom);
|
||||
@@ -241,7 +185,14 @@ export const usePageCommands = () => {
|
||||
let results: CMDKCommand[] = [];
|
||||
if (query.trim() === '') {
|
||||
results = recentPages.map(page => {
|
||||
return pageToCommand('affine:recent', page, store, navigationHelper, t);
|
||||
return pageToCommand(
|
||||
'affine:recent',
|
||||
page,
|
||||
store,
|
||||
navigationHelper,
|
||||
t,
|
||||
workspace
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// queried pages that has matched contents
|
||||
@@ -283,6 +234,7 @@ export const usePageCommands = () => {
|
||||
store,
|
||||
navigationHelper,
|
||||
t,
|
||||
workspace,
|
||||
label,
|
||||
blockId
|
||||
);
|
||||
@@ -334,27 +286,26 @@ export const usePageCommands = () => {
|
||||
}
|
||||
return results;
|
||||
}, [
|
||||
pageHelper,
|
||||
pageMetaHelper,
|
||||
navigationHelper,
|
||||
pages,
|
||||
searchTime,
|
||||
query,
|
||||
recentPages,
|
||||
store,
|
||||
navigationHelper,
|
||||
t,
|
||||
workspace.blockSuiteWorkspace,
|
||||
searchTime,
|
||||
workspace,
|
||||
pages,
|
||||
pageHelper,
|
||||
pageMetaHelper,
|
||||
]);
|
||||
};
|
||||
|
||||
export const collectionToCommand = (
|
||||
collection: Collection,
|
||||
store: ReturnType<typeof getCurrentStore>,
|
||||
navigationHelper: ReturnType<typeof useNavigateHelper>,
|
||||
selectCollection: (id: string) => void,
|
||||
t: ReturnType<typeof useAFFiNEI18N>
|
||||
t: ReturnType<typeof useAFFiNEI18N>,
|
||||
workspace: Workspace
|
||||
): CMDKCommand => {
|
||||
const currentWorkspace = store.get(currentWorkspaceAtom);
|
||||
const label = collection.name || t['Untitled']();
|
||||
const category = 'affine:collections';
|
||||
return {
|
||||
@@ -372,11 +323,7 @@ export const collectionToCommand = (
|
||||
originalValue: label,
|
||||
category: category,
|
||||
run: () => {
|
||||
if (!currentWorkspace) {
|
||||
console.error('current workspace not found');
|
||||
return;
|
||||
}
|
||||
navigationHelper.jumpToSubPath(currentWorkspace.id, WorkspaceSubPath.ALL);
|
||||
navigationHelper.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
||||
selectCollection(collection.id);
|
||||
},
|
||||
icon: <ViewLayersIcon />,
|
||||
@@ -385,12 +332,13 @@ export const collectionToCommand = (
|
||||
|
||||
export const useCollectionsCommands = () => {
|
||||
// todo: considering collections for searching pages
|
||||
const { savedCollections } = useCollectionManager(collectionsCRUDAtom);
|
||||
const store = getCurrentStore();
|
||||
const { savedCollections } = useCollectionManager(
|
||||
useService(CollectionService)
|
||||
);
|
||||
const query = useAtomValue(cmdkQueryAtom);
|
||||
const navigationHelper = useNavigateHelper();
|
||||
const t = useAFFiNEI18N();
|
||||
const workspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const workspace = useService(Workspace);
|
||||
const selectCollection = useCallback(
|
||||
(id: string) => {
|
||||
navigationHelper.jumpToCollection(workspace.id, id);
|
||||
@@ -405,22 +353,39 @@ export const useCollectionsCommands = () => {
|
||||
results = savedCollections.map(collection => {
|
||||
const command = collectionToCommand(
|
||||
collection,
|
||||
store,
|
||||
navigationHelper,
|
||||
selectCollection,
|
||||
t
|
||||
t,
|
||||
workspace
|
||||
);
|
||||
return command;
|
||||
});
|
||||
return results;
|
||||
}
|
||||
}, [query, savedCollections, store, navigationHelper, selectCollection, t]);
|
||||
}, [
|
||||
query,
|
||||
savedCollections,
|
||||
navigationHelper,
|
||||
selectCollection,
|
||||
t,
|
||||
workspace,
|
||||
]);
|
||||
};
|
||||
|
||||
export const useCMDKCommandGroups = () => {
|
||||
const pageCommands = usePageCommands();
|
||||
const collectionCommands = useCollectionsCommands();
|
||||
const affineCommands = useAtomValue(filteredAffineCommands);
|
||||
|
||||
const currentPageId = useAtomValue(currentPageIdAtom);
|
||||
const pageSettings = useAtomValue(pageSettingsAtom);
|
||||
const currentPageMode = currentPageId
|
||||
? pageSettings[currentPageId]?.mode
|
||||
: undefined;
|
||||
const affineCommands = useMemo(() => {
|
||||
return getAllCommand({
|
||||
pageMode: currentPageMode,
|
||||
});
|
||||
}, [currentPageMode]);
|
||||
|
||||
return useMemo(() => {
|
||||
const commands = [
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import type { CommandCategory } from '@toeverything/infra/command';
|
||||
|
||||
export interface CommandContext {
|
||||
currentPage: Page | undefined;
|
||||
pageMode: 'page' | 'edgeless' | undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,22 +2,25 @@ import { Button } from '@affine/component/ui/button';
|
||||
import { ConfirmModal } from '@affine/component/ui/modal';
|
||||
import { Tooltip } from '@affine/component/ui/tooltip';
|
||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { WorkspaceSubPath } from '@affine/core/shared';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { DeleteIcon, ResetIcon } from '@blocksuite/icons';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useLiveData } from '@toeverything/infra/livedata';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { useAppSettingHelper } from '../../../hooks/affine/use-app-setting-helper';
|
||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
import { CurrentWorkspaceService } from '../../../modules/workspace/current-workspace';
|
||||
import { WorkspaceSubPath } from '../../../shared';
|
||||
import { toast } from '../../../utils';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
export const TrashPageFooter = ({ pageId }: { pageId: string }) => {
|
||||
const workspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const workspace = useLiveData(
|
||||
useService(CurrentWorkspaceService).currentWorkspace
|
||||
);
|
||||
assertExists(workspace);
|
||||
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
||||
|
||||
@@ -7,21 +7,22 @@ import {
|
||||
filterPage,
|
||||
stopPropagation,
|
||||
useCollectionManager,
|
||||
useSavedCollections,
|
||||
} from '@affine/core/components/page-list';
|
||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { MoreHorizontalIcon, ViewLayersIcon } from '@blocksuite/icons';
|
||||
import type { PageMeta, Workspace } from '@blocksuite/store';
|
||||
import { useDroppable } from '@dnd-kit/core';
|
||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useLiveData } from '@toeverything/infra/livedata';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { collectionsCRUDAtom } from '../../../../atoms/collections';
|
||||
import { useAllPageListConfig } from '../../../../hooks/affine/use-all-page-list-config';
|
||||
import { getDropItemId } from '../../../../hooks/affine/use-sidebar-drag';
|
||||
import { useBlockSuitePageMeta } from '../../../../hooks/use-block-suite-page-meta';
|
||||
import type { CollectionsListProps } from '../index';
|
||||
import { Page } from './page';
|
||||
import * as styles from './styles.css';
|
||||
@@ -39,7 +40,7 @@ const CollectionRenderer = ({
|
||||
}) => {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const [open, setOpen] = useState(false);
|
||||
const setting = useCollectionManager(collectionsCRUDAtom);
|
||||
const setting = useCollectionManager(useService(CollectionService));
|
||||
const t = useAFFiNEI18N();
|
||||
const dragItemId = getDropItemId('collections', collection.id);
|
||||
|
||||
@@ -168,7 +169,7 @@ export const CollectionsList = ({
|
||||
onCreate,
|
||||
}: CollectionsListProps) => {
|
||||
const metas = useBlockSuitePageMeta(workspace);
|
||||
const { collections } = useSavedCollections(collectionsCRUDAtom);
|
||||
const collections = useLiveData(useService(CollectionService).collections);
|
||||
const t = useAFFiNEI18N();
|
||||
if (collections.length === 0) {
|
||||
return (
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Divider } from '@affine/component/ui/divider';
|
||||
import { MenuItem } from '@affine/component/ui/menu';
|
||||
import {
|
||||
workspaceListAtom,
|
||||
workspaceManagerAtom,
|
||||
} from '@affine/core/modules/workspace';
|
||||
import { Unreachable } from '@affine/env/constant';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Logo1Icon } from '@blocksuite/icons';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { WorkspaceManager } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useLiveData } from '@toeverything/infra/livedata';
|
||||
import { useSetAtom } from 'jotai';
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
@@ -85,9 +84,8 @@ export const UserWithWorkspaceList = ({
|
||||
onEventEnd?.();
|
||||
}, [onEventEnd, setOpenCreateWorkspaceModal]);
|
||||
|
||||
const workspaces = useAtomValue(workspaceListAtom);
|
||||
|
||||
const workspaceManager = useAtomValue(workspaceManagerAtom);
|
||||
const workspaceManager = useService(WorkspaceManager);
|
||||
const workspaces = useLiveData(workspaceManager.list.workspaceList);
|
||||
|
||||
// revalidate workspace list when mounted
|
||||
useEffect(() => {
|
||||
|
||||
@@ -5,16 +5,13 @@ import {
|
||||
useWorkspaceAvatar,
|
||||
useWorkspaceName,
|
||||
} from '@affine/core/hooks/use-workspace-info';
|
||||
import {
|
||||
currentWorkspaceAtom,
|
||||
workspaceListAtom,
|
||||
} from '@affine/core/modules/workspace';
|
||||
import { WorkspaceSubPath } from '@affine/core/shared';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { WorkspaceMetadata } from '@affine/workspace';
|
||||
import type { DragEndEvent } from '@dnd-kit/core';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { WorkspaceManager, type WorkspaceMetadata } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useLiveData } from '@toeverything/infra/livedata';
|
||||
import { useSetAtom } from 'jotai';
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
@@ -23,6 +20,8 @@ import {
|
||||
openCreateWorkspaceModalAtom,
|
||||
openSettingModalAtom,
|
||||
} from '../../../../../atoms';
|
||||
import { CurrentWorkspaceService } from '../../../../../modules/workspace/current-workspace';
|
||||
import { WorkspaceSubPath } from '../../../../../shared';
|
||||
import { useIsWorkspaceOwner } from '../.././../../../hooks/affine/use-is-workspace-owner';
|
||||
import { useNavigateHelper } from '../.././../../../hooks/use-navigate-helper';
|
||||
import * as styles from './index.css';
|
||||
@@ -106,13 +105,17 @@ export const AFFiNEWorkspaceList = ({
|
||||
}: {
|
||||
onEventEnd?: () => void;
|
||||
}) => {
|
||||
const workspaces = useAtomValue(workspaceListAtom);
|
||||
const workspaces = useLiveData(
|
||||
useService(WorkspaceManager).list.workspaceList
|
||||
);
|
||||
|
||||
const setOpenCreateWorkspaceModal = useSetAtom(openCreateWorkspaceModalAtom);
|
||||
|
||||
const { jumpToSubPath } = useNavigateHelper();
|
||||
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom);
|
||||
const currentWorkspace = useLiveData(
|
||||
useService(CurrentWorkspaceService).currentWorkspace
|
||||
);
|
||||
|
||||
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
|
||||
|
||||
@@ -6,11 +6,9 @@ import { openSettingModalAtom } from '@affine/core/atoms';
|
||||
import { useIsWorkspaceOwner } from '@affine/core/hooks/affine/use-is-workspace-owner';
|
||||
import { useWorkspaceBlobObjectUrl } from '@affine/core/hooks/use-workspace-blob';
|
||||
import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { type SyncEngineStatus, SyncEngineStep } from '@affine/workspace';
|
||||
import {
|
||||
CloudWorkspaceIcon,
|
||||
InformationFillDuotoneIcon,
|
||||
@@ -18,7 +16,13 @@ import {
|
||||
NoNetworkIcon,
|
||||
UnsyncIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import {
|
||||
type SyncEngineStatus,
|
||||
SyncEngineStep,
|
||||
Workspace,
|
||||
} from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { debounce, mean } from 'lodash-es';
|
||||
import {
|
||||
forwardRef,
|
||||
@@ -97,7 +101,7 @@ const useSyncEngineSyncProgress = () => {
|
||||
useState<SyncEngineStatus | null>(null);
|
||||
const [isOverCapacity, setIsOverCapacity] = useState(false);
|
||||
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const isOwner = useIsWorkspaceOwner(currentWorkspace.meta);
|
||||
|
||||
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
@@ -250,7 +254,7 @@ export const WorkspaceCard = forwardRef<
|
||||
HTMLDivElement,
|
||||
HTMLAttributes<HTMLDivElement>
|
||||
>(({ ...props }, ref) => {
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
|
||||
const information = useWorkspaceInfo(currentWorkspace.meta);
|
||||
|
||||
|
||||
@@ -12,15 +12,14 @@ import {
|
||||
SidebarScrollableContainer,
|
||||
} from '@affine/component/app-sidebar';
|
||||
import { Menu } from '@affine/component/ui/menu';
|
||||
import { collectionsCRUDAtom } from '@affine/core/atoms/collections';
|
||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||
import { WorkspaceSubPath } from '@affine/core/shared';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import { apis, events } from '@affine/electron-api';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { Workspace } from '@affine/workspace';
|
||||
import { FolderIcon, SettingsIcon } from '@blocksuite/icons';
|
||||
import { type Page } from '@blocksuite/store';
|
||||
import { useDroppable } from '@dnd-kit/core';
|
||||
import { useService, type Workspace } from '@toeverything/infra';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { nanoid } from 'nanoid';
|
||||
import type { HTMLAttributes, ReactElement } from 'react';
|
||||
@@ -35,6 +34,7 @@ import { getDropItemId } from '../../hooks/affine/use-sidebar-drag';
|
||||
import { useTrashModalHelper } from '../../hooks/affine/use-trash-modal-helper';
|
||||
import { useRegisterBrowserHistoryCommands } from '../../hooks/use-browser-history-commands';
|
||||
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
|
||||
import { WorkspaceSubPath } from '../../shared';
|
||||
import {
|
||||
createEmptyCollection,
|
||||
MoveToTrash,
|
||||
@@ -177,7 +177,7 @@ export const RootAppSidebar = ({
|
||||
useRegisterBrowserHistoryCommands(router.back, router.forward);
|
||||
const userInfo = useDeleteCollectionInfo();
|
||||
|
||||
const setting = useCollectionManager(collectionsCRUDAtom);
|
||||
const setting = useCollectionManager(useService(CollectionService));
|
||||
const { node, open } = useEditCollectionName({
|
||||
title: t['com.affine.editCollection.createCollection'](),
|
||||
showTips: true,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { BrowserWarning } from '@affine/component/affine-banner';
|
||||
import { LocalDemoTips } from '@affine/component/affine-banner';
|
||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||
import { workspaceManagerAtom } from '@affine/core/modules/workspace';
|
||||
import { WorkspaceSubPath } from '@affine/core/shared';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { Workspace } from '@affine/workspace';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { type Workspace, WorkspaceManager } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { authAtom } from '../atoms';
|
||||
import { useCurrentLoginStatus } from '../hooks/affine/use-current-login-status';
|
||||
import { useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||
import { WorkspaceSubPath } from '../shared';
|
||||
import { EnableAffineCloudModal } from './affine/enable-affine-cloud-modal';
|
||||
|
||||
const minimumChromeVersion = 106;
|
||||
@@ -77,7 +77,7 @@ export const TopTip = ({
|
||||
}, [setAuthModal]);
|
||||
|
||||
const { openPage } = useNavigateHelper();
|
||||
const workspaceManager = useAtomValue(workspaceManagerAtom);
|
||||
const workspaceManager = useService(WorkspaceManager);
|
||||
const handleConfirm = useAsyncCallback(async () => {
|
||||
if (workspace.flavour !== WorkspaceFlavour.LOCAL) {
|
||||
return;
|
||||
|
||||
@@ -3,15 +3,12 @@ import { AffineShapeIcon } from '@affine/core/components/page-list'; // TODO: im
|
||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
|
||||
import { useWorkspaceStatus } from '@affine/core/hooks/use-workspace-status';
|
||||
import {
|
||||
waitForCurrentWorkspaceAtom,
|
||||
workspaceManagerAtom,
|
||||
} from '@affine/core/modules/workspace';
|
||||
import { WorkspaceSubPath } from '@affine/core/shared';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { Workspace, WorkspaceManager } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { WorkspaceSubPath } from '../../shared';
|
||||
import * as styles from './upgrade.css';
|
||||
import { ArrowCircleIcon, HeartBreakIcon } from './upgrade-icon';
|
||||
|
||||
@@ -20,8 +17,8 @@ import { ArrowCircleIcon, HeartBreakIcon } from './upgrade-icon';
|
||||
*/
|
||||
export const WorkspaceUpgrade = function WorkspaceUpgrade() {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const workspaceManager = useAtomValue(workspaceManagerAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const workspaceManager = useService(WorkspaceManager);
|
||||
const upgradeStatus = useWorkspaceStatus(currentWorkspace, s => s.upgrade);
|
||||
const { openPage } = useNavigateHelper();
|
||||
const t = useAFFiNEI18N();
|
||||
@@ -32,10 +29,10 @@ export const WorkspaceUpgrade = function WorkspaceUpgrade() {
|
||||
}
|
||||
|
||||
try {
|
||||
const newWorkspaceId =
|
||||
const newWorkspace =
|
||||
await currentWorkspace.upgrade.upgrade(workspaceManager);
|
||||
if (newWorkspaceId) {
|
||||
openPage(newWorkspaceId, WorkspaceSubPath.ALL);
|
||||
if (newWorkspace) {
|
||||
openPage(newWorkspace.id, WorkspaceSubPath.ALL);
|
||||
} else {
|
||||
// blocksuite may enter an incorrect state, reload to reset it.
|
||||
location.reload();
|
||||
|
||||
@@ -3,101 +3,71 @@
|
||||
*/
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
import {
|
||||
currentWorkspaceAtom,
|
||||
WorkspacePropertiesAdapter,
|
||||
} from '@affine/core/modules/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import type { Workspace } from '@affine/workspace/workspace';
|
||||
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { type Page, Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
|
||||
import { Schema } from '@blocksuite/store';
|
||||
import { WorkspacePropertiesAdapter } from '@affine/core/modules/workspace';
|
||||
import { render } from '@testing-library/react';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { ServiceProviderContext, useService } from '@toeverything/infra/di';
|
||||
import { createStore, Provider } from 'jotai';
|
||||
import { Suspense } from 'react';
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
import { beforeEach } from 'vitest';
|
||||
|
||||
import { configureTestingEnvironment } from '../../testing';
|
||||
import { useBlockSuiteWorkspacePageTitle } from '../use-block-suite-workspace-page-title';
|
||||
|
||||
let blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||
const store = createStore();
|
||||
|
||||
const schema = new Schema();
|
||||
schema.register(AffineSchemas).register(__unstableSchemas);
|
||||
|
||||
const Component = () => {
|
||||
const title = useBlockSuiteWorkspacePageTitle(blockSuiteWorkspace, 'page0');
|
||||
const workspace = useService(Workspace);
|
||||
const title = useBlockSuiteWorkspacePageTitle(
|
||||
workspace.blockSuiteWorkspace,
|
||||
'page0'
|
||||
);
|
||||
return <div>title: {title}</div>;
|
||||
};
|
||||
|
||||
// todo: this module has some side-effects that will break the tests
|
||||
vi.mock('@affine/workspace-impl', () => ({
|
||||
default: {},
|
||||
}));
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.useFakeTimers({ toFake: ['requestIdleCallback'] });
|
||||
|
||||
blockSuiteWorkspace = new BlockSuiteWorkspace({ id: 'test', schema });
|
||||
|
||||
const workspace = {
|
||||
blockSuiteWorkspace,
|
||||
flavour: WorkspaceFlavour.LOCAL,
|
||||
} as Workspace;
|
||||
|
||||
store.set(currentWorkspaceAtom, workspace);
|
||||
|
||||
blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||
|
||||
blockSuiteWorkspace.doc.emit('sync', []);
|
||||
|
||||
const initPage = async (page: Page) => {
|
||||
await page.waitForLoaded();
|
||||
expect(page).not.toBeNull();
|
||||
assertExists(page);
|
||||
const pageBlockId = page.addBlock('affine:page', {
|
||||
title: new page.Text(''),
|
||||
});
|
||||
const frameId = page.addBlock('affine:note', {}, pageBlockId);
|
||||
page.addBlock('affine:paragraph', {}, frameId);
|
||||
};
|
||||
await initPage(blockSuiteWorkspace.createPage({ id: 'page0' }));
|
||||
await initPage(blockSuiteWorkspace.createPage({ id: 'page1' }));
|
||||
await initPage(blockSuiteWorkspace.createPage({ id: 'page2' }));
|
||||
});
|
||||
|
||||
describe('useBlockSuiteWorkspacePageTitle', () => {
|
||||
test('basic', async () => {
|
||||
const { workspace, page } = await configureTestingEnvironment();
|
||||
const { findByText, rerender } = render(
|
||||
<Provider store={store}>
|
||||
<Suspense fallback="loading">
|
||||
<Component />
|
||||
</Suspense>
|
||||
</Provider>
|
||||
<ServiceProviderContext.Provider value={page.services}>
|
||||
<Provider store={store}>
|
||||
<Suspense fallback="loading">
|
||||
<Component />
|
||||
</Suspense>
|
||||
</Provider>
|
||||
</ServiceProviderContext.Provider>
|
||||
);
|
||||
expect(await findByText('title: Untitled')).toBeDefined();
|
||||
blockSuiteWorkspace.setPageMeta('page0', { title: '1' });
|
||||
workspace.blockSuiteWorkspace.setPageMeta(page.id, { title: '1' });
|
||||
rerender(
|
||||
<Provider store={store}>
|
||||
<Suspense fallback="loading">
|
||||
<Component />
|
||||
</Suspense>
|
||||
</Provider>
|
||||
<ServiceProviderContext.Provider value={page.services}>
|
||||
<Provider store={store}>
|
||||
<Suspense fallback="loading">
|
||||
<Component />
|
||||
</Suspense>
|
||||
</Provider>
|
||||
</ServiceProviderContext.Provider>
|
||||
);
|
||||
expect(await findByText('title: 1')).toBeDefined();
|
||||
});
|
||||
|
||||
test('journal', async () => {
|
||||
const adapter = new WorkspacePropertiesAdapter(blockSuiteWorkspace);
|
||||
adapter.setJournalPageDateString('page0', '2021-01-01');
|
||||
const { workspace, page } = await configureTestingEnvironment();
|
||||
const adapter = workspace.services.get(WorkspacePropertiesAdapter);
|
||||
adapter.setJournalPageDateString(page.id, '2021-01-01');
|
||||
const { findByText } = render(
|
||||
<Provider store={store}>
|
||||
<Suspense fallback="loading">
|
||||
<Component />
|
||||
</Suspense>
|
||||
</Provider>
|
||||
<ServiceProviderContext.Provider value={page.services}>
|
||||
<Provider store={store}>
|
||||
<Suspense fallback="loading">
|
||||
<Component />
|
||||
</Suspense>
|
||||
</Provider>
|
||||
</ServiceProviderContext.Provider>
|
||||
);
|
||||
expect(await findByText('title: Jan 1, 2021')).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -4,17 +4,17 @@ import {
|
||||
FavoriteTag,
|
||||
} from '@affine/core/components/page-list';
|
||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { usePageHelper } from '../../components/blocksuite/block-suite-page-list/utils';
|
||||
import { useBlockSuiteMetaHelper } from './use-block-suite-meta-helper';
|
||||
|
||||
export const useAllPageListConfig = () => {
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const workspace = currentWorkspace.blockSuiteWorkspace;
|
||||
const pageMetas = useBlockSuitePageMeta(workspace);
|
||||
const { isPreferredEdgeless } = usePageHelper(workspace);
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
usePageMetaHelper,
|
||||
} from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { useBlockSuiteWorkspaceHelper } from '@affine/core/hooks/use-block-suite-workspace-helper';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
||||
@@ -11,7 +13,6 @@ import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
||||
import { setPageModeAtom } from '../../atoms';
|
||||
import { currentModeAtom } from '../../atoms/mode';
|
||||
import type { BlockSuiteWorkspace } from '../../shared';
|
||||
import { getWorkspaceSetting } from '../../utils/workspace-setting';
|
||||
import { useNavigateHelper } from '../use-navigate-helper';
|
||||
import { useReferenceLinkHelper } from './use-reference-link-helper';
|
||||
|
||||
@@ -26,6 +27,7 @@ export function useBlockSuiteMetaHelper(
|
||||
const currentMode = useAtomValue(currentModeAtom);
|
||||
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
|
||||
const { openPage } = useNavigateHelper();
|
||||
const collectionService = useService(CollectionService);
|
||||
|
||||
const switchToPageMode = useCallback(
|
||||
(pageId: string) => {
|
||||
@@ -89,9 +91,9 @@ export function useBlockSuiteMetaHelper(
|
||||
trashRelate: isRoot ? parentMeta?.id : undefined,
|
||||
});
|
||||
setPageReadonly(pageId, true);
|
||||
getWorkspaceSetting(blockSuiteWorkspace).deletePages([pageId]);
|
||||
collectionService.deletePagesFromCollections([pageId]);
|
||||
},
|
||||
[blockSuiteWorkspace, getPageMeta, metas, setPageMeta, setPageReadonly]
|
||||
[collectionService, getPageMeta, metas, setPageMeta, setPageReadonly]
|
||||
);
|
||||
|
||||
const restoreFromTrash = useCallback(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { getIsOwnerQuery } from '@affine/graphql';
|
||||
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
|
||||
import type { WorkspaceMetadata } from '@toeverything/infra';
|
||||
|
||||
import { useQueryImmutable } from '../use-query';
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { toast } from '@affine/component';
|
||||
import { usePageMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { EdgelessIcon, HistoryIcon, PageIcon } from '@blocksuite/icons';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import {
|
||||
PreconditionStrategy,
|
||||
registerAffineCommand,
|
||||
} from '@toeverything/infra/command';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { pageHistoryModalAtom } from '../../atoms/page-history';
|
||||
@@ -22,7 +23,7 @@ export function useRegisterBlocksuiteEditorCommands(
|
||||
mode: 'page' | 'edgeless'
|
||||
) {
|
||||
const t = useAFFiNEI18N();
|
||||
const workspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const workspace = useService(Workspace);
|
||||
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||
const { getPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
||||
const currentPage = blockSuiteWorkspace.getPage(pageId);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { toast } from '@affine/component';
|
||||
import type { DraggableTitleCellData } from '@affine/core/components/page-list';
|
||||
import { usePageMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { DragEndEvent, UniqueIdentifier } from '@dnd-kit/core';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useBlockSuiteMetaHelper } from './use-block-suite-meta-helper';
|
||||
@@ -69,7 +69,7 @@ export function getDragItemId(
|
||||
|
||||
export const useSidebarDrag = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const workspace = currentWorkspace.blockSuiteWorkspace;
|
||||
const { setTrashModal } = useTrashModalHelper(workspace);
|
||||
const { addToFavorite, removeFromFavorite } =
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useBlockSuiteWorkspacePage } from '@affine/core/hooks/use-block-suite-workspace-page';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useAtomValue } from 'jotai';
|
||||
|
||||
import { currentPageIdAtom } from '../../atoms/mode';
|
||||
|
||||
export const useCurrentPage = () => {
|
||||
const currentPageId = useAtomValue(currentPageIdAtom);
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
|
||||
const currentWorkspace = useService(Workspace);
|
||||
return useBlockSuiteWorkspacePage(
|
||||
currentWorkspace?.blockSuiteWorkspace,
|
||||
currentWorkspace.blockSuiteWorkspace,
|
||||
currentPageId
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import type { WorkspacePropertiesAdapter } from '../modules/workspace/properties';
|
||||
import {
|
||||
currentWorkspacePropertiesAdapterAtom,
|
||||
workspaceAdapterAtomFamily,
|
||||
} from '../modules/workspace/properties';
|
||||
import { WorkspacePropertiesAdapter } from '../modules/workspace/properties';
|
||||
|
||||
function getProxy<T extends object>(obj: T) {
|
||||
return new Proxy(obj, {});
|
||||
@@ -31,11 +26,6 @@ const useReactiveAdapter = (adapter: WorkspacePropertiesAdapter) => {
|
||||
};
|
||||
|
||||
export function useCurrentWorkspacePropertiesAdapter() {
|
||||
const adapter = useAtomValue(currentWorkspacePropertiesAdapterAtom);
|
||||
return useReactiveAdapter(adapter);
|
||||
}
|
||||
|
||||
export function useWorkspacePropertiesAdapter(workspace: Workspace) {
|
||||
const adapter = useAtomValue(workspaceAdapterAtomFamily(workspace));
|
||||
const adapter = useService(WorkspacePropertiesAdapter);
|
||||
return useReactiveAdapter(adapter);
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { useCallback, useSyncExternalStore } from 'react';
|
||||
import type { DataSourceAdapter, Status } from 'y-provider';
|
||||
|
||||
type UIStatus =
|
||||
| Status
|
||||
| {
|
||||
type: 'unknown';
|
||||
};
|
||||
|
||||
export function useDataSourceStatus(provider: DataSourceAdapter): UIStatus {
|
||||
return useSyncExternalStore(
|
||||
provider.subscribeStatusChange,
|
||||
useCallback(() => provider.status, [provider])
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { useCallback, useMemo } from 'react';
|
||||
|
||||
import type { BlockSuiteWorkspace } from '../shared';
|
||||
import { timestampToLocalDate } from '../utils';
|
||||
import { useWorkspacePropertiesAdapter } from './use-affine-adapter';
|
||||
import { useCurrentWorkspacePropertiesAdapter } from './use-affine-adapter';
|
||||
import { useBlockSuiteWorkspaceHelper } from './use-block-suite-workspace-helper';
|
||||
import { useNavigateHelper } from './use-navigate-helper';
|
||||
|
||||
@@ -24,7 +24,7 @@ function toDayjs(j?: string | false) {
|
||||
|
||||
export const useJournalHelper = (workspace: BlockSuiteWorkspace) => {
|
||||
const bsWorkspaceHelper = useBlockSuiteWorkspaceHelper(workspace);
|
||||
const adapter = useWorkspacePropertiesAdapter(workspace);
|
||||
const adapter = useCurrentWorkspacePropertiesAdapter();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useAtomValue, useSetAtom, useStore } from 'jotai';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useSetAtom, useStore } from 'jotai';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
@@ -22,7 +23,7 @@ export function useRegisterWorkspaceCommands() {
|
||||
const store = useStore();
|
||||
const t = useAFFiNEI18N();
|
||||
const theme = useTheme();
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const languageHelper = useLanguageHelper();
|
||||
const pageHelper = usePageHelper(currentWorkspace.blockSuiteWorkspace);
|
||||
const navigationHelper = useNavigateHelper();
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { workspaceManagerAtom } from '@affine/core/modules/workspace';
|
||||
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { WorkspaceMetadata } from '@toeverything/infra';
|
||||
import { WorkspaceManager } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function useWorkspaceBlobObjectUrl(
|
||||
meta?: WorkspaceMetadata,
|
||||
blobKey?: string | null
|
||||
) {
|
||||
const workspaceManager = useAtomValue(workspaceManagerAtom);
|
||||
const workspaceManager = useService(WorkspaceManager);
|
||||
|
||||
const [blob, setBlob] = useState<string | undefined>(undefined);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
enabledFeaturesQuery,
|
||||
setWorkspaceExperimentalFeatureMutation,
|
||||
} from '@affine/graphql';
|
||||
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
|
||||
import { type WorkspaceMetadata } from '@toeverything/infra';
|
||||
|
||||
import { useAsyncCallback } from './affine-async-hooks';
|
||||
import { useMutateQueryResource, useMutation } from './use-mutation';
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { workspaceManagerAtom } from '@affine/core/modules/workspace';
|
||||
import type { WorkspaceMetadata } from '@affine/workspace';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { WorkspaceManager, type WorkspaceMetadata } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useWorkspaceBlobObjectUrl } from './use-workspace-blob';
|
||||
|
||||
export function useWorkspaceInfo(meta: WorkspaceMetadata) {
|
||||
const workspaceManager = useAtomValue(workspaceManagerAtom);
|
||||
const workspaceManager = useService(WorkspaceManager);
|
||||
|
||||
const [information, setInformation] = useState(
|
||||
() => workspaceManager.list.getInformation(meta).info
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Workspace, WorkspaceStatus } from '@affine/workspace';
|
||||
import type { Workspace, WorkspaceStatus } from '@toeverything/infra';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function useWorkspaceStatus<
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { workspaceManagerAtom } from '@affine/core/modules/workspace';
|
||||
import type { Workspace } from '@affine/workspace';
|
||||
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { WorkspaceMetadata } from '@toeverything/infra';
|
||||
import { type Workspace, WorkspaceManager } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
/**
|
||||
* definitely be careful when using this hook, open workspace is a heavy operation
|
||||
*/
|
||||
export function useWorkspace(meta?: WorkspaceMetadata | null) {
|
||||
const workspaceManager = useAtomValue(workspaceManagerAtom);
|
||||
const workspaceManager = useService(WorkspaceManager);
|
||||
|
||||
const [workspace, setWorkspace] = useState<Workspace | null>(null);
|
||||
|
||||
@@ -17,7 +16,7 @@ export function useWorkspace(meta?: WorkspaceMetadata | null) {
|
||||
setWorkspace(null); // set to null if meta is null or undefined
|
||||
return;
|
||||
}
|
||||
const ref = workspaceManager.use(meta);
|
||||
const ref = workspaceManager.open(meta);
|
||||
setWorkspace(ref.workspace);
|
||||
return () => {
|
||||
ref.release();
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
import { MainContainer, WorkspaceFallback } from '@affine/component/workspace';
|
||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { useWorkspaceStatus } from '@affine/core/hooks/use-workspace-status';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import {
|
||||
DndContext,
|
||||
@@ -16,6 +15,8 @@ import {
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import type { PropsWithChildren, ReactNode } from 'react';
|
||||
import { lazy, Suspense, useCallback, useEffect, useState } from 'react';
|
||||
@@ -54,7 +55,7 @@ export const QuickSearch = () => {
|
||||
openQuickSearchModalAtom
|
||||
);
|
||||
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const { pageId } = useParams();
|
||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||
const pageMeta = useBlockSuitePageMeta(
|
||||
@@ -92,7 +93,7 @@ export const WorkspaceLayout = function WorkspaceLayout({
|
||||
};
|
||||
|
||||
export const WorkspaceLayoutInner = ({ children }: PropsWithChildren) => {
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const { openPage } = useNavigateHelper();
|
||||
const pageHelper = usePageHelper(currentWorkspace.blockSuiteWorkspace);
|
||||
|
||||
|
||||
1
packages/frontend/core/src/modules/collection/index.ts
Normal file
1
packages/frontend/core/src/modules/collection/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './service';
|
||||
@@ -3,40 +3,70 @@ import type {
|
||||
DeleteCollectionInfo,
|
||||
DeletedCollection,
|
||||
} from '@affine/env/filter';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import type { Workspace } from '@toeverything/infra';
|
||||
import { LiveData } from '@toeverything/infra/livedata';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Array as YArray } from 'yjs';
|
||||
|
||||
import { updateFirstOfYArray } from './yjs-utils';
|
||||
const SETTING_KEY = 'setting';
|
||||
|
||||
const COLLECTIONS_KEY = 'collections';
|
||||
const COLLECTIONS_TRASH_KEY = 'collections_trash';
|
||||
const SETTING_KEY = 'setting';
|
||||
|
||||
export class WorkspaceSetting {
|
||||
export class CollectionService {
|
||||
constructor(private readonly workspace: Workspace) {}
|
||||
|
||||
get doc() {
|
||||
return this.workspace.doc;
|
||||
private get doc() {
|
||||
return this.workspace.blockSuiteWorkspace.doc;
|
||||
}
|
||||
|
||||
get setting() {
|
||||
return this.workspace.doc.getMap(SETTING_KEY);
|
||||
private get setting() {
|
||||
return this.workspace.blockSuiteWorkspace.doc.getMap(SETTING_KEY);
|
||||
}
|
||||
|
||||
get collectionsYArray(): YArray<Collection> | undefined {
|
||||
private get collectionsYArray(): YArray<Collection> | undefined {
|
||||
return this.setting.get(COLLECTIONS_KEY) as YArray<Collection>;
|
||||
}
|
||||
|
||||
get collectionsTrashYArray(): YArray<DeletedCollection> | undefined {
|
||||
private get collectionsTrashYArray(): YArray<DeletedCollection> | undefined {
|
||||
return this.setting.get(COLLECTIONS_TRASH_KEY) as YArray<DeletedCollection>;
|
||||
}
|
||||
|
||||
get collections(): Collection[] {
|
||||
return this.collectionsYArray?.toArray() ?? [];
|
||||
}
|
||||
readonly collections = LiveData.from(
|
||||
new Observable<Collection[]>(subscriber => {
|
||||
subscriber.next(this.collectionsYArray?.toArray() ?? []);
|
||||
const fn = () => {
|
||||
subscriber.next(this.collectionsYArray?.toArray() ?? []);
|
||||
};
|
||||
this.setting.observeDeep(fn);
|
||||
return () => {
|
||||
this.setting.unobserveDeep(fn);
|
||||
};
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
get collectionsTrash(): DeletedCollection[] {
|
||||
return this.collectionsTrashYArray?.toArray() ?? [];
|
||||
readonly collectionsTrash = LiveData.from(
|
||||
new Observable<DeletedCollection[]>(subscriber => {
|
||||
subscriber.next(this.collectionsTrashYArray?.toArray() ?? []);
|
||||
const fn = () => {
|
||||
subscriber.next(this.collectionsTrashYArray?.toArray() ?? []);
|
||||
};
|
||||
this.setting.observeDeep(fn);
|
||||
return () => {
|
||||
this.setting.unobserveDeep(fn);
|
||||
};
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
addCollection(...collections: Collection[]) {
|
||||
if (!this.setting.has(COLLECTIONS_KEY)) {
|
||||
this.setting.set(COLLECTIONS_KEY, new YArray());
|
||||
}
|
||||
this.doc.transact(() => {
|
||||
this.collectionsYArray?.insert(0, collections);
|
||||
});
|
||||
}
|
||||
|
||||
updateCollection(id: string, updater: (value: Collection) => Collection) {
|
||||
@@ -51,22 +81,13 @@ export class WorkspaceSetting {
|
||||
}
|
||||
}
|
||||
|
||||
addCollection(...collections: Collection[]) {
|
||||
if (!this.setting.has(COLLECTIONS_KEY)) {
|
||||
this.setting.set(COLLECTIONS_KEY, new YArray());
|
||||
}
|
||||
this.doc.transact(() => {
|
||||
this.collectionsYArray?.insert(0, collections);
|
||||
});
|
||||
}
|
||||
|
||||
deleteCollection(info: DeleteCollectionInfo, ...ids: string[]) {
|
||||
const collectionsYArray = this.collectionsYArray;
|
||||
if (!collectionsYArray) {
|
||||
return;
|
||||
}
|
||||
const set = new Set(ids);
|
||||
this.workspace.doc.transact(() => {
|
||||
this.workspace.blockSuiteWorkspace.doc.transact(() => {
|
||||
const indexList: number[] = [];
|
||||
const list: Collection[] = [];
|
||||
collectionsYArray.forEach((collection, i) => {
|
||||
@@ -100,7 +121,10 @@ export class WorkspaceSetting {
|
||||
});
|
||||
}
|
||||
|
||||
deletePagesFromCollection(collection: Collection, idSet: Set<string>) {
|
||||
private deletePagesFromCollection(
|
||||
collection: Collection,
|
||||
idSet: Set<string>
|
||||
) {
|
||||
const newAllowList = collection.allowList.filter(id => !idSet.has(id));
|
||||
if (newAllowList.length !== collection.allowList.length) {
|
||||
this.updateCollection(collection.id, old => {
|
||||
@@ -112,16 +136,29 @@ export class WorkspaceSetting {
|
||||
}
|
||||
}
|
||||
|
||||
deletePages(ids: string[]) {
|
||||
deletePagesFromCollections(ids: string[]) {
|
||||
const idSet = new Set(ids);
|
||||
this.workspace.doc.transact(() => {
|
||||
this.collections.forEach(collection => {
|
||||
this.doc.transact(() => {
|
||||
this.collections.value.forEach(collection => {
|
||||
this.deletePagesFromCollection(collection, idSet);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const getWorkspaceSetting = (workspace: Workspace) => {
|
||||
return new WorkspaceSetting(workspace);
|
||||
const updateFirstOfYArray = <T>(
|
||||
array: YArray<T>,
|
||||
p: (value: T) => boolean,
|
||||
update: (value: T) => T
|
||||
) => {
|
||||
array.doc?.transact(() => {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const ele = array.get(i);
|
||||
if (p(ele)) {
|
||||
array.delete(i);
|
||||
array.insert(i, [update(ele)]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { Page } from '@toeverything/infra';
|
||||
import {
|
||||
LiveData,
|
||||
ServiceCollection,
|
||||
type ServiceProvider,
|
||||
ServiceProviderContext,
|
||||
useLiveData,
|
||||
useService,
|
||||
useServiceOptional,
|
||||
} from '@toeverything/infra';
|
||||
import type React from 'react';
|
||||
|
||||
import { CurrentPageService } from '../../page';
|
||||
import { CurrentWorkspaceService } from '../../workspace';
|
||||
|
||||
export const GlobalScopeProvider: React.FC<
|
||||
React.PropsWithChildren<{ provider: ServiceProvider }>
|
||||
> = ({ provider: rootProvider, children }) => {
|
||||
const currentWorkspaceService = useService(CurrentWorkspaceService, {
|
||||
provider: rootProvider,
|
||||
});
|
||||
|
||||
const workspaceProvider = useLiveData(
|
||||
currentWorkspaceService.currentWorkspace
|
||||
)?.services;
|
||||
|
||||
const currentPageService = useServiceOptional(CurrentPageService, {
|
||||
provider: workspaceProvider ?? ServiceCollection.EMPTY.provider(),
|
||||
});
|
||||
|
||||
const pageProvider = useLiveData(
|
||||
currentPageService?.currentPage ?? new LiveData<Page | null>(null)
|
||||
)?.services;
|
||||
|
||||
return (
|
||||
<ServiceProviderContext.Provider
|
||||
value={pageProvider ?? workspaceProvider ?? rootProvider}
|
||||
>
|
||||
{children}
|
||||
</ServiceProviderContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { GlobalCache } from '@toeverything/infra';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export class LocalStorageGlobalCache implements GlobalCache {
|
||||
prefix = 'cache:';
|
||||
|
||||
get<T>(key: string): T | null {
|
||||
const json = localStorage.getItem(this.prefix + key);
|
||||
return json ? JSON.parse(json) : null;
|
||||
}
|
||||
watch<T>(key: string): Observable<T | null> {
|
||||
return new Observable<T | null>(subscriber => {
|
||||
const json = localStorage.getItem(this.prefix + key);
|
||||
const first = json ? JSON.parse(json) : null;
|
||||
subscriber.next(first);
|
||||
|
||||
const channel = new BroadcastChannel(this.prefix + key);
|
||||
channel.addEventListener('message', event => {
|
||||
subscriber.next(event.data);
|
||||
});
|
||||
return () => {
|
||||
channel.close();
|
||||
};
|
||||
});
|
||||
}
|
||||
set<T>(key: string, value: T | null): void {
|
||||
localStorage.setItem(this.prefix + key, JSON.stringify(value));
|
||||
const channel = new BroadcastChannel(this.prefix + key);
|
||||
channel.postMessage(value);
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
24
packages/frontend/core/src/modules/page/current-page.tsx
Normal file
24
packages/frontend/core/src/modules/page/current-page.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { Page } from '@toeverything/infra';
|
||||
import { LiveData } from '@toeverything/infra/livedata';
|
||||
|
||||
/**
|
||||
* service to manage current page
|
||||
*/
|
||||
export class CurrentPageService {
|
||||
currentPage = new LiveData<Page | null>(null);
|
||||
|
||||
/**
|
||||
* open page, current page will be set to the page
|
||||
* @param page
|
||||
*/
|
||||
openPage(page: Page) {
|
||||
this.currentPage.next(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* close current page, current page will be null
|
||||
*/
|
||||
closePage() {
|
||||
this.currentPage.next(null);
|
||||
}
|
||||
}
|
||||
1
packages/frontend/core/src/modules/page/index.ts
Normal file
1
packages/frontend/core/src/modules/page/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './current-page';
|
||||
27
packages/frontend/core/src/modules/services.ts
Normal file
27
packages/frontend/core/src/modules/services.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
GlobalCache,
|
||||
type ServiceCollection,
|
||||
Workspace,
|
||||
WorkspaceScope,
|
||||
} from '@toeverything/infra';
|
||||
|
||||
import { CollectionService } from './collection';
|
||||
import { LocalStorageGlobalCache } from './infra-web/storage';
|
||||
import { CurrentPageService } from './page';
|
||||
import {
|
||||
CurrentWorkspaceService,
|
||||
WorkspacePropertiesAdapter,
|
||||
} from './workspace';
|
||||
|
||||
export function configureBusinessServices(services: ServiceCollection) {
|
||||
services.add(CurrentWorkspaceService);
|
||||
services
|
||||
.scope(WorkspaceScope)
|
||||
.add(CurrentPageService)
|
||||
.add(WorkspacePropertiesAdapter, [Workspace])
|
||||
.add(CollectionService, [Workspace]);
|
||||
}
|
||||
|
||||
export function configureWebInfraServices(services: ServiceCollection) {
|
||||
services.addImpl(GlobalCache, LocalStorageGlobalCache);
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import type { Workspace, WorkspaceMetadata } from '@affine/workspace';
|
||||
import { workspaceManager } from '@affine/workspace-impl';
|
||||
import { atom } from 'jotai';
|
||||
import { atomWithObservable } from 'jotai/utils';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
const logger = new DebugLogger('affine:workspace:atom');
|
||||
|
||||
// readonly atom for workspace manager, currently only one workspace manager is supported
|
||||
export const workspaceManagerAtom = atom(() => workspaceManager);
|
||||
|
||||
// workspace metadata list, use rxjs to push updates
|
||||
export const workspaceListAtom = atomWithObservable<WorkspaceMetadata[]>(
|
||||
get => {
|
||||
const workspaceManager = get(workspaceManagerAtom);
|
||||
return new Observable<WorkspaceMetadata[]>(subscriber => {
|
||||
subscriber.next(workspaceManager.list.workspaceList);
|
||||
return workspaceManager.list.onStatusChanged.on(status => {
|
||||
subscriber.next(status.workspaceList);
|
||||
}).dispose;
|
||||
});
|
||||
},
|
||||
{
|
||||
initialValue: [],
|
||||
}
|
||||
);
|
||||
|
||||
// workspace list loading status, if is false, UI can display not found page when workspace id is not in the list.
|
||||
export const workspaceListLoadingStatusAtom = atomWithObservable<boolean>(
|
||||
get => {
|
||||
const workspaceManager = get(workspaceManagerAtom);
|
||||
return new Observable<boolean>(subscriber => {
|
||||
subscriber.next(workspaceManager.list.status.loading);
|
||||
return workspaceManager.list.onStatusChanged.on(status => {
|
||||
subscriber.next(status.loading);
|
||||
}).dispose;
|
||||
});
|
||||
},
|
||||
{
|
||||
initialValue: true,
|
||||
}
|
||||
);
|
||||
|
||||
// current workspace
|
||||
export const currentWorkspaceAtom = atom<Workspace | null>(null);
|
||||
|
||||
// wait for current workspace, if current workspace is null, it will suspend
|
||||
export const waitForCurrentWorkspaceAtom = atom(get => {
|
||||
const currentWorkspace = get(currentWorkspaceAtom);
|
||||
if (!currentWorkspace) {
|
||||
// suspended
|
||||
logger.info('suspended for current workspace');
|
||||
return new Promise<Workspace>(_ => {});
|
||||
}
|
||||
return currentWorkspace;
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { Workspace } from '@toeverything/infra';
|
||||
import { LiveData } from '@toeverything/infra/livedata';
|
||||
|
||||
/**
|
||||
* service to manage current workspace
|
||||
*/
|
||||
export class CurrentWorkspaceService {
|
||||
currentWorkspace = new LiveData<Workspace | null>(null);
|
||||
|
||||
/**
|
||||
* open workspace, current workspace will be set to the workspace
|
||||
* @param workspace
|
||||
*/
|
||||
openWorkspace(workspace: Workspace) {
|
||||
this.currentWorkspace.next(workspace);
|
||||
}
|
||||
|
||||
/**
|
||||
* close current workspace, current workspace will be null
|
||||
*/
|
||||
closeWorkspace() {
|
||||
this.currentWorkspace.next(null);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './atoms';
|
||||
export * from './current-workspace';
|
||||
export * from './properties';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// the adapter is to bridge the workspace rootdoc & native js bindings
|
||||
|
||||
import { createYProxy, type Workspace, type Y } from '@blocksuite/store';
|
||||
import { createYProxy, type Y } from '@blocksuite/store';
|
||||
import type { Workspace } from '@toeverything/infra';
|
||||
import { defaultsDeep } from 'lodash-es';
|
||||
|
||||
import {
|
||||
@@ -29,7 +30,7 @@ export class WorkspacePropertiesAdapter {
|
||||
|
||||
constructor(private readonly workspace: Workspace) {
|
||||
// check if properties exists, if not, create one
|
||||
const rootDoc = workspace.doc;
|
||||
const rootDoc = workspace.blockSuiteWorkspace.doc;
|
||||
this.properties = rootDoc.getMap(AFFINE_PROPERTIES_ID);
|
||||
this.proxy = createYProxy(this.properties);
|
||||
|
||||
@@ -56,7 +57,9 @@ export class WorkspacePropertiesAdapter {
|
||||
name: 'Tags',
|
||||
source: 'system',
|
||||
type: PagePropertyType.Tags,
|
||||
options: this.workspace.meta.properties.tags?.options ?? [], // better use a one time migration
|
||||
options:
|
||||
this.workspace.blockSuiteWorkspace.meta.properties.tags
|
||||
?.options ?? [], // better use a one time migration
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
|
||||
import { atom } from 'jotai';
|
||||
import { atomFamily } from 'jotai/utils';
|
||||
|
||||
import { waitForCurrentWorkspaceAtom } from '../atoms';
|
||||
import { WorkspacePropertiesAdapter } from './adapter';
|
||||
|
||||
// todo: remove the inner atom when workspace is closed by using workspaceAdapterAtomFamily.remove
|
||||
export const workspaceAdapterAtomFamily = atomFamily(
|
||||
(workspace: BlockSuiteWorkspace) => {
|
||||
return atom(async () => {
|
||||
await workspace.doc.whenLoaded;
|
||||
return new WorkspacePropertiesAdapter(workspace);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export const currentWorkspacePropertiesAdapterAtom = atom(async get => {
|
||||
const workspace = await get(waitForCurrentWorkspaceAtom);
|
||||
return get(workspaceAdapterAtomFamily(workspace.blockSuiteWorkspace));
|
||||
});
|
||||
@@ -1,2 +1 @@
|
||||
export * from './adapter';
|
||||
export * from './atom';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Menu } from '@affine/component/ui/menu';
|
||||
import { WorkspaceFallback } from '@affine/component/workspace';
|
||||
import { workspaceListAtom } from '@affine/core/modules/workspace';
|
||||
import { WorkspaceSubPath } from '@affine/core/shared';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { WorkspaceManager } from '@toeverything/infra';
|
||||
import { WorkspaceListService } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useLiveData } from '@toeverything/infra';
|
||||
import { lazy, useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { type LoaderFunction, redirect } from 'react-router-dom';
|
||||
|
||||
@@ -10,6 +11,7 @@ import { createFirstAppData } from '../bootstrap/first-app-data';
|
||||
import { UserWithWorkspaceList } from '../components/pure/workspace-slider-bar/user-with-workspace-list';
|
||||
import { appConfigStorage } from '../hooks/use-app-config-storage';
|
||||
import { useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||
import { WorkspaceSubPath } from '../shared';
|
||||
|
||||
const AllWorkspaceModals = lazy(() =>
|
||||
import('../providers/modal-provider').then(({ AllWorkspaceModals }) => ({
|
||||
@@ -29,7 +31,7 @@ export const Component = () => {
|
||||
const [navigating, setNavigating] = useState(false);
|
||||
const [creating, setCreating] = useState(false);
|
||||
|
||||
const list = useAtomValue(workspaceListAtom);
|
||||
const list = useLiveData(useService(WorkspaceListService).workspaceList);
|
||||
const { openPage } = useNavigateHelper();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
@@ -44,16 +46,18 @@ export const Component = () => {
|
||||
setNavigating(true);
|
||||
}, [list, openPage]);
|
||||
|
||||
const workspaceManager = useService(WorkspaceManager);
|
||||
|
||||
useEffect(() => {
|
||||
setCreating(true);
|
||||
createFirstAppData()
|
||||
createFirstAppData(workspaceManager)
|
||||
.catch(err => {
|
||||
console.error('Failed to create first app data', err);
|
||||
})
|
||||
.finally(() => {
|
||||
setCreating(false);
|
||||
});
|
||||
}, []);
|
||||
}, [workspaceManager]);
|
||||
|
||||
if (navigating || creating) {
|
||||
return <WorkspaceFallback></WorkspaceFallback>;
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
import { MainContainer } from '@affine/component/workspace';
|
||||
import { usePageDocumentTitle } from '@affine/core/hooks/use-global-state';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { fetchWithTraceReport } from '@affine/graphql';
|
||||
import { globalBlockSuiteSchema } from '@affine/workspace';
|
||||
import {
|
||||
createAffineCloudBlobStorage,
|
||||
createStaticBlobStorage,
|
||||
AffineCloudBlobStorage,
|
||||
StaticBlobStorage,
|
||||
} from '@affine/workspace-impl';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { type Page, Workspace } from '@blocksuite/store';
|
||||
import {
|
||||
EmptyBlobStorage,
|
||||
LocalBlobStorage,
|
||||
LocalSyncStorage,
|
||||
Page,
|
||||
PageManager,
|
||||
ReadonlyMappingSyncStorage,
|
||||
RemoteBlobStorage,
|
||||
useService,
|
||||
useServiceOptional,
|
||||
WorkspaceIdContext,
|
||||
WorkspaceManager,
|
||||
WorkspaceScope,
|
||||
} from '@toeverything/infra';
|
||||
import { noop } from 'foxact/noop';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import type { LoaderFunction } from 'react-router-dom';
|
||||
import {
|
||||
isRouteErrorResponse,
|
||||
@@ -19,12 +29,13 @@ import {
|
||||
useLoaderData,
|
||||
useRouteError,
|
||||
} from 'react-router-dom';
|
||||
import { applyUpdate } from 'yjs';
|
||||
|
||||
import type { PageMode } from '../../atoms';
|
||||
import { AppContainer } from '../../components/affine/app-container';
|
||||
import { PageDetailEditor } from '../../components/page-detail-editor';
|
||||
import { SharePageNotFoundError } from '../../components/share-page-not-found-error';
|
||||
import { CurrentPageService } from '../../modules/page';
|
||||
import { CurrentWorkspaceService } from '../../modules/workspace';
|
||||
import { ShareHeader } from './share-header';
|
||||
|
||||
type DocPublishMode = 'edgeless' | 'page';
|
||||
@@ -57,8 +68,11 @@ export async function downloadBinaryFromCloud(
|
||||
}
|
||||
|
||||
type LoaderData = {
|
||||
page: Page;
|
||||
pageId: string;
|
||||
workspaceId: string;
|
||||
publishMode: PageMode;
|
||||
pageArrayBuffer: ArrayBuffer;
|
||||
workspaceArrayBuffer: ArrayBuffer;
|
||||
};
|
||||
|
||||
function assertDownloadResponse(
|
||||
@@ -73,55 +87,104 @@ function assertDownloadResponse(
|
||||
}
|
||||
}
|
||||
|
||||
const logger = new DebugLogger('public:share-page');
|
||||
|
||||
export const loader: LoaderFunction = async ({ params }) => {
|
||||
const workspaceId = params?.workspaceId;
|
||||
const pageId = params?.pageId;
|
||||
if (!workspaceId || !pageId) {
|
||||
return redirect('/404');
|
||||
}
|
||||
const workspace = new Workspace({
|
||||
id: workspaceId,
|
||||
blobStorages: [
|
||||
() => ({
|
||||
crud: createAffineCloudBlobStorage(workspaceId),
|
||||
}),
|
||||
() => ({
|
||||
crud: createStaticBlobStorage(),
|
||||
}),
|
||||
],
|
||||
schema: globalBlockSuiteSchema,
|
||||
});
|
||||
// download root workspace
|
||||
{
|
||||
const response = await downloadBinaryFromCloud(workspaceId, workspaceId);
|
||||
assertDownloadResponse(response);
|
||||
const { arrayBuffer } = response;
|
||||
applyUpdate(workspace.doc, new Uint8Array(arrayBuffer));
|
||||
workspace.doc.emit('sync', []);
|
||||
}
|
||||
const page = workspace.getPage(pageId);
|
||||
assertExists(page, 'cannot find page');
|
||||
// download page
|
||||
|
||||
const response = await downloadBinaryFromCloud(
|
||||
const [workspaceResponse, pageResponse] = await Promise.all([
|
||||
downloadBinaryFromCloud(workspaceId, workspaceId),
|
||||
downloadBinaryFromCloud(workspaceId, pageId),
|
||||
]);
|
||||
assertDownloadResponse(workspaceResponse);
|
||||
const { arrayBuffer: workspaceArrayBuffer } = workspaceResponse;
|
||||
assertDownloadResponse(pageResponse);
|
||||
const { arrayBuffer: pageArrayBuffer, publishMode } = pageResponse;
|
||||
|
||||
return {
|
||||
workspaceId,
|
||||
page.spaceDoc.guid
|
||||
);
|
||||
assertDownloadResponse(response);
|
||||
const { arrayBuffer, publishMode } = response;
|
||||
|
||||
applyUpdate(page.spaceDoc, new Uint8Array(arrayBuffer));
|
||||
|
||||
logger.info('workspace', workspace);
|
||||
workspace.awarenessStore.setReadonly(page, true);
|
||||
return { page, publishMode };
|
||||
pageId,
|
||||
publishMode,
|
||||
workspaceArrayBuffer,
|
||||
pageArrayBuffer,
|
||||
} satisfies LoaderData;
|
||||
};
|
||||
|
||||
export const Component = (): ReactElement => {
|
||||
const { page, publishMode } = useLoaderData() as LoaderData;
|
||||
usePageDocumentTitle(page.meta);
|
||||
export const Component = () => {
|
||||
const {
|
||||
workspaceId,
|
||||
pageId,
|
||||
publishMode,
|
||||
workspaceArrayBuffer,
|
||||
pageArrayBuffer,
|
||||
} = useLoaderData() as LoaderData;
|
||||
const workspaceManager = useService(WorkspaceManager);
|
||||
|
||||
const currentWorkspace = useService(CurrentWorkspaceService);
|
||||
|
||||
useEffect(() => {
|
||||
// create a workspace for share page
|
||||
const workspace = workspaceManager.instantiate(
|
||||
{
|
||||
id: workspaceId,
|
||||
flavour: WorkspaceFlavour.AFFINE_CLOUD,
|
||||
},
|
||||
services => {
|
||||
services
|
||||
.scope(WorkspaceScope)
|
||||
.addImpl(LocalBlobStorage, EmptyBlobStorage)
|
||||
.addImpl(RemoteBlobStorage('affine'), AffineCloudBlobStorage, [
|
||||
WorkspaceIdContext,
|
||||
])
|
||||
.addImpl(RemoteBlobStorage('static'), StaticBlobStorage)
|
||||
.addImpl(
|
||||
LocalSyncStorage,
|
||||
ReadonlyMappingSyncStorage({
|
||||
[workspaceId]: new Uint8Array(workspaceArrayBuffer),
|
||||
[pageId]: new Uint8Array(pageArrayBuffer),
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
workspace.engine.sync
|
||||
.waitForSynced()
|
||||
.then(() => {
|
||||
const { page } = workspace.services
|
||||
.get(PageManager)
|
||||
.openByPageId(pageId);
|
||||
|
||||
workspace.blockSuiteWorkspace.awarenessStore.setReadonly(
|
||||
page.blockSuitePage,
|
||||
true
|
||||
);
|
||||
|
||||
const currentPage = workspace.services.get(CurrentPageService);
|
||||
|
||||
currentWorkspace.openWorkspace(workspace);
|
||||
currentPage.openPage(page);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [
|
||||
currentWorkspace,
|
||||
pageArrayBuffer,
|
||||
pageId,
|
||||
workspaceArrayBuffer,
|
||||
workspaceId,
|
||||
workspaceManager,
|
||||
]);
|
||||
|
||||
const page = useServiceOptional(Page);
|
||||
|
||||
usePageDocumentTitle(page?.meta);
|
||||
|
||||
if (!page) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppContainer>
|
||||
@@ -129,14 +192,14 @@ export const Component = (): ReactElement => {
|
||||
<ShareHeader
|
||||
pageId={page.id}
|
||||
publishMode={publishMode}
|
||||
blockSuiteWorkspace={page.workspace}
|
||||
blockSuiteWorkspace={page.blockSuitePage.workspace}
|
||||
/>
|
||||
<PageDetailEditor
|
||||
isPublic
|
||||
publishMode={publishMode}
|
||||
workspace={page.workspace}
|
||||
workspace={page.blockSuitePage.workspace}
|
||||
pageId={page.id}
|
||||
onLoad={useCallback(() => noop, [])}
|
||||
onLoad={() => noop}
|
||||
/>
|
||||
</MainContainer>
|
||||
</AppContainer>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import type { Collection, Filter } from '@affine/env/filter';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { collectionsCRUDAtom } from '../../../atoms/collections';
|
||||
import { filterContainerStyle } from '../../../components/filter-container.css';
|
||||
import {
|
||||
FilterList,
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
|
||||
export const FilterContainer = () => {
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const navigateHelper = useNavigateHelper();
|
||||
const setting = useCollectionManager(collectionsCRUDAtom);
|
||||
const setting = useCollectionManager(useService(CollectionService));
|
||||
const saveToCollection = useCallback(
|
||||
(collection: Collection) => {
|
||||
setting.createCollection({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { IconButton } from '@affine/component';
|
||||
import type { AllPageFilterOption } from '@affine/core/atoms';
|
||||
import { collectionsCRUDAtom } from '@affine/core/atoms/collections';
|
||||
import {
|
||||
CollectionList,
|
||||
PageListNewPageButton,
|
||||
@@ -13,9 +12,11 @@ import { useAllPageListConfig } from '@affine/core/hooks/affine/use-all-page-lis
|
||||
import { useDeleteCollectionInfo } from '@affine/core/hooks/affine/use-delete-collection-info';
|
||||
import { PlusIcon } from '@blocksuite/icons';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { CollectionService } from '../../../modules/collection';
|
||||
import * as styles from './all-page.css';
|
||||
import { FilterContainer } from './all-page-filter';
|
||||
|
||||
@@ -32,7 +33,7 @@ export const AllPageHeader = ({
|
||||
activeFilter: AllPageFilterOption;
|
||||
onCreateCollection?: () => void;
|
||||
}) => {
|
||||
const setting = useCollectionManager(collectionsCRUDAtom);
|
||||
const setting = useCollectionManager(useService(CollectionService));
|
||||
const config = useAllPageListConfig();
|
||||
const userInfo = useDeleteCollectionInfo();
|
||||
const isWindowsDesktop = environment.isDesktop && environment.isWindows;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { AllPageFilterOption } from '@affine/core/atoms';
|
||||
import { collectionsCRUDAtom } from '@affine/core/atoms/collections';
|
||||
import { HubIsland } from '@affine/core/components/affine/hub-island';
|
||||
import {
|
||||
CollectionListHeader,
|
||||
@@ -10,7 +9,6 @@ import {
|
||||
useCollectionManager,
|
||||
useEditCollectionName,
|
||||
useFilteredPageMetas,
|
||||
useSavedCollections,
|
||||
useTagMetas,
|
||||
VirtualizedCollectionList,
|
||||
VirtualizedPageList,
|
||||
@@ -22,15 +20,18 @@ import {
|
||||
import { useAllPageListConfig } from '@affine/core/hooks/affine/use-all-page-list-config';
|
||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { performanceRenderLogger } from '@affine/core/shared';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useLiveData } from '@toeverything/infra';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { NIL } from 'uuid';
|
||||
|
||||
import { CollectionService } from '../../../modules/collection';
|
||||
import {
|
||||
EmptyCollectionList,
|
||||
EmptyPageList,
|
||||
@@ -47,13 +48,14 @@ export const AllPage = ({
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const params = useParams();
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const pageMetas = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace);
|
||||
const [hideHeaderCreateNew, setHideHeaderCreateNew] = useState(true);
|
||||
|
||||
const setting = useCollectionManager(collectionsCRUDAtom);
|
||||
const collectionService = useService(CollectionService);
|
||||
const collections = useLiveData(collectionService.collections);
|
||||
const setting = useCollectionManager(collectionService);
|
||||
const config = useAllPageListConfig();
|
||||
const { collections } = useSavedCollections(collectionsCRUDAtom);
|
||||
const { tags, tagMetas, filterPageMetaByTag, deleteTags } = useTagMetas(
|
||||
currentWorkspace.blockSuiteWorkspace,
|
||||
pageMetas
|
||||
@@ -212,7 +214,7 @@ export const AllPage = ({
|
||||
export const Component = () => {
|
||||
performanceRenderLogger.info('AllPage');
|
||||
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const currentCollection = useSetAtom(currentCollectionAtom);
|
||||
const navigateHelper = useNavigateHelper();
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
|
||||
import { useAllPageListConfig } from '@affine/core/hooks/affine/use-all-page-list-config';
|
||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
@@ -22,19 +22,17 @@ import {
|
||||
PageIcon,
|
||||
ViewLayersIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useLiveData } from '@toeverything/infra/livedata';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { type LoaderFunction, redirect, useParams } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
collectionsCRUDAtom,
|
||||
pageCollectionBaseAtom,
|
||||
} from '../../atoms/collections';
|
||||
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
|
||||
import { WorkspaceSubPath } from '../../shared';
|
||||
import { getWorkspaceSetting } from '../../utils/workspace-setting';
|
||||
import { AllPage } from './all-page/all-page';
|
||||
import * as styles from './collection.css';
|
||||
|
||||
@@ -48,18 +46,19 @@ export const loader: LoaderFunction = async args => {
|
||||
};
|
||||
|
||||
export const Component = function CollectionPage() {
|
||||
const { collections, loading } = useAtomValue(pageCollectionBaseAtom);
|
||||
const collectionService = useService(CollectionService);
|
||||
const collections = useLiveData(collectionService.collections);
|
||||
const navigate = useNavigateHelper();
|
||||
const params = useParams();
|
||||
const workspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const workspace = useService(Workspace);
|
||||
const collection = collections.find(v => v.id === params.collectionId);
|
||||
const pushNotification = useSetAtom(pushNotificationAtom);
|
||||
useEffect(() => {
|
||||
if (!loading && !collection) {
|
||||
if (!collection) {
|
||||
navigate.jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
||||
const collection = getWorkspaceSetting(
|
||||
workspace.blockSuiteWorkspace
|
||||
).collectionsTrash.find(v => v.collection.id === params.collectionId);
|
||||
const collection = collectionService.collectionsTrash.value.find(
|
||||
v => v.collection.id === params.collectionId
|
||||
);
|
||||
let text = 'Collection is not exist';
|
||||
if (collection) {
|
||||
if (collection.userId) {
|
||||
@@ -75,21 +74,18 @@ export const Component = function CollectionPage() {
|
||||
}
|
||||
}, [
|
||||
collection,
|
||||
loading,
|
||||
collectionService.collectionsTrash.value,
|
||||
navigate,
|
||||
params.collectionId,
|
||||
pushNotification,
|
||||
workspace.blockSuiteWorkspace,
|
||||
workspace.id,
|
||||
]);
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
if (!collection) {
|
||||
return null;
|
||||
}
|
||||
return isEmpty(collection) ? (
|
||||
<Placeholder collection={collection} workspaceId={workspace.id} />
|
||||
<Placeholder collection={collection} />
|
||||
) : (
|
||||
<AllPage activeFilter="collections" />
|
||||
);
|
||||
@@ -97,24 +93,19 @@ export const Component = function CollectionPage() {
|
||||
|
||||
const isWindowsDesktop = environment.isDesktop && environment.isWindows;
|
||||
|
||||
const Placeholder = ({
|
||||
collection,
|
||||
workspaceId,
|
||||
}: {
|
||||
collection: Collection;
|
||||
workspaceId: string;
|
||||
}) => {
|
||||
const { updateCollection } = useCollectionManager(collectionsCRUDAtom);
|
||||
const Placeholder = ({ collection }: { collection: Collection }) => {
|
||||
const workspace = useService(Workspace);
|
||||
const collectionService = useCollectionManager(useService(CollectionService));
|
||||
const { node, open } = useEditCollection(useAllPageListConfig());
|
||||
const { jumpToCollections } = useNavigateHelper();
|
||||
const openPageEdit = useAsyncCallback(async () => {
|
||||
const ret = await open({ ...collection }, 'page');
|
||||
updateCollection(ret);
|
||||
}, [open, collection, updateCollection]);
|
||||
collectionService.updateCollection(ret);
|
||||
}, [open, collection, collectionService]);
|
||||
const openRuleEdit = useAsyncCallback(async () => {
|
||||
const ret = await open({ ...collection }, 'rule');
|
||||
updateCollection(ret);
|
||||
}, [collection, open, updateCollection]);
|
||||
collectionService.updateCollection(ret);
|
||||
}, [collection, open, collectionService]);
|
||||
const [showTips, setShowTips] = useState(false);
|
||||
useEffect(() => {
|
||||
setShowTips(!localStorage.getItem('hide-empty-collection-help-info'));
|
||||
@@ -127,8 +118,8 @@ const Placeholder = ({
|
||||
const leftSidebarOpen = useAtomValue(appSidebarOpenAtom);
|
||||
|
||||
const handleJumpToCollections = useCallback(() => {
|
||||
jumpToCollections(workspaceId);
|
||||
}, [jumpToCollections, workspaceId]);
|
||||
jumpToCollections(workspace.id);
|
||||
}, [jumpToCollections, workspace]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -11,9 +11,9 @@ import { JournalTodayButton } from '@affine/core/components/blocksuite/block-sui
|
||||
import { PageHeaderMenuButton } from '@affine/core/components/blocksuite/block-suite-header/menu';
|
||||
import { EditorModeSwitch } from '@affine/core/components/blocksuite/block-suite-mode-switch';
|
||||
import { useJournalInfoHelper } from '@affine/core/hooks/use-journal';
|
||||
import type { Workspace } from '@affine/workspace';
|
||||
import { RightSidebarIcon } from '@blocksuite/icons';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import type { Workspace } from '@toeverything/infra';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
|
||||
import { ResizePanel } from '@affine/component/resize-panel';
|
||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { useWorkspaceStatus } from '@affine/core/hooks/use-workspace-status';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { WorkspaceSubPath } from '@affine/core/shared';
|
||||
import { globalBlockSuiteSchema, SyncEngineStep } from '@affine/workspace';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import {
|
||||
BookmarkService,
|
||||
customImageProxyMiddleware,
|
||||
ImageService,
|
||||
} from '@blocksuite/blocks';
|
||||
import type { AffineEditorContainer } from '@blocksuite/presets';
|
||||
import type { Page, Workspace } from '@blocksuite/store';
|
||||
import { appSettingAtom } from '@toeverything/infra/atom';
|
||||
import type { Page as BlockSuitePage } from '@blocksuite/store';
|
||||
import {
|
||||
globalBlockSuiteSchema,
|
||||
Page,
|
||||
PageListService,
|
||||
PageManager,
|
||||
useLiveData,
|
||||
useServiceOptional,
|
||||
} from '@toeverything/infra';
|
||||
import { appSettingAtom, Workspace } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import {
|
||||
memo,
|
||||
@@ -20,13 +26,12 @@ import {
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import type { Map as YMap } from 'yjs';
|
||||
|
||||
import { setPageModeAtom } from '../../../atoms';
|
||||
import { collectionsCRUDAtom } from '../../../atoms/collections';
|
||||
import { currentModeAtom, currentPageIdAtom } from '../../../atoms/mode';
|
||||
import { AffineErrorBoundary } from '../../../components/affine/affine-error-boundary';
|
||||
import { HubIsland } from '../../../components/affine/hub-island';
|
||||
@@ -42,7 +47,8 @@ import { TopTip } from '../../../components/top-tip';
|
||||
import { useRegisterBlocksuiteEditorCommands } from '../../../hooks/affine/use-register-blocksuite-editor-commands';
|
||||
import { usePageDocumentTitle } from '../../../hooks/use-global-state';
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
import { performanceRenderLogger } from '../../../shared';
|
||||
import { CurrentPageService } from '../../../modules/page';
|
||||
import { performanceRenderLogger, WorkspaceSubPath } from '../../../shared';
|
||||
import { PageNotFound } from '../../404';
|
||||
import * as styles from './detail-page.css';
|
||||
import { DetailPageHeader, RightSidebarHeader } from './detail-page-header';
|
||||
@@ -104,10 +110,11 @@ const DetailPageLayout = ({
|
||||
);
|
||||
};
|
||||
|
||||
const DetailPageImpl = memo(function DetailPageImpl({ page }: { page: Page }) {
|
||||
const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
const page = useService(Page);
|
||||
const currentPageId = page.id;
|
||||
const { openPage, jumpToSubPath } = useNavigateHelper();
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||
|
||||
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
||||
@@ -116,14 +123,15 @@ const DetailPageImpl = memo(function DetailPageImpl({ page }: { page: Page }) {
|
||||
|
||||
const isInTrash = pageMeta?.trash;
|
||||
|
||||
const { setTemporaryFilter } = useCollectionManager(collectionsCRUDAtom);
|
||||
const collectionService = useService(CollectionService);
|
||||
const { setTemporaryFilter } = useCollectionManager(collectionService);
|
||||
const mode = useAtomValue(currentModeAtom);
|
||||
const setPageMode = useSetAtom(setPageModeAtom);
|
||||
useRegisterBlocksuiteEditorCommands(currentPageId, mode);
|
||||
usePageDocumentTitle(pageMeta);
|
||||
|
||||
const onLoad = useCallback(
|
||||
(page: Page, editor: AffineEditorContainer) => {
|
||||
(page: BlockSuitePage, editor: AffineEditorContainer) => {
|
||||
try {
|
||||
// todo(joooye34): improve the following migration code
|
||||
const surfaceBlock = page.getBlockByFlavour('affine:surface')[0];
|
||||
@@ -182,7 +190,7 @@ const DetailPageImpl = memo(function DetailPageImpl({ page }: { page: Page }) {
|
||||
header={
|
||||
<>
|
||||
<DetailPageHeader
|
||||
page={page}
|
||||
page={page.blockSuitePage}
|
||||
workspace={currentWorkspace}
|
||||
showSidebarSwitch={!isInTrash}
|
||||
/>
|
||||
@@ -206,8 +214,14 @@ const DetailPageImpl = memo(function DetailPageImpl({ page }: { page: Page }) {
|
||||
sidebar={
|
||||
!isInTrash ? (
|
||||
<div className={styles.sidebarContainerInner}>
|
||||
<RightSidebarHeader workspace={currentWorkspace} page={page} />
|
||||
<EditorSidebar workspace={blockSuiteWorkspace} page={page} />
|
||||
<RightSidebarHeader
|
||||
workspace={currentWorkspace}
|
||||
page={page.blockSuitePage}
|
||||
/>
|
||||
<EditorSidebar
|
||||
workspace={blockSuiteWorkspace}
|
||||
page={page.blockSuitePage}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
@@ -221,38 +235,44 @@ const DetailPageImpl = memo(function DetailPageImpl({ page }: { page: Page }) {
|
||||
);
|
||||
});
|
||||
|
||||
const useForceUpdate = () => {
|
||||
const [, setCount] = useState(0);
|
||||
return useCallback(() => setCount(count => count + 1), []);
|
||||
};
|
||||
const useSafePage = (workspace: Workspace, pageId: string) => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
useEffect(() => {
|
||||
const disposable = workspace.slots.pagesUpdated.on(() => {
|
||||
forceUpdate();
|
||||
});
|
||||
return disposable.dispose;
|
||||
}, [pageId, workspace.slots.pagesUpdated, forceUpdate]);
|
||||
|
||||
return workspace.getPage(pageId);
|
||||
};
|
||||
|
||||
export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => {
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentSyncEngineStep = useWorkspaceStatus(
|
||||
currentWorkspace,
|
||||
s => s.engine.sync.step
|
||||
const pageListService = useService(PageListService);
|
||||
|
||||
const pageListReady = useLiveData(pageListService.isReady);
|
||||
|
||||
const pageMetas = useLiveData(pageListService.pages);
|
||||
|
||||
const pageMeta = useMemo(
|
||||
() => pageMetas.find(page => page.id === pageId),
|
||||
[pageMetas, pageId]
|
||||
);
|
||||
|
||||
const pageManager = useService(PageManager);
|
||||
const currentPageService = useService(CurrentPageService);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pageMeta) {
|
||||
return;
|
||||
}
|
||||
const { page, release } = pageManager.open(pageMeta);
|
||||
currentPageService.openPage(page);
|
||||
return () => {
|
||||
currentPageService.closePage();
|
||||
release();
|
||||
};
|
||||
}, [currentPageService, pageManager, pageMeta]);
|
||||
|
||||
const page = useServiceOptional(Page);
|
||||
|
||||
const currentWorkspace = useService(Workspace);
|
||||
|
||||
// set sync engine priority target
|
||||
useEffect(() => {
|
||||
currentWorkspace.setPriorityRule(id => id.endsWith(pageId));
|
||||
}, [pageId, currentWorkspace]);
|
||||
|
||||
const page = useSafePage(currentWorkspace?.blockSuiteWorkspace, pageId);
|
||||
|
||||
// if sync engine has been synced and the page is null, show 404 page.
|
||||
if (currentSyncEngineStep === SyncEngineStep.Synced && !page) {
|
||||
if (pageListReady && !page) {
|
||||
return <PageNotFound />;
|
||||
}
|
||||
|
||||
@@ -266,7 +286,7 @@ export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => {
|
||||
});
|
||||
}
|
||||
|
||||
return <DetailPageImpl page={page} />;
|
||||
return <DetailPageImpl />;
|
||||
};
|
||||
|
||||
export const Component = () => {
|
||||
|
||||
@@ -2,8 +2,8 @@ import { IconButton } from '@affine/component';
|
||||
import { useJournalInfoHelper } from '@affine/core/hooks/use-journal';
|
||||
import { useWorkspaceEnabledFeatures } from '@affine/core/hooks/use-workspace-features';
|
||||
import { FeatureType } from '@affine/graphql';
|
||||
import type { Workspace } from '@affine/workspace/workspace';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import type { Workspace } from '@toeverything/infra';
|
||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { WorkspaceFallback } from '@affine/component/workspace';
|
||||
import { useWorkspace } from '@affine/core/hooks/use-workspace';
|
||||
import {
|
||||
currentWorkspaceAtom,
|
||||
workspaceListAtom,
|
||||
workspaceListLoadingStatusAtom,
|
||||
workspaceManagerAtom,
|
||||
} from '@affine/core/modules/workspace';
|
||||
import { type Workspace } from '@affine/workspace';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
Workspace,
|
||||
WorkspaceListService,
|
||||
WorkspaceManager,
|
||||
} from '@toeverything/infra';
|
||||
import { useService, useServiceOptional } from '@toeverything/infra/di';
|
||||
import { useLiveData } from '@toeverything/infra/livedata';
|
||||
import { type ReactElement, Suspense, useEffect, useMemo } from 'react';
|
||||
import { Outlet, useParams } from 'react-router-dom';
|
||||
|
||||
import { AffineErrorBoundary } from '../../components/affine/affine-error-boundary';
|
||||
import { WorkspaceLayout } from '../../layouts/workspace-layout';
|
||||
import { CurrentWorkspaceService } from '../../modules/workspace/current-workspace';
|
||||
import { performanceRenderLogger } from '../../shared';
|
||||
import { PageNotFound } from '../404';
|
||||
|
||||
@@ -30,29 +30,27 @@ declare global {
|
||||
export const Component = (): ReactElement => {
|
||||
performanceRenderLogger.info('WorkspaceLayout');
|
||||
|
||||
const [
|
||||
_ /* read this atom here to make sure children refresh when currentWorkspace changed */,
|
||||
setCurrentWorkspace,
|
||||
] = useAtom(currentWorkspaceAtom);
|
||||
const currentWorkspaceService = useService(CurrentWorkspaceService);
|
||||
|
||||
const params = useParams();
|
||||
|
||||
const list = useAtomValue(workspaceListAtom);
|
||||
const listLoading = useAtomValue(workspaceListLoadingStatusAtom);
|
||||
const workspaceManager = useAtomValue(workspaceManagerAtom);
|
||||
const { workspaceList, loading: listLoading } = useLiveData(
|
||||
useService(WorkspaceListService).status
|
||||
);
|
||||
const workspaceManager = useService(WorkspaceManager);
|
||||
|
||||
const meta = useMemo(() => {
|
||||
return list.find(({ id }) => id === params.workspaceId);
|
||||
}, [list, params.workspaceId]);
|
||||
return workspaceList.find(({ id }) => id === params.workspaceId);
|
||||
}, [workspaceList, params.workspaceId]);
|
||||
|
||||
const workspace = useWorkspace(meta);
|
||||
|
||||
useEffect(() => {
|
||||
if (!workspace) {
|
||||
setCurrentWorkspace(null);
|
||||
currentWorkspaceService.closeWorkspace();
|
||||
return undefined;
|
||||
}
|
||||
setCurrentWorkspace(workspace);
|
||||
currentWorkspaceService.openWorkspace(workspace ?? null);
|
||||
|
||||
// for debug purpose
|
||||
window.currentWorkspace = workspace;
|
||||
@@ -65,14 +63,16 @@ export const Component = (): ReactElement => {
|
||||
);
|
||||
|
||||
localStorage.setItem('last_workspace_id', workspace.id);
|
||||
}, [setCurrentWorkspace, meta, workspaceManager, workspace]);
|
||||
}, [meta, workspaceManager, workspace, currentWorkspaceService]);
|
||||
|
||||
const currentWorkspace = useServiceOptional(Workspace);
|
||||
|
||||
// if listLoading is false, we can show 404 page, otherwise we should show loading page.
|
||||
if (listLoading === false && meta === undefined) {
|
||||
return <PageNotFound />;
|
||||
}
|
||||
|
||||
if (!workspace) {
|
||||
if (!currentWorkspace) {
|
||||
return <WorkspaceFallback key="workspaceLoading" />;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { TagListHeader, useTagMetas } from '@affine/core/components/page-list';
|
||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useService, Workspace } from '@toeverything/infra';
|
||||
import { useMemo } from 'react';
|
||||
import { type LoaderFunction, redirect, useParams } from 'react-router-dom';
|
||||
|
||||
@@ -19,7 +18,7 @@ export const loader: LoaderFunction = async args => {
|
||||
|
||||
export const Component = function TagPage() {
|
||||
const params = useParams();
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const pageMetas = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace);
|
||||
const { tagUsageCounts } = useTagMetas(
|
||||
currentWorkspace.blockSuiteWorkspace,
|
||||
|
||||
@@ -14,13 +14,13 @@ import { Header } from '@affine/core/components/pure/header';
|
||||
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
|
||||
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper';
|
||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { DeleteIcon } from '@blocksuite/icons';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useCallback } from 'react';
|
||||
import { type LoaderFunction } from 'react-router-dom';
|
||||
import { NIL } from 'uuid';
|
||||
@@ -61,7 +61,7 @@ export const loader: LoaderFunction = async () => {
|
||||
};
|
||||
|
||||
export const TrashPage = () => {
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||
assertExists(blockSuiteWorkspace);
|
||||
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import {
|
||||
currentWorkspaceAtom,
|
||||
waitForCurrentWorkspaceAtom,
|
||||
workspaceListAtom,
|
||||
} from '@affine/core/modules/workspace';
|
||||
import { WorkspaceSubPath } from '@affine/core/shared';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { WorkspaceManager } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useLiveData } from '@toeverything/infra/livedata';
|
||||
import { useAtom } from 'jotai';
|
||||
import type { ReactElement } from 'react';
|
||||
import { lazy, Suspense, useCallback } from 'react';
|
||||
|
||||
import type { SettingAtom } from '../atoms';
|
||||
import {
|
||||
authAtom,
|
||||
openCreateWorkspaceModalAtom,
|
||||
openDisableCloudAlertModalAtom,
|
||||
openSettingModalAtom,
|
||||
openSignOutModalAtom,
|
||||
type SettingAtom,
|
||||
} from '../atoms';
|
||||
import { PaymentDisableModal } from '../components/affine/payment-disable';
|
||||
import { useAsyncCallback } from '../hooks/affine-async-hooks';
|
||||
import { useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||
import { CurrentWorkspaceService } from '../modules/workspace/current-workspace';
|
||||
import { WorkspaceSubPath } from '../shared';
|
||||
import { signOutCloud } from '../utils/cloud-utils';
|
||||
|
||||
const SettingModal = lazy(() =>
|
||||
@@ -28,6 +26,7 @@ const SettingModal = lazy(() =>
|
||||
default: module.SettingModal,
|
||||
}))
|
||||
);
|
||||
|
||||
const Auth = lazy(() =>
|
||||
import('../components/affine/auth').then(module => ({
|
||||
default: module.AuthModal,
|
||||
@@ -80,10 +79,8 @@ const CloudQuotaModal = lazy(() =>
|
||||
);
|
||||
|
||||
export const Setting = () => {
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const [{ open, workspaceMetadata, activeTab }, setOpenSettingModalAtom] =
|
||||
useAtom(openSettingModalAtom);
|
||||
assertExists(currentWorkspace);
|
||||
|
||||
const onSettingClick = useCallback(
|
||||
({
|
||||
@@ -162,7 +159,9 @@ export const AuthModal = (): ReactElement => {
|
||||
};
|
||||
|
||||
export function CurrentWorkspaceModals() {
|
||||
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
|
||||
const currentWorkspace = useLiveData(
|
||||
useService(CurrentWorkspaceService).currentWorkspace
|
||||
);
|
||||
const [openDisableCloudAlertModal, setOpenDisableCloudAlertModal] = useAtom(
|
||||
openDisableCloudAlertModalAtom
|
||||
);
|
||||
@@ -195,8 +194,12 @@ export function CurrentWorkspaceModals() {
|
||||
export const SignOutConfirmModal = () => {
|
||||
const { openPage } = useNavigateHelper();
|
||||
const [open, setOpen] = useAtom(openSignOutModalAtom);
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom);
|
||||
const workspaceList = useAtomValue(workspaceListAtom);
|
||||
const currentWorkspace = useLiveData(
|
||||
useService(CurrentWorkspaceService).currentWorkspace
|
||||
);
|
||||
const workspaces = useLiveData(
|
||||
useService(WorkspaceManager).list.workspaceList
|
||||
);
|
||||
|
||||
const onConfirm = useAsyncCallback(async () => {
|
||||
setOpen(false);
|
||||
@@ -204,14 +207,14 @@ export const SignOutConfirmModal = () => {
|
||||
|
||||
// if current workspace is affine cloud, switch to local workspace
|
||||
if (currentWorkspace?.flavour === WorkspaceFlavour.AFFINE_CLOUD) {
|
||||
const localWorkspace = workspaceList.find(
|
||||
const localWorkspace = workspaces.find(
|
||||
w => w.flavour === WorkspaceFlavour.LOCAL
|
||||
);
|
||||
if (localWorkspace) {
|
||||
openPage(localWorkspace.id, WorkspaceSubPath.ALL);
|
||||
}
|
||||
}
|
||||
}, [currentWorkspace?.flavour, openPage, setOpen, workspaceList]);
|
||||
}, [currentWorkspace?.flavour, openPage, setOpen, workspaces]);
|
||||
|
||||
return (
|
||||
<SignOutModal open={open} onOpenChange={setOpen} onConfirm={onConfirm} />
|
||||
|
||||
46
packages/frontend/core/src/testing.ts
Normal file
46
packages/frontend/core/src/testing.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import type { Page as BlockSuitePage } from '@blocksuite/store';
|
||||
import {
|
||||
configureTestingInfraServices,
|
||||
PageManager,
|
||||
ServiceCollection,
|
||||
WorkspaceManager,
|
||||
} from '@toeverything/infra';
|
||||
|
||||
import { CurrentPageService } from './modules/page';
|
||||
import { CurrentWorkspaceService } from './modules/workspace';
|
||||
import { configureWebServices } from './web';
|
||||
|
||||
export async function configureTestingEnvironment() {
|
||||
const serviceCollection = new ServiceCollection();
|
||||
|
||||
configureWebServices(serviceCollection);
|
||||
configureTestingInfraServices(serviceCollection);
|
||||
|
||||
const rootServices = serviceCollection.provider();
|
||||
|
||||
const workspaceManager = rootServices.get(WorkspaceManager);
|
||||
|
||||
const { workspace } = workspaceManager.open(
|
||||
await workspaceManager.createWorkspace(WorkspaceFlavour.LOCAL, async ws => {
|
||||
const initPage = async (page: BlockSuitePage) => {
|
||||
await page.load();
|
||||
const pageBlockId = page.addBlock('affine:page', {
|
||||
title: new page.Text(''),
|
||||
});
|
||||
const frameId = page.addBlock('affine:note', {}, pageBlockId);
|
||||
page.addBlock('affine:paragraph', {}, frameId);
|
||||
};
|
||||
await initPage(ws.createPage({ id: 'page0' }));
|
||||
})
|
||||
);
|
||||
|
||||
await workspace.engine.sync.waitForSynced();
|
||||
|
||||
const { page } = workspace.services.get(PageManager).openByPageId('page0');
|
||||
|
||||
rootServices.get(CurrentWorkspaceService).openWorkspace(workspace);
|
||||
workspace.services.get(CurrentPageService).openPage(page);
|
||||
|
||||
return { services: rootServices, workspace, page };
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { Array as YArray } from 'yjs';
|
||||
|
||||
export const updateFirstOfYArray = <T>(
|
||||
array: YArray<T>,
|
||||
p: (value: T) => boolean,
|
||||
update: (value: T) => T
|
||||
) => {
|
||||
array.doc?.transact(() => {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const ele = array.get(i);
|
||||
if (p(ele)) {
|
||||
array.delete(i);
|
||||
array.insert(i, [update(ele)]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
15
packages/frontend/core/src/web.ts
Normal file
15
packages/frontend/core/src/web.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { configureWorkspaceImplServices } from '@affine/workspace-impl';
|
||||
import type { ServiceCollection } from '@toeverything/infra';
|
||||
import { configureInfraServices } from '@toeverything/infra';
|
||||
|
||||
import {
|
||||
configureBusinessServices,
|
||||
configureWebInfraServices,
|
||||
} from './modules/services';
|
||||
|
||||
export function configureWebServices(services: ServiceCollection) {
|
||||
configureInfraServices(services);
|
||||
configureWebInfraServices(services);
|
||||
configureBusinessServices(services);
|
||||
configureWorkspaceImplServices(services);
|
||||
}
|
||||
@@ -17,9 +17,6 @@
|
||||
{
|
||||
"path": "../../frontend/i18n"
|
||||
},
|
||||
{
|
||||
"path": "../../common/workspace"
|
||||
},
|
||||
{
|
||||
"path": "../../frontend/workspace-impl"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user