mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-17 22:37:04 +08:00
feat(core): loading ui for favorite and organize (#7700)
This commit is contained in:
28
packages/common/infra/src/modules/db/entities/db.ts
Normal file
28
packages/common/infra/src/modules/db/entities/db.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Entity } from '../../../framework';
|
||||||
|
import type { DBSchemaBuilder, TableMap } from '../../../orm';
|
||||||
|
import { Table } from './table';
|
||||||
|
|
||||||
|
export class DB<Schema extends DBSchemaBuilder> extends Entity<{
|
||||||
|
db: TableMap<Schema>;
|
||||||
|
schema: Schema;
|
||||||
|
storageDocId: (tableName: string) => string;
|
||||||
|
}> {
|
||||||
|
readonly db = this.props.db;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
Object.entries(this.props.schema).forEach(([tableName]) => {
|
||||||
|
const table = this.framework.createEntity(Table, {
|
||||||
|
table: this.db[tableName],
|
||||||
|
storageDocId: this.props.storageDocId(tableName),
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, tableName, {
|
||||||
|
get: () => table,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DBWithTables<Schema extends DBSchemaBuilder> = DB<Schema> & {
|
||||||
|
[K in keyof Schema]: Table<Schema[K]>;
|
||||||
|
};
|
||||||
33
packages/common/infra/src/modules/db/entities/table.ts
Normal file
33
packages/common/infra/src/modules/db/entities/table.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Entity } from '../../../framework';
|
||||||
|
import type { Table as OrmTable, TableSchemaBuilder } from '../../../orm/core';
|
||||||
|
import type { WorkspaceService } from '../../workspace';
|
||||||
|
|
||||||
|
export class Table<Schema extends TableSchemaBuilder> extends Entity<{
|
||||||
|
table: OrmTable<Schema>;
|
||||||
|
storageDocId: string;
|
||||||
|
}> {
|
||||||
|
readonly table = this.props.table;
|
||||||
|
|
||||||
|
constructor(private readonly workspaceService: WorkspaceService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
isSyncing$ = this.workspaceService.workspace.engine.doc
|
||||||
|
.docState$(this.props.storageDocId)
|
||||||
|
.map(docState => docState.syncing);
|
||||||
|
|
||||||
|
isLoading$ = this.workspaceService.workspace.engine.doc
|
||||||
|
.docState$(this.props.storageDocId)
|
||||||
|
.map(docState => docState.loading);
|
||||||
|
|
||||||
|
create = this.table.create.bind(this.table);
|
||||||
|
update = this.table.update.bind(this.table);
|
||||||
|
get = this.table.get.bind(this.table);
|
||||||
|
// eslint-disable-next-line rxjs/finnish
|
||||||
|
get$ = this.table.get$.bind(this.table);
|
||||||
|
find = this.table.find.bind(this.table);
|
||||||
|
// eslint-disable-next-line rxjs/finnish
|
||||||
|
find$ = this.table.find$.bind(this.table);
|
||||||
|
keys = this.table.keys.bind(this.table);
|
||||||
|
delete = this.table.delete.bind(this.table);
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { Framework } from '../../framework';
|
import type { Framework } from '../../framework';
|
||||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||||
|
import { DB } from './entities/db';
|
||||||
|
import { Table } from './entities/table';
|
||||||
import { WorkspaceDBService } from './services/db';
|
import { WorkspaceDBService } from './services/db';
|
||||||
|
|
||||||
export { AFFiNE_WORKSPACE_DB_SCHEMA } from './schema';
|
export { AFFiNE_WORKSPACE_DB_SCHEMA } from './schema';
|
||||||
@@ -8,5 +10,7 @@ export { WorkspaceDBService } from './services/db';
|
|||||||
export function configureWorkspaceDBModule(framework: Framework) {
|
export function configureWorkspaceDBModule(framework: Framework) {
|
||||||
framework
|
framework
|
||||||
.scope(WorkspaceScope)
|
.scope(WorkspaceScope)
|
||||||
.service(WorkspaceDBService, [WorkspaceService]);
|
.service(WorkspaceDBService, [WorkspaceService])
|
||||||
|
.entity(DB)
|
||||||
|
.entity(Table, [WorkspaceService]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Doc as YDoc } from 'yjs';
|
import { Doc as YDoc } from 'yjs';
|
||||||
|
|
||||||
import { Service } from '../../../framework';
|
import { Service } from '../../../framework';
|
||||||
import { createORMClient, type TableMap, YjsDBAdapter } from '../../../orm';
|
import { createORMClient, YjsDBAdapter } from '../../../orm';
|
||||||
import { ObjectPool } from '../../../utils';
|
import { ObjectPool } from '../../../utils';
|
||||||
import type { WorkspaceService } from '../../workspace';
|
import type { WorkspaceService } from '../../workspace';
|
||||||
|
import { DB, type DBWithTables } from '../entities/db';
|
||||||
import { AFFiNE_WORKSPACE_DB_SCHEMA } from '../schema';
|
import { AFFiNE_WORKSPACE_DB_SCHEMA } from '../schema';
|
||||||
import { AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA } from '../schema/schema';
|
import { AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA } from '../schema/schema';
|
||||||
|
|
||||||
@@ -11,11 +12,13 @@ const WorkspaceDBClient = createORMClient(AFFiNE_WORKSPACE_DB_SCHEMA);
|
|||||||
const WorkspaceUserdataDBClient = createORMClient(
|
const WorkspaceUserdataDBClient = createORMClient(
|
||||||
AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA
|
AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA
|
||||||
);
|
);
|
||||||
type WorkspaceUserdataDBClient = InstanceType<typeof WorkspaceUserdataDBClient>;
|
|
||||||
|
|
||||||
export class WorkspaceDBService extends Service {
|
export class WorkspaceDBService extends Service {
|
||||||
db: TableMap<AFFiNE_WORKSPACE_DB_SCHEMA>;
|
db: DBWithTables<AFFiNE_WORKSPACE_DB_SCHEMA>;
|
||||||
userdataDBPool = new ObjectPool<string, WorkspaceUserdataDBClient>({
|
userdataDBPool = new ObjectPool<
|
||||||
|
string,
|
||||||
|
DB<AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA>
|
||||||
|
>({
|
||||||
onDangling() {
|
onDangling() {
|
||||||
return false; // never release
|
return false; // never release
|
||||||
},
|
},
|
||||||
@@ -23,19 +26,27 @@ export class WorkspaceDBService extends Service {
|
|||||||
|
|
||||||
constructor(private readonly workspaceService: WorkspaceService) {
|
constructor(private readonly workspaceService: WorkspaceService) {
|
||||||
super();
|
super();
|
||||||
this.db = new WorkspaceDBClient(
|
this.db = this.framework.createEntity(DB<AFFiNE_WORKSPACE_DB_SCHEMA>, {
|
||||||
new YjsDBAdapter(AFFiNE_WORKSPACE_DB_SCHEMA, {
|
db: new WorkspaceDBClient(
|
||||||
getDoc: guid => {
|
new YjsDBAdapter(AFFiNE_WORKSPACE_DB_SCHEMA, {
|
||||||
const ydoc = new YDoc({
|
getDoc: guid => {
|
||||||
// guid format: db${workspaceId}${guid}
|
const ydoc = new YDoc({
|
||||||
guid: `db$${this.workspaceService.workspace.id}$${guid}`,
|
// guid format: db${workspaceId}${guid}
|
||||||
});
|
guid: `db$${this.workspaceService.workspace.id}$${guid}`,
|
||||||
this.workspaceService.workspace.engine.doc.addDoc(ydoc, false);
|
});
|
||||||
this.workspaceService.workspace.engine.doc.setPriority(ydoc.guid, 50);
|
this.workspaceService.workspace.engine.doc.addDoc(ydoc, false);
|
||||||
return ydoc;
|
this.workspaceService.workspace.engine.doc.setPriority(
|
||||||
},
|
ydoc.guid,
|
||||||
})
|
50
|
||||||
);
|
);
|
||||||
|
return ydoc;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
),
|
||||||
|
schema: AFFiNE_WORKSPACE_DB_SCHEMA,
|
||||||
|
storageDocId: tableName =>
|
||||||
|
`db$${this.workspaceService.workspace.id}$${tableName}`,
|
||||||
|
}) as DBWithTables<AFFiNE_WORKSPACE_DB_SCHEMA>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
@@ -43,25 +54,36 @@ export class WorkspaceDBService extends Service {
|
|||||||
// __local__ for local workspace
|
// __local__ for local workspace
|
||||||
const userdataDb = this.userdataDBPool.get(userId);
|
const userdataDb = this.userdataDBPool.get(userId);
|
||||||
if (userdataDb) {
|
if (userdataDb) {
|
||||||
return userdataDb.obj;
|
return userdataDb.obj as DBWithTables<AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newDB = new WorkspaceUserdataDBClient(
|
const newDB = this.framework.createEntity(
|
||||||
new YjsDBAdapter(AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA, {
|
DB<AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA>,
|
||||||
getDoc: guid => {
|
{
|
||||||
const ydoc = new YDoc({
|
db: new WorkspaceUserdataDBClient(
|
||||||
// guid format: userdata${userId}${workspaceId}${guid}
|
new YjsDBAdapter(AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA, {
|
||||||
guid: `userdata$${userId}$${this.workspaceService.workspace.id}$${guid}`,
|
getDoc: guid => {
|
||||||
});
|
const ydoc = new YDoc({
|
||||||
this.workspaceService.workspace.engine.doc.addDoc(ydoc, false);
|
// guid format: userdata${userId}${workspaceId}${guid}
|
||||||
this.workspaceService.workspace.engine.doc.setPriority(ydoc.guid, 50);
|
guid: `userdata$${userId}$${this.workspaceService.workspace.id}$${guid}`,
|
||||||
return ydoc;
|
});
|
||||||
},
|
this.workspaceService.workspace.engine.doc.addDoc(ydoc, false);
|
||||||
})
|
this.workspaceService.workspace.engine.doc.setPriority(
|
||||||
|
ydoc.guid,
|
||||||
|
50
|
||||||
|
);
|
||||||
|
return ydoc;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
),
|
||||||
|
schema: AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA,
|
||||||
|
storageDocId: tableName =>
|
||||||
|
`userdata$${userId}$${this.workspaceService.workspace.id}$${tableName}`,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.userdataDBPool.put(userId, newDB);
|
this.userdataDBPool.put(userId, newDB);
|
||||||
return newDB;
|
return newDB as DBWithTables<AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA>;
|
||||||
}
|
}
|
||||||
|
|
||||||
static isDBDocId(docId: string) {
|
static isDBDocId(docId: string) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { validators } from './validators';
|
|||||||
|
|
||||||
export class ORMClient {
|
export class ORMClient {
|
||||||
static hooksMap: Map<string, Hook<any>[]> = new Map();
|
static hooksMap: Map<string, Hook<any>[]> = new Map();
|
||||||
private readonly tables = new Map<string, Table<any>>();
|
readonly tables = new Map<string, Table<any>>();
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly db: DBSchemaBuilder,
|
protected readonly db: DBSchemaBuilder,
|
||||||
protected readonly adapter: DBAdapter
|
protected readonly adapter: DBAdapter
|
||||||
|
|||||||
@@ -22,6 +22,25 @@ export {
|
|||||||
ReadonlyStorage as ReadonlyDocStorage,
|
ReadonlyStorage as ReadonlyDocStorage,
|
||||||
} from './storage';
|
} from './storage';
|
||||||
|
|
||||||
|
export interface DocEngineDocState {
|
||||||
|
/**
|
||||||
|
* is syncing with the server
|
||||||
|
*/
|
||||||
|
syncing: boolean;
|
||||||
|
/**
|
||||||
|
* is saving to local storage
|
||||||
|
*/
|
||||||
|
saving: boolean;
|
||||||
|
/**
|
||||||
|
* is loading from local storage
|
||||||
|
*/
|
||||||
|
loading: boolean;
|
||||||
|
retrying: boolean;
|
||||||
|
ready: boolean;
|
||||||
|
errorMessage: string | null;
|
||||||
|
serverClock: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
export class DocEngine {
|
export class DocEngine {
|
||||||
readonly clientId: string;
|
readonly clientId: string;
|
||||||
localPart: DocEngineLocalPart;
|
localPart: DocEngineLocalPart;
|
||||||
@@ -53,13 +72,14 @@ export class DocEngine {
|
|||||||
docState$(docId: string) {
|
docState$(docId: string) {
|
||||||
const localState$ = this.localPart.docState$(docId);
|
const localState$ = this.localPart.docState$(docId);
|
||||||
const remoteState$ = this.remotePart?.docState$(docId);
|
const remoteState$ = this.remotePart?.docState$(docId);
|
||||||
return LiveData.computed(get => {
|
return LiveData.computed<DocEngineDocState>(get => {
|
||||||
const localState = get(localState$);
|
const localState = get(localState$);
|
||||||
const remoteState = remoteState$ ? get(remoteState$) : null;
|
const remoteState = remoteState$ ? get(remoteState$) : null;
|
||||||
if (remoteState) {
|
if (remoteState) {
|
||||||
return {
|
return {
|
||||||
syncing: remoteState.syncing,
|
syncing: remoteState.syncing,
|
||||||
saving: localState.syncing,
|
saving: localState.syncing,
|
||||||
|
loading: localState.syncing,
|
||||||
retrying: remoteState.retrying,
|
retrying: remoteState.retrying,
|
||||||
ready: localState.ready,
|
ready: localState.ready,
|
||||||
errorMessage: remoteState.errorMessage,
|
errorMessage: remoteState.errorMessage,
|
||||||
@@ -69,6 +89,7 @@ export class DocEngine {
|
|||||||
return {
|
return {
|
||||||
syncing: localState.syncing,
|
syncing: localState.syncing,
|
||||||
saving: localState.syncing,
|
saving: localState.syncing,
|
||||||
|
loading: localState.syncing,
|
||||||
ready: localState.ready,
|
ready: localState.ready,
|
||||||
retrying: false,
|
retrying: false,
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export interface LocalEngineState {
|
|||||||
|
|
||||||
export interface LocalDocState {
|
export interface LocalDocState {
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
|
loading: boolean;
|
||||||
syncing: boolean;
|
syncing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +82,7 @@ export class DocEngineLocalPart {
|
|||||||
const next = () => {
|
const next = () => {
|
||||||
subscribe.next({
|
subscribe.next({
|
||||||
ready: this.status.readyDocs.has(docId) ?? false,
|
ready: this.status.readyDocs.has(docId) ?? false,
|
||||||
|
loading: this.status.connectedDocs.has(docId),
|
||||||
syncing:
|
syncing:
|
||||||
(this.status.jobMap.get(docId)?.length ?? 0) > 0 ||
|
(this.status.jobMap.get(docId)?.length ?? 0) > 0 ||
|
||||||
this.status.currentJob?.docId === docId,
|
this.status.currentJob?.docId === docId,
|
||||||
@@ -91,7 +93,7 @@ export class DocEngineLocalPart {
|
|||||||
if (updatedId === docId) next();
|
if (updatedId === docId) next();
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
{ ready: false, syncing: false }
|
{ ready: false, loading: false, syncing: false }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
type DropTargetDropEvent,
|
type DropTargetDropEvent,
|
||||||
type DropTargetOptions,
|
type DropTargetOptions,
|
||||||
|
Skeleton,
|
||||||
useDropTarget,
|
useDropTarget,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||||
@@ -13,11 +14,13 @@ import { DropEffect, type ExplorerTreeNodeDropEffect } from '../../tree';
|
|||||||
export const RootEmpty = ({
|
export const RootEmpty = ({
|
||||||
onDrop,
|
onDrop,
|
||||||
canDrop,
|
canDrop,
|
||||||
|
isLoading,
|
||||||
dropEffect,
|
dropEffect,
|
||||||
}: {
|
}: {
|
||||||
onDrop?: (data: DropTargetDropEvent<AffineDNDData>) => void;
|
onDrop?: (data: DropTargetDropEvent<AffineDNDData>) => void;
|
||||||
canDrop?: DropTargetOptions<AffineDNDData>['canDrop'];
|
canDrop?: DropTargetOptions<AffineDNDData>['canDrop'];
|
||||||
dropEffect?: ExplorerTreeNodeDropEffect;
|
dropEffect?: ExplorerTreeNodeDropEffect;
|
||||||
|
isLoading?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
@@ -33,6 +36,10 @@ export const RootEmpty = ({
|
|||||||
[onDrop, canDrop]
|
[onDrop, canDrop]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Skeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExplorerEmptySection
|
<ExplorerEmptySection
|
||||||
ref={dropTargetRef}
|
ref={dropTargetRef}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ export const ExplorerFavorites = () => {
|
|||||||
|
|
||||||
const favorites = useLiveData(favoriteService.favoriteList.sortedList$);
|
const favorites = useLiveData(favoriteService.favoriteList.sortedList$);
|
||||||
|
|
||||||
|
const isLoading = useLiveData(favoriteService.favoriteList.isLoading$);
|
||||||
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
const handleDrop = useCallback(
|
const handleDrop = useCallback(
|
||||||
@@ -262,6 +264,7 @@ export const ExplorerFavorites = () => {
|
|||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
canDrop={handleCanDrop}
|
canDrop={handleCanDrop}
|
||||||
dropEffect={handleDropEffect}
|
dropEffect={handleDropEffect}
|
||||||
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Skeleton } from '@affine/component';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { FolderIcon } from '@blocksuite/icons/rc';
|
import { FolderIcon } from '@blocksuite/icons/rc';
|
||||||
|
|
||||||
@@ -5,11 +6,17 @@ import { ExplorerEmptySection } from '../../layouts/empty-section';
|
|||||||
|
|
||||||
export const RootEmpty = ({
|
export const RootEmpty = ({
|
||||||
onClickCreate,
|
onClickCreate,
|
||||||
|
isLoading,
|
||||||
}: {
|
}: {
|
||||||
onClickCreate?: () => void;
|
onClickCreate?: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Skeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExplorerEmptySection
|
<ExplorerEmptySection
|
||||||
icon={FolderIcon}
|
icon={FolderIcon}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export const ExplorerOrganize = () => {
|
|||||||
const rootFolder = organizeService.folderTree.rootFolder;
|
const rootFolder = organizeService.folderTree.rootFolder;
|
||||||
|
|
||||||
const folders = useLiveData(rootFolder.sortedChildren$);
|
const folders = useLiveData(rootFolder.sortedChildren$);
|
||||||
|
const isLoading = useLiveData(organizeService.folderTree.isLoading$);
|
||||||
|
|
||||||
const handleCreateFolder = useCallback(() => {
|
const handleCreateFolder = useCallback(() => {
|
||||||
const newFolderId = rootFolder.createFolder(
|
const newFolderId = rootFolder.createFolder(
|
||||||
@@ -128,7 +129,9 @@ export const ExplorerOrganize = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ExplorerTreeRoot
|
<ExplorerTreeRoot
|
||||||
placeholder={<RootEmpty onClickCreate={handleCreateFolder} />}
|
placeholder={
|
||||||
|
<RootEmpty onClickCreate={handleCreateFolder} isLoading={isLoading} />
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{folders.map(child => (
|
{folders.map(child => (
|
||||||
<ExplorerFolderNode
|
<ExplorerFolderNode
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export class FavoriteList extends Entity {
|
|||||||
sortedList$ = this.list$.map(v =>
|
sortedList$ = this.list$.map(v =>
|
||||||
v.sort((a, b) => (a.index > b.index ? 1 : -1))
|
v.sort((a, b) => (a.index > b.index ? 1 : -1))
|
||||||
);
|
);
|
||||||
|
isLoading$ = this.store.watchIsLoading();
|
||||||
|
|
||||||
constructor(private readonly store: FavoriteStore) {
|
constructor(private readonly store: FavoriteStore) {
|
||||||
super();
|
super();
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ export class FavoriteStore extends Store {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watchIsLoading() {
|
||||||
|
return this.userdataDB$
|
||||||
|
.map(db => LiveData.from(db.favorite.isLoading$, false))
|
||||||
|
.flat();
|
||||||
|
}
|
||||||
|
|
||||||
watchFavorites() {
|
watchFavorites() {
|
||||||
return this.userdataDB$
|
return this.userdataDB$
|
||||||
.map(db => LiveData.from(db.favorite.find$(), []))
|
.map(db => LiveData.from(db.favorite.find$(), []))
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ export class FolderTree extends Entity {
|
|||||||
id: null,
|
id: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
isLoading$ = this.folderStore.watchIsLoading();
|
||||||
|
|
||||||
// get folder by id
|
// get folder by id
|
||||||
folderNode$(id: string) {
|
folderNode$(id: string) {
|
||||||
return LiveData.from(
|
return LiveData.from(
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ export class FolderStore extends Store {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watchIsLoading() {
|
||||||
|
return this.dbService.db.folders.isLoading$;
|
||||||
|
}
|
||||||
|
|
||||||
isAncestor(childId: string, ancestorId: string): boolean {
|
isAncestor(childId: string, ancestorId: string): boolean {
|
||||||
if (childId === ancestorId) {
|
if (childId === ancestorId) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user