mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-24 22:09:08 +08:00
feat(core): cache navigation collapsed state (#13315)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Collapsible section state in navigation panels is now managed using a unified path-based approach, enabling more consistent and centralized control across desktop and mobile interfaces. * The collapsed/expanded state of navigation sections and nodes is now persistently tracked using hierarchical paths, improving reliability across sessions and devices. * Internal state management is streamlined, with local state replaced by a shared service, resulting in more predictable navigation behavior. * **Chores** * Removed obsolete types and legacy section management logic for improved maintainability. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -229,7 +229,7 @@ export const RootAppSidebar = memo((): ReactElement => {
|
|||||||
<NavigationPanelTags />
|
<NavigationPanelTags />
|
||||||
<NavigationPanelCollections />
|
<NavigationPanelCollections />
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
name="others"
|
path={['others']}
|
||||||
title={t['com.affine.rootAppSidebar.others']()}
|
title={t['com.affine.rootAppSidebar.others']()}
|
||||||
contentStyle={{ padding: '6px 8px 0 8px' }}
|
contentStyle={{ padding: '6px 8px 0 8px' }}
|
||||||
>
|
>
|
||||||
|
|||||||
+7
-10
@@ -1,8 +1,5 @@
|
|||||||
import { CategoryDivider } from '@affine/core/modules/app-sidebar/views';
|
import { CategoryDivider } from '@affine/core/modules/app-sidebar/views';
|
||||||
import {
|
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||||
type CollapsibleSectionName,
|
|
||||||
NavigationPanelService,
|
|
||||||
} from '@affine/core/modules/navigation-panel';
|
|
||||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
@@ -17,7 +14,7 @@ import {
|
|||||||
import { content, header, root } from './collapsible-section.css';
|
import { content, header, root } from './collapsible-section.css';
|
||||||
|
|
||||||
interface CollapsibleSectionProps extends PropsWithChildren {
|
interface CollapsibleSectionProps extends PropsWithChildren {
|
||||||
name: CollapsibleSectionName;
|
path: string[];
|
||||||
title: string;
|
title: string;
|
||||||
actions?: ReactNode;
|
actions?: ReactNode;
|
||||||
|
|
||||||
@@ -33,7 +30,7 @@ interface CollapsibleSectionProps extends PropsWithChildren {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const CollapsibleSection = ({
|
export const CollapsibleSection = ({
|
||||||
name,
|
path,
|
||||||
title,
|
title,
|
||||||
actions,
|
actions,
|
||||||
children,
|
children,
|
||||||
@@ -48,15 +45,15 @@ export const CollapsibleSection = ({
|
|||||||
contentClassName,
|
contentClassName,
|
||||||
contentStyle,
|
contentStyle,
|
||||||
}: CollapsibleSectionProps) => {
|
}: CollapsibleSectionProps) => {
|
||||||
const section = useService(NavigationPanelService).sections[name];
|
const navigationPanelService = useService(NavigationPanelService);
|
||||||
|
|
||||||
const collapsed = useLiveData(section.collapsed$);
|
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||||
|
|
||||||
const setCollapsed = useCallback(
|
const setCollapsed = useCallback(
|
||||||
(v: boolean) => {
|
(v: boolean) => {
|
||||||
section.setCollapsed(v);
|
navigationPanelService.setCollapsed(path, v);
|
||||||
},
|
},
|
||||||
[section]
|
[navigationPanelService, path]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
+22
-3
@@ -11,6 +11,7 @@ import {
|
|||||||
} from '@affine/core/modules/collection';
|
} from '@affine/core/modules/collection';
|
||||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||||
|
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
@@ -47,6 +48,7 @@ export const NavigationPanelCollectionNode = ({
|
|||||||
operations: additionalOperations,
|
operations: additionalOperations,
|
||||||
canDrop,
|
canDrop,
|
||||||
dropEffect,
|
dropEffect,
|
||||||
|
parentPath,
|
||||||
}: {
|
}: {
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
} & GenericNavigationPanelNode) => {
|
} & GenericNavigationPanelNode) => {
|
||||||
@@ -55,10 +57,21 @@ export const NavigationPanelCollectionNode = ({
|
|||||||
GlobalContextService,
|
GlobalContextService,
|
||||||
WorkspaceDialogService,
|
WorkspaceDialogService,
|
||||||
});
|
});
|
||||||
|
const navigationPanelService = useService(NavigationPanelService);
|
||||||
const active =
|
const active =
|
||||||
useLiveData(globalContextService.globalContext.collectionId.$) ===
|
useLiveData(globalContextService.globalContext.collectionId.$) ===
|
||||||
collectionId;
|
collectionId;
|
||||||
const [collapsed, setCollapsed] = useState(true);
|
const path = useMemo(
|
||||||
|
() => [...(parentPath ?? []), `collection-${collectionId}`],
|
||||||
|
[parentPath, collectionId]
|
||||||
|
);
|
||||||
|
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||||
|
const setCollapsed = useCallback(
|
||||||
|
(value: boolean) => {
|
||||||
|
navigationPanelService.setCollapsed(path, value);
|
||||||
|
},
|
||||||
|
[navigationPanelService, path]
|
||||||
|
);
|
||||||
|
|
||||||
const collectionService = useService(CollectionService);
|
const collectionService = useService(CollectionService);
|
||||||
const collection = useLiveData(collectionService.collection$(collectionId));
|
const collection = useLiveData(collectionService.collection$(collectionId));
|
||||||
@@ -160,7 +173,7 @@ export const NavigationPanelCollectionNode = ({
|
|||||||
|
|
||||||
const handleOpenCollapsed = useCallback(() => {
|
const handleOpenCollapsed = useCallback(() => {
|
||||||
setCollapsed(false);
|
setCollapsed(false);
|
||||||
}, []);
|
}, [setCollapsed]);
|
||||||
|
|
||||||
const handleEditCollection = useCallback(() => {
|
const handleEditCollection = useCallback(() => {
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
@@ -217,15 +230,20 @@ export const NavigationPanelCollectionNode = ({
|
|||||||
dropEffect={handleDropEffectOnCollection}
|
dropEffect={handleDropEffectOnCollection}
|
||||||
data-testid={`navigation-panel-collection-${collectionId}`}
|
data-testid={`navigation-panel-collection-${collectionId}`}
|
||||||
>
|
>
|
||||||
<NavigationPanelCollectionNodeChildren collection={collection} />
|
<NavigationPanelCollectionNodeChildren
|
||||||
|
collection={collection}
|
||||||
|
path={path}
|
||||||
|
/>
|
||||||
</NavigationPanelTreeNode>
|
</NavigationPanelTreeNode>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const NavigationPanelCollectionNodeChildren = ({
|
const NavigationPanelCollectionNodeChildren = ({
|
||||||
collection,
|
collection,
|
||||||
|
path,
|
||||||
}: {
|
}: {
|
||||||
collection: Collection;
|
collection: Collection;
|
||||||
|
path: string[];
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { collectionService } = useServices({
|
const { collectionService } = useServices({
|
||||||
@@ -264,6 +282,7 @@ const NavigationPanelCollectionNodeChildren = ({
|
|||||||
at: 'navigation-panel:collection:filtered-docs',
|
at: 'navigation-panel:collection:filtered-docs',
|
||||||
collectionId: collection.id,
|
collectionId: collection.id,
|
||||||
}}
|
}}
|
||||||
|
parentPath={path}
|
||||||
operations={
|
operations={
|
||||||
allowList.has(docId)
|
allowList.has(docId)
|
||||||
? [
|
? [
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
|||||||
import { DocsSearchService } from '@affine/core/modules/docs-search';
|
import { DocsSearchService } from '@affine/core/modules/docs-search';
|
||||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||||
|
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||||
import { GuardService } from '@affine/core/modules/permissions';
|
import { GuardService } from '@affine/core/modules/permissions';
|
||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
@@ -46,6 +47,7 @@ export const NavigationPanelDocNode = ({
|
|||||||
canDrop,
|
canDrop,
|
||||||
operations: additionalOperations,
|
operations: additionalOperations,
|
||||||
dropEffect,
|
dropEffect,
|
||||||
|
parentPath,
|
||||||
}: {
|
}: {
|
||||||
docId: string;
|
docId: string;
|
||||||
isLinked?: boolean;
|
isLinked?: boolean;
|
||||||
@@ -67,11 +69,22 @@ export const NavigationPanelDocNode = ({
|
|||||||
FeatureFlagService,
|
FeatureFlagService,
|
||||||
GuardService,
|
GuardService,
|
||||||
});
|
});
|
||||||
|
const navigationPanelService = useService(NavigationPanelService);
|
||||||
const { appSettings } = useAppSettingHelper();
|
const { appSettings } = useAppSettingHelper();
|
||||||
|
|
||||||
const active =
|
const active =
|
||||||
useLiveData(globalContextService.globalContext.docId.$) === docId;
|
useLiveData(globalContextService.globalContext.docId.$) === docId;
|
||||||
const [collapsed, setCollapsed] = useState(true);
|
const path = useMemo(
|
||||||
|
() => [...(parentPath ?? []), `doc-${docId}`],
|
||||||
|
[parentPath, docId]
|
||||||
|
);
|
||||||
|
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||||
|
const setCollapsed = useCallback(
|
||||||
|
(value: boolean) => {
|
||||||
|
navigationPanelService.setCollapsed(path, value);
|
||||||
|
},
|
||||||
|
[navigationPanelService, path]
|
||||||
|
);
|
||||||
const isCollapsed = appSettings.showLinkedDocInSidebar ? collapsed : true;
|
const isCollapsed = appSettings.showLinkedDocInSidebar ? collapsed : true;
|
||||||
|
|
||||||
const docRecord = useLiveData(docsService.list.doc$(docId));
|
const docRecord = useLiveData(docsService.list.doc$(docId));
|
||||||
@@ -227,7 +240,7 @@ export const NavigationPanelDocNode = ({
|
|||||||
openInfoModal: () => workspaceDialogService.open('doc-info', { docId }),
|
openInfoModal: () => workspaceDialogService.open('doc-info', { docId }),
|
||||||
openNodeCollapsed: () => setCollapsed(false),
|
openNodeCollapsed: () => setCollapsed(false),
|
||||||
}),
|
}),
|
||||||
[docId, workspaceDialogService]
|
[docId, setCollapsed, workspaceDialogService]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -302,6 +315,7 @@ export const NavigationPanelDocNode = ({
|
|||||||
at: 'navigation-panel:doc:linked-docs',
|
at: 'navigation-panel:doc:linked-docs',
|
||||||
docId,
|
docId,
|
||||||
}}
|
}}
|
||||||
|
parentPath={path}
|
||||||
isLinked
|
isLinked
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
|||||||
+35
-13
@@ -13,6 +13,7 @@ import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/uti
|
|||||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
|
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||||
import {
|
import {
|
||||||
type FolderNode,
|
type FolderNode,
|
||||||
OrganizeService,
|
OrganizeService,
|
||||||
@@ -31,7 +32,7 @@ import {
|
|||||||
RemoveFolderIcon,
|
RemoveFolderIcon,
|
||||||
TagsIcon,
|
TagsIcon,
|
||||||
} from '@blocksuite/icons/rc';
|
} from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||||
import { difference } from 'lodash-es';
|
import { difference } from 'lodash-es';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ export const NavigationPanelFolderNode = ({
|
|||||||
dropEffect,
|
dropEffect,
|
||||||
canDrop,
|
canDrop,
|
||||||
reorderable,
|
reorderable,
|
||||||
|
parentPath,
|
||||||
}: {
|
}: {
|
||||||
defaultRenaming?: boolean;
|
defaultRenaming?: boolean;
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
@@ -104,6 +106,7 @@ export const NavigationPanelFolderNode = ({
|
|||||||
dropEffect={dropEffect}
|
dropEffect={dropEffect}
|
||||||
reorderable={reorderable}
|
reorderable={reorderable}
|
||||||
canDrop={canDrop}
|
canDrop={canDrop}
|
||||||
|
parentPath={parentPath}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (type === 'doc') {
|
} else if (type === 'doc') {
|
||||||
@@ -117,6 +120,7 @@ export const NavigationPanelFolderNode = ({
|
|||||||
canDrop={canDrop}
|
canDrop={canDrop}
|
||||||
dropEffect={dropEffect}
|
dropEffect={dropEffect}
|
||||||
operations={additionalOperations}
|
operations={additionalOperations}
|
||||||
|
parentPath={parentPath}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -131,6 +135,7 @@ export const NavigationPanelFolderNode = ({
|
|||||||
reorderable={reorderable}
|
reorderable={reorderable}
|
||||||
dropEffect={dropEffect}
|
dropEffect={dropEffect}
|
||||||
operations={additionalOperations}
|
operations={additionalOperations}
|
||||||
|
parentPath={parentPath}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -145,6 +150,7 @@ export const NavigationPanelFolderNode = ({
|
|||||||
reorderable
|
reorderable
|
||||||
dropEffect={dropEffect}
|
dropEffect={dropEffect}
|
||||||
operations={additionalOperations}
|
operations={additionalOperations}
|
||||||
|
parentPath={parentPath}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -177,6 +183,7 @@ const NavigationPanelFolderNodeFolder = ({
|
|||||||
canDrop,
|
canDrop,
|
||||||
dropEffect,
|
dropEffect,
|
||||||
reorderable,
|
reorderable,
|
||||||
|
parentPath,
|
||||||
}: {
|
}: {
|
||||||
defaultRenaming?: boolean;
|
defaultRenaming?: boolean;
|
||||||
node: FolderNode;
|
node: FolderNode;
|
||||||
@@ -189,11 +196,22 @@ const NavigationPanelFolderNodeFolder = ({
|
|||||||
FeatureFlagService,
|
FeatureFlagService,
|
||||||
WorkspaceDialogService,
|
WorkspaceDialogService,
|
||||||
});
|
});
|
||||||
|
const navigationPanelService = useService(NavigationPanelService);
|
||||||
const name = useLiveData(node.name$);
|
const name = useLiveData(node.name$);
|
||||||
const enableEmojiIcon = useLiveData(
|
const enableEmojiIcon = useLiveData(
|
||||||
featureFlagService.flags.enable_emoji_folder_icon.$
|
featureFlagService.flags.enable_emoji_folder_icon.$
|
||||||
);
|
);
|
||||||
const [collapsed, setCollapsed] = useState(true);
|
const path = useMemo(
|
||||||
|
() => [...(parentPath ?? []), `folder-${node.id}`],
|
||||||
|
[parentPath, node.id]
|
||||||
|
);
|
||||||
|
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||||
|
const setCollapsed = useCallback(
|
||||||
|
(value: boolean) => {
|
||||||
|
navigationPanelService.setCollapsed(path, value);
|
||||||
|
},
|
||||||
|
[navigationPanelService, path]
|
||||||
|
);
|
||||||
const [newFolderId, setNewFolderId] = useState<string | null>(null);
|
const [newFolderId, setNewFolderId] = useState<string | null>(null);
|
||||||
|
|
||||||
const { createPage } = usePageHelper(
|
const { createPage } = usePageHelper(
|
||||||
@@ -575,7 +593,7 @@ const NavigationPanelFolderNodeFolder = ({
|
|||||||
target: 'doc',
|
target: 'doc',
|
||||||
});
|
});
|
||||||
setCollapsed(false);
|
setCollapsed(false);
|
||||||
}, [createPage, node]);
|
}, [createPage, node, setCollapsed]);
|
||||||
|
|
||||||
const handleCreateSubfolder = useCallback(() => {
|
const handleCreateSubfolder = useCallback(() => {
|
||||||
const newFolderId = node.createFolder(
|
const newFolderId = node.createFolder(
|
||||||
@@ -585,7 +603,7 @@ const NavigationPanelFolderNodeFolder = ({
|
|||||||
track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
|
track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
|
||||||
setCollapsed(false);
|
setCollapsed(false);
|
||||||
setNewFolderId(newFolderId);
|
setNewFolderId(newFolderId);
|
||||||
}, [node, t]);
|
}, [node, setCollapsed, t]);
|
||||||
|
|
||||||
const handleAddToFolder = useCallback(
|
const handleAddToFolder = useCallback(
|
||||||
(type: 'doc' | 'collection' | 'tag') => {
|
(type: 'doc' | 'collection' | 'tag') => {
|
||||||
@@ -628,7 +646,7 @@ const NavigationPanelFolderNodeFolder = ({
|
|||||||
target: type,
|
target: type,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[children, node, workspaceDialogService]
|
[children, node, setCollapsed, workspaceDialogService]
|
||||||
);
|
);
|
||||||
|
|
||||||
const folderOperations = useMemo(() => {
|
const folderOperations = useMemo(() => {
|
||||||
@@ -761,14 +779,17 @@ const NavigationPanelFolderNodeFolder = ({
|
|||||||
[t]
|
[t]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCollapsedChange = useCallback((collapsed: boolean) => {
|
const handleCollapsedChange = useCallback(
|
||||||
if (collapsed) {
|
(collapsed: boolean) => {
|
||||||
setNewFolderId(null); // reset new folder id to clear the renaming state
|
if (collapsed) {
|
||||||
setCollapsed(true);
|
setNewFolderId(null); // reset new folder id to clear the renaming state
|
||||||
} else {
|
setCollapsed(true);
|
||||||
setCollapsed(false);
|
} else {
|
||||||
}
|
setCollapsed(false);
|
||||||
}, []);
|
}
|
||||||
|
},
|
||||||
|
[setCollapsed]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationPanelTreeNode
|
<NavigationPanelTreeNode
|
||||||
@@ -804,6 +825,7 @@ const NavigationPanelFolderNodeFolder = ({
|
|||||||
at: 'navigation-panel:organize:folder-node',
|
at: 'navigation-panel:organize:folder-node',
|
||||||
nodeId: child.id as string,
|
nodeId: child.id as string,
|
||||||
}}
|
}}
|
||||||
|
parentPath={path}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</NavigationPanelTreeNode>
|
</NavigationPanelTreeNode>
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ import {
|
|||||||
toast,
|
toast,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||||
|
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||||
import type { Tag } from '@affine/core/modules/tag';
|
import type { Tag } from '@affine/core/modules/tag';
|
||||||
import { TagService } from '@affine/core/modules/tag';
|
import { TagService } from '@affine/core/modules/tag';
|
||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NavigationPanelTreeNode,
|
NavigationPanelTreeNode,
|
||||||
@@ -31,6 +32,7 @@ export const NavigationPanelTagNode = ({
|
|||||||
operations: additionalOperations,
|
operations: additionalOperations,
|
||||||
dropEffect,
|
dropEffect,
|
||||||
canDrop,
|
canDrop,
|
||||||
|
parentPath,
|
||||||
}: {
|
}: {
|
||||||
tagId: string;
|
tagId: string;
|
||||||
} & GenericNavigationPanelNode) => {
|
} & GenericNavigationPanelNode) => {
|
||||||
@@ -39,9 +41,20 @@ export const NavigationPanelTagNode = ({
|
|||||||
TagService,
|
TagService,
|
||||||
GlobalContextService,
|
GlobalContextService,
|
||||||
});
|
});
|
||||||
|
const navigationPanelService = useService(NavigationPanelService);
|
||||||
const active =
|
const active =
|
||||||
useLiveData(globalContextService.globalContext.tagId.$) === tagId;
|
useLiveData(globalContextService.globalContext.tagId.$) === tagId;
|
||||||
const [collapsed, setCollapsed] = useState(true);
|
const path = useMemo(
|
||||||
|
() => [...(parentPath ?? []), `tag-${tagId}`],
|
||||||
|
[parentPath, tagId]
|
||||||
|
);
|
||||||
|
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||||
|
const setCollapsed = useCallback(
|
||||||
|
(value: boolean) => {
|
||||||
|
navigationPanelService.setCollapsed(path, value);
|
||||||
|
},
|
||||||
|
[navigationPanelService, path]
|
||||||
|
);
|
||||||
|
|
||||||
const tagRecord = useLiveData(tagService.tagList.tagByTagId$(tagId));
|
const tagRecord = useLiveData(tagService.tagList.tagByTagId$(tagId));
|
||||||
const tagColor = useLiveData(tagRecord?.color$);
|
const tagColor = useLiveData(tagRecord?.color$);
|
||||||
@@ -154,7 +167,7 @@ export const NavigationPanelTagNode = ({
|
|||||||
() => ({
|
() => ({
|
||||||
openNodeCollapsed: () => setCollapsed(false),
|
openNodeCollapsed: () => setCollapsed(false),
|
||||||
}),
|
}),
|
||||||
[]
|
[setCollapsed]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -188,7 +201,7 @@ export const NavigationPanelTagNode = ({
|
|||||||
dropEffect={handleDropEffectOnTag}
|
dropEffect={handleDropEffectOnTag}
|
||||||
data-testid={`navigation-panel-tag-${tagId}`}
|
data-testid={`navigation-panel-tag-${tagId}`}
|
||||||
>
|
>
|
||||||
<NavigationPanelTagNodeDocs tag={tagRecord} />
|
<NavigationPanelTagNodeDocs tag={tagRecord} path={path} />
|
||||||
</NavigationPanelTreeNode>
|
</NavigationPanelTreeNode>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -198,7 +211,13 @@ export const NavigationPanelTagNode = ({
|
|||||||
* so we split the tag node children into a separate component,
|
* so we split the tag node children into a separate component,
|
||||||
* so it won't be rendered when the tag node is collapsed.
|
* so it won't be rendered when the tag node is collapsed.
|
||||||
*/
|
*/
|
||||||
export const NavigationPanelTagNodeDocs = ({ tag }: { tag: Tag }) => {
|
export const NavigationPanelTagNodeDocs = ({
|
||||||
|
tag,
|
||||||
|
path,
|
||||||
|
}: {
|
||||||
|
tag: Tag;
|
||||||
|
path: string[];
|
||||||
|
}) => {
|
||||||
const tagDocIds = useLiveData(tag.pageIds$);
|
const tagDocIds = useLiveData(tag.pageIds$);
|
||||||
|
|
||||||
return tagDocIds.map(docId => (
|
return tagDocIds.map(docId => (
|
||||||
@@ -209,6 +228,7 @@ export const NavigationPanelTagNodeDocs = ({ tag }: { tag: Tag }) => {
|
|||||||
location={{
|
location={{
|
||||||
at: 'navigation-panel:tags:docs',
|
at: 'navigation-panel:tags:docs',
|
||||||
}}
|
}}
|
||||||
|
parentPath={path}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,4 +46,9 @@ export interface GenericNavigationPanelNode {
|
|||||||
* The drop effect to be used when an element is dropped over the node.
|
* The drop effect to be used when an element is dropped over the node.
|
||||||
*/
|
*/
|
||||||
dropEffect?: NavigationPanelTreeNodeDropEffect;
|
dropEffect?: NavigationPanelTreeNodeDropEffect;
|
||||||
|
/**
|
||||||
|
* The path segments to the parent node in the navigation tree.
|
||||||
|
* Used to persist the node's collapsed/expanded state in cache storage.
|
||||||
|
*/
|
||||||
|
parentPath: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-6
@@ -6,7 +6,7 @@ import { useI18n } from '@affine/i18n';
|
|||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import { AddCollectionIcon } from '@blocksuite/icons/rc';
|
import { AddCollectionIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
import { NavigationPanelCollectionNode } from '../../nodes/collection';
|
import { NavigationPanelCollectionNode } from '../../nodes/collection';
|
||||||
@@ -22,10 +22,9 @@ export const NavigationPanelCollections = () => {
|
|||||||
WorkbenchService,
|
WorkbenchService,
|
||||||
NavigationPanelService,
|
NavigationPanelService,
|
||||||
});
|
});
|
||||||
const navigationPanelSection = navigationPanelService.sections.collections;
|
|
||||||
const collections = useLiveData(collectionService.collections$);
|
const collections = useLiveData(collectionService.collections$);
|
||||||
const { openPromptModal } = usePromptModal();
|
const { openPromptModal } = usePromptModal();
|
||||||
|
const path = useMemo(() => ['collections'], []);
|
||||||
const handleCreateCollection = useCallback(() => {
|
const handleCreateCollection = useCallback(() => {
|
||||||
openPromptModal({
|
openPromptModal({
|
||||||
title: t['com.affine.editCollection.saveCollection'](),
|
title: t['com.affine.editCollection.saveCollection'](),
|
||||||
@@ -49,20 +48,21 @@ export const NavigationPanelCollections = () => {
|
|||||||
type: 'collection',
|
type: 'collection',
|
||||||
});
|
});
|
||||||
workbenchService.workbench.openCollection(id);
|
workbenchService.workbench.openCollection(id);
|
||||||
navigationPanelSection.setCollapsed(false);
|
navigationPanelService.setCollapsed(path, false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
collectionService,
|
collectionService,
|
||||||
navigationPanelSection,
|
navigationPanelService,
|
||||||
openPromptModal,
|
openPromptModal,
|
||||||
|
path,
|
||||||
t,
|
t,
|
||||||
workbenchService.workbench,
|
workbenchService.workbench,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
name="collections"
|
path={path}
|
||||||
testId="navigation-panel-collections"
|
testId="navigation-panel-collections"
|
||||||
title={t['com.affine.rootAppSidebar.collections']()}
|
title={t['com.affine.rootAppSidebar.collections']()}
|
||||||
actions={
|
actions={
|
||||||
@@ -89,6 +89,7 @@ export const NavigationPanelCollections = () => {
|
|||||||
location={{
|
location={{
|
||||||
at: 'navigation-panel:collection:list',
|
at: 'navigation-panel:collection:list',
|
||||||
}}
|
}}
|
||||||
|
parentPath={path}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</NavigationPanelTreeRoot>
|
</NavigationPanelTreeRoot>
|
||||||
|
|||||||
+14
-7
@@ -17,7 +17,7 @@ import { useI18n } from '@affine/i18n';
|
|||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { type MouseEventHandler, useCallback } from 'react';
|
import { type MouseEventHandler, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
import { NavigationPanelCollectionNode } from '../../nodes/collection';
|
import { NavigationPanelCollectionNode } from '../../nodes/collection';
|
||||||
@@ -41,7 +41,7 @@ export const NavigationPanelFavorites = () => {
|
|||||||
NavigationPanelService,
|
NavigationPanelService,
|
||||||
});
|
});
|
||||||
|
|
||||||
const navigationPanelSection = navigationPanelService.sections.favorites;
|
const path = useMemo(() => ['favorites'], []);
|
||||||
|
|
||||||
const favorites = useLiveData(favoriteService.favoriteList.sortedList$);
|
const favorites = useLiveData(favoriteService.favoriteList.sortedList$);
|
||||||
|
|
||||||
@@ -71,10 +71,10 @@ export const NavigationPanelFavorites = () => {
|
|||||||
track.$.navigationPanel.favorites.drop({
|
track.$.navigationPanel.favorites.drop({
|
||||||
type: data.source.data.entity.type,
|
type: data.source.data.entity.type,
|
||||||
});
|
});
|
||||||
navigationPanelSection.setCollapsed(false);
|
navigationPanelService.setCollapsed(path, false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[navigationPanelSection, favoriteService.favoriteList]
|
[navigationPanelService, favoriteService.favoriteList, path]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCreateNewFavoriteDoc: MouseEventHandler = useCallback(
|
const handleCreateNewFavoriteDoc: MouseEventHandler = useCallback(
|
||||||
@@ -85,9 +85,9 @@ export const NavigationPanelFavorites = () => {
|
|||||||
newDoc.id,
|
newDoc.id,
|
||||||
favoriteService.favoriteList.indexAt('before')
|
favoriteService.favoriteList.indexAt('before')
|
||||||
);
|
);
|
||||||
navigationPanelSection.setCollapsed(false);
|
navigationPanelService.setCollapsed(path, false);
|
||||||
},
|
},
|
||||||
[createPage, navigationPanelSection, favoriteService.favoriteList]
|
[createPage, navigationPanelService, favoriteService.favoriteList, path]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOnChildrenDrop = useCallback(
|
const handleOnChildrenDrop = useCallback(
|
||||||
@@ -162,7 +162,7 @@ export const NavigationPanelFavorites = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
name="favorites"
|
path={path}
|
||||||
title={t['com.affine.rootAppSidebar.favorites']()}
|
title={t['com.affine.rootAppSidebar.favorites']()}
|
||||||
headerRef={dropTargetRef}
|
headerRef={dropTargetRef}
|
||||||
testId="navigation-panel-favorites"
|
testId="navigation-panel-favorites"
|
||||||
@@ -202,6 +202,7 @@ export const NavigationPanelFavorites = () => {
|
|||||||
key={favorite.id}
|
key={favorite.id}
|
||||||
favorite={favorite}
|
favorite={favorite}
|
||||||
onDrop={handleOnChildrenDrop}
|
onDrop={handleOnChildrenDrop}
|
||||||
|
parentPath={path}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</NavigationPanelTreeRoot>
|
</NavigationPanelTreeRoot>
|
||||||
@@ -215,11 +216,13 @@ const childLocation = {
|
|||||||
const NavigationPanelFavoriteNode = ({
|
const NavigationPanelFavoriteNode = ({
|
||||||
favorite,
|
favorite,
|
||||||
onDrop,
|
onDrop,
|
||||||
|
parentPath,
|
||||||
}: {
|
}: {
|
||||||
favorite: {
|
favorite: {
|
||||||
id: string;
|
id: string;
|
||||||
type: FavoriteSupportTypeUnion;
|
type: FavoriteSupportTypeUnion;
|
||||||
};
|
};
|
||||||
|
parentPath: string[];
|
||||||
onDrop: (
|
onDrop: (
|
||||||
favorite: {
|
favorite: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -242,6 +245,7 @@ const NavigationPanelFavoriteNode = ({
|
|||||||
onDrop={handleOnChildrenDrop}
|
onDrop={handleOnChildrenDrop}
|
||||||
dropEffect={favoriteChildrenDropEffect}
|
dropEffect={favoriteChildrenDropEffect}
|
||||||
canDrop={favoriteChildrenCanDrop}
|
canDrop={favoriteChildrenCanDrop}
|
||||||
|
parentPath={parentPath}
|
||||||
/>
|
/>
|
||||||
) : favorite.type === 'tag' ? (
|
) : favorite.type === 'tag' ? (
|
||||||
<NavigationPanelTagNode
|
<NavigationPanelTagNode
|
||||||
@@ -251,6 +255,7 @@ const NavigationPanelFavoriteNode = ({
|
|||||||
onDrop={handleOnChildrenDrop}
|
onDrop={handleOnChildrenDrop}
|
||||||
dropEffect={favoriteChildrenDropEffect}
|
dropEffect={favoriteChildrenDropEffect}
|
||||||
canDrop={favoriteChildrenCanDrop}
|
canDrop={favoriteChildrenCanDrop}
|
||||||
|
parentPath={parentPath}
|
||||||
/>
|
/>
|
||||||
) : favorite.type === 'folder' ? (
|
) : favorite.type === 'folder' ? (
|
||||||
<NavigationPanelFolderNode
|
<NavigationPanelFolderNode
|
||||||
@@ -260,6 +265,7 @@ const NavigationPanelFavoriteNode = ({
|
|||||||
onDrop={handleOnChildrenDrop}
|
onDrop={handleOnChildrenDrop}
|
||||||
dropEffect={favoriteChildrenDropEffect}
|
dropEffect={favoriteChildrenDropEffect}
|
||||||
canDrop={favoriteChildrenCanDrop}
|
canDrop={favoriteChildrenCanDrop}
|
||||||
|
parentPath={parentPath}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<NavigationPanelCollectionNode
|
<NavigationPanelCollectionNode
|
||||||
@@ -269,6 +275,7 @@ const NavigationPanelFavoriteNode = ({
|
|||||||
onDrop={handleOnChildrenDrop}
|
onDrop={handleOnChildrenDrop}
|
||||||
dropEffect={favoriteChildrenDropEffect}
|
dropEffect={favoriteChildrenDropEffect}
|
||||||
canDrop={favoriteChildrenCanDrop}
|
canDrop={favoriteChildrenCanDrop}
|
||||||
|
parentPath={parentPath}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+8
-2
@@ -5,7 +5,7 @@ import { Trans, useI18n } from '@affine/i18n';
|
|||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import { BroomIcon, HelpIcon } from '@blocksuite/icons/rc';
|
import { BroomIcon, HelpIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
import { NavigationPanelCollectionNode } from '../../nodes/collection';
|
import { NavigationPanelCollectionNode } from '../../nodes/collection';
|
||||||
@@ -25,6 +25,7 @@ export const NavigationPanelMigrationFavorites = () => {
|
|||||||
const trashDocs = useLiveData(docsService.list.trashDocs$);
|
const trashDocs = useLiveData(docsService.list.trashDocs$);
|
||||||
const migrated = useLiveData(migrationFavoriteItemsAdapter.migrated$);
|
const migrated = useLiveData(migrationFavoriteItemsAdapter.migrated$);
|
||||||
const { openConfirmModal } = useConfirmModal();
|
const { openConfirmModal } = useConfirmModal();
|
||||||
|
const path = useMemo(() => ['migration-favorites'], []);
|
||||||
|
|
||||||
const favorites = useLiveData(
|
const favorites = useLiveData(
|
||||||
migrationFavoriteItemsAdapter.favorites$.map(favs => {
|
migrationFavoriteItemsAdapter.favorites$.map(favs => {
|
||||||
@@ -99,7 +100,7 @@ export const NavigationPanelMigrationFavorites = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
name="migrationFavorites"
|
path={path}
|
||||||
className={styles.container}
|
className={styles.container}
|
||||||
title={t['com.affine.rootAppSidebar.migration-data']()}
|
title={t['com.affine.rootAppSidebar.migration-data']()}
|
||||||
actions={
|
actions={
|
||||||
@@ -126,6 +127,7 @@ export const NavigationPanelMigrationFavorites = () => {
|
|||||||
<NavigationPanelMigrationFavoriteNode
|
<NavigationPanelMigrationFavoriteNode
|
||||||
key={favorite.id + ':' + i}
|
key={favorite.id + ':' + i}
|
||||||
favorite={favorite}
|
favorite={favorite}
|
||||||
|
parentPath={path}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</NavigationPanelTreeRoot>
|
</NavigationPanelTreeRoot>
|
||||||
@@ -138,11 +140,13 @@ const childLocation = {
|
|||||||
};
|
};
|
||||||
const NavigationPanelMigrationFavoriteNode = ({
|
const NavigationPanelMigrationFavoriteNode = ({
|
||||||
favorite,
|
favorite,
|
||||||
|
parentPath,
|
||||||
}: {
|
}: {
|
||||||
favorite: {
|
favorite: {
|
||||||
id: string;
|
id: string;
|
||||||
type: 'collection' | 'doc';
|
type: 'collection' | 'doc';
|
||||||
};
|
};
|
||||||
|
parentPath: string[];
|
||||||
}) => {
|
}) => {
|
||||||
return favorite.type === 'doc' ? (
|
return favorite.type === 'doc' ? (
|
||||||
<NavigationPanelDocNode
|
<NavigationPanelDocNode
|
||||||
@@ -151,6 +155,7 @@ const NavigationPanelMigrationFavoriteNode = ({
|
|||||||
location={childLocation}
|
location={childLocation}
|
||||||
reorderable={false}
|
reorderable={false}
|
||||||
canDrop={false}
|
canDrop={false}
|
||||||
|
parentPath={parentPath}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<NavigationPanelCollectionNode
|
<NavigationPanelCollectionNode
|
||||||
@@ -159,6 +164,7 @@ const NavigationPanelMigrationFavoriteNode = ({
|
|||||||
location={childLocation}
|
location={childLocation}
|
||||||
reorderable={false}
|
reorderable={false}
|
||||||
canDrop={false}
|
canDrop={false}
|
||||||
|
parentPath={parentPath}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+6
-6
@@ -27,10 +27,9 @@ export const NavigationPanelOrganize = () => {
|
|||||||
OrganizeService,
|
OrganizeService,
|
||||||
NavigationPanelService,
|
NavigationPanelService,
|
||||||
});
|
});
|
||||||
const navigationPanelSection = navigationPanelService.sections.organize;
|
const path = useMemo(() => ['organize'], []);
|
||||||
const collapsed = useLiveData(navigationPanelSection.collapsed$);
|
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||||
const [newFolderId, setNewFolderId] = useState<string | null>(null);
|
const [newFolderId, setNewFolderId] = useState<string | null>(null);
|
||||||
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
const folderTree = organizeService.folderTree;
|
const folderTree = organizeService.folderTree;
|
||||||
@@ -46,9 +45,9 @@ export const NavigationPanelOrganize = () => {
|
|||||||
);
|
);
|
||||||
track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
|
track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
|
||||||
setNewFolderId(newFolderId);
|
setNewFolderId(newFolderId);
|
||||||
navigationPanelSection.setCollapsed(false);
|
navigationPanelService.setCollapsed(path, false);
|
||||||
return newFolderId;
|
return newFolderId;
|
||||||
}, [navigationPanelSection, rootFolder]);
|
}, [navigationPanelService, path, rootFolder]);
|
||||||
|
|
||||||
const handleOnChildrenDrop = useCallback(
|
const handleOnChildrenDrop = useCallback(
|
||||||
(data: DropTargetDropEvent<AffineDNDData>, node?: FolderNode) => {
|
(data: DropTargetDropEvent<AffineDNDData>, node?: FolderNode) => {
|
||||||
@@ -105,7 +104,7 @@ export const NavigationPanelOrganize = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
name="organize"
|
path={path}
|
||||||
title={t['com.affine.rootAppSidebar.organize']()}
|
title={t['com.affine.rootAppSidebar.organize']()}
|
||||||
actions={
|
actions={
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -141,6 +140,7 @@ export const NavigationPanelOrganize = () => {
|
|||||||
at: 'navigation-panel:organize:folder-node',
|
at: 'navigation-panel:organize:folder-node',
|
||||||
nodeId: child.id as string,
|
nodeId: child.id as string,
|
||||||
}}
|
}}
|
||||||
|
parentPath={path}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</NavigationPanelTreeRoot>
|
</NavigationPanelTreeRoot>
|
||||||
|
|||||||
+7
-6
@@ -5,7 +5,7 @@ import { useI18n } from '@affine/i18n';
|
|||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import { AddTagIcon } from '@blocksuite/icons/rc';
|
import { AddTagIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
import { NavigationPanelTagNode } from '../../nodes/tag';
|
import { NavigationPanelTagNode } from '../../nodes/tag';
|
||||||
@@ -19,8 +19,8 @@ export const NavigationPanelTags = () => {
|
|||||||
TagService,
|
TagService,
|
||||||
NavigationPanelService,
|
NavigationPanelService,
|
||||||
});
|
});
|
||||||
const navigationPanelSection = navigationPanelService.sections.tags;
|
const path = useMemo(() => ['tags'], []);
|
||||||
const collapsed = useLiveData(navigationPanelSection.collapsed$);
|
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||||
const [creating, setCreating] = useState(false);
|
const [creating, setCreating] = useState(false);
|
||||||
const tags = useLiveData(tagService.tagList.tags$);
|
const tags = useLiveData(tagService.tagList.tags$);
|
||||||
|
|
||||||
@@ -30,9 +30,9 @@ export const NavigationPanelTags = () => {
|
|||||||
(name: string) => {
|
(name: string) => {
|
||||||
tagService.tagList.createTag(name, tagService.randomTagColor());
|
tagService.tagList.createTag(name, tagService.randomTagColor());
|
||||||
track.$.navigationPanel.organize.createOrganizeItem({ type: 'tag' });
|
track.$.navigationPanel.organize.createOrganizeItem({ type: 'tag' });
|
||||||
navigationPanelSection.setCollapsed(false);
|
navigationPanelService.setCollapsed(path, false);
|
||||||
},
|
},
|
||||||
[navigationPanelSection, tagService]
|
[navigationPanelService, path, tagService]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -45,7 +45,7 @@ export const NavigationPanelTags = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
name="tags"
|
path={path}
|
||||||
testId="navigation-panel-tags"
|
testId="navigation-panel-tags"
|
||||||
headerClassName={styles.draggedOverHighlight}
|
headerClassName={styles.draggedOverHighlight}
|
||||||
title={t['com.affine.rootAppSidebar.tags']()}
|
title={t['com.affine.rootAppSidebar.tags']()}
|
||||||
@@ -81,6 +81,7 @@ export const NavigationPanelTags = () => {
|
|||||||
location={{
|
location={{
|
||||||
at: 'navigation-panel:tags:list',
|
at: 'navigation-panel:tags:list',
|
||||||
}}
|
}}
|
||||||
|
parentPath={path}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</NavigationPanelTreeRoot>
|
</NavigationPanelTreeRoot>
|
||||||
|
|||||||
+7
-10
@@ -1,7 +1,4 @@
|
|||||||
import {
|
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||||
type CollapsibleSectionName,
|
|
||||||
NavigationPanelService,
|
|
||||||
} from '@affine/core/modules/navigation-panel';
|
|
||||||
import { ToggleRightIcon } from '@blocksuite/icons/rc';
|
import { ToggleRightIcon } from '@blocksuite/icons/rc';
|
||||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
@@ -22,7 +19,7 @@ import {
|
|||||||
} from './collapsible-section.css';
|
} from './collapsible-section.css';
|
||||||
|
|
||||||
interface CollapsibleSectionProps extends HTMLAttributes<HTMLDivElement> {
|
interface CollapsibleSectionProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
name: CollapsibleSectionName;
|
path: string[];
|
||||||
title: string;
|
title: string;
|
||||||
actions?: ReactNode;
|
actions?: ReactNode;
|
||||||
testId?: string;
|
testId?: string;
|
||||||
@@ -76,7 +73,7 @@ const CollapsibleSectionTrigger = forwardRef<
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const CollapsibleSection = ({
|
export const CollapsibleSection = ({
|
||||||
name,
|
path,
|
||||||
title,
|
title,
|
||||||
actions,
|
actions,
|
||||||
testId,
|
testId,
|
||||||
@@ -86,12 +83,12 @@ export const CollapsibleSection = ({
|
|||||||
children,
|
children,
|
||||||
...attrs
|
...attrs
|
||||||
}: CollapsibleSectionProps) => {
|
}: CollapsibleSectionProps) => {
|
||||||
const section = useService(NavigationPanelService).sections[name];
|
const navigationPanelService = useService(NavigationPanelService);
|
||||||
const collapsed = useLiveData(section.collapsed$);
|
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||||
|
|
||||||
const setCollapsed = useCallback(
|
const setCollapsed = useCallback(
|
||||||
(v: boolean) => section.setCollapsed(v),
|
(v: boolean) => navigationPanelService.setCollapsed(path, v),
|
||||||
[section]
|
[navigationPanelService, path]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import {
|
|||||||
} from '@affine/core/modules/collection';
|
} from '@affine/core/modules/collection';
|
||||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||||
|
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||||
import { ShareDocsListService } from '@affine/core/modules/share-doc';
|
import { ShareDocsListService } from '@affine/core/modules/share-doc';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import track from '@affine/track';
|
import track from '@affine/track';
|
||||||
import { FilterMinusIcon, ViewLayersIcon } from '@blocksuite/icons/rc';
|
import { FilterMinusIcon, ViewLayersIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
||||||
@@ -26,9 +27,11 @@ const CollectionIcon = () => <ViewLayersIcon />;
|
|||||||
export const NavigationPanelCollectionNode = ({
|
export const NavigationPanelCollectionNode = ({
|
||||||
collectionId,
|
collectionId,
|
||||||
operations: additionalOperations,
|
operations: additionalOperations,
|
||||||
|
parentPath,
|
||||||
}: {
|
}: {
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
operations?: NodeOperation[];
|
operations?: NodeOperation[];
|
||||||
|
parentPath: string[];
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { globalContextService, collectionService, workspaceDialogService } =
|
const { globalContextService, collectionService, workspaceDialogService } =
|
||||||
@@ -37,17 +40,28 @@ export const NavigationPanelCollectionNode = ({
|
|||||||
CollectionService,
|
CollectionService,
|
||||||
WorkspaceDialogService,
|
WorkspaceDialogService,
|
||||||
});
|
});
|
||||||
|
const navigationPanelService = useService(NavigationPanelService);
|
||||||
const active =
|
const active =
|
||||||
useLiveData(globalContextService.globalContext.collectionId.$) ===
|
useLiveData(globalContextService.globalContext.collectionId.$) ===
|
||||||
collectionId;
|
collectionId;
|
||||||
const [collapsed, setCollapsed] = useState(true);
|
const path = useMemo(
|
||||||
|
() => [...parentPath, `collection-${collectionId}`],
|
||||||
|
[parentPath, collectionId]
|
||||||
|
);
|
||||||
|
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||||
|
const setCollapsed = useCallback(
|
||||||
|
(value: boolean) => {
|
||||||
|
navigationPanelService.setCollapsed(path, value);
|
||||||
|
},
|
||||||
|
[navigationPanelService, path]
|
||||||
|
);
|
||||||
|
|
||||||
const collection = useLiveData(collectionService.collection$(collectionId));
|
const collection = useLiveData(collectionService.collection$(collectionId));
|
||||||
const name = useLiveData(collection?.name$);
|
const name = useLiveData(collection?.name$);
|
||||||
|
|
||||||
const handleOpenCollapsed = useCallback(() => {
|
const handleOpenCollapsed = useCallback(() => {
|
||||||
setCollapsed(false);
|
setCollapsed(false);
|
||||||
}, []);
|
}, [setCollapsed]);
|
||||||
|
|
||||||
const handleEditCollection = useCallback(() => {
|
const handleEditCollection = useCallback(() => {
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
@@ -95,6 +109,7 @@ export const NavigationPanelCollectionNode = ({
|
|||||||
<NavigationPanelCollectionNodeChildren
|
<NavigationPanelCollectionNodeChildren
|
||||||
collection={collection}
|
collection={collection}
|
||||||
onAddDoc={handleAddDocToCollection}
|
onAddDoc={handleAddDocToCollection}
|
||||||
|
path={path}
|
||||||
/>
|
/>
|
||||||
</NavigationPanelTreeNode>
|
</NavigationPanelTreeNode>
|
||||||
);
|
);
|
||||||
@@ -103,9 +118,11 @@ export const NavigationPanelCollectionNode = ({
|
|||||||
const NavigationPanelCollectionNodeChildren = ({
|
const NavigationPanelCollectionNodeChildren = ({
|
||||||
collection,
|
collection,
|
||||||
onAddDoc,
|
onAddDoc,
|
||||||
|
path,
|
||||||
}: {
|
}: {
|
||||||
collection: Collection;
|
collection: Collection;
|
||||||
onAddDoc?: () => void;
|
onAddDoc?: () => void;
|
||||||
|
path: string[];
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { shareDocsListService, collectionService } = useServices({
|
const { shareDocsListService, collectionService } = useServices({
|
||||||
@@ -147,6 +164,7 @@ const NavigationPanelCollectionNodeChildren = ({
|
|||||||
<NavigationPanelDocNode
|
<NavigationPanelDocNode
|
||||||
key={docId}
|
key={docId}
|
||||||
docId={docId}
|
docId={docId}
|
||||||
|
parentPath={path}
|
||||||
operations={
|
operations={
|
||||||
allowList
|
allowList
|
||||||
? [
|
? [
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
|||||||
import { DocsSearchService } from '@affine/core/modules/docs-search';
|
import { DocsSearchService } from '@affine/core/modules/docs-search';
|
||||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||||
|
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import {
|
import {
|
||||||
LiveData,
|
LiveData,
|
||||||
@@ -29,10 +30,12 @@ export const NavigationPanelDocNode = ({
|
|||||||
docId,
|
docId,
|
||||||
isLinked,
|
isLinked,
|
||||||
operations: additionalOperations,
|
operations: additionalOperations,
|
||||||
|
parentPath,
|
||||||
}: {
|
}: {
|
||||||
docId: string;
|
docId: string;
|
||||||
isLinked?: boolean;
|
isLinked?: boolean;
|
||||||
operations?: NodeOperation[];
|
operations?: NodeOperation[];
|
||||||
|
parentPath: string[];
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const {
|
const {
|
||||||
@@ -48,9 +51,20 @@ export const NavigationPanelDocNode = ({
|
|||||||
DocDisplayMetaService,
|
DocDisplayMetaService,
|
||||||
FeatureFlagService,
|
FeatureFlagService,
|
||||||
});
|
});
|
||||||
|
const navigationPanelService = useService(NavigationPanelService);
|
||||||
const active =
|
const active =
|
||||||
useLiveData(globalContextService.globalContext.docId.$) === docId;
|
useLiveData(globalContextService.globalContext.docId.$) === docId;
|
||||||
const [collapsed, setCollapsed] = useState(true);
|
const path = useMemo(
|
||||||
|
() => [...parentPath, `doc-${docId}`],
|
||||||
|
[parentPath, docId]
|
||||||
|
);
|
||||||
|
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||||
|
const setCollapsed = useCallback(
|
||||||
|
(value: boolean) => {
|
||||||
|
navigationPanelService.setCollapsed(path, value);
|
||||||
|
},
|
||||||
|
[navigationPanelService, path]
|
||||||
|
);
|
||||||
|
|
||||||
const docRecord = useLiveData(docsService.list.doc$(docId));
|
const docRecord = useLiveData(docsService.list.doc$(docId));
|
||||||
const DocIcon = useLiveData(
|
const DocIcon = useLiveData(
|
||||||
@@ -103,7 +117,7 @@ export const NavigationPanelDocNode = ({
|
|||||||
openInfoModal: () => workspaceDialogService.open('doc-info', { docId }),
|
openInfoModal: () => workspaceDialogService.open('doc-info', { docId }),
|
||||||
openNodeCollapsed: () => setCollapsed(false),
|
openNodeCollapsed: () => setCollapsed(false),
|
||||||
}),
|
}),
|
||||||
[docId, workspaceDialogService]
|
[docId, setCollapsed, workspaceDialogService]
|
||||||
);
|
);
|
||||||
const operations = useNavigationPanelDocNodeOperationsMenu(docId, option);
|
const operations = useNavigationPanelDocNodeOperationsMenu(docId, option);
|
||||||
const { handleAddLinkedPage } = useNavigationPanelDocNodeOperations(
|
const { handleAddLinkedPage } = useNavigationPanelDocNodeOperations(
|
||||||
@@ -150,6 +164,7 @@ export const NavigationPanelDocNode = ({
|
|||||||
key={`${child.docId}-${index}`}
|
key={`${child.docId}-${index}`}
|
||||||
docId={child.docId}
|
docId={child.docId}
|
||||||
isLinked
|
isLinked
|
||||||
|
parentPath={path}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
: null
|
: null
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import type {
|
|||||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
|
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||||
import {
|
import {
|
||||||
type FolderNode,
|
type FolderNode,
|
||||||
OrganizeService,
|
OrganizeService,
|
||||||
@@ -31,9 +32,9 @@ import {
|
|||||||
RemoveFolderIcon,
|
RemoveFolderIcon,
|
||||||
TagsIcon,
|
TagsIcon,
|
||||||
} from '@blocksuite/icons/rc';
|
} from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||||
import { difference } from 'lodash-es';
|
import { difference } from 'lodash-es';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
||||||
import { NavigationPanelTreeNode } from '../../tree/node';
|
import { NavigationPanelTreeNode } from '../../tree/node';
|
||||||
@@ -46,11 +47,13 @@ import { FavoriteFolderOperation } from './operations';
|
|||||||
export const NavigationPanelFolderNode = ({
|
export const NavigationPanelFolderNode = ({
|
||||||
nodeId,
|
nodeId,
|
||||||
operations,
|
operations,
|
||||||
|
parentPath,
|
||||||
}: {
|
}: {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
operations?:
|
operations?:
|
||||||
| NodeOperation[]
|
| NodeOperation[]
|
||||||
| ((type: string, node: FolderNode) => NodeOperation[]);
|
| ((type: string, node: FolderNode) => NodeOperation[]);
|
||||||
|
parentPath: string[];
|
||||||
}) => {
|
}) => {
|
||||||
const { organizeService } = useServices({
|
const { organizeService } = useServices({
|
||||||
OrganizeService,
|
OrganizeService,
|
||||||
@@ -78,24 +81,34 @@ export const NavigationPanelFolderNode = ({
|
|||||||
<NavigationPanelFolderNodeFolder
|
<NavigationPanelFolderNodeFolder
|
||||||
node={node}
|
node={node}
|
||||||
operations={additionalOperations}
|
operations={additionalOperations}
|
||||||
|
parentPath={parentPath}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
if (type === 'doc') {
|
if (type === 'doc') {
|
||||||
return (
|
return (
|
||||||
<NavigationPanelDocNode docId={data} operations={additionalOperations} />
|
<NavigationPanelDocNode
|
||||||
|
docId={data}
|
||||||
|
operations={additionalOperations}
|
||||||
|
parentPath={parentPath}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
} else if (type === 'collection') {
|
} else if (type === 'collection') {
|
||||||
return (
|
return (
|
||||||
<NavigationPanelCollectionNode
|
<NavigationPanelCollectionNode
|
||||||
collectionId={data}
|
collectionId={data}
|
||||||
operations={additionalOperations}
|
operations={additionalOperations}
|
||||||
|
parentPath={parentPath}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (type === 'tag') {
|
} else if (type === 'tag') {
|
||||||
return (
|
return (
|
||||||
<NavigationPanelTagNode tagId={data} operations={additionalOperations} />
|
<NavigationPanelTagNode
|
||||||
|
tagId={data}
|
||||||
|
operations={additionalOperations}
|
||||||
|
parentPath={parentPath}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,9 +132,11 @@ const NavigationPanelFolderIcon: NavigationPanelTreeNodeIcon = ({
|
|||||||
const NavigationPanelFolderNodeFolder = ({
|
const NavigationPanelFolderNodeFolder = ({
|
||||||
node,
|
node,
|
||||||
operations: additionalOperations,
|
operations: additionalOperations,
|
||||||
|
parentPath,
|
||||||
}: {
|
}: {
|
||||||
node: FolderNode;
|
node: FolderNode;
|
||||||
operations?: NodeOperation[];
|
operations?: NodeOperation[];
|
||||||
|
parentPath: string[];
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { workspaceService, featureFlagService, workspaceDialogService } =
|
const { workspaceService, featureFlagService, workspaceDialogService } =
|
||||||
@@ -135,7 +150,18 @@ const NavigationPanelFolderNodeFolder = ({
|
|||||||
const enableEmojiIcon = useLiveData(
|
const enableEmojiIcon = useLiveData(
|
||||||
featureFlagService.flags.enable_emoji_folder_icon.$
|
featureFlagService.flags.enable_emoji_folder_icon.$
|
||||||
);
|
);
|
||||||
const [collapsed, setCollapsed] = useState(true);
|
const navigationPanelService = useService(NavigationPanelService);
|
||||||
|
const path = useMemo(
|
||||||
|
() => [...parentPath, `folder-${node.id}`],
|
||||||
|
[parentPath, node.id]
|
||||||
|
);
|
||||||
|
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||||
|
const setCollapsed = useCallback(
|
||||||
|
(value: boolean) => {
|
||||||
|
navigationPanelService.setCollapsed(path, value);
|
||||||
|
},
|
||||||
|
[navigationPanelService, path]
|
||||||
|
);
|
||||||
|
|
||||||
const { createPage } = usePageHelper(
|
const { createPage } = usePageHelper(
|
||||||
workspaceService.workspace.docCollection
|
workspaceService.workspace.docCollection
|
||||||
@@ -171,7 +197,7 @@ const NavigationPanelFolderNodeFolder = ({
|
|||||||
target: 'doc',
|
target: 'doc',
|
||||||
});
|
});
|
||||||
setCollapsed(false);
|
setCollapsed(false);
|
||||||
}, [createPage, node]);
|
}, [createPage, node, setCollapsed]);
|
||||||
|
|
||||||
const handleCreateSubfolder = useCallback(
|
const handleCreateSubfolder = useCallback(
|
||||||
(name: string) => {
|
(name: string) => {
|
||||||
@@ -179,7 +205,7 @@ const NavigationPanelFolderNodeFolder = ({
|
|||||||
track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
|
track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
|
||||||
setCollapsed(false);
|
setCollapsed(false);
|
||||||
},
|
},
|
||||||
[node]
|
[node, setCollapsed]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleAddToFolder = useCallback(
|
const handleAddToFolder = useCallback(
|
||||||
@@ -223,7 +249,7 @@ const NavigationPanelFolderNodeFolder = ({
|
|||||||
target: type,
|
target: type,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[children, node, workspaceDialogService]
|
[children, node, setCollapsed, workspaceDialogService]
|
||||||
);
|
);
|
||||||
|
|
||||||
const createSubTipRenderer = useCallback(
|
const createSubTipRenderer = useCallback(
|
||||||
@@ -388,13 +414,16 @@ const NavigationPanelFolderNodeFolder = ({
|
|||||||
[t]
|
[t]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCollapsedChange = useCallback((collapsed: boolean) => {
|
const handleCollapsedChange = useCallback(
|
||||||
if (collapsed) {
|
(collapsed: boolean) => {
|
||||||
setCollapsed(true);
|
if (collapsed) {
|
||||||
} else {
|
setCollapsed(true);
|
||||||
setCollapsed(false);
|
} else {
|
||||||
}
|
setCollapsed(false);
|
||||||
}, []);
|
}
|
||||||
|
},
|
||||||
|
[setCollapsed]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationPanelTreeNode
|
<NavigationPanelTreeNode
|
||||||
@@ -413,6 +442,7 @@ const NavigationPanelFolderNodeFolder = ({
|
|||||||
key={child.id}
|
key={child.id}
|
||||||
nodeId={child.id as string}
|
nodeId={child.id as string}
|
||||||
operations={childrenOperations}
|
operations={childrenOperations}
|
||||||
|
parentPath={path}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<AddItemPlaceholder
|
<AddItemPlaceholder
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import type { NodeOperation } from '@affine/core/desktop/components/navigation-panel';
|
import type { NodeOperation } from '@affine/core/desktop/components/navigation-panel';
|
||||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||||
|
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
||||||
import type { Tag } from '@affine/core/modules/tag';
|
import type { Tag } from '@affine/core/modules/tag';
|
||||||
import { TagService } from '@affine/core/modules/tag';
|
import { TagService } from '@affine/core/modules/tag';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
||||||
import { NavigationPanelTreeNode } from '../../tree/node';
|
import { NavigationPanelTreeNode } from '../../tree/node';
|
||||||
@@ -19,18 +20,31 @@ import * as styles from './styles.css';
|
|||||||
export const NavigationPanelTagNode = ({
|
export const NavigationPanelTagNode = ({
|
||||||
tagId,
|
tagId,
|
||||||
operations: additionalOperations,
|
operations: additionalOperations,
|
||||||
|
parentPath,
|
||||||
}: {
|
}: {
|
||||||
tagId: string;
|
tagId: string;
|
||||||
operations?: NodeOperation[];
|
operations?: NodeOperation[];
|
||||||
|
parentPath: string[];
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { tagService, globalContextService } = useServices({
|
const { tagService, globalContextService } = useServices({
|
||||||
TagService,
|
TagService,
|
||||||
GlobalContextService,
|
GlobalContextService,
|
||||||
});
|
});
|
||||||
|
const navigationPanelService = useService(NavigationPanelService);
|
||||||
const active =
|
const active =
|
||||||
useLiveData(globalContextService.globalContext.tagId.$) === tagId;
|
useLiveData(globalContextService.globalContext.tagId.$) === tagId;
|
||||||
const [collapsed, setCollapsed] = useState(true);
|
const path = useMemo(
|
||||||
|
() => [...parentPath, `tag-${tagId}`],
|
||||||
|
[parentPath, tagId]
|
||||||
|
);
|
||||||
|
const collapsed = useLiveData(navigationPanelService.collapsed$(path));
|
||||||
|
const setCollapsed = useCallback(
|
||||||
|
(value: boolean) => {
|
||||||
|
navigationPanelService.setCollapsed(path, value);
|
||||||
|
},
|
||||||
|
[navigationPanelService, path]
|
||||||
|
);
|
||||||
|
|
||||||
const tagRecord = useLiveData(tagService.tagList.tagByTagId$(tagId));
|
const tagRecord = useLiveData(tagService.tagList.tagByTagId$(tagId));
|
||||||
const tagColor = useLiveData(tagRecord?.color$);
|
const tagColor = useLiveData(tagRecord?.color$);
|
||||||
@@ -57,7 +71,7 @@ export const NavigationPanelTagNode = ({
|
|||||||
() => ({
|
() => ({
|
||||||
openNodeCollapsed: () => setCollapsed(false),
|
openNodeCollapsed: () => setCollapsed(false),
|
||||||
}),
|
}),
|
||||||
[]
|
[setCollapsed]
|
||||||
);
|
);
|
||||||
const operations = useNavigationPanelTagNodeOperationsMenu(tagId, option);
|
const operations = useNavigationPanelTagNodeOperationsMenu(tagId, option);
|
||||||
const { handleNewDoc } = useNavigationPanelTagNodeOperations(tagId, option);
|
const { handleNewDoc } = useNavigationPanelTagNodeOperations(tagId, option);
|
||||||
@@ -86,7 +100,11 @@ export const NavigationPanelTagNode = ({
|
|||||||
aria-label={tagName}
|
aria-label={tagName}
|
||||||
data-role="navigation-panel-tag"
|
data-role="navigation-panel-tag"
|
||||||
>
|
>
|
||||||
<NavigationPanelTagNodeDocs tag={tagRecord} onNewDoc={handleNewDoc} />
|
<NavigationPanelTagNodeDocs
|
||||||
|
tag={tagRecord}
|
||||||
|
onNewDoc={handleNewDoc}
|
||||||
|
path={path}
|
||||||
|
/>
|
||||||
</NavigationPanelTreeNode>
|
</NavigationPanelTreeNode>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -99,9 +117,11 @@ export const NavigationPanelTagNode = ({
|
|||||||
export const NavigationPanelTagNodeDocs = ({
|
export const NavigationPanelTagNodeDocs = ({
|
||||||
tag,
|
tag,
|
||||||
onNewDoc,
|
onNewDoc,
|
||||||
|
path,
|
||||||
}: {
|
}: {
|
||||||
tag: Tag;
|
tag: Tag;
|
||||||
onNewDoc?: () => void;
|
onNewDoc?: () => void;
|
||||||
|
path: string[];
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const tagDocIds = useLiveData(tag.pageIds$);
|
const tagDocIds = useLiveData(tag.pageIds$);
|
||||||
@@ -109,7 +129,7 @@ export const NavigationPanelTagNodeDocs = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{tagDocIds.map(docId => (
|
{tagDocIds.map(docId => (
|
||||||
<NavigationPanelDocNode key={docId} docId={docId} />
|
<NavigationPanelDocNode key={docId} docId={docId} parentPath={path} />
|
||||||
))}
|
))}
|
||||||
<AddItemPlaceholder label={t['New Page']()} onClick={onNewDoc} />
|
<AddItemPlaceholder label={t['New Page']()} onClick={onNewDoc} />
|
||||||
</>
|
</>
|
||||||
|
|||||||
+7
-5
@@ -7,7 +7,7 @@ import { useI18n } from '@affine/i18n';
|
|||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import { AddCollectionIcon } from '@blocksuite/icons/rc';
|
import { AddCollectionIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
||||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
@@ -22,7 +22,7 @@ export const NavigationPanelCollections = () => {
|
|||||||
WorkbenchService,
|
WorkbenchService,
|
||||||
NavigationPanelService,
|
NavigationPanelService,
|
||||||
});
|
});
|
||||||
const navigationPanelSection = navigationPanelService.sections.collections;
|
const path = useMemo(() => ['collections'], []);
|
||||||
const collectionMetas = useLiveData(collectionService.collectionMetas$);
|
const collectionMetas = useLiveData(collectionService.collectionMetas$);
|
||||||
const { openPromptModal } = usePromptModal();
|
const { openPromptModal } = usePromptModal();
|
||||||
|
|
||||||
@@ -49,12 +49,13 @@ export const NavigationPanelCollections = () => {
|
|||||||
type: 'collection',
|
type: 'collection',
|
||||||
});
|
});
|
||||||
workbenchService.workbench.openCollection(id);
|
workbenchService.workbench.openCollection(id);
|
||||||
navigationPanelSection.setCollapsed(false);
|
navigationPanelService.setCollapsed(path, false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
collectionService,
|
collectionService,
|
||||||
navigationPanelSection,
|
navigationPanelService,
|
||||||
|
path,
|
||||||
openPromptModal,
|
openPromptModal,
|
||||||
t,
|
t,
|
||||||
workbenchService.workbench,
|
workbenchService.workbench,
|
||||||
@@ -62,7 +63,7 @@ export const NavigationPanelCollections = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
name="collections"
|
path={path}
|
||||||
testId="navigation-panel-collections"
|
testId="navigation-panel-collections"
|
||||||
title={t['com.affine.rootAppSidebar.collections']()}
|
title={t['com.affine.rootAppSidebar.collections']()}
|
||||||
>
|
>
|
||||||
@@ -71,6 +72,7 @@ export const NavigationPanelCollections = () => {
|
|||||||
<NavigationPanelCollectionNode
|
<NavigationPanelCollectionNode
|
||||||
key={collection.id}
|
key={collection.id}
|
||||||
collectionId={collection.id}
|
collectionId={collection.id}
|
||||||
|
parentPath={path}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<AddItemPlaceholder
|
<AddItemPlaceholder
|
||||||
|
|||||||
+19
-10
@@ -6,7 +6,7 @@ import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
|
|||||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
||||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
@@ -24,7 +24,7 @@ export const NavigationPanelFavorites = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const navigationPanelSection = navigationPanelService.sections.favorites;
|
const path = useMemo(() => ['favorites'], []);
|
||||||
const favorites = useLiveData(favoriteService.favoriteList.sortedList$);
|
const favorites = useLiveData(favoriteService.favoriteList.sortedList$);
|
||||||
const isLoading = useLiveData(favoriteService.favoriteList.isLoading$);
|
const isLoading = useLiveData(favoriteService.favoriteList.isLoading$);
|
||||||
const { createPage } = usePageHelper(
|
const { createPage } = usePageHelper(
|
||||||
@@ -38,19 +38,23 @@ export const NavigationPanelFavorites = () => {
|
|||||||
newDoc.id,
|
newDoc.id,
|
||||||
favoriteService.favoriteList.indexAt('before')
|
favoriteService.favoriteList.indexAt('before')
|
||||||
);
|
);
|
||||||
navigationPanelSection.setCollapsed(false);
|
navigationPanelService.setCollapsed(path, false);
|
||||||
}, [createPage, navigationPanelSection, favoriteService.favoriteList]);
|
}, [createPage, favoriteService.favoriteList, navigationPanelService, path]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
name="favorites"
|
path={path}
|
||||||
title={t['com.affine.rootAppSidebar.favorites']()}
|
title={t['com.affine.rootAppSidebar.favorites']()}
|
||||||
testId="navigation-panel-favorites"
|
testId="navigation-panel-favorites"
|
||||||
headerTestId="navigation-panel-favorite-category-divider"
|
headerTestId="navigation-panel-favorite-category-divider"
|
||||||
>
|
>
|
||||||
<NavigationPanelTreeRoot placeholder={isLoading ? 'Loading' : null}>
|
<NavigationPanelTreeRoot placeholder={isLoading ? 'Loading' : null}>
|
||||||
{favorites.map(favorite => (
|
{favorites.map(favorite => (
|
||||||
<FavoriteNode key={favorite.id} favorite={favorite} />
|
<FavoriteNode
|
||||||
|
key={favorite.id}
|
||||||
|
favorite={favorite}
|
||||||
|
parentPath={path}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
<AddItemPlaceholder
|
<AddItemPlaceholder
|
||||||
data-testid="navigation-panel-bar-add-favorite-button"
|
data-testid="navigation-panel-bar-add-favorite-button"
|
||||||
@@ -66,19 +70,24 @@ export const NavigationPanelFavorites = () => {
|
|||||||
|
|
||||||
export const FavoriteNode = ({
|
export const FavoriteNode = ({
|
||||||
favorite,
|
favorite,
|
||||||
|
parentPath,
|
||||||
}: {
|
}: {
|
||||||
favorite: {
|
favorite: {
|
||||||
id: string;
|
id: string;
|
||||||
type: FavoriteSupportTypeUnion;
|
type: FavoriteSupportTypeUnion;
|
||||||
};
|
};
|
||||||
|
parentPath: string[];
|
||||||
}) => {
|
}) => {
|
||||||
return favorite.type === 'doc' ? (
|
return favorite.type === 'doc' ? (
|
||||||
<NavigationPanelDocNode docId={favorite.id} />
|
<NavigationPanelDocNode docId={favorite.id} parentPath={parentPath} />
|
||||||
) : favorite.type === 'tag' ? (
|
) : favorite.type === 'tag' ? (
|
||||||
<NavigationPanelTagNode tagId={favorite.id} />
|
<NavigationPanelTagNode tagId={favorite.id} parentPath={parentPath} />
|
||||||
) : favorite.type === 'folder' ? (
|
) : favorite.type === 'folder' ? (
|
||||||
<NavigationPanelFolderNode nodeId={favorite.id} />
|
<NavigationPanelFolderNode nodeId={favorite.id} parentPath={parentPath} />
|
||||||
) : (
|
) : (
|
||||||
<NavigationPanelCollectionNode collectionId={favorite.id} />
|
<NavigationPanelCollectionNode
|
||||||
|
collectionId={favorite.id}
|
||||||
|
parentPath={parentPath}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useI18n } from '@affine/i18n';
|
|||||||
import track from '@affine/track';
|
import track from '@affine/track';
|
||||||
import { AddOrganizeIcon } from '@blocksuite/icons/rc';
|
import { AddOrganizeIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
||||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
@@ -18,7 +18,7 @@ export const NavigationPanelOrganize = () => {
|
|||||||
OrganizeService,
|
OrganizeService,
|
||||||
NavigationPanelService,
|
NavigationPanelService,
|
||||||
});
|
});
|
||||||
const navigationPanelSection = navigationPanelService.sections.organize;
|
const path = useMemo(() => ['organize'], []);
|
||||||
const [openNewFolderDialog, setOpenNewFolderDialog] = useState(false);
|
const [openNewFolderDialog, setOpenNewFolderDialog] = useState(false);
|
||||||
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
@@ -36,15 +36,15 @@ export const NavigationPanelOrganize = () => {
|
|||||||
rootFolder.indexAt('before')
|
rootFolder.indexAt('before')
|
||||||
);
|
);
|
||||||
track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
|
track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
|
||||||
navigationPanelSection.setCollapsed(false);
|
navigationPanelService.setCollapsed(path, false);
|
||||||
return newFolderId;
|
return newFolderId;
|
||||||
},
|
},
|
||||||
[navigationPanelSection, rootFolder]
|
[navigationPanelService, path, rootFolder]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
name="organize"
|
path={path}
|
||||||
title={t['com.affine.rootAppSidebar.organize']()}
|
title={t['com.affine.rootAppSidebar.organize']()}
|
||||||
>
|
>
|
||||||
{/* TODO(@CatsJuice): Organize loading UI */}
|
{/* TODO(@CatsJuice): Organize loading UI */}
|
||||||
@@ -53,6 +53,7 @@ export const NavigationPanelOrganize = () => {
|
|||||||
<NavigationPanelFolderNode
|
<NavigationPanelFolderNode
|
||||||
key={child.id}
|
key={child.id}
|
||||||
nodeId={child.id as string}
|
nodeId={child.id as string}
|
||||||
|
parentPath={path}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<AddItemPlaceholder
|
<AddItemPlaceholder
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useI18n } from '@affine/i18n';
|
|||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import { AddTagIcon } from '@blocksuite/icons/rc';
|
import { AddTagIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
|
||||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
@@ -25,7 +25,7 @@ export const NavigationPanelTags = () => {
|
|||||||
TagService,
|
TagService,
|
||||||
NavigationPanelService,
|
NavigationPanelService,
|
||||||
});
|
});
|
||||||
const navigationPanelSection = navigationPanelService.sections.tags;
|
const path = useMemo(() => ['tags'], []);
|
||||||
const tags = useLiveData(tagService.tagList.tags$);
|
const tags = useLiveData(tagService.tagList.tags$);
|
||||||
const [showNewTagDialog, setShowNewTagDialog] = useState(false);
|
const [showNewTagDialog, setShowNewTagDialog] = useState(false);
|
||||||
|
|
||||||
@@ -36,19 +36,23 @@ export const NavigationPanelTags = () => {
|
|||||||
setShowNewTagDialog(false);
|
setShowNewTagDialog(false);
|
||||||
tagService.tagList.createTag(name, color);
|
tagService.tagList.createTag(name, color);
|
||||||
track.$.navigationPanel.organize.createOrganizeItem({ type: 'tag' });
|
track.$.navigationPanel.organize.createOrganizeItem({ type: 'tag' });
|
||||||
navigationPanelSection.setCollapsed(false);
|
navigationPanelService.setCollapsed(path, false);
|
||||||
},
|
},
|
||||||
[navigationPanelSection, tagService]
|
[navigationPanelService, path, tagService]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
name="tags"
|
path={path}
|
||||||
title={t['com.affine.rootAppSidebar.tags']()}
|
title={t['com.affine.rootAppSidebar.tags']()}
|
||||||
>
|
>
|
||||||
<NavigationPanelTreeRoot>
|
<NavigationPanelTreeRoot>
|
||||||
{tags.map(tag => (
|
{tags.map(tag => (
|
||||||
<NavigationPanelTagNode key={tag.id} tagId={tag.id} />
|
<NavigationPanelTagNode
|
||||||
|
key={tag.id}
|
||||||
|
tagId={tag.id}
|
||||||
|
parentPath={path}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
<AddItemPlaceholder
|
<AddItemPlaceholder
|
||||||
icon={<AddTagIcon />}
|
icon={<AddTagIcon />}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const RecentDocs = ({ max = 5 }: { max?: number }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
name="recent"
|
path={['recent']}
|
||||||
title="Recent"
|
title="Recent"
|
||||||
headerClassName={styles.header}
|
headerClassName={styles.header}
|
||||||
className={styles.recentSection}
|
className={styles.recentSection}
|
||||||
|
|||||||
-39
@@ -1,39 +0,0 @@
|
|||||||
import { Entity, LiveData } from '@toeverything/infra';
|
|
||||||
import { map } from 'rxjs';
|
|
||||||
|
|
||||||
import type { GlobalCache } from '../../storage';
|
|
||||||
import type { CollapsibleSectionName } from '../types';
|
|
||||||
|
|
||||||
const DEFAULT_COLLAPSABLE_STATE: Record<CollapsibleSectionName, boolean> = {
|
|
||||||
recent: true,
|
|
||||||
favorites: false,
|
|
||||||
organize: false,
|
|
||||||
collections: true,
|
|
||||||
tags: true,
|
|
||||||
favoritesOld: true,
|
|
||||||
migrationFavorites: true,
|
|
||||||
others: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export class NavigationPanelSection extends Entity<{
|
|
||||||
name: CollapsibleSectionName;
|
|
||||||
}> {
|
|
||||||
name: CollapsibleSectionName = this.props.name;
|
|
||||||
key = `explorer.section.${this.name}`;
|
|
||||||
defaultValue = DEFAULT_COLLAPSABLE_STATE[this.name];
|
|
||||||
|
|
||||||
constructor(private readonly globalCache: GlobalCache) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
collapsed$ = LiveData.from(
|
|
||||||
this.globalCache
|
|
||||||
.watch<boolean>(this.key)
|
|
||||||
.pipe(map(v => v ?? this.defaultValue)),
|
|
||||||
this.defaultValue
|
|
||||||
);
|
|
||||||
|
|
||||||
setCollapsed(collapsed: boolean) {
|
|
||||||
this.globalCache.set(this.key, collapsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
import { type Framework } from '@toeverything/infra';
|
import { type Framework } from '@toeverything/infra';
|
||||||
|
|
||||||
import { GlobalCache } from '../storage';
|
import { GlobalCache } from '../storage';
|
||||||
import { WorkspaceScope } from '../workspace';
|
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||||
import { NavigationPanelSection } from './entities/navigation-panel-section';
|
|
||||||
import { NavigationPanelService } from './services/navigation-panel';
|
import { NavigationPanelService } from './services/navigation-panel';
|
||||||
export { NavigationPanelService } from './services/navigation-panel';
|
export { NavigationPanelService } from './services/navigation-panel';
|
||||||
export type { CollapsibleSectionName } from './types';
|
|
||||||
|
|
||||||
export function configureNavigationPanelModule(framework: Framework) {
|
export function configureNavigationPanelModule(framework: Framework) {
|
||||||
framework
|
framework
|
||||||
.scope(WorkspaceScope)
|
.scope(WorkspaceScope)
|
||||||
.service(NavigationPanelService)
|
.service(NavigationPanelService, [GlobalCache, WorkspaceService]);
|
||||||
.entity(NavigationPanelSection, [GlobalCache]);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,47 @@
|
|||||||
import { Service } from '@toeverything/infra';
|
import { LiveData, Service } from '@toeverything/infra';
|
||||||
|
|
||||||
import { NavigationPanelSection } from '../entities/navigation-panel-section';
|
import type { GlobalCache } from '../../storage/providers/global';
|
||||||
import type { CollapsibleSectionName } from '../types';
|
import type { WorkspaceService } from '../../workspace';
|
||||||
|
|
||||||
const allSectionName: Array<CollapsibleSectionName> = [
|
const DEFAULT_COLLAPSABLE_STATE: Record<string, boolean> = {
|
||||||
'recent', // mobile only
|
recent: true,
|
||||||
'favorites',
|
favorites: false,
|
||||||
'organize',
|
organize: false,
|
||||||
'collections',
|
collections: true,
|
||||||
'tags',
|
tags: true,
|
||||||
'favoritesOld',
|
favoritesOld: true,
|
||||||
'migrationFavorites',
|
migrationFavorites: true,
|
||||||
'others',
|
others: false,
|
||||||
];
|
};
|
||||||
|
|
||||||
export class NavigationPanelService extends Service {
|
export class NavigationPanelService extends Service {
|
||||||
readonly sections = allSectionName.reduce(
|
constructor(
|
||||||
(prev, name) =>
|
private readonly globalCache: GlobalCache,
|
||||||
Object.assign(prev, {
|
private readonly workspaceService: WorkspaceService
|
||||||
[name]: this.framework.createEntity(NavigationPanelSection, { name }),
|
) {
|
||||||
}),
|
super();
|
||||||
{} as Record<CollapsibleSectionName, NavigationPanelSection>
|
}
|
||||||
);
|
|
||||||
|
private readonly collapsedCache = new Map<string, LiveData<boolean>>();
|
||||||
|
|
||||||
|
collapsed$(path: string[]) {
|
||||||
|
const pathKey = path.join(':');
|
||||||
|
const key = `navigation:${this.workspaceService.workspace.id}:${pathKey}`;
|
||||||
|
const cached$ = this.collapsedCache.get(key);
|
||||||
|
if (!cached$) {
|
||||||
|
const liveData$ = LiveData.from(
|
||||||
|
this.globalCache.watch<boolean>(key),
|
||||||
|
undefined
|
||||||
|
).map(v => v ?? DEFAULT_COLLAPSABLE_STATE[pathKey] ?? true);
|
||||||
|
this.collapsedCache.set(key, liveData$);
|
||||||
|
return liveData$;
|
||||||
|
}
|
||||||
|
return cached$;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCollapsed(path: string[], collapsed: boolean) {
|
||||||
|
const pathKey = path.join(':');
|
||||||
|
const key = `navigation:${this.workspaceService.workspace.id}:${pathKey}`;
|
||||||
|
this.globalCache.set(key, collapsed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
export type CollapsibleSectionName =
|
|
||||||
| 'recent'
|
|
||||||
| 'collections'
|
|
||||||
| 'favorites'
|
|
||||||
| 'tags'
|
|
||||||
| 'organize'
|
|
||||||
| 'favoritesOld'
|
|
||||||
| 'migrationFavorites'
|
|
||||||
| 'others';
|
|
||||||
Reference in New Issue
Block a user