feat: new collections (#4530)

Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
3720
2023-10-27 17:06:59 +08:00
committed by GitHub
parent 9fc0152cb1
commit ef8024c657
133 changed files with 8382 additions and 3743 deletions

View File

@@ -0,0 +1,210 @@
import type { CollectionsCRUDAtom } from '@affine/component/page-list';
import type { Collection, DeprecatedCollection } from '@affine/env/filter';
import { DisposableGroup } from '@blocksuite/global/utils';
import type { Workspace } from '@blocksuite/store';
import { currentWorkspaceAtom } from '@toeverything/infra/atom';
import { type DBSchema, openDB } from 'idb';
import { atom } from 'jotai';
import { atomWithObservable } from 'jotai/utils';
import { Observable } from 'rxjs';
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 currentWorkspacePromise = get(currentWorkspaceAtom);
const session = get(sessionAtom);
const userId = session?.data?.user.id ?? null;
const migrateCollectionsFromIdbData = async (
workspace: Workspace
): Promise<Collection[]> => {
workspace.awarenessStore.awareness.emit('change log');
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,
mode: 'rule',
filterList: v.filterList,
allowList: v.allowList ?? [],
pages: [],
};
});
};
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: DeprecatedCollection[] = [...view.values()];
//delete collections
view.clear();
return collections.map(v => {
return {
id: v.id,
name: v.name,
mode: 'rule',
filterList: v.filterList,
allowList: v.allowList ?? [],
pages: [],
};
});
}
return [];
};
return new Observable<BaseCollectionsDataType>(subscriber => {
const group = new DisposableGroup();
currentWorkspacePromise.then(async currentWorkspace => {
const collectionsFromLocal =
await migrateCollectionsFromIdbData(currentWorkspace);
const collectionFromUserSetting =
await migrateCollectionsFromUserData(currentWorkspace);
const workspaceSetting = getWorkspaceSetting(currentWorkspace);
if (collectionsFromLocal.length || collectionFromUserSetting.length) {
// migrate collections
workspaceSetting.addCollection(
...collectionFromUserSetting,
...collectionsFromLocal
);
}
subscriber.next({
loading: false,
collections: workspaceSetting.collections,
});
if (group.disposed) {
return;
}
const fn = () => {
subscriber.next({
loading: false,
collections: workspaceSetting.collections,
});
};
workspaceSetting.collectionsYArray.observe(fn);
group.add(() => {
workspaceSetting.collectionsYArray.unobserve(fn);
});
});
return () => {
group.dispose();
};
});
},
{ initialValue: { loading: true, collections: [] } }
);
export const collectionsCRUDAtom: CollectionsCRUDAtom = atom(get => {
const workspacePromise = get(currentWorkspaceAtom);
return {
addCollection: async (...collections) => {
const workspace = await workspacePromise;
getWorkspaceSetting(workspace).addCollection(...collections);
},
collections: get(pageCollectionBaseAtom).collections,
updateCollection: async (id, updater) => {
const workspace = await workspacePromise;
getWorkspaceSetting(workspace).updateCollection(id, updater);
},
deleteCollection: async (info, ...ids) => {
const workspace = await workspacePromise;
getWorkspaceSetting(workspace).deleteCollection(info, ...ids);
},
};
});

View File

@@ -2,12 +2,12 @@ import { atom } from 'jotai';
export type TrashModal = {
open: boolean;
pageId: string;
pageTitle: string;
pageIds: string[];
pageTitles: string[];
};
export const trashModalAtom = atom<TrashModal>({
open: false,
pageId: '',
pageTitle: '',
pageIds: [],
pageTitles: [],
});