mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
refactor: workspace manager (#5060)
This commit is contained in:
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 = ({
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user