refactor: workspace manager (#5060)

This commit is contained in:
EYHN
2023-12-15 07:20:50 +00:00
parent af15aa06d4
commit fe2851d3e9
217 changed files with 3605 additions and 4244 deletions

View File

@@ -1,11 +1,10 @@
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
import type { WorkspaceMetadata } from '@affine/workspace';
import { CollaborationIcon, SettingsIcon } from '@blocksuite/icons';
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import { getBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
import { useAtomValue } from 'jotai/react';
import { useWorkspaceBlobObjectUrl } from '@toeverything/hooks/use-workspace-blob';
import { useWorkspaceInfo } from '@toeverything/hooks/use-workspace-info';
import { useCallback } from 'react';
import { Avatar } from '../../../ui/avatar';
@@ -68,10 +67,10 @@ const WorkspaceType = ({ flavour, isOwner }: WorkspaceTypeProps) => {
};
export interface WorkspaceCardProps {
currentWorkspaceId: string | null;
meta: RootWorkspaceMetadata;
onClick: (workspaceId: string) => void;
onSettingClick: (workspaceId: string) => void;
currentWorkspaceId?: string | null;
meta: WorkspaceMetadata;
onClick: (metadata: WorkspaceMetadata) => void;
onSettingClick: (metadata: WorkspaceMetadata) => void;
isOwner?: boolean;
}
@@ -98,29 +97,31 @@ export const WorkspaceCard = ({
meta,
isOwner = true,
}: WorkspaceCardProps) => {
const [workspaceAtom] = getBlockSuiteWorkspaceAtom(meta.id);
const workspace = useAtomValue(workspaceAtom);
const [name] = useBlockSuiteWorkspaceName(workspace);
const [workspaceAvatar] = useBlockSuiteWorkspaceAvatarUrl(workspace);
const information = useWorkspaceInfo(meta);
const avatarUrl = useWorkspaceBlobObjectUrl(meta, information?.avatar);
const name = information?.name ?? UNTITLED_WORKSPACE_NAME;
return (
<StyledCard
data-testid="workspace-card"
onClick={useCallback(() => {
onClick(meta.id);
}, [onClick, meta.id])}
active={workspace.id === currentWorkspaceId}
onClick(meta);
}, [onClick, meta])}
active={meta.id === currentWorkspaceId}
>
<Avatar size={28} url={workspaceAvatar} name={name} colorfulFallback />
<Avatar size={28} url={avatarUrl} name={name} colorfulFallback />
<StyledWorkspaceInfo>
<StyledWorkspaceTitleArea style={{ display: 'flex' }}>
<StyledWorkspaceTitle>{name}</StyledWorkspaceTitle>
<StyledWorkspaceTitle>
{information?.name ?? UNTITLED_WORKSPACE_NAME}
</StyledWorkspaceTitle>
<StyledSettingLink
size="small"
className="setting-entry"
onClick={e => {
e.stopPropagation();
onSettingClick(meta.id);
onSettingClick(meta);
}}
withoutHoverStyle={true}
>

View File

@@ -12,7 +12,7 @@ import { expect, test } from 'vitest';
import { createDefaultFilter, vars } from '../filter/vars';
import {
type CollectionsCRUDAtom,
type CollectionsCRUD,
useCollectionManager,
} from '../use-collection-manager';
@@ -27,18 +27,18 @@ const baseAtom = atomWithObservable<Collection[]>(
}
);
const mockAtom: CollectionsCRUDAtom = atom(get => {
const mockAtom = atom(get => {
return {
collections: get(baseAtom),
addCollection: async (...collections) => {
addCollection: (...collections) => {
const prev = collectionsSubject.value;
collectionsSubject.next([...collections, ...prev]);
},
deleteCollection: async (...ids) => {
deleteCollection: (...ids) => {
const prev = collectionsSubject.value;
collectionsSubject.next(prev.filter(v => !ids.includes(v.id)));
},
updateCollection: async (id, updater) => {
updateCollection: (id, updater) => {
const prev = collectionsSubject.value;
collectionsSubject.next(
prev.map(v => {
@@ -49,14 +49,14 @@ const mockAtom: CollectionsCRUDAtom = atom(get => {
})
);
},
};
} satisfies CollectionsCRUD;
});
test('useAllPageSetting', async () => {
const settingHook = renderHook(() => useCollectionManager(mockAtom));
const prevCollection = settingHook.result.current.currentCollection;
expect(settingHook.result.current.savedCollections).toEqual([]);
await settingHook.result.current.updateCollection({
settingHook.result.current.updateCollection({
...settingHook.result.current.currentCollection,
filterList: [createDefaultFilter(vars[0], defaultMeta)],
});
@@ -66,7 +66,7 @@ test('useAllPageSetting', async () => {
expect(nextCollection.filterList).toEqual([
createDefaultFilter(vars[0], defaultMeta),
]);
await settingHook.result.current.createCollection({
settingHook.result.current.createCollection({
...settingHook.result.current.currentCollection,
id: '1',
});

View File

@@ -33,22 +33,21 @@ export const currentCollectionAtom = atomWithReset<string>(NIL);
export type Updater<T> = (value: T) => T;
export type CollectionUpdater = Updater<Collection>;
export type CollectionsCRUD = {
addCollection: (...collections: Collection[]) => Promise<void>;
addCollection: (...collections: Collection[]) => void;
collections: Collection[];
updateCollection: (id: string, updater: CollectionUpdater) => Promise<void>;
deleteCollection: (
info: DeleteCollectionInfo,
...ids: string[]
) => Promise<void>;
updateCollection: (id: string, updater: CollectionUpdater) => void;
deleteCollection: (info: DeleteCollectionInfo, ...ids: string[]) => void;
};
export type CollectionsCRUDAtom = Atom<CollectionsCRUD>;
export type CollectionsCRUDAtom = Atom<
Promise<CollectionsCRUD> | CollectionsCRUD
>;
export const useSavedCollections = (collectionAtom: CollectionsCRUDAtom) => {
const [{ collections, addCollection, deleteCollection, updateCollection }] =
useAtom(collectionAtom);
const addPage = useCallback(
async (collectionId: string, pageId: string) => {
await updateCollection(collectionId, old => {
(collectionId: string, pageId: string) => {
updateCollection(collectionId, old => {
return {
...old,
allowList: [pageId, ...(old.allowList ?? [])],
@@ -79,11 +78,11 @@ export const useCollectionManager = (collectionsAtom: CollectionsCRUDAtom) => {
defaultCollectionAtom
);
const update = useCallback(
async (collection: Collection) => {
(collection: Collection) => {
if (collection.id === NIL) {
updateDefaultCollection(collection);
} else {
await updateCollection(collection.id, () => collection);
updateCollection(collection.id, () => collection);
}
},
[updateDefaultCollection, updateCollection]

View File

@@ -35,14 +35,10 @@ export const CollectionList = ({
const [collection, setCollection] = useState<Collection>();
const onChange = useCallback(
(filterList: Filter[]) => {
setting
.updateCollection({
...setting.currentCollection,
filterList,
})
.catch(err => {
console.error(err);
});
setting.updateCollection({
...setting.currentCollection,
filterList,
});
},
[setting]
);
@@ -53,8 +49,8 @@ export const CollectionList = ({
}, []);
const onConfirm = useCallback(
async (view: Collection) => {
await setting.updateCollection(view);
(view: Collection) => {
setting.updateCollection(view);
closeUpdateCollectionModal(false);
},
[closeUpdateCollectionModal, setting]

View File

@@ -107,9 +107,7 @@ export const CollectionOperations = ({
),
name: t['Delete'](),
click: () => {
setting.deleteCollection(info, collection.id).catch(err => {
console.error(err);
});
setting.deleteCollection(info, collection.id);
},
type: 'danger',
},

View File

@@ -10,7 +10,7 @@ export interface CreateCollectionModalProps {
title?: string;
onConfirmText?: string;
init: string;
onConfirm: (title: string) => Promise<void>;
onConfirm: (title: string) => void;
open: boolean;
showTips?: boolean;
onOpenChange: (open: boolean) => void;
@@ -27,13 +27,8 @@ export const CreateCollectionModal = ({
const t = useAFFiNEI18N();
const onConfirmTitle = useCallback(
(title: string) => {
onConfirm(title)
.then(() => {
onOpenChange(false);
})
.catch(err => {
console.error(err);
});
onConfirm(title);
onOpenChange(false);
},
[onConfirm, onOpenChange]
);

View File

@@ -19,7 +19,7 @@ export interface EditCollectionModalProps {
open: boolean;
mode?: EditCollectionMode;
onOpenChange: (open: boolean) => void;
onConfirm: (view: Collection) => Promise<void>;
onConfirm: (view: Collection) => void;
allPageListConfig: AllPageListConfig;
}
@@ -45,13 +45,7 @@ export const EditCollectionModal = ({
const t = useAFFiNEI18N();
const onConfirmOnCollection = useCallback(
(view: Collection) => {
onConfirm(view)
.then(() => {
onOpenChange(false);
})
.catch(err => {
console.error(err);
});
onConfirm(view);
onOpenChange(false);
},
[onConfirm, onOpenChange]

View File

@@ -9,7 +9,7 @@ import { createEmptyCollection } from '../use-collection-manager';
import { useEditCollectionName } from './use-edit-collection';
interface SaveAsCollectionButtonProps {
onConfirm: (collection: Collection) => Promise<void>;
onConfirm: (collection: Collection) => void;
}
export const SaveAsCollectionButton = ({

View File

@@ -40,9 +40,7 @@ export const useActions = ({
name: 'delete',
tooltip: t['com.affine.collection-bar.action.tooltip.delete'](),
click: () => {
setting.deleteCollection(info, collection.id).catch(err => {
console.error(err);
});
setting.deleteCollection(info, collection.id);
},
},
];

View File

@@ -12,7 +12,7 @@ export const useEditCollection = (config: AllPageListConfig) => {
const [data, setData] = useState<{
collection: Collection;
mode?: 'page' | 'rule';
onConfirm: (collection: Collection) => Promise<void>;
onConfirm: (collection: Collection) => void;
}>();
const close = useCallback(() => setData(undefined), []);
@@ -35,7 +35,7 @@ export const useEditCollection = (config: AllPageListConfig) => {
setData({
collection,
mode,
onConfirm: async collection => {
onConfirm: collection => {
res(collection);
},
});
@@ -52,7 +52,7 @@ export const useEditCollectionName = ({
}) => {
const [data, setData] = useState<{
name: string;
onConfirm: (name: string) => Promise<void>;
onConfirm: (name: string) => void;
}>();
const close = useCallback(() => setData(undefined), []);
@@ -71,7 +71,7 @@ export const useEditCollectionName = ({
new Promise<string>(res => {
setData({
name,
onConfirm: async collection => {
onConfirm: collection => {
res(collection);
},
});

View File

@@ -1,8 +1,4 @@
import type {
AffineCloudWorkspace,
LocalWorkspace,
} from '@affine/env/workspace';
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
import type { WorkspaceMetadata } from '@affine/workspace';
import type { DragEndEvent } from '@dnd-kit/core';
import {
DndContext,
@@ -26,17 +22,17 @@ import { workspaceItemStyle } from './index.css';
export interface WorkspaceListProps {
disabled?: boolean;
currentWorkspaceId: string | null;
items: (AffineCloudWorkspace | LocalWorkspace)[];
onClick: (workspaceId: string) => void;
onSettingClick: (workspaceId: string) => void;
currentWorkspaceId?: string | null;
items: WorkspaceMetadata[];
onClick: (workspaceMetadata: WorkspaceMetadata) => void;
onSettingClick: (workspaceMetadata: WorkspaceMetadata) => void;
onDragEnd: (event: DragEndEvent) => void;
useIsWorkspaceOwner?: (workspaceId: string) => boolean;
useIsWorkspaceOwner?: (workspaceMetadata: WorkspaceMetadata) => boolean;
}
interface SortableWorkspaceItemProps extends Omit<WorkspaceListProps, 'items'> {
item: RootWorkspaceMetadata;
useIsWorkspaceOwner?: (workspaceId: string) => boolean;
item: WorkspaceMetadata;
useIsWorkspaceOwner?: (workspaceMetadata: WorkspaceMetadata) => boolean;
}
const SortableWorkspaceItem = ({
@@ -62,7 +58,7 @@ const SortableWorkspaceItem = ({
}),
[disabled, transform, transition]
);
const isOwner = useIsWorkspaceOwner?.(item.id);
const isOwner = useIsWorkspaceOwner?.(item);
return (
<div
className={workspaceItemStyle}