refactor(core): favorite adapter (#6285)

1. abstraction over favourites that supports different type of resources
2. sorting abstraction
This commit is contained in:
pengx17
2024-03-29 04:04:07 +00:00
parent 296362ced1
commit 5490944d04
26 changed files with 450 additions and 213 deletions

View File

@@ -18,6 +18,7 @@ import { TagService } from './tag';
import { Workbench } from './workbench';
import {
CurrentWorkspaceService,
FavoriteItemsAdapter,
WorkspaceLegacyProperties,
WorkspacePropertiesAdapter,
} from './workspace';
@@ -30,6 +31,7 @@ export function configureBusinessServices(services: ServiceCollection) {
.add(Navigator, [Workbench])
.add(RightSidebar, [GlobalState])
.add(WorkspacePropertiesAdapter, [Workspace])
.add(FavoriteItemsAdapter, [WorkspacePropertiesAdapter])
.add(CollectionService, [Workspace])
.add(WorkspaceLegacyProperties, [Workspace])
.add(TagService, [WorkspaceLegacyProperties, PageRecordList]);

View File

@@ -1,16 +1,17 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
// the adapter is to bridge the workspace rootdoc & native js bindings
import type { Y } from '@blocksuite/store';
import { createYProxy } from '@blocksuite/store';
import type { Workspace } from '@toeverything/infra';
import { createFractionalIndexingSortableHelper } from '@affine/core/utils';
import { createYProxy, type Y } from '@blocksuite/store';
import { LiveData, type Workspace } from '@toeverything/infra';
import { defaultsDeep } from 'lodash-es';
import { Observable } from 'rxjs';
import type {
WorkspaceAffineProperties,
WorkspaceFavoriteItem,
import {
PagePropertyType,
PageSystemPropertyId,
type WorkspaceAffineProperties,
type WorkspaceFavoriteItem,
} from './schema';
import { PagePropertyType, PageSystemPropertyId } from './schema';
const AFFINE_PROPERTIES_ID = 'affine:workspace-properties';
@@ -27,6 +28,7 @@ export class WorkspacePropertiesAdapter {
// provides a easy-to-use interface for workspace properties
public readonly proxy: WorkspaceAffineProperties;
public readonly properties: Y.Map<any>;
public readonly properties$: LiveData<WorkspaceAffineProperties>;
private ensuredRoot = false;
private ensuredPages = {} as Record<string, boolean>;
@@ -36,9 +38,25 @@ export class WorkspacePropertiesAdapter {
const rootDoc = workspace.docCollection.doc;
this.properties = rootDoc.getMap(AFFINE_PROPERTIES_ID);
this.proxy = createYProxy(this.properties);
this.properties$ = LiveData.from(
new Observable(observer => {
const update = () => {
requestAnimationFrame(() => {
observer.next(new Proxy(this.proxy, {}));
});
};
update();
this.properties.observeDeep(update);
return () => {
this.properties.unobserveDeep(update);
};
}),
this.proxy
);
}
private ensureRootProperties() {
public ensureRootProperties() {
if (this.ensuredRoot) {
return;
}
@@ -120,10 +138,6 @@ export class WorkspacePropertiesAdapter {
return this.pageProperties?.[pageId] ?? null;
}
isFavorite(id: string, type: WorkspaceFavoriteItem['type']) {
return this.favorites?.[id]?.type === type;
}
getJournalPageDateString(id: string) {
return this.pageProperties?.[id]?.system[PageSystemPropertyId.Journal]
?.value;
@@ -135,3 +149,119 @@ export class WorkspacePropertiesAdapter {
pageProperties!.system[PageSystemPropertyId.Journal].value = date;
}
}
export class FavoriteItemsAdapter {
constructor(private readonly adapter: WorkspacePropertiesAdapter) {
this.migrateFavorites();
}
readonly sorter = createFractionalIndexingSortableHelper<
WorkspaceFavoriteItem,
string
>(this);
static getFavItemKey(id: string, type: WorkspaceFavoriteItem['type']) {
return `${type}:${id}`;
}
favorites$ = this.adapter.properties$.map(() =>
this.getItems().filter(i => i.value)
);
getItems() {
return Object.values(this.adapter.favorites ?? {});
}
get favorites() {
return this.adapter.favorites;
}
get workspace() {
return this.adapter.workspace;
}
getItemId(item: WorkspaceFavoriteItem) {
return item.id;
}
getItemOrder(item: WorkspaceFavoriteItem) {
return item.order;
}
setItemOrder(item: WorkspaceFavoriteItem, order: string) {
item.order = order;
}
// read from the workspace meta and migrate to the properties
private migrateFavorites() {
// only migrate if favorites is empty
if (Object.keys(this.favorites ?? {}).length > 0) {
return;
}
// old favorited pages
const oldFavorites = this.workspace.docCollection.meta.docMetas
.filter(meta => meta.favorite)
.map(meta => meta.id);
this.adapter.transact(() => {
for (const id of oldFavorites) {
this.set(id, 'doc', true);
}
});
}
isFavorite(id: string, type: WorkspaceFavoriteItem['type']) {
const existing = this.getFavoriteItem(id, type);
return existing?.value ?? false;
}
isFavorite$(id: string, type: WorkspaceFavoriteItem['type']) {
return this.favorites$.map(() => {
return this.isFavorite(id, type);
});
}
private getFavoriteItem(id: string, type: WorkspaceFavoriteItem['type']) {
return this.favorites?.[FavoriteItemsAdapter.getFavItemKey(id, type)];
}
// add or set a new fav item to the list. note the id added with prefix
set(
id: string,
type: WorkspaceFavoriteItem['type'],
value: boolean,
order?: string
) {
this.adapter.ensureRootProperties();
if (!this.favorites) {
throw new Error('Favorites is not initialized');
}
const existing = this.getFavoriteItem(id, type);
if (!existing) {
this.favorites[FavoriteItemsAdapter.getFavItemKey(id, type)] = {
id,
type,
value: true,
order: order ?? this.sorter.getNewItemOrder(),
};
} else {
Object.assign(existing, {
value,
order: order ?? existing.order,
});
}
}
toggle(id: string, type: WorkspaceFavoriteItem['type']) {
this.set(id, type, !this.isFavorite(id, type));
}
remove(id: string, type: WorkspaceFavoriteItem['type']) {
this.adapter.ensureRootProperties();
const existing = this.getFavoriteItem(id, type);
if (existing) {
existing.value = false;
}
}
}

View File

@@ -64,8 +64,9 @@ export type PageInfoTagsItem = z.infer<typeof PageInfoTagsItemSchema>;
// ====== workspace properties schema ======
export const WorkspaceFavoriteItemSchema = z.object({
id: z.string(),
order: z.number(),
type: z.enum(['page', 'collection']),
order: z.string(),
type: z.enum(['doc', 'collection']),
value: z.boolean(),
});
export type WorkspaceFavoriteItem = z.infer<typeof WorkspaceFavoriteItemSchema>;