refactor(core): rename explorer to navigation-panel (#11876)

This commit is contained in:
EYHN
2025-04-22 12:47:35 +08:00
committed by GitHub
parent f918573ba8
commit e4d6833296
104 changed files with 913 additions and 769 deletions

View File

@@ -12,14 +12,6 @@ import {
import { ExternalMenuLinkItem } from '@affine/core/modules/app-sidebar/views/menu-item/external-menu-link-item'; import { ExternalMenuLinkItem } from '@affine/core/modules/app-sidebar/views/menu-item/external-menu-link-item';
import { AuthService } from '@affine/core/modules/cloud'; import { AuthService } from '@affine/core/modules/cloud';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import {
CollapsibleSection,
ExplorerCollections,
ExplorerFavorites,
ExplorerMigrationFavorites,
ExplorerOrganize,
} from '@affine/core/modules/explorer';
import { ExplorerTags } from '@affine/core/modules/explorer/views/sections/tags';
import { CMDKQuickSearchService } from '@affine/core/modules/quicksearch/services/cmdk'; import { CMDKQuickSearchService } from '@affine/core/modules/quicksearch/services/cmdk';
import type { Workspace } from '@affine/core/modules/workspace'; import type { Workspace } from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
@@ -35,6 +27,14 @@ import { useLiveData, useService, useServices } from '@toeverything/infra';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import {
CollapsibleSection,
NavigationPanelCollections,
NavigationPanelFavorites,
NavigationPanelMigrationFavorites,
NavigationPanelOrganize,
NavigationPanelTags,
} from '../../desktop/components/navigation-panel';
import { WorkbenchService } from '../../modules/workbench'; import { WorkbenchService } from '../../modules/workbench';
import { WorkspaceNavigator } from '../workspace-selector'; import { WorkspaceNavigator } from '../workspace-selector';
import { import {
@@ -180,11 +180,11 @@ export const RootAppSidebar = memo((): ReactElement => {
</MenuItem> </MenuItem>
</SidebarContainer> </SidebarContainer>
<SidebarScrollableContainer> <SidebarScrollableContainer>
<ExplorerFavorites /> <NavigationPanelFavorites />
<ExplorerOrganize /> <NavigationPanelOrganize />
<ExplorerMigrationFavorites /> <NavigationPanelMigrationFavorites />
<ExplorerCollections /> <NavigationPanelCollections />
<ExplorerTags /> <NavigationPanelTags />
<CollapsibleSection <CollapsibleSection
name="others" name="others"
title={t['com.affine.rootAppSidebar.others']()} title={t['com.affine.rootAppSidebar.others']()}

View File

@@ -0,0 +1,13 @@
export { CollapsibleSection } from './layouts/collapsible-section';
export { NavigationPanelCollections } from './sections/collections';
export { NavigationPanelFavorites } from './sections/favorites';
export { NavigationPanelMigrationFavorites } from './sections/migration-favorites';
export { NavigationPanelOrganize } from './sections/organize';
export { NavigationPanelTags } from './sections/tags';
export { NavigationPanelTreeRoot } from './tree';
export { NavigationPanelTreeContext } from './tree/context';
export type {
BaseNavigationPanelTreeNodeProps,
NavigationPanelTreeNodeIcon,
} from './tree/node';
export type { NodeOperation } from './tree/types';

View File

@@ -1,4 +1,8 @@
import { CategoryDivider } from '@affine/core/modules/app-sidebar/views'; import { CategoryDivider } from '@affine/core/modules/app-sidebar/views';
import {
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';
@@ -10,8 +14,6 @@ import {
useCallback, useCallback,
} from 'react'; } from 'react';
import { ExplorerService } from '../../services/explorer';
import type { CollapsibleSectionName } from '../../types';
import { content, header, root } from './collapsible-section.css'; import { content, header, root } from './collapsible-section.css';
interface CollapsibleSectionProps extends PropsWithChildren { interface CollapsibleSectionProps extends PropsWithChildren {
@@ -46,7 +48,7 @@ export const CollapsibleSection = ({
contentClassName, contentClassName,
contentStyle, contentStyle,
}: CollapsibleSectionProps) => { }: CollapsibleSectionProps) => {
const section = useService(ExplorerService).sections[name]; const section = useService(NavigationPanelService).sections[name];
const collapsed = useLiveData(section.collapsed$); const collapsed = useLiveData(section.collapsed$);

View File

@@ -0,0 +1,63 @@
import { Button } from '@affine/component';
import clsx from 'clsx';
import {
cloneElement,
forwardRef,
type HTMLAttributes,
type JSX,
type ReactElement,
type Ref,
type SVGAttributes,
type SVGProps,
} from 'react';
import * as styles from './empty-section.css';
interface NavigationPanelEmptySectionProps
extends HTMLAttributes<HTMLDivElement> {
icon:
| ((props: SVGProps<SVGSVGElement>) => JSX.Element)
| ReactElement<SVGAttributes<SVGElement>>;
message: string;
messageTestId?: string;
actionText?: string;
onActionClick?: () => void;
}
export const NavigationPanelEmptySection = forwardRef(
function NavigationPanelEmptySection(
{
icon: Icon,
message,
messageTestId,
actionText,
children,
className,
onActionClick,
...attrs
}: NavigationPanelEmptySectionProps,
ref: Ref<HTMLDivElement>
) {
const icon =
typeof Icon === 'function' ? (
<Icon className={styles.icon} />
) : (
cloneElement(Icon, { className: styles.icon })
);
return (
<div className={clsx(styles.content, className)} ref={ref} {...attrs}>
<div className={styles.iconWrapper}>{icon}</div>
<div data-testid={messageTestId} className={styles.message}>
{message}
</div>
{actionText ? (
<Button className={styles.newButton} onClick={onActionClick}>
{actionText}
</Button>
) : null}
{children}
</div>
);
}
);

View File

@@ -27,14 +27,17 @@ import {
} from '@toeverything/infra'; } from '@toeverything/infra';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { ExplorerTreeNode, type ExplorerTreeNodeDropEffect } from '../../tree'; import {
import type { ExplorerTreeNodeIcon } from '../../tree/node'; NavigationPanelTreeNode,
import { ExplorerDocNode } from '../doc'; type NavigationPanelTreeNodeDropEffect,
import type { GenericExplorerNode } from '../types'; } from '../../tree';
import type { NavigationPanelTreeNodeIcon } from '../../tree/node';
import { NavigationPanelDocNode } from '../doc';
import type { GenericNavigationPanelNode } from '../types';
import { Empty } from './empty'; import { Empty } from './empty';
import { useExplorerCollectionNodeOperations } from './operations'; import { useNavigationPanelCollectionNodeOperations } from './operations';
const CollectionIcon: ExplorerTreeNodeIcon = ({ const CollectionIcon: NavigationPanelTreeNodeIcon = ({
className, className,
draggedOver, draggedOver,
treeInstruction, treeInstruction,
@@ -45,7 +48,7 @@ const CollectionIcon: ExplorerTreeNodeIcon = ({
/> />
); );
export const ExplorerCollectionNode = ({ export const NavigationPanelCollectionNode = ({
collectionId, collectionId,
onDrop, onDrop,
location, location,
@@ -55,7 +58,7 @@ export const ExplorerCollectionNode = ({
dropEffect, dropEffect,
}: { }: {
collectionId: string; collectionId: string;
} & GenericExplorerNode) => { } & GenericNavigationPanelNode) => {
const t = useI18n(); const t = useI18n();
const { globalContextService, workspaceDialogService } = useServices({ const { globalContextService, workspaceDialogService } = useServices({
GlobalContextService, GlobalContextService,
@@ -79,7 +82,7 @@ export const ExplorerCollectionNode = ({
from: location, from: location,
}, },
dropTarget: { dropTarget: {
at: 'explorer:doc', at: 'navigation-panel:doc',
}, },
} satisfies AffineDNDData; } satisfies AffineDNDData;
}, [collectionId, location]); }, [collectionId, location]);
@@ -136,19 +139,20 @@ export const ExplorerCollectionNode = ({
[collection, onDrop, handleAddDocToCollection] [collection, onDrop, handleAddDocToCollection]
); );
const handleDropEffectOnCollection = useCallback<ExplorerTreeNodeDropEffect>( const handleDropEffectOnCollection =
data => { useCallback<NavigationPanelTreeNodeDropEffect>(
if (collection && data.treeInstruction?.type === 'make-child') { data => {
if (data.source.data.entity?.type === 'doc') { if (collection && data.treeInstruction?.type === 'make-child') {
return 'link'; if (data.source.data.entity?.type === 'doc') {
return 'link';
}
} else {
return dropEffect?.(data);
} }
} else { return;
return dropEffect?.(data); },
} [collection, dropEffect]
return; );
},
[collection, dropEffect]
);
const handleDropOnPlaceholder = useCallback( const handleDropOnPlaceholder = useCallback(
(data: DropTargetDropEvent<AffineDNDData>) => { (data: DropTargetDropEvent<AffineDNDData>) => {
@@ -176,7 +180,7 @@ export const ExplorerCollectionNode = ({
}); });
}, [collection, workspaceDialogService]); }, [collection, workspaceDialogService]);
const collectionOperations = useExplorerCollectionNodeOperations( const collectionOperations = useNavigationPanelCollectionNodeOperations(
collectionId, collectionId,
handleOpenCollapsed, handleOpenCollapsed,
handleEditCollection handleEditCollection
@@ -204,7 +208,7 @@ export const ExplorerCollectionNode = ({
} }
return ( return (
<ExplorerTreeNode <NavigationPanelTreeNode
icon={CollectionIcon} icon={CollectionIcon}
name={collection.name || t['Untitled']()} name={collection.name || t['Untitled']()}
dndData={dndData} dndData={dndData}
@@ -220,14 +224,14 @@ export const ExplorerCollectionNode = ({
childrenPlaceholder={<Empty onDrop={handleDropOnPlaceholder} />} childrenPlaceholder={<Empty onDrop={handleDropOnPlaceholder} />}
operations={finalOperations} operations={finalOperations}
dropEffect={handleDropEffectOnCollection} dropEffect={handleDropEffectOnCollection}
data-testid={`explorer-collection-${collectionId}`} data-testid={`navigation-panel-collection-${collectionId}`}
> >
<ExplorerCollectionNodeChildren collection={collection} /> <NavigationPanelCollectionNodeChildren collection={collection} />
</ExplorerTreeNode> </NavigationPanelTreeNode>
); );
}; };
const ExplorerCollectionNodeChildren = ({ const NavigationPanelCollectionNodeChildren = ({
collection, collection,
}: { }: {
collection: Collection; collection: Collection;
@@ -294,12 +298,12 @@ const ExplorerCollectionNodeChildren = ({
}); });
return filtered.map(doc => ( return filtered.map(doc => (
<ExplorerDocNode <NavigationPanelDocNode
key={doc.id} key={doc.id}
docId={doc.id} docId={doc.id}
reorderable={false} reorderable={false}
location={{ location={{
at: 'explorer:collection:filtered-docs', at: 'navigation-panel:collection:filtered-docs',
collectionId: collection.id, collectionId: collection.id,
}} }}
operations={ operations={

View File

@@ -25,7 +25,7 @@ import { useCallback, useMemo } from 'react';
import type { NodeOperation } from '../../tree/types'; import type { NodeOperation } from '../../tree/types';
export const useExplorerCollectionNodeOperations = ( export const useNavigationPanelCollectionNodeOperations = (
collectionId: string, collectionId: string,
onOpenCollapsed: () => void, onOpenCollapsed: () => void,
onOpenEdit: () => void onOpenEdit: () => void

View File

@@ -27,13 +27,16 @@ import {
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { NEVER } from 'rxjs'; import { NEVER } from 'rxjs';
import { ExplorerTreeNode, type ExplorerTreeNodeDropEffect } from '../../tree'; import {
import type { GenericExplorerNode } from '../types'; NavigationPanelTreeNode,
type NavigationPanelTreeNodeDropEffect,
} from '../../tree';
import type { GenericNavigationPanelNode } from '../types';
import { Empty } from './empty'; import { Empty } from './empty';
import { useExplorerDocNodeOperations } from './operations'; import { useNavigationPanelDocNodeOperations } from './operations';
import * as styles from './styles.css'; import * as styles from './styles.css';
export const ExplorerDocNode = ({ export const NavigationPanelDocNode = ({
docId, docId,
onDrop, onDrop,
location, location,
@@ -46,7 +49,7 @@ export const ExplorerDocNode = ({
docId: string; docId: string;
isLinked?: boolean; isLinked?: boolean;
forwardKey?: string; forwardKey?: string;
} & GenericExplorerNode) => { } & GenericNavigationPanelNode) => {
const t = useI18n(); const t = useI18n();
const { const {
docsSearchService, docsSearchService,
@@ -127,7 +130,7 @@ export const ExplorerDocNode = ({
from: location, from: location,
}, },
dropTarget: { dropTarget: {
at: 'explorer:doc', at: 'navigation-panel:doc',
}, },
} satisfies AffineDNDData; } satisfies AffineDNDData;
}, [docId, location]); }, [docId, location]);
@@ -166,7 +169,7 @@ export const ExplorerDocNode = ({
[docId, docsService, guardService, onDrop, t] [docId, docsService, guardService, onDrop, t]
); );
const handleDropEffectOnDoc = useCallback<ExplorerTreeNodeDropEffect>( const handleDropEffectOnDoc = useCallback<NavigationPanelTreeNodeDropEffect>(
data => { data => {
if (data.treeInstruction?.type === 'make-child') { if (data.treeInstruction?.type === 'make-child') {
if (data.source.data.entity?.type === 'doc') { if (data.source.data.entity?.type === 'doc') {
@@ -214,7 +217,7 @@ export const ExplorerDocNode = ({
); );
const workspaceDialogService = useService(WorkspaceDialogService); const workspaceDialogService = useService(WorkspaceDialogService);
const operations = useExplorerDocNodeOperations( const operations = useNavigationPanelDocNodeOperations(
docId, docId,
useMemo( useMemo(
() => ({ () => ({
@@ -237,7 +240,7 @@ export const ExplorerDocNode = ({
} }
return ( return (
<ExplorerTreeNode <NavigationPanelTreeNode
icon={Icon} icon={Icon}
name={t.t(docTitle)} name={t.t(docTitle)}
dndData={dndData} dndData={dndData}
@@ -277,18 +280,18 @@ export const ExplorerDocNode = ({
} }
operations={finalOperations} operations={finalOperations}
dropEffect={handleDropEffectOnDoc} dropEffect={handleDropEffectOnDoc}
data-testid={`explorer-doc-${docId}`} data-testid={`navigation-panel-doc-${docId}`}
> >
<Guard docId={docId} permission="Doc_Read"> <Guard docId={docId} permission="Doc_Read">
{canRead => {canRead =>
canRead canRead
? children?.map((child, index) => ( ? children?.map((child, index) => (
<ExplorerDocNode <NavigationPanelDocNode
key={`${child.docId}-${index}`} key={`${child.docId}-${index}`}
docId={child.docId} docId={child.docId}
reorderable={false} reorderable={false}
location={{ location={{
at: 'explorer:doc:linked-docs', at: 'navigation-panel:doc:linked-docs',
docId, docId,
}} }}
isLinked isLinked
@@ -297,6 +300,6 @@ export const ExplorerDocNode = ({
: null : null
} }
</Guard> </Guard>
</ExplorerTreeNode> </NavigationPanelTreeNode>
); );
}; };

View File

@@ -31,7 +31,7 @@ import { useCallback, useMemo, useState } from 'react';
import type { NodeOperation } from '../../tree/types'; import type { NodeOperation } from '../../tree/types';
export const useExplorerDocNodeOperations = ( export const useNavigationPanelDocNodeOperations = (
docId: string, docId: string,
options: { options: {
openInfoModal: () => void; openInfoModal: () => void;

View File

@@ -35,17 +35,20 @@ import { useLiveData, 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';
import { ExplorerTreeNode, type ExplorerTreeNodeDropEffect } from '../../tree'; import {
import type { ExplorerTreeNodeIcon } from '../../tree/node'; NavigationPanelTreeNode,
type NavigationPanelTreeNodeDropEffect,
} from '../../tree';
import type { NavigationPanelTreeNodeIcon } from '../../tree/node';
import type { NodeOperation } from '../../tree/types'; import type { NodeOperation } from '../../tree/types';
import { ExplorerCollectionNode } from '../collection'; import { NavigationPanelCollectionNode } from '../collection';
import { ExplorerDocNode } from '../doc'; import { NavigationPanelDocNode } from '../doc';
import { ExplorerTagNode } from '../tag'; import { NavigationPanelTagNode } from '../tag';
import type { GenericExplorerNode } from '../types'; import type { GenericNavigationPanelNode } from '../types';
import { FolderEmpty } from './empty'; import { FolderEmpty } from './empty';
import { FavoriteFolderOperation } from './operations'; import { FavoriteFolderOperation } from './operations';
export const ExplorerFolderNode = ({ export const NavigationPanelFolderNode = ({
nodeId, nodeId,
onDrop, onDrop,
defaultRenaming, defaultRenaming,
@@ -61,7 +64,7 @@ export const ExplorerFolderNode = ({
operations?: operations?:
| NodeOperation[] | NodeOperation[]
| ((type: string, node: FolderNode) => NodeOperation[]); | ((type: string, node: FolderNode) => NodeOperation[]);
} & Omit<GenericExplorerNode, 'operations'>) => { } & Omit<GenericNavigationPanelNode, 'operations'>) => {
const { organizeService } = useServices({ const { organizeService } = useServices({
OrganizeService, OrganizeService,
}); });
@@ -93,7 +96,7 @@ export const ExplorerFolderNode = ({
if (type === 'folder') { if (type === 'folder') {
return ( return (
<ExplorerFolderNodeFolder <NavigationPanelFolderNodeFolder
node={node} node={node}
onDrop={handleDrop} onDrop={handleDrop}
defaultRenaming={defaultRenaming} defaultRenaming={defaultRenaming}
@@ -106,7 +109,7 @@ export const ExplorerFolderNode = ({
} else if (type === 'doc') { } else if (type === 'doc') {
return ( return (
data && ( data && (
<ExplorerDocNode <NavigationPanelDocNode
docId={data} docId={data}
location={location} location={location}
onDrop={handleDrop} onDrop={handleDrop}
@@ -120,7 +123,7 @@ export const ExplorerFolderNode = ({
} else if (type === 'collection') { } else if (type === 'collection') {
return ( return (
data && ( data && (
<ExplorerCollectionNode <NavigationPanelCollectionNode
collectionId={data} collectionId={data}
location={location} location={location}
onDrop={handleDrop} onDrop={handleDrop}
@@ -134,7 +137,7 @@ export const ExplorerFolderNode = ({
} else if (type === 'tag') { } else if (type === 'tag') {
return ( return (
data && ( data && (
<ExplorerTagNode <NavigationPanelTagNode
tagId={data} tagId={data}
location={location} location={location}
onDrop={handleDrop} onDrop={handleDrop}
@@ -150,8 +153,8 @@ export const ExplorerFolderNode = ({
return; return;
}; };
// Define outside the `ExplorerFolderNodeFolder` to avoid re-render(the close animation won't play) // Define outside the `NavigationPanelFolderNodeFolder` to avoid re-render(the close animation won't play)
const ExplorerFolderIcon: ExplorerTreeNodeIcon = ({ const NavigationPanelFolderIcon: NavigationPanelTreeNodeIcon = ({
collapsed, collapsed,
className, className,
draggedOver, draggedOver,
@@ -165,7 +168,7 @@ const ExplorerFolderIcon: ExplorerTreeNodeIcon = ({
/> />
); );
const ExplorerFolderNodeFolder = ({ const NavigationPanelFolderNodeFolder = ({
node, node,
onDrop, onDrop,
defaultRenaming, defaultRenaming,
@@ -177,7 +180,7 @@ const ExplorerFolderNodeFolder = ({
}: { }: {
defaultRenaming?: boolean; defaultRenaming?: boolean;
node: FolderNode; node: FolderNode;
} & GenericExplorerNode) => { } & GenericNavigationPanelNode) => {
const t = useI18n(); const t = useI18n();
const { workspaceService, featureFlagService, workspaceDialogService } = const { workspaceService, featureFlagService, workspaceDialogService } =
useServices({ useServices({
@@ -224,7 +227,7 @@ const ExplorerFolderNodeFolder = ({
from: location, from: location,
}, },
dropTarget: { dropTarget: {
at: 'explorer:organize:folder', at: 'navigation-panel:organize:folder',
}, },
} satisfies AffineDNDData; } satisfies AffineDNDData;
}, [location, node.id]); }, [location, node.id]);
@@ -258,7 +261,10 @@ const ExplorerFolderNodeFolder = ({
data.source.data.entity?.type === 'doc' || data.source.data.entity?.type === 'doc' ||
data.source.data.entity?.type === 'tag' data.source.data.entity?.type === 'tag'
) { ) {
if (data.source.data.from?.at === 'explorer:organize:folder-node') { if (
data.source.data.from?.at ===
'navigation-panel:organize:folder-node'
) {
node.moveHere(data.source.data.from.nodeId, node.indexAt('before')); node.moveHere(data.source.data.from.nodeId, node.indexAt('before'));
track.$.navigationPanel.organize.moveOrganizeItem({ track.$.navigationPanel.organize.moveOrganizeItem({
type: 'link', type: 'link',
@@ -283,7 +289,7 @@ const ExplorerFolderNodeFolder = ({
[node, onDrop] [node, onDrop]
); );
const handleDropEffect = useCallback<ExplorerTreeNodeDropEffect>( const handleDropEffect = useCallback<NavigationPanelTreeNodeDropEffect>(
data => { data => {
if (data.treeInstruction?.type === 'make-child') { if (data.treeInstruction?.type === 'make-child') {
if (data.source.data.entity?.type === 'folder') { if (data.source.data.entity?.type === 'folder') {
@@ -295,7 +301,7 @@ const ExplorerFolderNodeFolder = ({
} }
return 'move'; return 'move';
} else if ( } else if (
data.source.data.from?.at === 'explorer:organize:folder-node' data.source.data.from?.at === 'navigation-panel:organize:folder-node'
) { ) {
return 'move'; return 'move';
} else if ( } else if (
@@ -334,7 +340,9 @@ const ExplorerFolderNodeFolder = ({
data.source.data.entity?.type === 'doc' || data.source.data.entity?.type === 'doc' ||
data.source.data.entity?.type === 'tag' data.source.data.entity?.type === 'tag'
) { ) {
if (data.source.data.from?.at === 'explorer:organize:folder-node') { if (
data.source.data.from?.at === 'navigation-panel:organize:folder-node'
) {
node.moveHere(data.source.data.from.nodeId, node.indexAt('before')); node.moveHere(data.source.data.from.nodeId, node.indexAt('before'));
track.$.navigationPanel.organize.moveOrganizeItem({ track.$.navigationPanel.organize.moveOrganizeItem({
type: data.source.data.entity?.type, type: data.source.data.entity?.type,
@@ -388,7 +396,10 @@ const ExplorerFolderNodeFolder = ({
data.source.data.entity?.type === 'doc' || data.source.data.entity?.type === 'doc' ||
data.source.data.entity?.type === 'tag' data.source.data.entity?.type === 'tag'
) { ) {
if (data.source.data.from?.at === 'explorer:organize:folder-node') { if (
data.source.data.from?.at ===
'navigation-panel:organize:folder-node'
) {
node.moveHere( node.moveHere(
data.source.data.from.nodeId, data.source.data.from.nodeId,
node.indexAt(at, dropAtNode.id) node.indexAt(at, dropAtNode.id)
@@ -437,58 +448,60 @@ const ExplorerFolderNodeFolder = ({
[node, onDrop] [node, onDrop]
); );
const handleDropEffectOnChildren = useCallback<ExplorerTreeNodeDropEffect>( const handleDropEffectOnChildren =
data => { useCallback<NavigationPanelTreeNodeDropEffect>(
if ( data => {
data.treeInstruction?.type === 'reorder-above' || if (
data.treeInstruction?.type === 'reorder-below' data.treeInstruction?.type === 'reorder-above' ||
) { data.treeInstruction?.type === 'reorder-below'
if (data.source.data.entity?.type === 'folder') { ) {
if ( if (data.source.data.entity?.type === 'folder') {
node.id === data.source.data.entity.id || if (
node.beChildOf(data.source.data.entity.id) node.id === data.source.data.entity.id ||
node.beChildOf(data.source.data.entity.id)
) {
return;
}
return 'move';
} else if (
data.source.data.from?.at ===
'navigation-panel:organize:folder-node'
) { ) {
return; return 'move';
} else if (
data.source.data.entity?.type === 'collection' ||
data.source.data.entity?.type === 'doc' ||
data.source.data.entity?.type === 'tag'
) {
return 'link';
}
} else if (data.treeInstruction?.type === 'reparent') {
const currentLevel = data.treeInstruction.currentLevel;
const desiredLevel = data.treeInstruction.desiredLevel;
if (currentLevel === desiredLevel + 1) {
dropEffect?.({
...data,
treeInstruction: {
type: 'reorder-below',
currentLevel,
indentPerLevel: data.treeInstruction.indentPerLevel,
},
});
return;
} else {
dropEffect?.({
...data,
treeInstruction: {
...data.treeInstruction,
currentLevel: currentLevel - 1,
},
});
} }
return 'move';
} else if (
data.source.data.from?.at === 'explorer:organize:folder-node'
) {
return 'move';
} else if (
data.source.data.entity?.type === 'collection' ||
data.source.data.entity?.type === 'doc' ||
data.source.data.entity?.type === 'tag'
) {
return 'link';
} }
} else if (data.treeInstruction?.type === 'reparent') { return;
const currentLevel = data.treeInstruction.currentLevel; },
const desiredLevel = data.treeInstruction.desiredLevel; [dropEffect, node]
if (currentLevel === desiredLevel + 1) { );
dropEffect?.({
...data,
treeInstruction: {
type: 'reorder-below',
currentLevel,
indentPerLevel: data.treeInstruction.indentPerLevel,
},
});
return;
} else {
dropEffect?.({
...data,
treeInstruction: {
...data.treeInstruction,
currentLevel: currentLevel - 1,
},
});
}
}
return;
},
[dropEffect, node]
);
const handleCanDrop = useMemo<DropTargetOptions<AffineDNDData>['canDrop']>( const handleCanDrop = useMemo<DropTargetOptions<AffineDNDData>['canDrop']>(
() => args => { () => args => {
@@ -508,7 +521,7 @@ const ExplorerFolderNodeFolder = ({
} }
return true; return true;
} else if ( } else if (
args.source.data.from?.at === 'explorer:organize:folder-node' args.source.data.from?.at === 'navigation-panel:organize:folder-node'
) { ) {
return true; return true;
} else if ( } else if (
@@ -538,7 +551,7 @@ const ExplorerFolderNodeFolder = ({
} }
return true; return true;
} else if ( } else if (
args.source.data.from?.at === 'explorer:organize:folder-node' args.source.data.from?.at === 'navigation-panel:organize:folder-node'
) { ) {
return true; return true;
} else if ( } else if (
@@ -758,8 +771,8 @@ const ExplorerFolderNodeFolder = ({
}, []); }, []);
return ( return (
<ExplorerTreeNode <NavigationPanelTreeNode
icon={ExplorerFolderIcon} icon={NavigationPanelFolderIcon}
name={name} name={name}
dndData={dndData} dndData={dndData}
onDrop={handleDropOnFolder} onDrop={handleDropOnFolder}
@@ -776,10 +789,10 @@ const ExplorerFolderNodeFolder = ({
<FolderEmpty canDrop={handleCanDrop} onDrop={handleDropOnPlaceholder} /> <FolderEmpty canDrop={handleCanDrop} onDrop={handleDropOnPlaceholder} />
} }
dropEffect={handleDropEffect} dropEffect={handleDropEffect}
data-testid={`explorer-folder-${node.id}`} data-testid={`navigation-panel-folder-${node.id}`}
> >
{children.map(child => ( {children.map(child => (
<ExplorerFolderNode <NavigationPanelFolderNode
key={child.id} key={child.id}
nodeId={child.id as string} nodeId={child.id as string}
defaultRenaming={child.id === newFolderId} defaultRenaming={child.id === newFolderId}
@@ -788,11 +801,11 @@ const ExplorerFolderNodeFolder = ({
dropEffect={handleDropEffectOnChildren} dropEffect={handleDropEffectOnChildren}
canDrop={handleChildrenCanDrop} canDrop={handleChildrenCanDrop}
location={{ location={{
at: 'explorer:organize:folder-node', at: 'navigation-panel:organize:folder-node',
nodeId: child.id as string, nodeId: child.id as string,
}} }}
/> />
))} ))}
</ExplorerTreeNode> </NavigationPanelTreeNode>
); );
}; };

View File

@@ -13,14 +13,17 @@ import { useLiveData, useServices } from '@toeverything/infra';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { ExplorerTreeNode, type ExplorerTreeNodeDropEffect } from '../../tree'; import {
import { ExplorerDocNode } from '../doc'; NavigationPanelTreeNode,
import type { GenericExplorerNode } from '../types'; type NavigationPanelTreeNodeDropEffect,
} from '../../tree';
import { NavigationPanelDocNode } from '../doc';
import type { GenericNavigationPanelNode } from '../types';
import { Empty } from './empty'; import { Empty } from './empty';
import { useExplorerTagNodeOperations } from './operations'; import { useNavigationPanelTagNodeOperations } from './operations';
import * as styles from './styles.css'; import * as styles from './styles.css';
export const ExplorerTagNode = ({ export const NavigationPanelTagNode = ({
tagId, tagId,
onDrop, onDrop,
location, location,
@@ -30,7 +33,7 @@ export const ExplorerTagNode = ({
canDrop, canDrop,
}: { }: {
tagId: string; tagId: string;
} & GenericExplorerNode) => { } & GenericNavigationPanelNode) => {
const t = useI18n(); const t = useI18n();
const { tagService, globalContextService } = useServices({ const { tagService, globalContextService } = useServices({
TagService, TagService,
@@ -70,7 +73,7 @@ export const ExplorerTagNode = ({
from: location, from: location,
}, },
dropTarget: { dropTarget: {
at: 'explorer:tag', at: 'navigation-panel:tag',
}, },
} satisfies AffineDNDData; } satisfies AffineDNDData;
}, [location, tagId]); }, [location, tagId]);
@@ -108,7 +111,7 @@ export const ExplorerTagNode = ({
[onDrop, t, tagRecord] [onDrop, t, tagRecord]
); );
const handleDropEffectOnTag = useCallback<ExplorerTreeNodeDropEffect>( const handleDropEffectOnTag = useCallback<NavigationPanelTreeNodeDropEffect>(
data => { data => {
if (data.treeInstruction?.type === 'make-child') { if (data.treeInstruction?.type === 'make-child') {
if (data.source.data.entity?.type === 'doc') { if (data.source.data.entity?.type === 'doc') {
@@ -145,7 +148,7 @@ export const ExplorerTagNode = ({
[canDrop] [canDrop]
); );
const operations = useExplorerTagNodeOperations( const operations = useNavigationPanelTagNodeOperations(
tagId, tagId,
useMemo( useMemo(
() => ({ () => ({
@@ -167,7 +170,7 @@ export const ExplorerTagNode = ({
} }
return ( return (
<ExplorerTreeNode <NavigationPanelTreeNode
icon={Icon} icon={Icon}
name={tagName || t['Untitled']()} name={tagName || t['Untitled']()}
dndData={dndData} dndData={dndData}
@@ -183,10 +186,10 @@ export const ExplorerTagNode = ({
childrenPlaceholder={<Empty onDrop={handleDropOnPlaceholder} />} childrenPlaceholder={<Empty onDrop={handleDropOnPlaceholder} />}
operations={finalOperations} operations={finalOperations}
dropEffect={handleDropEffectOnTag} dropEffect={handleDropEffectOnTag}
data-testid={`explorer-tag-${tagId}`} data-testid={`navigation-panel-tag-${tagId}`}
> >
<ExplorerTagNodeDocs tag={tagRecord} /> <NavigationPanelTagNodeDocs tag={tagRecord} />
</ExplorerTreeNode> </NavigationPanelTreeNode>
); );
}; };
@@ -195,16 +198,16 @@ export const ExplorerTagNode = ({
* 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 ExplorerTagNodeDocs = ({ tag }: { tag: Tag }) => { export const NavigationPanelTagNodeDocs = ({ tag }: { tag: Tag }) => {
const tagDocIds = useLiveData(tag.pageIds$); const tagDocIds = useLiveData(tag.pageIds$);
return tagDocIds.map(docId => ( return tagDocIds.map(docId => (
<ExplorerDocNode <NavigationPanelDocNode
key={docId} key={docId}
docId={docId} docId={docId}
reorderable={false} reorderable={false}
location={{ location={{
at: 'explorer:tags:docs', at: 'navigation-panel:tags:docs',
}} }}
/> />
)); ));

View File

@@ -19,7 +19,7 @@ import { useCallback, useMemo } from 'react';
import type { NodeOperation } from '../../tree/types'; import type { NodeOperation } from '../../tree/types';
export const useExplorerTagNodeOperations = ( export const useNavigationPanelTagNodeOperations = (
tagId: string, tagId: string,
{ {
openNodeCollapsed, openNodeCollapsed,

View File

@@ -1,11 +1,11 @@
import type { DropTargetDropEvent, DropTargetOptions } from '@affine/component'; import type { DropTargetDropEvent, DropTargetOptions } from '@affine/component';
import type { AffineDNDData } from '@affine/core/types/dnd'; import type { AffineDNDData } from '@affine/core/types/dnd';
import type { ExplorerTreeNodeDropEffect } from '../tree'; import type { NavigationPanelTreeNodeDropEffect } from '../tree';
import type { NodeOperation } from '../tree/types'; import type { NodeOperation } from '../tree/types';
/** /**
* The interface for a generic explorer node. * The interface for a generic navigation panel node.
* *
* # Drop controlled area * # Drop controlled area
* *
@@ -21,7 +21,7 @@ import type { NodeOperation } from '../tree/types';
* *
* The controlled area can be distinguished by `data.treeInstruction.type` in the callback parameter. * The controlled area can be distinguished by `data.treeInstruction.type` in the callback parameter.
*/ */
export interface GenericExplorerNode { export interface GenericNavigationPanelNode {
/** /**
* Tell the node and dropTarget where the node is located in the tree * Tell the node and dropTarget where the node is located in the tree
*/ */
@@ -45,5 +45,5 @@ export interface GenericExplorerNode {
/** /**
* 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?: ExplorerTreeNodeDropEffect; dropEffect?: NavigationPanelTreeNodeDropEffect;
} }

View File

@@ -1,7 +1,7 @@
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { ViewLayersIcon } from '@blocksuite/icons/rc'; import { ViewLayersIcon } from '@blocksuite/icons/rc';
import { ExplorerEmptySection } from '../../layouts/empty-section'; import { NavigationPanelEmptySection } from '../../layouts/empty-section';
export const RootEmpty = ({ export const RootEmpty = ({
onClickCreate, onClickCreate,
@@ -11,7 +11,7 @@ export const RootEmpty = ({
const t = useI18n(); const t = useI18n();
return ( return (
<ExplorerEmptySection <NavigationPanelEmptySection
icon={ViewLayersIcon} icon={ViewLayersIcon}
message={t['com.affine.collections.empty.message']()} message={t['com.affine.collections.empty.message']()}
messageTestId="slider-bar-collection-empty-message" messageTestId="slider-bar-collection-empty-message"

View File

@@ -1,7 +1,7 @@
import { IconButton, usePromptModal } from '@affine/component'; import { IconButton, usePromptModal } from '@affine/component';
import { createEmptyCollection } from '@affine/core/components/page-list/use-collection-manager'; import { createEmptyCollection } from '@affine/core/components/page-list/use-collection-manager';
import { CollectionService } from '@affine/core/modules/collection'; import { CollectionService } from '@affine/core/modules/collection';
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree'; import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { track } from '@affine/track'; import { track } from '@affine/track';
@@ -10,20 +10,21 @@ import { useLiveData, useServices } from '@toeverything/infra';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { ExplorerService } from '../../../services/explorer';
import { CollapsibleSection } from '../../layouts/collapsible-section'; import { CollapsibleSection } from '../../layouts/collapsible-section';
import { ExplorerCollectionNode } from '../../nodes/collection'; import { NavigationPanelCollectionNode } from '../../nodes/collection';
import { NavigationPanelTreeRoot } from '../../tree';
import { RootEmpty } from './empty'; import { RootEmpty } from './empty';
import * as styles from './index.css'; import * as styles from './index.css';
export const ExplorerCollections = () => { export const NavigationPanelCollections = () => {
const t = useI18n(); const t = useI18n();
const { collectionService, workbenchService, explorerService } = useServices({ const { collectionService, workbenchService, navigationPanelService } =
CollectionService, useServices({
WorkbenchService, CollectionService,
ExplorerService, WorkbenchService,
}); NavigationPanelService,
const explorerSection = explorerService.sections.collections; });
const navigationPanelSection = navigationPanelService.sections.collections;
const collections = useLiveData(collectionService.collections$); const collections = useLiveData(collectionService.collections$);
const { openPromptModal } = usePromptModal(); const { openPromptModal } = usePromptModal();
@@ -51,12 +52,12 @@ export const ExplorerCollections = () => {
type: 'collection', type: 'collection',
}); });
workbenchService.workbench.openCollection(id); workbenchService.workbench.openCollection(id);
explorerSection.setCollapsed(false); navigationPanelSection.setCollapsed(false);
}, },
}); });
}, [ }, [
collectionService, collectionService,
explorerSection, navigationPanelSection,
openPromptModal, openPromptModal,
t, t,
workbenchService.workbench, workbenchService.workbench,
@@ -65,11 +66,11 @@ export const ExplorerCollections = () => {
return ( return (
<CollapsibleSection <CollapsibleSection
name="collections" name="collections"
testId="explorer-collections" testId="navigation-panel-collections"
title={t['com.affine.rootAppSidebar.collections']()} title={t['com.affine.rootAppSidebar.collections']()}
actions={ actions={
<IconButton <IconButton
data-testid="explorer-bar-add-collection-button" data-testid="navigation-panel-bar-add-collection-button"
onClick={handleCreateCollection} onClick={handleCreateCollection}
size="16" size="16"
tooltip={t[ tooltip={t[
@@ -80,20 +81,20 @@ export const ExplorerCollections = () => {
</IconButton> </IconButton>
} }
> >
<ExplorerTreeRoot <NavigationPanelTreeRoot
placeholder={<RootEmpty onClickCreate={handleCreateCollection} />} placeholder={<RootEmpty onClickCreate={handleCreateCollection} />}
> >
{collections.map(collection => ( {collections.map(collection => (
<ExplorerCollectionNode <NavigationPanelCollectionNode
key={collection.id} key={collection.id}
collectionId={collection.id} collectionId={collection.id}
reorderable={false} reorderable={false}
location={{ location={{
at: 'explorer:collection:list', at: 'navigation-panel:collection:list',
}} }}
/> />
))} ))}
</ExplorerTreeRoot> </NavigationPanelTreeRoot>
</CollapsibleSection> </CollapsibleSection>
); );
}; };

View File

@@ -0,0 +1,47 @@
import type { DropTargetOptions } from '@affine/component';
import { isFavoriteSupportType } from '@affine/core/modules/favorite';
import type { AffineDNDData } from '@affine/core/types/dnd';
import type { NavigationPanelTreeNodeDropEffect } from '../../tree';
export const favoriteChildrenDropEffect: NavigationPanelTreeNodeDropEffect =
data => {
if (
data.treeInstruction?.type === 'reorder-above' ||
data.treeInstruction?.type === 'reorder-below'
) {
if (
data.source.data.from?.at === 'navigation-panel:favorite:list' &&
data.source.data.entity?.type &&
isFavoriteSupportType(data.source.data.entity.type)
) {
return 'move';
} else if (
data.source.data.entity?.type &&
isFavoriteSupportType(data.source.data.entity.type)
) {
return 'link';
}
}
return; // not supported
};
export const favoriteRootDropEffect: NavigationPanelTreeNodeDropEffect =
data => {
const sourceType = data.source.data.entity?.type;
if (sourceType && isFavoriteSupportType(sourceType)) {
return 'link';
}
return;
};
export const favoriteRootCanDrop: DropTargetOptions<AffineDNDData>['canDrop'] =
data => {
return data.source.data.entity?.type
? isFavoriteSupportType(data.source.data.entity.type)
: false;
};
export const favoriteChildrenCanDrop: DropTargetOptions<AffineDNDData>['canDrop'] =
// Same as favoriteRootCanDrop
data => favoriteRootCanDrop(data);

View File

@@ -7,7 +7,7 @@ import type { AffineDNDData } from '@affine/core/types/dnd';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { FavoriteIcon } from '@blocksuite/icons/rc'; import { FavoriteIcon } from '@blocksuite/icons/rc';
import { ExplorerEmptySection } from '../../layouts/empty-section'; import { NavigationPanelEmptySection } from '../../layouts/empty-section';
import { DropEffect } from '../../tree'; import { DropEffect } from '../../tree';
import { favoriteRootCanDrop, favoriteRootDropEffect } from './dnd'; import { favoriteRootCanDrop, favoriteRootDropEffect } from './dnd';
@@ -26,7 +26,7 @@ const RootEmptyReady = ({ onDrop }: Omit<RootEmptyProps, 'isLoading'>) => {
useDropTarget<AffineDNDData>( useDropTarget<AffineDNDData>(
() => ({ () => ({
data: { data: {
at: 'explorer:favorite:root', at: 'navigation-panel:favorite:root',
}, },
onDrop: onDrop, onDrop: onDrop,
canDrop: favoriteRootCanDrop, canDrop: favoriteRootCanDrop,
@@ -36,7 +36,7 @@ const RootEmptyReady = ({ onDrop }: Omit<RootEmptyProps, 'isLoading'>) => {
); );
return ( return (
<ExplorerEmptySection <NavigationPanelEmptySection
ref={dropTargetRef} ref={dropTargetRef}
icon={FavoriteIcon} icon={FavoriteIcon}
message={t['com.affine.rootAppSidebar.favorites.empty']()} message={t['com.affine.rootAppSidebar.favorites.empty']()}
@@ -51,7 +51,7 @@ const RootEmptyReady = ({ onDrop }: Omit<RootEmptyProps, 'isLoading'>) => {
})} })}
/> />
)} )}
</ExplorerEmptySection> </NavigationPanelEmptySection>
); );
}; };

View File

@@ -4,15 +4,12 @@ import {
useDropTarget, useDropTarget,
} from '@affine/component'; } from '@affine/component';
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils'; import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
import {
DropEffect,
ExplorerTreeRoot,
} from '@affine/core/modules/explorer/views/tree';
import type { FavoriteSupportTypeUnion } from '@affine/core/modules/favorite'; import type { FavoriteSupportTypeUnion } from '@affine/core/modules/favorite';
import { import {
FavoriteService, FavoriteService,
isFavoriteSupportType, isFavoriteSupportType,
} from '@affine/core/modules/favorite'; } from '@affine/core/modules/favorite';
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
import { WorkspaceService } from '@affine/core/modules/workspace'; import { WorkspaceService } from '@affine/core/modules/workspace';
import type { AffineDNDData } from '@affine/core/types/dnd'; import type { AffineDNDData } from '@affine/core/types/dnd';
import { inferOpenMode } from '@affine/core/utils'; import { inferOpenMode } from '@affine/core/utils';
@@ -22,12 +19,12 @@ 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 } from 'react';
import { ExplorerService } from '../../../services/explorer';
import { CollapsibleSection } from '../../layouts/collapsible-section'; import { CollapsibleSection } from '../../layouts/collapsible-section';
import { ExplorerCollectionNode } from '../../nodes/collection'; import { NavigationPanelCollectionNode } from '../../nodes/collection';
import { ExplorerDocNode } from '../../nodes/doc'; import { NavigationPanelDocNode } from '../../nodes/doc';
import { ExplorerFolderNode } from '../../nodes/folder'; import { NavigationPanelFolderNode } from '../../nodes/folder';
import { ExplorerTagNode } from '../../nodes/tag'; import { NavigationPanelTagNode } from '../../nodes/tag';
import { DropEffect, NavigationPanelTreeRoot } from '../../tree';
import { import {
favoriteChildrenCanDrop, favoriteChildrenCanDrop,
favoriteChildrenDropEffect, favoriteChildrenDropEffect,
@@ -36,14 +33,15 @@ import {
} from './dnd'; } from './dnd';
import { RootEmpty } from './empty'; import { RootEmpty } from './empty';
export const ExplorerFavorites = () => { export const NavigationPanelFavorites = () => {
const { favoriteService, workspaceService, explorerService } = useServices({ const { favoriteService, workspaceService, navigationPanelService } =
FavoriteService, useServices({
WorkspaceService, FavoriteService,
ExplorerService, WorkspaceService,
}); NavigationPanelService,
});
const explorerSection = explorerService.sections.favorites; const navigationPanelSection = navigationPanelService.sections.favorites;
const favorites = useLiveData(favoriteService.favoriteList.sortedList$); const favorites = useLiveData(favoriteService.favoriteList.sortedList$);
@@ -73,10 +71,10 @@ export const ExplorerFavorites = () => {
track.$.navigationPanel.favorites.drop({ track.$.navigationPanel.favorites.drop({
type: data.source.data.entity.type, type: data.source.data.entity.type,
}); });
explorerSection.setCollapsed(false); navigationPanelSection.setCollapsed(false);
} }
}, },
[explorerSection, favoriteService.favoriteList] [navigationPanelSection, favoriteService.favoriteList]
); );
const handleCreateNewFavoriteDoc: MouseEventHandler = useCallback( const handleCreateNewFavoriteDoc: MouseEventHandler = useCallback(
@@ -87,9 +85,9 @@ export const ExplorerFavorites = () => {
newDoc.id, newDoc.id,
favoriteService.favoriteList.indexAt('before') favoriteService.favoriteList.indexAt('before')
); );
explorerSection.setCollapsed(false); navigationPanelSection.setCollapsed(false);
}, },
[createPage, explorerSection, favoriteService.favoriteList] [createPage, navigationPanelSection, favoriteService.favoriteList]
); );
const handleOnChildrenDrop = useCallback( const handleOnChildrenDrop = useCallback(
@@ -102,7 +100,7 @@ export const ExplorerFavorites = () => {
data.treeInstruction?.type === 'reorder-below' data.treeInstruction?.type === 'reorder-below'
) { ) {
if ( if (
data.source.data.from?.at === 'explorer:favorite:list' && data.source.data.from?.at === 'navigation-panel:favorite:list' &&
data.source.data.entity?.type && data.source.data.entity?.type &&
isFavoriteSupportType(data.source.data.entity.type) isFavoriteSupportType(data.source.data.entity.type)
) { ) {
@@ -153,7 +151,7 @@ export const ExplorerFavorites = () => {
useDropTarget<AffineDNDData>( useDropTarget<AffineDNDData>(
() => ({ () => ({
data: { data: {
at: 'explorer:favorite:root', at: 'navigation-panel:favorite:root',
}, },
onDrop: handleDrop, onDrop: handleDrop,
canDrop: favoriteRootCanDrop, canDrop: favoriteRootCanDrop,
@@ -167,12 +165,12 @@ export const ExplorerFavorites = () => {
name="favorites" name="favorites"
title={t['com.affine.rootAppSidebar.favorites']()} title={t['com.affine.rootAppSidebar.favorites']()}
headerRef={dropTargetRef} headerRef={dropTargetRef}
testId="explorer-favorites" testId="navigation-panel-favorites"
headerTestId="explorer-favorite-category-divider" headerTestId="navigation-panel-favorite-category-divider"
actions={ actions={
<> <>
<IconButton <IconButton
data-testid="explorer-bar-add-favorite-button" data-testid="navigation-panel-bar-add-favorite-button"
data-event-props="$.navigationPanel.favorites.createDoc" data-event-props="$.navigationPanel.favorites.createDoc"
data-event-args-control="addFavorite" data-event-args-control="addFavorite"
onClick={handleCreateNewFavoriteDoc} onClick={handleCreateNewFavoriteDoc}
@@ -196,25 +194,25 @@ export const ExplorerFavorites = () => {
</> </>
} }
> >
<ExplorerTreeRoot <NavigationPanelTreeRoot
placeholder={<RootEmpty onDrop={handleDrop} isLoading={isLoading} />} placeholder={<RootEmpty onDrop={handleDrop} isLoading={isLoading} />}
> >
{favorites.map(favorite => ( {favorites.map(favorite => (
<ExplorerFavoriteNode <NavigationPanelFavoriteNode
key={favorite.id} key={favorite.id}
favorite={favorite} favorite={favorite}
onDrop={handleOnChildrenDrop} onDrop={handleOnChildrenDrop}
/> />
))} ))}
</ExplorerTreeRoot> </NavigationPanelTreeRoot>
</CollapsibleSection> </CollapsibleSection>
); );
}; };
const childLocation = { const childLocation = {
at: 'explorer:favorite:list' as const, at: 'navigation-panel:favorite:list' as const,
}; };
const ExplorerFavoriteNode = ({ const NavigationPanelFavoriteNode = ({
favorite, favorite,
onDrop, onDrop,
}: { }: {
@@ -237,7 +235,7 @@ const ExplorerFavoriteNode = ({
[favorite, onDrop] [favorite, onDrop]
); );
return favorite.type === 'doc' ? ( return favorite.type === 'doc' ? (
<ExplorerDocNode <NavigationPanelDocNode
key={favorite.id} key={favorite.id}
docId={favorite.id} docId={favorite.id}
location={childLocation} location={childLocation}
@@ -246,7 +244,7 @@ const ExplorerFavoriteNode = ({
canDrop={favoriteChildrenCanDrop} canDrop={favoriteChildrenCanDrop}
/> />
) : favorite.type === 'tag' ? ( ) : favorite.type === 'tag' ? (
<ExplorerTagNode <NavigationPanelTagNode
key={favorite.id} key={favorite.id}
tagId={favorite.id} tagId={favorite.id}
location={childLocation} location={childLocation}
@@ -255,7 +253,7 @@ const ExplorerFavoriteNode = ({
canDrop={favoriteChildrenCanDrop} canDrop={favoriteChildrenCanDrop}
/> />
) : favorite.type === 'folder' ? ( ) : favorite.type === 'folder' ? (
<ExplorerFolderNode <NavigationPanelFolderNode
key={favorite.id} key={favorite.id}
nodeId={favorite.id} nodeId={favorite.id}
location={childLocation} location={childLocation}
@@ -264,7 +262,7 @@ const ExplorerFavoriteNode = ({
canDrop={favoriteChildrenCanDrop} canDrop={favoriteChildrenCanDrop}
/> />
) : ( ) : (
<ExplorerCollectionNode <NavigationPanelCollectionNode
key={favorite.id} key={favorite.id}
collectionId={favorite.id} collectionId={favorite.id}
location={childLocation} location={childLocation}

View File

@@ -1,6 +1,5 @@
import { IconButton, useConfirmModal } from '@affine/component'; import { IconButton, useConfirmModal } from '@affine/component';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
import { MigrationFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import { MigrationFavoriteItemsAdapter } from '@affine/core/modules/favorite';
import { Trans, useI18n } from '@affine/i18n'; import { Trans, useI18n } from '@affine/i18n';
import { track } from '@affine/track'; import { track } from '@affine/track';
@@ -9,11 +8,12 @@ import { useLiveData, useServices } from '@toeverything/infra';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { CollapsibleSection } from '../../layouts/collapsible-section'; import { CollapsibleSection } from '../../layouts/collapsible-section';
import { ExplorerCollectionNode } from '../../nodes/collection'; import { NavigationPanelCollectionNode } from '../../nodes/collection';
import { ExplorerDocNode } from '../../nodes/doc'; import { NavigationPanelDocNode } from '../../nodes/doc';
import { NavigationPanelTreeRoot } from '../../tree';
import * as styles from './styles.css'; import * as styles from './styles.css';
export const ExplorerMigrationFavorites = () => { export const NavigationPanelMigrationFavorites = () => {
const t = useI18n(); const t = useI18n();
const { migrationFavoriteItemsAdapter, docsService } = useServices({ const { migrationFavoriteItemsAdapter, docsService } = useServices({
@@ -105,14 +105,14 @@ export const ExplorerMigrationFavorites = () => {
actions={ actions={
<> <>
<IconButton <IconButton
data-testid="explorer-bar-favorite-migration-clear-button" data-testid="navigation-panel-bar-favorite-migration-clear-button"
onClick={handleClickClear} onClick={handleClickClear}
size="16" size="16"
> >
<BroomIcon /> <BroomIcon />
</IconButton> </IconButton>
<IconButton <IconButton
data-testid="explorer-bar-favorite-migration-help-button" data-testid="navigation-panel-bar-favorite-migration-help-button"
size="16" size="16"
onClick={handleClickHelp} onClick={handleClickHelp}
> >
@@ -121,22 +121,22 @@ export const ExplorerMigrationFavorites = () => {
</> </>
} }
> >
<ExplorerTreeRoot> <NavigationPanelTreeRoot>
{favorites.map((favorite, i) => ( {favorites.map((favorite, i) => (
<ExplorerMigrationFavoriteNode <NavigationPanelMigrationFavoriteNode
key={favorite.id + ':' + i} key={favorite.id + ':' + i}
favorite={favorite} favorite={favorite}
/> />
))} ))}
</ExplorerTreeRoot> </NavigationPanelTreeRoot>
</CollapsibleSection> </CollapsibleSection>
); );
}; };
const childLocation = { const childLocation = {
at: 'explorer:migration-data:list' as const, at: 'navigation-panel:migration-data:list' as const,
}; };
const ExplorerMigrationFavoriteNode = ({ const NavigationPanelMigrationFavoriteNode = ({
favorite, favorite,
}: { }: {
favorite: { favorite: {
@@ -145,7 +145,7 @@ const ExplorerMigrationFavoriteNode = ({
}; };
}) => { }) => {
return favorite.type === 'doc' ? ( return favorite.type === 'doc' ? (
<ExplorerDocNode <NavigationPanelDocNode
key={favorite.id} key={favorite.id}
docId={favorite.id} docId={favorite.id}
location={childLocation} location={childLocation}
@@ -153,7 +153,7 @@ const ExplorerMigrationFavoriteNode = ({
canDrop={false} canDrop={false}
/> />
) : ( ) : (
<ExplorerCollectionNode <NavigationPanelCollectionNode
key={favorite.id} key={favorite.id}
collectionId={favorite.id} collectionId={favorite.id}
location={childLocation} location={childLocation}

View File

@@ -0,0 +1,38 @@
import type { DropTargetOptions } from '@affine/component';
import { isOrganizeSupportType } from '@affine/core/modules/organize/constants';
import type { AffineDNDData } from '@affine/core/types/dnd';
import type { NavigationPanelTreeNodeDropEffect } from '../../tree';
export const organizeChildrenDropEffect: NavigationPanelTreeNodeDropEffect =
data => {
if (
data.treeInstruction?.type === 'reorder-above' ||
data.treeInstruction?.type === 'reorder-below'
) {
if (data.source.data.entity?.type === 'folder') {
return 'move';
}
} else {
return; // not supported
}
return;
};
export const organizeEmptyDropEffect: NavigationPanelTreeNodeDropEffect =
data => {
const sourceType = data.source.data.entity?.type;
if (sourceType && isOrganizeSupportType(sourceType)) {
return 'link';
}
return;
};
/**
* Check whether the data can be dropped on the empty state of the organize section
*/
export const organizeEmptyRootCanDrop: DropTargetOptions<AffineDNDData>['canDrop'] =
data => {
const type = data.source.data.entity?.type;
return !!type && isOrganizeSupportType(type);
};

View File

@@ -7,7 +7,7 @@ import {
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 { ExplorerEmptySection } from '../../layouts/empty-section'; import { NavigationPanelEmptySection } from '../../layouts/empty-section';
import { DropEffect } from '../../tree'; import { DropEffect } from '../../tree';
import { organizeEmptyDropEffect, organizeEmptyRootCanDrop } from './dnd'; import { organizeEmptyDropEffect, organizeEmptyRootCanDrop } from './dnd';
@@ -30,7 +30,7 @@ export const RootEmptyReady = ({
const { dropTargetRef, draggedOverDraggable, draggedOverPosition } = const { dropTargetRef, draggedOverDraggable, draggedOverPosition } =
useDropTarget<AffineDNDData>( useDropTarget<AffineDNDData>(
() => ({ () => ({
data: { at: 'explorer:organize:root' }, data: { at: 'navigation-panel:organize:root' },
onDrop, onDrop,
canDrop: organizeEmptyRootCanDrop, canDrop: organizeEmptyRootCanDrop,
}), }),
@@ -38,7 +38,7 @@ export const RootEmptyReady = ({
); );
return ( return (
<ExplorerEmptySection <NavigationPanelEmptySection
ref={dropTargetRef} ref={dropTargetRef}
icon={<AnimatedFolderIcon open={!!draggedOverDraggable} />} icon={<AnimatedFolderIcon open={!!draggedOverDraggable} />}
message={t['com.affine.rootAppSidebar.organize.empty']()} message={t['com.affine.rootAppSidebar.organize.empty']()}
@@ -57,7 +57,7 @@ export const RootEmptyReady = ({
})} })}
/> />
)} )}
</ExplorerEmptySection> </NavigationPanelEmptySection>
); );
}; };

View File

@@ -4,7 +4,7 @@ import {
IconButton, IconButton,
toast, toast,
} from '@affine/component'; } from '@affine/component';
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree'; import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
import { import {
type FolderNode, type FolderNode,
OrganizeService, OrganizeService,
@@ -16,19 +16,19 @@ import { AddOrganizeIcon } from '@blocksuite/icons/rc';
import { useLiveData, useServices } from '@toeverything/infra'; import { useLiveData, useServices } from '@toeverything/infra';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { ExplorerService } from '../../../services/explorer';
import { CollapsibleSection } from '../../layouts/collapsible-section'; import { CollapsibleSection } from '../../layouts/collapsible-section';
import { ExplorerFolderNode } from '../../nodes/folder'; import { NavigationPanelFolderNode } from '../../nodes/folder';
import { NavigationPanelTreeRoot } from '../../tree';
import { organizeChildrenDropEffect } from './dnd'; import { organizeChildrenDropEffect } from './dnd';
import { RootEmpty } from './empty'; import { RootEmpty } from './empty';
export const ExplorerOrganize = () => { export const NavigationPanelOrganize = () => {
const { organizeService, explorerService } = useServices({ const { organizeService, navigationPanelService } = useServices({
OrganizeService, OrganizeService,
ExplorerService, NavigationPanelService,
}); });
const explorerSection = explorerService.sections.organize; const navigationPanelSection = navigationPanelService.sections.organize;
const collapsed = useLiveData(explorerSection.collapsed$); const collapsed = useLiveData(navigationPanelSection.collapsed$);
const [newFolderId, setNewFolderId] = useState<string | null>(null); const [newFolderId, setNewFolderId] = useState<string | null>(null);
const t = useI18n(); const t = useI18n();
@@ -46,9 +46,9 @@ export const ExplorerOrganize = () => {
); );
track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' }); track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
setNewFolderId(newFolderId); setNewFolderId(newFolderId);
explorerSection.setCollapsed(false); navigationPanelSection.setCollapsed(false);
return newFolderId; return newFolderId;
}, [explorerSection, rootFolder]); }, [navigationPanelSection, rootFolder]);
const handleOnChildrenDrop = useCallback( const handleOnChildrenDrop = useCallback(
(data: DropTargetDropEvent<AffineDNDData>, node?: FolderNode) => { (data: DropTargetDropEvent<AffineDNDData>, node?: FolderNode) => {
@@ -109,7 +109,7 @@ export const ExplorerOrganize = () => {
title={t['com.affine.rootAppSidebar.organize']()} title={t['com.affine.rootAppSidebar.organize']()}
actions={ actions={
<IconButton <IconButton
data-testid="explorer-bar-add-organize-button" data-testid="navigation-panel-bar-add-organize-button"
onClick={handleCreateFolder} onClick={handleCreateFolder}
size="16" size="16"
tooltip={t[ tooltip={t[
@@ -120,7 +120,7 @@ export const ExplorerOrganize = () => {
</IconButton> </IconButton>
} }
> >
<ExplorerTreeRoot <NavigationPanelTreeRoot
placeholder={ placeholder={
<RootEmpty <RootEmpty
onClickCreate={handleCreateFolder} onClickCreate={handleCreateFolder}
@@ -130,7 +130,7 @@ export const ExplorerOrganize = () => {
} }
> >
{folders.map(child => ( {folders.map(child => (
<ExplorerFolderNode <NavigationPanelFolderNode
key={child.id} key={child.id}
nodeId={child.id as string} nodeId={child.id as string}
defaultRenaming={child.id === newFolderId} defaultRenaming={child.id === newFolderId}
@@ -138,12 +138,12 @@ export const ExplorerOrganize = () => {
dropEffect={organizeChildrenDropEffect} dropEffect={organizeChildrenDropEffect}
canDrop={handleChildrenCanDrop} canDrop={handleChildrenCanDrop}
location={{ location={{
at: 'explorer:organize:folder-node', at: 'navigation-panel:organize:folder-node',
nodeId: child.id as string, nodeId: child.id as string,
}} }}
/> />
))} ))}
</ExplorerTreeRoot> </NavigationPanelTreeRoot>
</CollapsibleSection> </CollapsibleSection>
); );
}; };

View File

@@ -1,13 +1,13 @@
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { TagIcon } from '@blocksuite/icons/rc'; import { TagIcon } from '@blocksuite/icons/rc';
import { ExplorerEmptySection } from '../../layouts/empty-section'; import { NavigationPanelEmptySection } from '../../layouts/empty-section';
export const RootEmpty = () => { export const RootEmpty = () => {
const t = useI18n(); const t = useI18n();
return ( return (
<ExplorerEmptySection <NavigationPanelEmptySection
icon={TagIcon} icon={TagIcon}
message={t['com.affine.rootAppSidebar.tags.empty']()} message={t['com.affine.rootAppSidebar.tags.empty']()}
messageTestId="slider-bar-tags-empty-message" messageTestId="slider-bar-tags-empty-message"

View File

@@ -1,5 +1,5 @@
import { IconButton } from '@affine/component'; import { IconButton } from '@affine/component';
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree'; import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
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 { track } from '@affine/track'; import { track } from '@affine/track';
@@ -7,20 +7,20 @@ 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, useState } from 'react';
import { ExplorerService } from '../../../services/explorer';
import { CollapsibleSection } from '../../layouts/collapsible-section'; import { CollapsibleSection } from '../../layouts/collapsible-section';
import { ExplorerTagNode } from '../../nodes/tag'; import { NavigationPanelTagNode } from '../../nodes/tag';
import { ExplorerTreeNodeRenameModal as CreateTagModal } from '../../tree/node'; import { NavigationPanelTreeRoot } from '../../tree';
import { NavigationPanelTreeNodeRenameModal as CreateTagModal } from '../../tree/node';
import { RootEmpty } from './empty'; import { RootEmpty } from './empty';
import * as styles from './styles.css'; import * as styles from './styles.css';
export const ExplorerTags = () => { export const NavigationPanelTags = () => {
const { tagService, explorerService } = useServices({ const { tagService, navigationPanelService } = useServices({
TagService, TagService,
ExplorerService, NavigationPanelService,
}); });
const explorerSection = explorerService.sections.tags; const navigationPanelSection = navigationPanelService.sections.tags;
const collapsed = useLiveData(explorerSection.collapsed$); const collapsed = useLiveData(navigationPanelSection.collapsed$);
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 ExplorerTags = () => {
(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' });
explorerSection.setCollapsed(false); navigationPanelSection.setCollapsed(false);
}, },
[explorerSection, tagService] [navigationPanelSection, tagService]
); );
useEffect(() => { useEffect(() => {
@@ -46,13 +46,13 @@ export const ExplorerTags = () => {
return ( return (
<CollapsibleSection <CollapsibleSection
name="tags" name="tags"
testId="explorer-tags" testId="navigation-panel-tags"
headerClassName={styles.draggedOverHighlight} headerClassName={styles.draggedOverHighlight}
title={t['com.affine.rootAppSidebar.tags']()} title={t['com.affine.rootAppSidebar.tags']()}
actions={ actions={
<div className={styles.iconContainer}> <div className={styles.iconContainer}>
<IconButton <IconButton
data-testid="explorer-bar-add-tag-button" data-testid="navigation-panel-bar-add-tag-button"
onClick={handleOpenCreateModal} onClick={handleOpenCreateModal}
size="16" size="16"
tooltip={t[ tooltip={t[
@@ -72,18 +72,18 @@ export const ExplorerTags = () => {
</div> </div>
} }
> >
<ExplorerTreeRoot placeholder={<RootEmpty />}> <NavigationPanelTreeRoot placeholder={<RootEmpty />}>
{tags.map(tag => ( {tags.map(tag => (
<ExplorerTagNode <NavigationPanelTagNode
key={tag.id} key={tag.id}
tagId={tag.id} tagId={tag.id}
reorderable={false} reorderable={false}
location={{ location={{
at: 'explorer:tags:list', at: 'navigation-panel:tags:list',
}} }}
/> />
))} ))}
</ExplorerTreeRoot> </NavigationPanelTreeRoot>
</CollapsibleSection> </CollapsibleSection>
); );
}; };

View File

@@ -0,0 +1,11 @@
import React from 'react';
export interface NavigationPanelTreeContextData {
/**
* The level of the current tree node.
*/
level: number;
}
export const NavigationPanelTreeContext =
React.createContext<NavigationPanelTreeContextData | null>(null);

View File

@@ -0,0 +1,7 @@
export { DropEffect } from './drop-effect';
export type {
NavigationPanelTreeNodeDropEffect,
NavigationPanelTreeNodeDropEffectData,
} from './node';
export { NavigationPanelTreeNode } from './node';
export { NavigationPanelTreeRoot } from './root';

View File

@@ -39,28 +39,28 @@ import {
useState, useState,
} from 'react'; } from 'react';
import { ExplorerTreeContext } from './context'; import { NavigationPanelTreeContext } from './context';
import { DropEffect } from './drop-effect'; import { DropEffect } from './drop-effect';
import * as styles from './node.css'; import * as styles from './node.css';
import type { NodeOperation } from './types'; import type { NodeOperation } from './types';
export type ExplorerTreeNodeDropEffectData = { export type NavigationPanelTreeNodeDropEffectData = {
source: { data: AffineDNDData['draggable'] }; source: { data: AffineDNDData['draggable'] };
treeInstruction: DropTargetTreeInstruction | null; treeInstruction: DropTargetTreeInstruction | null;
}; };
export type ExplorerTreeNodeDropEffect = ( export type NavigationPanelTreeNodeDropEffect = (
data: ExplorerTreeNodeDropEffectData data: NavigationPanelTreeNodeDropEffectData
) => 'copy' | 'move' | 'link' | undefined; ) => 'copy' | 'move' | 'link' | undefined;
export type ExplorerTreeNodeIcon = React.ComponentType<{ export type NavigationPanelTreeNodeIcon = React.ComponentType<{
className?: string; className?: string;
draggedOver?: boolean; draggedOver?: boolean;
treeInstruction?: DropTargetTreeInstruction | null; treeInstruction?: DropTargetTreeInstruction | null;
collapsed?: boolean; collapsed?: boolean;
}>; }>;
export interface BaseExplorerTreeNodeProps { export interface BaseNavigationPanelTreeNodeProps {
name?: string; name?: string;
icon?: ExplorerTreeNodeIcon; icon?: NavigationPanelTreeNodeIcon;
children?: React.ReactNode; children?: React.ReactNode;
active?: boolean; active?: boolean;
extractEmojiAsIcon?: boolean; extractEmojiAsIcon?: boolean;
@@ -81,7 +81,8 @@ export interface BaseExplorerTreeNodeProps {
[key: `data-${string}`]: any; [key: `data-${string}`]: any;
} }
interface WebExplorerTreeNodeProps extends BaseExplorerTreeNodeProps { interface WebNavigationPanelTreeNodeProps
extends BaseNavigationPanelTreeNodeProps {
renameable?: boolean; renameable?: boolean;
onRename?: (newName: string) => void; onRename?: (newName: string) => void;
renameableGuard?: { docId: string; action: DocPermissionActions }; renameableGuard?: { docId: string; action: DocPermissionActions };
@@ -91,14 +92,14 @@ interface WebExplorerTreeNodeProps extends BaseExplorerTreeNodeProps {
reorderable?: boolean; reorderable?: boolean;
dndData?: AffineDNDData; dndData?: AffineDNDData;
onDrop?: (data: DropTargetDropEvent<AffineDNDData>) => void; onDrop?: (data: DropTargetDropEvent<AffineDNDData>) => void;
dropEffect?: ExplorerTreeNodeDropEffect; dropEffect?: NavigationPanelTreeNodeDropEffect;
} }
/** /**
* specific rename modal for explorer tree node, * specific rename modal for navigation panel tree node,
* Separate it into a separate component to prevent re-rendering the entire component when width changes. * Separate it into a separate component to prevent re-rendering the entire component when width changes.
*/ */
export const ExplorerTreeNodeRenameModal = ({ export const NavigationPanelTreeNodeRenameModal = ({
setRenaming, setRenaming,
handleRename, handleRename,
rawName, rawName,
@@ -124,7 +125,7 @@ export const ExplorerTreeNodeRenameModal = ({
); );
}; };
export const ExplorerTreeNode = ({ export const NavigationPanelTreeNode = ({
children, children,
icon: Icon, icon: Icon,
name: rawName, name: rawName,
@@ -150,10 +151,10 @@ export const ExplorerTreeNode = ({
onDrop, onDrop,
dropEffect, dropEffect,
...otherProps ...otherProps
}: WebExplorerTreeNodeProps) => { }: WebNavigationPanelTreeNodeProps) => {
const t = useI18n(); const t = useI18n();
const cid = useId(); const cid = useId();
const context = useContext(ExplorerTreeContext); const context = useContext(NavigationPanelTreeContext);
const level = context?.level ?? 0; const level = context?.level ?? 0;
// If no onClick or to is provided, clicking on the node will toggle the collapse state // If no onClick or to is provided, clicking on the node will toggle the collapse state
const clickForCollapse = !onClick && !to && !disabled; const clickForCollapse = !onClick && !to && !disabled;
@@ -293,7 +294,7 @@ export const ExplorerTreeNode = ({
> >
{can => ( {can => (
<MenuItem <MenuItem
key={'explorer-tree-rename'} key={'navigation-panel-tree-rename'}
type={'default'} type={'default'}
prefixIcon={<EditIcon />} prefixIcon={<EditIcon />}
onClick={() => setRenaming(true)} onClick={() => setRenaming(true)}
@@ -305,7 +306,7 @@ export const ExplorerTreeNode = ({
</Guard> </Guard>
) : ( ) : (
<MenuItem <MenuItem
key={'explorer-tree-rename'} key={'navigation-panel-tree-rename'}
type={'default'} type={'default'}
prefixIcon={<EditIcon />} prefixIcon={<EditIcon />}
onClick={() => setRenaming(true)} onClick={() => setRenaming(true)}
@@ -381,7 +382,7 @@ export const ExplorerTreeNode = ({
<div <div
data-disabled={disabled} data-disabled={disabled}
onClick={handleCollapsedChange} onClick={handleCollapsedChange}
data-testid="explorer-collapsed-button" data-testid="navigation-panel-collapsed-button"
className={styles.collapsedIconContainer} className={styles.collapsedIconContainer}
> >
<ArrowDownSmallIcon <ArrowDownSmallIcon
@@ -421,7 +422,7 @@ export const ExplorerTreeNode = ({
> >
<IconButton <IconButton
size="16" size="16"
data-testid="explorer-tree-node-operation-button" data-testid="navigation-panel-tree-node-operation-button"
style={{ marginLeft: 4 }} style={{ marginLeft: 4 }}
> >
<MoreHorizontalIcon /> <MoreHorizontalIcon />
@@ -432,7 +433,7 @@ export const ExplorerTreeNode = ({
</div> </div>
{renameable && renaming && ( {renameable && renaming && (
<ExplorerTreeNodeRenameModal <NavigationPanelTreeNodeRenameModal
setRenaming={setRenaming} setRenaming={setRenaming}
handleRename={handleRename} handleRename={handleRename}
rawName={rawName} rawName={rawName}
@@ -497,9 +498,9 @@ export const ExplorerTreeNode = ({
<div className={styles.collapseContentPlaceholder}> <div className={styles.collapseContentPlaceholder}>
{childCount === 0 && !collapsed ? childrenPlaceholder : null} {childCount === 0 && !collapsed ? childrenPlaceholder : null}
</div> </div>
<ExplorerTreeContext.Provider value={contextValue}> <NavigationPanelTreeContext.Provider value={contextValue}>
{collapsed ? null : children} {collapsed ? null : children}
</ExplorerTreeContext.Provider> </NavigationPanelTreeContext.Provider>
</Collapsible.Content> </Collapsible.Content>
</Collapsible.Root> </Collapsible.Root>
); );

View File

@@ -1,10 +1,10 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { ExplorerTreeContext } from './context'; import { NavigationPanelTreeContext } from './context';
import * as styles from './root.css'; import * as styles from './root.css';
import type { NodeOperation } from './types'; import type { NodeOperation } from './types';
export const ExplorerTreeRoot = ({ export const NavigationPanelTreeRoot = ({
children, children,
childrenOperations = [], childrenOperations = [],
placeholder, placeholder,
@@ -33,9 +33,9 @@ export const ExplorerTreeRoot = ({
<div className={styles.placeholder}> <div className={styles.placeholder}>
{childCount === 0 && placeholder} {childCount === 0 && placeholder}
</div> </div>
<ExplorerTreeContext.Provider value={contextValue}> <NavigationPanelTreeContext.Provider value={contextValue}>
{children} {children}
</ExplorerTreeContext.Provider> </NavigationPanelTreeContext.Provider>
</div> </div>
); );
}; };

View File

@@ -1,5 +0,0 @@
export { CollapsibleSection } from './layouts/collapsible-section';
export { ExplorerCollections } from './sections/collections';
export { ExplorerFavorites } from './sections/favorites';
export { ExplorerOrganize } from './sections/organize';
export { ExplorerTags } from './sections/tags';

View File

@@ -0,0 +1,5 @@
export { CollapsibleSection } from './layouts/collapsible-section';
export { NavigationPanelCollections } from './sections/collections';
export { NavigationPanelFavorites } from './sections/favorites';
export { NavigationPanelOrganize } from './sections/organize';
export { NavigationPanelTags } from './sections/tags';

View File

@@ -1,4 +1,4 @@
import { ExplorerTreeContext } from '@affine/core/modules/explorer'; import { NavigationPanelTreeContext } from '@affine/core/desktop/components/navigation-panel';
import { PlusIcon } from '@blocksuite/icons/rc'; import { PlusIcon } from '@blocksuite/icons/rc';
import { assignInlineVars } from '@vanilla-extract/dynamic'; import { assignInlineVars } from '@vanilla-extract/dynamic';
import clsx from 'clsx'; import clsx from 'clsx';
@@ -21,7 +21,7 @@ export const AddItemPlaceholder = ({
className, className,
...attrs ...attrs
}: AddItemPlaceholderProps) => { }: AddItemPlaceholderProps) => {
const context = useContext(ExplorerTreeContext); const context = useContext(NavigationPanelTreeContext);
const level = context?.level ?? 0; const level = context?.level ?? 0;
return ( return (

View File

@@ -1,7 +1,7 @@
import { import {
type CollapsibleSectionName, type CollapsibleSectionName,
ExplorerService, NavigationPanelService,
} from '@affine/core/modules/explorer'; } 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';
@@ -86,7 +86,7 @@ export const CollapsibleSection = ({
children, children,
...attrs ...attrs
}: CollapsibleSectionProps) => { }: CollapsibleSectionProps) => {
const section = useService(ExplorerService).sections[name]; const section = useService(NavigationPanelService).sections[name];
const collapsed = useLiveData(section.collapsed$); const collapsed = useLiveData(section.collapsed$);
const setCollapsed = useCallback( const setCollapsed = useCallback(

View File

@@ -1,9 +1,9 @@
import { MenuItem, notify } from '@affine/component'; import { MenuItem, notify } from '@affine/component';
import { filterPage } from '@affine/core/components/page-list'; import { filterPage } from '@affine/core/components/page-list';
import type { NodeOperation } from '@affine/core/desktop/components/navigation-panel';
import { CollectionService } from '@affine/core/modules/collection'; import { CollectionService } from '@affine/core/modules/collection';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
import type { NodeOperation } from '@affine/core/modules/explorer';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
import { GlobalContextService } from '@affine/core/modules/global-context'; import { GlobalContextService } from '@affine/core/modules/global-context';
import { ShareDocsListService } from '@affine/core/modules/share-doc'; import { ShareDocsListService } from '@affine/core/modules/share-doc';
@@ -17,16 +17,16 @@ import { LiveData, useLiveData, 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';
import { ExplorerTreeNode } from '../../tree/node'; import { NavigationPanelTreeNode } from '../../tree/node';
import { ExplorerDocNode } from '../doc'; import { NavigationPanelDocNode } from '../doc';
import { import {
useExplorerCollectionNodeOperations, useNavigationPanelCollectionNodeOperations,
useExplorerCollectionNodeOperationsMenu, useNavigationPanelCollectionNodeOperationsMenu,
} from './operations'; } from './operations';
const CollectionIcon = () => <ViewLayersIcon />; const CollectionIcon = () => <ViewLayersIcon />;
export const ExplorerCollectionNode = ({ export const NavigationPanelCollectionNode = ({
collectionId, collectionId,
operations: additionalOperations, operations: additionalOperations,
}: { }: {
@@ -60,16 +60,17 @@ export const ExplorerCollectionNode = ({
}); });
}, [collection, workspaceDialogService]); }, [collection, workspaceDialogService]);
const collectionOperations = useExplorerCollectionNodeOperationsMenu( const collectionOperations = useNavigationPanelCollectionNodeOperationsMenu(
collectionId,
handleOpenCollapsed,
handleEditCollection
);
const { handleAddDocToCollection } = useExplorerCollectionNodeOperations(
collectionId, collectionId,
handleOpenCollapsed, handleOpenCollapsed,
handleEditCollection handleEditCollection
); );
const { handleAddDocToCollection } =
useNavigationPanelCollectionNodeOperations(
collectionId,
handleOpenCollapsed,
handleEditCollection
);
const finalOperations = useMemo(() => { const finalOperations = useMemo(() => {
if (additionalOperations) { if (additionalOperations) {
@@ -83,7 +84,7 @@ export const ExplorerCollectionNode = ({
} }
return ( return (
<ExplorerTreeNode <NavigationPanelTreeNode
icon={CollectionIcon} icon={CollectionIcon}
name={collection.name || t['Untitled']()} name={collection.name || t['Untitled']()}
collapsed={collapsed} collapsed={collapsed}
@@ -91,17 +92,17 @@ export const ExplorerCollectionNode = ({
to={`/collection/${collection.id}`} to={`/collection/${collection.id}`}
active={active} active={active}
operations={finalOperations} operations={finalOperations}
data-testid={`explorer-collection-${collectionId}`} data-testid={`navigation-panel-collection-${collectionId}`}
> >
<ExplorerCollectionNodeChildren <NavigationPanelCollectionNodeChildren
collection={collection} collection={collection}
onAddDoc={handleAddDocToCollection} onAddDoc={handleAddDocToCollection}
/> />
</ExplorerTreeNode> </NavigationPanelTreeNode>
); );
}; };
const ExplorerCollectionNodeChildren = ({ const NavigationPanelCollectionNodeChildren = ({
collection, collection,
onAddDoc, onAddDoc,
}: { }: {
@@ -174,7 +175,7 @@ const ExplorerCollectionNodeChildren = ({
return ( return (
<> <>
{filtered.map(doc => ( {filtered.map(doc => (
<ExplorerDocNode <NavigationPanelDocNode
key={doc.id} key={doc.id}
docId={doc.id} docId={doc.id}
operations={ operations={

View File

@@ -8,8 +8,8 @@ import {
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils'; import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
import { useDeleteCollectionInfo } from '@affine/core/components/hooks/affine/use-delete-collection-info'; import { useDeleteCollectionInfo } from '@affine/core/components/hooks/affine/use-delete-collection-info';
import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
import type { NodeOperation } from '@affine/core/desktop/components/navigation-panel';
import { CollectionService } from '@affine/core/modules/collection'; import { CollectionService } from '@affine/core/modules/collection';
import type { NodeOperation } from '@affine/core/modules/explorer';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import { WorkspaceService } from '@affine/core/modules/workspace'; import { WorkspaceService } from '@affine/core/modules/workspace';
@@ -27,7 +27,7 @@ import { useCallback, useMemo } from 'react';
import { CollectionRenameSubMenu } from './dialog'; import { CollectionRenameSubMenu } from './dialog';
export const useExplorerCollectionNodeOperations = ( export const useNavigationPanelCollectionNodeOperations = (
collectionId: string, collectionId: string,
onOpenCollapsed: () => void, onOpenCollapsed: () => void,
onOpenEdit: () => void onOpenEdit: () => void
@@ -154,7 +154,7 @@ export const useExplorerCollectionNodeOperations = (
); );
}; };
export const useExplorerCollectionNodeOperationsMenu = ( export const useNavigationPanelCollectionNodeOperationsMenu = (
collectionId: string, collectionId: string,
onOpenCollapsed: () => void, onOpenCollapsed: () => void,
onOpenEdit: () => void onOpenEdit: () => void
@@ -170,7 +170,7 @@ export const useExplorerCollectionNodeOperationsMenu = (
handleShowEdit, handleShowEdit,
handleToggleFavoriteCollection, handleToggleFavoriteCollection,
handleRename, handleRename,
} = useExplorerCollectionNodeOperations( } = useNavigationPanelCollectionNodeOperations(
collectionId, collectionId,
onOpenCollapsed, onOpenCollapsed,
onOpenEdit onOpenEdit

View File

@@ -1,10 +1,10 @@
import { Loading } from '@affine/component'; import { Loading } from '@affine/component';
import { Guard } from '@affine/core/components/guard'; import { Guard } from '@affine/core/components/guard';
import type { NodeOperation } from '@affine/core/desktop/components/navigation-panel';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; 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 type { NodeOperation } from '@affine/core/modules/explorer';
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 { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
@@ -18,14 +18,14 @@ import {
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder'; import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
import { ExplorerTreeNode } from '../../tree/node'; import { NavigationPanelTreeNode } from '../../tree/node';
import { import {
useExplorerDocNodeOperations, useNavigationPanelDocNodeOperations,
useExplorerDocNodeOperationsMenu, useNavigationPanelDocNodeOperationsMenu,
} from './operations'; } from './operations';
import * as styles from './styles.css'; import * as styles from './styles.css';
export const ExplorerDocNode = ({ export const NavigationPanelDocNode = ({
docId, docId,
isLinked, isLinked,
operations: additionalOperations, operations: additionalOperations,
@@ -105,8 +105,11 @@ export const ExplorerDocNode = ({
}), }),
[docId, workspaceDialogService] [docId, workspaceDialogService]
); );
const operations = useExplorerDocNodeOperationsMenu(docId, option); const operations = useNavigationPanelDocNodeOperationsMenu(docId, option);
const { handleAddLinkedPage } = useExplorerDocNodeOperations(docId, option); const { handleAddLinkedPage } = useNavigationPanelDocNodeOperations(
docId,
option
);
const finalOperations = useMemo(() => { const finalOperations = useMemo(() => {
if (additionalOperations) { if (additionalOperations) {
@@ -120,7 +123,7 @@ export const ExplorerDocNode = ({
} }
return ( return (
<ExplorerTreeNode <NavigationPanelTreeNode
icon={Icon} icon={Icon}
name={t.t(docTitle)} name={t.t(docTitle)}
extractEmojiAsIcon={enableEmojiIcon} extractEmojiAsIcon={enableEmojiIcon}
@@ -137,13 +140,13 @@ export const ExplorerDocNode = ({
) )
} }
operations={finalOperations} operations={finalOperations}
data-testid={`explorer-doc-${docId}`} data-testid={`navigation-panel-doc-${docId}`}
> >
<Guard docId={docId} permission="Doc_Read"> <Guard docId={docId} permission="Doc_Read">
{canRead => {canRead =>
canRead canRead
? children?.map((child, index) => ( ? children?.map((child, index) => (
<ExplorerDocNode <NavigationPanelDocNode
key={`${child.docId}-${index}`} key={`${child.docId}-${index}`}
docId={child.docId} docId={child.docId}
isLinked isLinked
@@ -162,6 +165,6 @@ export const ExplorerDocNode = ({
) : null ) : null
} }
</Guard> </Guard>
</ExplorerTreeNode> </NavigationPanelTreeNode>
); );
}; };

View File

@@ -10,8 +10,8 @@ import { Guard } from '@affine/core/components/guard';
import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper'; import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
import type { NodeOperation } from '@affine/core/desktop/components/navigation-panel';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
import type { NodeOperation } from '@affine/core/modules/explorer';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import { WorkspaceService } from '@affine/core/modules/workspace'; import { WorkspaceService } from '@affine/core/modules/workspace';
@@ -31,7 +31,7 @@ import { useCallback, useMemo } from 'react';
import { DocFrameScope, DocInfoSheet } from '../../../doc-info'; import { DocFrameScope, DocInfoSheet } from '../../../doc-info';
import { DocRenameSubMenu } from './dialog'; import { DocRenameSubMenu } from './dialog';
export const useExplorerDocNodeOperations = ( export const useNavigationPanelDocNodeOperations = (
docId: string, docId: string,
options: { options: {
openNodeCollapsed: () => void; openNodeCollapsed: () => void;
@@ -160,7 +160,7 @@ export const useExplorerDocNodeOperations = (
); );
}; };
export const useExplorerDocNodeOperationsMenu = ( export const useNavigationPanelDocNodeOperationsMenu = (
docId: string, docId: string,
options: { options: {
openInfoModal: () => void; openInfoModal: () => void;
@@ -176,7 +176,7 @@ export const useExplorerDocNodeOperationsMenu = (
handleOpenInNewTab, handleOpenInNewTab,
handleMoveToTrash, handleMoveToTrash,
handleRename, handleRename,
} = useExplorerDocNodeOperations(docId, options); } = useNavigationPanelDocNodeOperations(docId, options);
const docService = useService(DocsService); const docService = useService(DocsService);
const docRecord = useLiveData(docService.list.doc$(docId)); const docRecord = useLiveData(docService.list.doc$(docId));

View File

@@ -7,11 +7,11 @@ import {
notify, notify,
} from '@affine/component'; } from '@affine/component';
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils'; import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import type { import type {
ExplorerTreeNodeIcon, NavigationPanelTreeNodeIcon,
NodeOperation, NodeOperation,
} from '@affine/core/modules/explorer'; } from '@affine/core/desktop/components/navigation-panel';
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 { import {
@@ -36,14 +36,14 @@ import { difference } from 'lodash-es';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder'; import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
import { ExplorerTreeNode } from '../../tree/node'; import { NavigationPanelTreeNode } from '../../tree/node';
import { ExplorerCollectionNode } from '../collection'; import { NavigationPanelCollectionNode } from '../collection';
import { ExplorerDocNode } from '../doc'; import { NavigationPanelDocNode } from '../doc';
import { ExplorerTagNode } from '../tag'; import { NavigationPanelTagNode } from '../tag';
import { FolderCreateTip, FolderRenameSubMenu } from './dialog'; import { FolderCreateTip, FolderRenameSubMenu } from './dialog';
import { FavoriteFolderOperation } from './operations'; import { FavoriteFolderOperation } from './operations';
export const ExplorerFolderNode = ({ export const NavigationPanelFolderNode = ({
nodeId, nodeId,
operations, operations,
}: { }: {
@@ -75,27 +75,34 @@ export const ExplorerFolderNode = ({
if (type === 'folder') { if (type === 'folder') {
return ( return (
<ExplorerFolderNodeFolder node={node} operations={additionalOperations} /> <NavigationPanelFolderNodeFolder
node={node}
operations={additionalOperations}
/>
); );
} }
if (!data) return null; if (!data) return null;
if (type === 'doc') { if (type === 'doc') {
return <ExplorerDocNode docId={data} operations={additionalOperations} />; return (
<NavigationPanelDocNode docId={data} operations={additionalOperations} />
);
} else if (type === 'collection') { } else if (type === 'collection') {
return ( return (
<ExplorerCollectionNode <NavigationPanelCollectionNode
collectionId={data} collectionId={data}
operations={additionalOperations} operations={additionalOperations}
/> />
); );
} else if (type === 'tag') { } else if (type === 'tag') {
return <ExplorerTagNode tagId={data} operations={additionalOperations} />; return (
<NavigationPanelTagNode tagId={data} operations={additionalOperations} />
);
} }
return; return;
}; };
const ExplorerFolderIcon: ExplorerTreeNodeIcon = ({ const NavigationPanelFolderIcon: NavigationPanelTreeNodeIcon = ({
collapsed, collapsed,
className, className,
draggedOver, draggedOver,
@@ -109,7 +116,7 @@ const ExplorerFolderIcon: ExplorerTreeNodeIcon = ({
/> />
); );
const ExplorerFolderNodeFolder = ({ const NavigationPanelFolderNodeFolder = ({
node, node,
operations: additionalOperations, operations: additionalOperations,
}: { }: {
@@ -390,19 +397,19 @@ const ExplorerFolderNodeFolder = ({
}, []); }, []);
return ( return (
<ExplorerTreeNode <NavigationPanelTreeNode
icon={ExplorerFolderIcon} icon={NavigationPanelFolderIcon}
name={name} name={name}
extractEmojiAsIcon={enableEmojiIcon} extractEmojiAsIcon={enableEmojiIcon}
collapsed={collapsed} collapsed={collapsed}
setCollapsed={handleCollapsedChange} setCollapsed={handleCollapsedChange}
operations={finalOperations} operations={finalOperations}
data-testid={`explorer-folder-${node.id}`} data-testid={`navigation-panel-folder-${node.id}`}
aria-label={name} aria-label={name}
data-role="explorer-folder" data-role="navigation-panel-folder"
> >
{children.map(child => ( {children.map(child => (
<ExplorerFolderNode <NavigationPanelFolderNode
key={child.id} key={child.id}
nodeId={child.id as string} nodeId={child.id as string}
operations={childrenOperations} operations={childrenOperations}
@@ -413,6 +420,6 @@ const ExplorerFolderNodeFolder = ({
onClick={handleNewDoc} onClick={handleNewDoc}
data-testid="new-folder-in-folder-button" data-testid="new-folder-in-folder-button"
/> />
</ExplorerTreeNode> </NavigationPanelTreeNode>
); );
}; };

View File

@@ -1,4 +1,4 @@
import type { NodeOperation } from '@affine/core/modules/explorer'; 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 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';
@@ -8,15 +8,15 @@ import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder'; import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
import { ExplorerTreeNode } from '../../tree/node'; import { NavigationPanelTreeNode } from '../../tree/node';
import { ExplorerDocNode } from '../doc'; import { NavigationPanelDocNode } from '../doc';
import { import {
useExplorerTagNodeOperations, useNavigationPanelTagNodeOperations,
useExplorerTagNodeOperationsMenu, useNavigationPanelTagNodeOperationsMenu,
} from './operations'; } from './operations';
import * as styles from './styles.css'; import * as styles from './styles.css';
export const ExplorerTagNode = ({ export const NavigationPanelTagNode = ({
tagId, tagId,
operations: additionalOperations, operations: additionalOperations,
}: { }: {
@@ -41,7 +41,7 @@ export const ExplorerTagNode = ({
return ( return (
<div className={clsx(styles.tagIconContainer, className)}> <div className={clsx(styles.tagIconContainer, className)}>
<div <div
data-testid="explorer-tag-icon-dot" data-testid="navigation-panel-tag-icon-dot"
className={styles.tagIcon} className={styles.tagIcon}
style={{ style={{
backgroundColor: tagColor, backgroundColor: tagColor,
@@ -59,8 +59,8 @@ export const ExplorerTagNode = ({
}), }),
[] []
); );
const operations = useExplorerTagNodeOperationsMenu(tagId, option); const operations = useNavigationPanelTagNodeOperationsMenu(tagId, option);
const { handleNewDoc } = useExplorerTagNodeOperations(tagId, option); const { handleNewDoc } = useNavigationPanelTagNodeOperations(tagId, option);
const finalOperations = useMemo(() => { const finalOperations = useMemo(() => {
if (additionalOperations) { if (additionalOperations) {
@@ -74,7 +74,7 @@ export const ExplorerTagNode = ({
} }
return ( return (
<ExplorerTreeNode <NavigationPanelTreeNode
icon={Icon} icon={Icon}
name={tagName || t['Untitled']()} name={tagName || t['Untitled']()}
collapsed={collapsed} collapsed={collapsed}
@@ -82,12 +82,12 @@ export const ExplorerTagNode = ({
to={`/tag/${tagId}`} to={`/tag/${tagId}`}
active={active} active={active}
operations={finalOperations} operations={finalOperations}
data-testid={`explorer-tag-${tagId}`} data-testid={`navigation-panel-tag-${tagId}`}
aria-label={tagName} aria-label={tagName}
data-role="explorer-tag" data-role="navigation-panel-tag"
> >
<ExplorerTagNodeDocs tag={tagRecord} onNewDoc={handleNewDoc} /> <NavigationPanelTagNodeDocs tag={tagRecord} onNewDoc={handleNewDoc} />
</ExplorerTreeNode> </NavigationPanelTreeNode>
); );
}; };
@@ -96,7 +96,7 @@ export const ExplorerTagNode = ({
* 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 ExplorerTagNodeDocs = ({ export const NavigationPanelTagNodeDocs = ({
tag, tag,
onNewDoc, onNewDoc,
}: { }: {
@@ -109,7 +109,7 @@ export const ExplorerTagNodeDocs = ({
return ( return (
<> <>
{tagDocIds.map(docId => ( {tagDocIds.map(docId => (
<ExplorerDocNode key={docId} docId={docId} /> <NavigationPanelDocNode key={docId} docId={docId} />
))} ))}
<AddItemPlaceholder label={t['New Page']()} onClick={onNewDoc} /> <AddItemPlaceholder label={t['New Page']()} onClick={onNewDoc} />
</> </>

View File

@@ -7,9 +7,9 @@ import {
} from '@affine/component'; } from '@affine/component';
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils'; import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
import type { NodeOperation } from '@affine/core/desktop/components/navigation-panel';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
import type { NodeOperation } from '@affine/core/modules/explorer';
import { FavoriteService } from '@affine/core/modules/favorite'; import { FavoriteService } from '@affine/core/modules/favorite';
import { GlobalCacheService } from '@affine/core/modules/storage'; import { GlobalCacheService } from '@affine/core/modules/storage';
import { TagService } from '@affine/core/modules/tag'; import { TagService } from '@affine/core/modules/tag';
@@ -28,7 +28,7 @@ import { useCallback, useMemo } from 'react';
import { TagRenameSubMenu } from './dialog'; import { TagRenameSubMenu } from './dialog';
export const useExplorerTagNodeOperations = ( export const useNavigationPanelTagNodeOperations = (
tagId: string, tagId: string,
{ {
openNodeCollapsed, openNodeCollapsed,
@@ -211,7 +211,7 @@ export const useExplorerTagNodeOperations = (
] ]
); );
}; };
export const useExplorerTagNodeOperationsMenu = ( export const useNavigationPanelTagNodeOperationsMenu = (
tagId: string, tagId: string,
option: { option: {
openNodeCollapsed: () => void; openNodeCollapsed: () => void;
@@ -226,7 +226,7 @@ export const useExplorerTagNodeOperationsMenu = (
handleToggleFavoriteTag, handleToggleFavoriteTag,
handleChangeNameOrColor, handleChangeNameOrColor,
handleOpenDocSelector, handleOpenDocSelector,
} = useExplorerTagNodeOperations(tagId, option); } = useNavigationPanelTagNodeOperations(tagId, option);
return useMemo( return useMemo(
() => [ () => [

View File

@@ -1,8 +1,8 @@
import { usePromptModal } from '@affine/component'; import { usePromptModal } from '@affine/component';
import { createEmptyCollection } from '@affine/core/components/page-list/use-collection-manager'; import { createEmptyCollection } from '@affine/core/components/page-list/use-collection-manager';
import { NavigationPanelTreeRoot } from '@affine/core/desktop/components/navigation-panel';
import { CollectionService } from '@affine/core/modules/collection'; import { CollectionService } from '@affine/core/modules/collection';
import { ExplorerService } from '@affine/core/modules/explorer'; import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { track } from '@affine/track'; import { track } from '@affine/track';
@@ -13,17 +13,18 @@ import { useCallback } 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';
import { ExplorerCollectionNode } from '../../nodes/collection'; import { NavigationPanelCollectionNode } from '../../nodes/collection';
import * as styles from './index.css'; import * as styles from './index.css';
export const ExplorerCollections = () => { export const NavigationPanelCollections = () => {
const t = useI18n(); const t = useI18n();
const { collectionService, workbenchService, explorerService } = useServices({ const { collectionService, workbenchService, navigationPanelService } =
CollectionService, useServices({
WorkbenchService, CollectionService,
ExplorerService, WorkbenchService,
}); NavigationPanelService,
const explorerSection = explorerService.sections.collections; });
const navigationPanelSection = navigationPanelService.sections.collections;
const collections = useLiveData(collectionService.collections$); const collections = useLiveData(collectionService.collections$);
const { openPromptModal } = usePromptModal(); const { openPromptModal } = usePromptModal();
@@ -51,12 +52,12 @@ export const ExplorerCollections = () => {
type: 'collection', type: 'collection',
}); });
workbenchService.workbench.openCollection(id); workbenchService.workbench.openCollection(id);
explorerSection.setCollapsed(false); navigationPanelSection.setCollapsed(false);
}, },
}); });
}, [ }, [
collectionService, collectionService,
explorerSection, navigationPanelSection,
openPromptModal, openPromptModal,
t, t,
workbenchService.workbench, workbenchService.workbench,
@@ -65,23 +66,23 @@ export const ExplorerCollections = () => {
return ( return (
<CollapsibleSection <CollapsibleSection
name="collections" name="collections"
testId="explorer-collections" testId="navigation-panel-collections"
title={t['com.affine.rootAppSidebar.collections']()} title={t['com.affine.rootAppSidebar.collections']()}
> >
<ExplorerTreeRoot> <NavigationPanelTreeRoot>
{collections.map(collection => ( {collections.map(collection => (
<ExplorerCollectionNode <NavigationPanelCollectionNode
key={collection.id} key={collection.id}
collectionId={collection.id} collectionId={collection.id}
/> />
))} ))}
<AddItemPlaceholder <AddItemPlaceholder
icon={<AddCollectionIcon />} icon={<AddCollectionIcon />}
data-testid="explorer-bar-add-collection-button" data-testid="navigation-panel-bar-add-collection-button"
label={t['com.affine.rootAppSidebar.collection.new']()} label={t['com.affine.rootAppSidebar.collection.new']()}
onClick={() => handleCreateCollection()} onClick={() => handleCreateCollection()}
/> />
</ExplorerTreeRoot> </NavigationPanelTreeRoot>
</CollapsibleSection> </CollapsibleSection>
); );
}; };

View File

@@ -1,10 +1,8 @@
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils'; import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
import { import { NavigationPanelTreeRoot } from '@affine/core/desktop/components/navigation-panel';
ExplorerService,
ExplorerTreeRoot,
} from '@affine/core/modules/explorer';
import type { FavoriteSupportTypeUnion } from '@affine/core/modules/favorite'; import type { FavoriteSupportTypeUnion } from '@affine/core/modules/favorite';
import { FavoriteService } from '@affine/core/modules/favorite'; import { FavoriteService } from '@affine/core/modules/favorite';
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';
@@ -12,20 +10,21 @@ import { useCallback } 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';
import { ExplorerCollectionNode } from '../../nodes/collection'; import { NavigationPanelCollectionNode } from '../../nodes/collection';
import { ExplorerDocNode } from '../../nodes/doc'; import { NavigationPanelDocNode } from '../../nodes/doc';
import { ExplorerFolderNode } from '../../nodes/folder'; import { NavigationPanelFolderNode } from '../../nodes/folder';
import { ExplorerTagNode } from '../../nodes/tag'; import { NavigationPanelTagNode } from '../../nodes/tag';
export const ExplorerFavorites = () => { export const NavigationPanelFavorites = () => {
const { favoriteService, workspaceService, explorerService } = useServices({ const { favoriteService, workspaceService, navigationPanelService } =
FavoriteService, useServices({
WorkspaceService, FavoriteService,
ExplorerService, WorkspaceService,
}); NavigationPanelService,
});
const t = useI18n(); const t = useI18n();
const explorerSection = explorerService.sections.favorites; const navigationPanelSection = navigationPanelService.sections.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(
@@ -39,28 +38,28 @@ export const ExplorerFavorites = () => {
newDoc.id, newDoc.id,
favoriteService.favoriteList.indexAt('before') favoriteService.favoriteList.indexAt('before')
); );
explorerSection.setCollapsed(false); navigationPanelSection.setCollapsed(false);
}, [createPage, explorerSection, favoriteService.favoriteList]); }, [createPage, navigationPanelSection, favoriteService.favoriteList]);
return ( return (
<CollapsibleSection <CollapsibleSection
name="favorites" name="favorites"
title={t['com.affine.rootAppSidebar.favorites']()} title={t['com.affine.rootAppSidebar.favorites']()}
testId="explorer-favorites" testId="navigation-panel-favorites"
headerTestId="explorer-favorite-category-divider" headerTestId="navigation-panel-favorite-category-divider"
> >
<ExplorerTreeRoot 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} />
))} ))}
<AddItemPlaceholder <AddItemPlaceholder
data-testid="explorer-bar-add-favorite-button" data-testid="navigation-panel-bar-add-favorite-button"
data-event-props="$.navigationPanel.favorites.createDoc" data-event-props="$.navigationPanel.favorites.createDoc"
data-event-args-control="addFavorite" data-event-args-control="addFavorite"
onClick={handleCreateNewFavoriteDoc} onClick={handleCreateNewFavoriteDoc}
label={t['New Page']()} label={t['New Page']()}
/> />
</ExplorerTreeRoot> </NavigationPanelTreeRoot>
</CollapsibleSection> </CollapsibleSection>
); );
}; };
@@ -74,12 +73,12 @@ export const FavoriteNode = ({
}; };
}) => { }) => {
return favorite.type === 'doc' ? ( return favorite.type === 'doc' ? (
<ExplorerDocNode docId={favorite.id} /> <NavigationPanelDocNode docId={favorite.id} />
) : favorite.type === 'tag' ? ( ) : favorite.type === 'tag' ? (
<ExplorerTagNode tagId={favorite.id} /> <NavigationPanelTagNode tagId={favorite.id} />
) : favorite.type === 'folder' ? ( ) : favorite.type === 'folder' ? (
<ExplorerFolderNode nodeId={favorite.id} /> <NavigationPanelFolderNode nodeId={favorite.id} />
) : ( ) : (
<ExplorerCollectionNode collectionId={favorite.id} /> <NavigationPanelCollectionNode collectionId={favorite.id} />
); );
}; };

View File

@@ -1,8 +1,6 @@
import { Skeleton } from '@affine/component'; import { Skeleton } from '@affine/component';
import { import { NavigationPanelTreeRoot } from '@affine/core/desktop/components/navigation-panel';
ExplorerService, import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
ExplorerTreeRoot,
} from '@affine/core/modules/explorer';
import { OrganizeService } from '@affine/core/modules/organize'; import { OrganizeService } from '@affine/core/modules/organize';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import track from '@affine/track'; import track from '@affine/track';
@@ -12,15 +10,15 @@ import { useCallback, 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';
import { ExplorerFolderNode } from '../../nodes/folder'; import { NavigationPanelFolderNode } from '../../nodes/folder';
import { FolderCreateTip, FolderRenameDialog } from '../../nodes/folder/dialog'; import { FolderCreateTip, FolderRenameDialog } from '../../nodes/folder/dialog';
export const ExplorerOrganize = () => { export const NavigationPanelOrganize = () => {
const { organizeService, explorerService } = useServices({ const { organizeService, navigationPanelService } = useServices({
OrganizeService, OrganizeService,
ExplorerService, NavigationPanelService,
}); });
const explorerSection = explorerService.sections.organize; const navigationPanelSection = navigationPanelService.sections.organize;
const [openNewFolderDialog, setOpenNewFolderDialog] = useState(false); const [openNewFolderDialog, setOpenNewFolderDialog] = useState(false);
const t = useI18n(); const t = useI18n();
@@ -38,10 +36,10 @@ export const ExplorerOrganize = () => {
rootFolder.indexAt('before') rootFolder.indexAt('before')
); );
track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' }); track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
explorerSection.setCollapsed(false); navigationPanelSection.setCollapsed(false);
return newFolderId; return newFolderId;
}, },
[explorerSection, rootFolder] [navigationPanelSection, rootFolder]
); );
return ( return (
@@ -50,17 +48,20 @@ export const ExplorerOrganize = () => {
title={t['com.affine.rootAppSidebar.organize']()} title={t['com.affine.rootAppSidebar.organize']()}
> >
{/* TODO(@CatsJuice): Organize loading UI */} {/* TODO(@CatsJuice): Organize loading UI */}
<ExplorerTreeRoot placeholder={isLoading ? <Skeleton /> : null}> <NavigationPanelTreeRoot placeholder={isLoading ? <Skeleton /> : null}>
{folders.map(child => ( {folders.map(child => (
<ExplorerFolderNode key={child.id} nodeId={child.id as string} /> <NavigationPanelFolderNode
key={child.id}
nodeId={child.id as string}
/>
))} ))}
<AddItemPlaceholder <AddItemPlaceholder
icon={<AddOrganizeIcon />} icon={<AddOrganizeIcon />}
data-testid="explorer-bar-add-organize-button" data-testid="navigation-panel-bar-add-organize-button"
label={t['com.affine.rootAppSidebar.organize.add-folder']()} label={t['com.affine.rootAppSidebar.organize.add-folder']()}
onClick={() => setOpenNewFolderDialog(true)} onClick={() => setOpenNewFolderDialog(true)}
/> />
</ExplorerTreeRoot> </NavigationPanelTreeRoot>
<FolderRenameDialog <FolderRenameDialog
open={openNewFolderDialog} open={openNewFolderDialog}
onConfirm={handleCreateFolder} onConfirm={handleCreateFolder}

View File

@@ -1,5 +1,5 @@
import { ExplorerService } from '@affine/core/modules/explorer'; import { NavigationPanelTreeRoot } from '@affine/core/desktop/components/navigation-panel';
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree'; import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
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 { track } from '@affine/track'; import { track } from '@affine/track';
@@ -9,7 +9,7 @@ import { useCallback, 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';
import { ExplorerTagNode } from '../../nodes/tag'; import { NavigationPanelTagNode } from '../../nodes/tag';
import { TagRenameDialog } from '../../nodes/tag/dialog'; import { TagRenameDialog } from '../../nodes/tag/dialog';
export const TagDesc = ({ input }: { input: string }) => { export const TagDesc = ({ input }: { input: string }) => {
@@ -20,12 +20,12 @@ export const TagDesc = ({ input }: { input: string }) => {
: t['com.affine.m.explorer.tag.new-tip-empty'](); : t['com.affine.m.explorer.tag.new-tip-empty']();
}; };
export const ExplorerTags = () => { export const NavigationPanelTags = () => {
const { tagService, explorerService } = useServices({ const { tagService, navigationPanelService } = useServices({
TagService, TagService,
ExplorerService, NavigationPanelService,
}); });
const explorerSection = explorerService.sections.tags; const navigationPanelSection = navigationPanelService.sections.tags;
const tags = useLiveData(tagService.tagList.tags$); const tags = useLiveData(tagService.tagList.tags$);
const [showNewTagDialog, setShowNewTagDialog] = useState(false); const [showNewTagDialog, setShowNewTagDialog] = useState(false);
@@ -36,9 +36,9 @@ export const ExplorerTags = () => {
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' });
explorerSection.setCollapsed(false); navigationPanelSection.setCollapsed(false);
}, },
[explorerSection, tagService] [navigationPanelSection, tagService]
); );
return ( return (
@@ -46,13 +46,13 @@ export const ExplorerTags = () => {
name="tags" name="tags"
title={t['com.affine.rootAppSidebar.tags']()} title={t['com.affine.rootAppSidebar.tags']()}
> >
<ExplorerTreeRoot> <NavigationPanelTreeRoot>
{tags.map(tag => ( {tags.map(tag => (
<ExplorerTagNode key={tag.id} tagId={tag.id} /> <NavigationPanelTagNode key={tag.id} tagId={tag.id} />
))} ))}
<AddItemPlaceholder <AddItemPlaceholder
icon={<AddTagIcon />} icon={<AddTagIcon />}
data-testid="explorer-add-tag-button" data-testid="navigation-panel-add-tag-button"
onClick={() => setShowNewTagDialog(true)} onClick={() => setShowNewTagDialog(true)}
label={t[ label={t[
'com.affine.rootAppSidebar.explorer.tag-section-add-tooltip' 'com.affine.rootAppSidebar.explorer.tag-section-add-tooltip'
@@ -65,7 +65,7 @@ export const ExplorerTags = () => {
enableAnimation enableAnimation
descRenderer={TagDesc} descRenderer={TagDesc}
/> />
</ExplorerTreeRoot> </NavigationPanelTreeRoot>
</CollapsibleSection> </CollapsibleSection>
); );
}; };

View File

@@ -1,6 +1,8 @@
import { MobileMenu } from '@affine/component'; import { MobileMenu } from '@affine/component';
import type { BaseExplorerTreeNodeProps } from '@affine/core/modules/explorer'; import {
import { ExplorerTreeContext } from '@affine/core/modules/explorer'; type BaseNavigationPanelTreeNodeProps,
NavigationPanelTreeContext,
} from '@affine/core/desktop/components/navigation-panel';
import { WorkbenchLink } from '@affine/core/modules/workbench'; import { WorkbenchLink } from '@affine/core/modules/workbench';
import { extractEmojiIcon } from '@affine/core/utils'; import { extractEmojiIcon } from '@affine/core/utils';
import { ArrowDownSmallIcon, MoreHorizontalIcon } from '@blocksuite/icons/rc'; import { ArrowDownSmallIcon, MoreHorizontalIcon } from '@blocksuite/icons/rc';
@@ -18,9 +20,10 @@ import {
import { SwipeMenu } from '../../swipe-menu'; import { SwipeMenu } from '../../swipe-menu';
import * as styles from './node.css'; import * as styles from './node.css';
interface ExplorerTreeNodeProps extends BaseExplorerTreeNodeProps {} interface NavigationPanelTreeNodeProps
extends BaseNavigationPanelTreeNodeProps {}
export const ExplorerTreeNode = ({ export const NavigationPanelTreeNode = ({
children, children,
icon: Icon, icon: Icon,
name: rawName, name: rawName,
@@ -37,8 +40,8 @@ export const ExplorerTreeNode = ({
childrenPlaceholder, childrenPlaceholder,
linkComponent: LinkComponent = WorkbenchLink, linkComponent: LinkComponent = WorkbenchLink,
...otherProps ...otherProps
}: ExplorerTreeNodeProps) => { }: NavigationPanelTreeNodeProps) => {
const context = useContext(ExplorerTreeContext); const context = useContext(NavigationPanelTreeContext);
const level = context?.level ?? 0; const level = context?.level ?? 0;
// If no onClick or to is provided, clicking on the node will toggle the collapse state // If no onClick or to is provided, clicking on the node will toggle the collapse state
const clickForCollapse = !onClick && !to && !disabled; const clickForCollapse = !onClick && !to && !disabled;
@@ -141,7 +144,7 @@ export const ExplorerTreeNode = ({
<div <div
data-disabled={disabled} data-disabled={disabled}
onClick={handleCollapsedChange} onClick={handleCollapsedChange}
data-testid="explorer-collapsed-button" data-testid="navigation-panel-collapsed-button"
className={styles.collapsedIconContainer} className={styles.collapsedIconContainer}
> >
<ArrowDownSmallIcon <ArrowDownSmallIcon
@@ -197,9 +200,9 @@ export const ExplorerTreeNode = ({
<div className={styles.collapseContentPlaceholder}> <div className={styles.collapseContentPlaceholder}>
{childCount === 0 && !collapsed && childrenPlaceholder} {childCount === 0 && !collapsed && childrenPlaceholder}
</div> </div>
<ExplorerTreeContext.Provider value={contextValue}> <NavigationPanelTreeContext.Provider value={contextValue}>
{collapsed ? null : children} {collapsed ? null : children}
</ExplorerTreeContext.Provider> </NavigationPanelTreeContext.Provider>
</Collapsible.Content> </Collapsible.Content>
</Collapsible.Root> </Collapsible.Root>
); );

View File

@@ -0,0 +1,10 @@
import { style } from '@vanilla-extract/css';
export const placeholder = style({
display: 'none',
selectors: {
'&:only-child': {
display: 'initial',
},
},
});

View File

@@ -0,0 +1,43 @@
import {
NavigationPanelTreeContext,
type NodeOperation,
} from '@affine/core/desktop/components/navigation-panel';
import { useMemo, useState } from 'react';
import * as styles from './root.css';
export const NavigationPanelTreeRoot = ({
children,
childrenOperations = [],
placeholder,
}: {
children?: React.ReactNode;
childrenOperations?: NodeOperation[];
className?: string;
placeholder?: React.ReactNode;
}) => {
const [childCount, setChildCount] = useState(0);
const contextValue = useMemo(() => {
return {
operations: childrenOperations,
level: 0,
registerChild: () => {
setChildCount(c => c + 1);
return () => setChildCount(c => c - 1);
},
};
}, [childrenOperations]);
return (
// <div> is for placeholder:last-child selector
<div>
{/* For lastInGroup check, the placeholder must be placed above all children in the dom */}
<div className={styles.placeholder}>
{childCount === 0 && placeholder}
</div>
<NavigationPanelTreeContext.Provider value={contextValue}>
{children}
</NavigationPanelTreeContext.Provider>
</div>
);
};

View File

@@ -2,11 +2,11 @@ import { SafeArea, useThemeColorV2 } from '@affine/component';
import { AppTabs } from '../../components'; import { AppTabs } from '../../components';
import { import {
ExplorerCollections, NavigationPanelCollections,
ExplorerFavorites, NavigationPanelFavorites,
ExplorerOrganize, NavigationPanelOrganize,
ExplorerTags, NavigationPanelTags,
} from '../../components/explorer'; } from '../../components/navigation';
import { HomeHeader, RecentDocs } from '../../views'; import { HomeHeader, RecentDocs } from '../../views';
export const Component = () => { export const Component = () => {
@@ -25,10 +25,10 @@ export const Component = () => {
padding: '0 8px 32px 8px', padding: '0 8px 32px 8px',
}} }}
> >
<ExplorerFavorites /> <NavigationPanelFavorites />
<ExplorerOrganize /> <NavigationPanelOrganize />
<ExplorerCollections /> <NavigationPanelCollections />
<ExplorerTags /> <NavigationPanelTags />
</div> </div>
</SafeArea> </SafeArea>
<AppTabs /> <AppTabs />

View File

@@ -4,7 +4,7 @@ import { useService } from '@toeverything/infra';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { DocCard } from '../../components/doc-card'; import { DocCard } from '../../components/doc-card';
import { CollapsibleSection } from '../../components/explorer'; import { CollapsibleSection } from '../../components/navigation';
import * as styles from './styles.css'; import * as styles from './styles.css';
export const RecentDocs = ({ max = 5 }: { max?: number }) => { export const RecentDocs = ({ max = 5 }: { max?: number }) => {

View File

@@ -1,3 +0,0 @@
# explorer
file manager in app left sidebar

View File

@@ -1,28 +0,0 @@
import { type Framework } from '@toeverything/infra';
import { GlobalCache } from '../storage';
import { WorkspaceScope } from '../workspace';
import { ExplorerSection } from './entities/explore-section';
import { ExplorerService } from './services/explorer';
export { ExplorerService } from './services/explorer';
export type { CollapsibleSectionName } from './types';
export { CollapsibleSection } from './views/layouts/collapsible-section';
export { ExplorerCollections } from './views/sections/collections';
export { ExplorerFavorites } from './views/sections/favorites';
export { ExplorerMigrationFavorites } from './views/sections/migration-favorites';
export { ExplorerOrganize } from './views/sections/organize';
// for mobile
export { ExplorerTreeRoot } from './views/tree';
export { ExplorerTreeContext } from './views/tree/context';
export type {
BaseExplorerTreeNodeProps,
ExplorerTreeNodeIcon,
} from './views/tree/node';
export type { NodeOperation } from './views/tree/types';
export function configureExplorerModule(framework: Framework) {
framework
.scope(WorkspaceScope)
.service(ExplorerService)
.entity(ExplorerSection, [GlobalCache]);
}

View File

@@ -1,60 +0,0 @@
import { Button } from '@affine/component';
import clsx from 'clsx';
import {
cloneElement,
forwardRef,
type HTMLAttributes,
type JSX,
type ReactElement,
type Ref,
type SVGAttributes,
type SVGProps,
} from 'react';
import * as styles from './empty-section.css';
interface ExplorerEmptySectionProps extends HTMLAttributes<HTMLDivElement> {
icon:
| ((props: SVGProps<SVGSVGElement>) => JSX.Element)
| ReactElement<SVGAttributes<SVGElement>>;
message: string;
messageTestId?: string;
actionText?: string;
onActionClick?: () => void;
}
export const ExplorerEmptySection = forwardRef(function ExplorerEmptySection(
{
icon: Icon,
message,
messageTestId,
actionText,
children,
className,
onActionClick,
...attrs
}: ExplorerEmptySectionProps,
ref: Ref<HTMLDivElement>
) {
const icon =
typeof Icon === 'function' ? (
<Icon className={styles.icon} />
) : (
cloneElement(Icon, { className: styles.icon })
);
return (
<div className={clsx(styles.content, className)} ref={ref} {...attrs}>
<div className={styles.iconWrapper}>{icon}</div>
<div data-testid={messageTestId} className={styles.message}>
{message}
</div>
{actionText ? (
<Button className={styles.newButton} onClick={onActionClick}>
{actionText}
</Button>
) : null}
{children}
</div>
);
});

View File

@@ -1,45 +0,0 @@
import type { DropTargetOptions } from '@affine/component';
import { isFavoriteSupportType } from '@affine/core/modules/favorite';
import type { AffineDNDData } from '@affine/core/types/dnd';
import type { ExplorerTreeNodeDropEffect } from '../../tree';
export const favoriteChildrenDropEffect: ExplorerTreeNodeDropEffect = data => {
if (
data.treeInstruction?.type === 'reorder-above' ||
data.treeInstruction?.type === 'reorder-below'
) {
if (
data.source.data.from?.at === 'explorer:favorite:list' &&
data.source.data.entity?.type &&
isFavoriteSupportType(data.source.data.entity.type)
) {
return 'move';
} else if (
data.source.data.entity?.type &&
isFavoriteSupportType(data.source.data.entity.type)
) {
return 'link';
}
}
return; // not supported
};
export const favoriteRootDropEffect: ExplorerTreeNodeDropEffect = data => {
const sourceType = data.source.data.entity?.type;
if (sourceType && isFavoriteSupportType(sourceType)) {
return 'link';
}
return;
};
export const favoriteRootCanDrop: DropTargetOptions<AffineDNDData>['canDrop'] =
data => {
return data.source.data.entity?.type
? isFavoriteSupportType(data.source.data.entity.type)
: false;
};
export const favoriteChildrenCanDrop: DropTargetOptions<AffineDNDData>['canDrop'] =
// Same as favoriteRootCanDrop
data => favoriteRootCanDrop(data);

View File

@@ -1,36 +0,0 @@
import type { DropTargetOptions } from '@affine/component';
import { isOrganizeSupportType } from '@affine/core/modules/organize/constants';
import type { AffineDNDData } from '@affine/core/types/dnd';
import type { ExplorerTreeNodeDropEffect } from '../../tree';
export const organizeChildrenDropEffect: ExplorerTreeNodeDropEffect = data => {
if (
data.treeInstruction?.type === 'reorder-above' ||
data.treeInstruction?.type === 'reorder-below'
) {
if (data.source.data.entity?.type === 'folder') {
return 'move';
}
} else {
return; // not supported
}
return;
};
export const organizeEmptyDropEffect: ExplorerTreeNodeDropEffect = data => {
const sourceType = data.source.data.entity?.type;
if (sourceType && isOrganizeSupportType(sourceType)) {
return 'link';
}
return;
};
/**
* Check whether the data can be dropped on the empty state of the organize section
*/
export const organizeEmptyRootCanDrop: DropTargetOptions<AffineDNDData>['canDrop'] =
data => {
const type = data.source.data.entity?.type;
return !!type && isOrganizeSupportType(type);
};

View File

@@ -1,11 +0,0 @@
import React from 'react';
export interface ExplorerTreeContextData {
/**
* The level of the current tree node.
*/
level: number;
}
export const ExplorerTreeContext =
React.createContext<ExplorerTreeContextData | null>(null);

View File

@@ -1,7 +0,0 @@
export { DropEffect } from './drop-effect';
export type {
ExplorerTreeNodeDropEffect,
ExplorerTreeNodeDropEffectData,
} from './node';
export { ExplorerTreeNode } from './node';
export { ExplorerTreeRoot } from './root';

View File

@@ -20,7 +20,6 @@ import { configureDocLinksModule } from './doc-link';
import { configureDocsSearchModule } from './docs-search'; import { configureDocsSearchModule } from './docs-search';
import { configureEditorModule } from './editor'; import { configureEditorModule } from './editor';
import { configureEditorSettingModule } from './editor-setting'; import { configureEditorSettingModule } from './editor-setting';
import { configureExplorerModule } from './explorer';
import { configureFavoriteModule } from './favorite'; import { configureFavoriteModule } from './favorite';
import { configureFeatureFlagModule } from './feature-flag'; import { configureFeatureFlagModule } from './feature-flag';
import { configureGlobalContextModule } from './global-context'; import { configureGlobalContextModule } from './global-context';
@@ -32,6 +31,7 @@ import { configureJournalModule } from './journal';
import { configureLifecycleModule } from './lifecycle'; import { configureLifecycleModule } from './lifecycle';
import { configureMediaModule } from './media'; import { configureMediaModule } from './media';
import { configureNavigationModule } from './navigation'; import { configureNavigationModule } from './navigation';
import { configureNavigationPanelModule } from './navigation-panel';
import { configureNotificationModule } from './notification'; import { configureNotificationModule } from './notification';
import { configureOpenInApp } from './open-in-app'; import { configureOpenInApp } from './open-in-app';
import { configureOrganizeModule } from './organize'; import { configureOrganizeModule } from './organize';
@@ -82,7 +82,7 @@ export function configureCommonModules(framework: Framework) {
configureDocLinksModule(framework); configureDocLinksModule(framework);
configureOrganizeModule(framework); configureOrganizeModule(framework);
configureFavoriteModule(framework); configureFavoriteModule(framework);
configureExplorerModule(framework); configureNavigationPanelModule(framework);
configureThemeEditorModule(framework); configureThemeEditorModule(framework);
configureEditorModule(framework); configureEditorModule(framework);
configureSystemFontFamilyModule(framework); configureSystemFontFamilyModule(framework);

View File

@@ -15,7 +15,9 @@ const DEFAULT_COLLAPSABLE_STATE: Record<CollapsibleSectionName, boolean> = {
others: false, others: false,
}; };
export class ExplorerSection extends Entity<{ name: CollapsibleSectionName }> { export class NavigationPanelSection extends Entity<{
name: CollapsibleSectionName;
}> {
name: CollapsibleSectionName = this.props.name; name: CollapsibleSectionName = this.props.name;
key = `explorer.section.${this.name}`; key = `explorer.section.${this.name}`;
defaultValue = DEFAULT_COLLAPSABLE_STATE[this.name]; defaultValue = DEFAULT_COLLAPSABLE_STATE[this.name];

View File

@@ -0,0 +1,15 @@
import { type Framework } from '@toeverything/infra';
import { GlobalCache } from '../storage';
import { WorkspaceScope } from '../workspace';
import { NavigationPanelSection } from './entities/navigation-panel-section';
import { NavigationPanelService } from './services/navigation-panel';
export { NavigationPanelService } from './services/navigation-panel';
export type { CollapsibleSectionName } from './types';
export function configureNavigationPanelModule(framework: Framework) {
framework
.scope(WorkspaceScope)
.service(NavigationPanelService)
.entity(NavigationPanelSection, [GlobalCache]);
}

View File

@@ -1,6 +1,6 @@
import { Service } from '@toeverything/infra'; import { Service } from '@toeverything/infra';
import { ExplorerSection } from '../entities/explore-section'; import { NavigationPanelSection } from '../entities/navigation-panel-section';
import type { CollapsibleSectionName } from '../types'; import type { CollapsibleSectionName } from '../types';
const allSectionName: Array<CollapsibleSectionName> = [ const allSectionName: Array<CollapsibleSectionName> = [
@@ -14,12 +14,12 @@ const allSectionName: Array<CollapsibleSectionName> = [
'others', 'others',
]; ];
export class ExplorerService extends Service { export class NavigationPanelService extends Service {
readonly sections = allSectionName.reduce( readonly sections = allSectionName.reduce(
(prev, name) => (prev, name) =>
Object.assign(prev, { Object.assign(prev, {
[name]: this.framework.createEntity(ExplorerSection, { name }), [name]: this.framework.createEntity(NavigationPanelSection, { name }),
}), }),
{} as Record<CollapsibleSectionName, ExplorerSection> {} as Record<CollapsibleSectionName, NavigationPanelSection>
); );
} }

View File

@@ -27,28 +27,28 @@ export interface AffineDNDData extends DNDData {
entity?: AffineDNDEntity; entity?: AffineDNDEntity;
from?: from?:
| { | {
at: 'explorer:organize:folder-node'; at: 'navigation-panel:organize:folder-node';
nodeId: string; nodeId: string;
} }
| { | {
at: 'explorer:collection:list'; at: 'navigation-panel:collection:list';
} }
| { | {
at: 'explorer:doc:linked-docs'; at: 'navigation-panel:doc:linked-docs';
docId: string; docId: string;
} }
| { | {
at: 'explorer:collection:filtered-docs'; at: 'navigation-panel:collection:filtered-docs';
collectionId: string; collectionId: string;
} }
| { | {
at: 'explorer:favorite:list'; at: 'navigation-panel:favorite:list';
} }
| { | {
at: 'explorer:old-favorite:list'; at: 'navigation-panel:old-favorite:list';
} }
| { | {
at: 'explorer:migration-data:list'; at: 'navigation-panel:migration-data:list';
} }
| { | {
at: 'all-docs:list'; at: 'all-docs:list';
@@ -60,10 +60,10 @@ export interface AffineDNDData extends DNDData {
at: 'all-collections:list'; at: 'all-collections:list';
} }
| { | {
at: 'explorer:tags:list'; at: 'navigation-panel:tags:list';
} }
| { | {
at: 'explorer:tags:docs'; at: 'navigation-panel:tags:docs';
} }
| { | {
at: 'app-header:tabs'; at: 'app-header:tabs';
@@ -103,28 +103,28 @@ export interface AffineDNDData extends DNDData {
}; };
dropTarget: dropTarget:
| { | {
at: 'explorer:organize:root'; at: 'navigation-panel:organize:root';
} }
| { | {
at: 'explorer:favorite:root'; at: 'navigation-panel:favorite:root';
} }
| { | {
at: 'explorer:organize:folder'; at: 'navigation-panel:organize:folder';
} }
| { | {
at: 'explorer:favorite:root'; at: 'navigation-panel:favorite:root';
} }
| { | {
at: 'explorer:old-favorite:root'; at: 'navigation-panel:old-favorite:root';
} }
| { | {
at: 'explorer:doc'; at: 'navigation-panel:doc';
} }
| { | {
at: 'app-sidebar:trash'; at: 'app-sidebar:trash';
} }
| { | {
at: 'explorer:tag'; at: 'navigation-panel:tag';
} }
| { | {
at: 'app-header:tabs'; at: 'app-header:tabs';

View File

@@ -241,8 +241,8 @@ export class EditorUtils {
public static async clearAllCollections(page: Page) { public static async clearAllCollections(page: Page) {
while (true) { while (true) {
const collection = await page const collection = await page
.getByTestId('explorer-collections') .getByTestId('navigation-panel-collections')
.locator('[data-testid^="explorer-collection-"]') .locator('[data-testid^="navigation-panel-collection-"]')
.first(); .first();
if (!(await collection.isVisible())) { if (!(await collection.isVisible())) {
@@ -252,7 +252,7 @@ export class EditorUtils {
const collectionContent = await collection.locator('div').first(); const collectionContent = await collection.locator('div').first();
await collectionContent.hover(); await collectionContent.hover();
const more = await collectionContent.getByTestId( const more = await collectionContent.getByTestId(
'explorer-tree-node-operation-button' 'navigation-panel-tree-node-operation-button'
); );
await more.click(); await more.click();
await page.getByTestId('collection-delete-button').click(); await page.getByTestId('collection-delete-button').click();
@@ -263,8 +263,8 @@ export class EditorUtils {
public static async clearAllTags(page: Page) { public static async clearAllTags(page: Page) {
while (true) { while (true) {
const tag = await page const tag = await page
.getByTestId('explorer-tags') .getByTestId('navigation-panel-tags')
.locator('[data-testid^="explorer-tag-"]') .locator('[data-testid^="navigation-panel-tag-"]')
.first(); .first();
if (!(await tag.isVisible())) { if (!(await tag.isVisible())) {
@@ -274,7 +274,7 @@ export class EditorUtils {
const tagContent = await tag.locator('div').first(); const tagContent = await tag.locator('div').first();
await tagContent.hover(); await tagContent.hover();
const more = await tagContent.getByTestId( const more = await tagContent.getByTestId(
'explorer-tree-node-operation-button' 'navigation-panel-tree-node-operation-button'
); );
await more.click(); await more.click();
await page.getByTestId('tag-delete-button').click(); await page.getByTestId('tag-delete-button').click();
@@ -288,7 +288,9 @@ export class EditorUtils {
docContent: string docContent: string
) { ) {
// Create collection // Create collection
await page.getByTestId('explorer-bar-add-collection-button').click(); await page
.getByTestId('navigation-panel-bar-add-collection-button')
.click();
const input = await page.getByTestId('prompt-modal-input'); const input = await page.getByTestId('prompt-modal-input');
await input.focus(); await input.focus();
await input.pressSequentially(collectionName); await input.pressSequentially(collectionName);
@@ -318,9 +320,9 @@ export class EditorUtils {
docContent: string docContent: string
) { ) {
// Create tag // Create tag
const tags = await page.getByTestId('explorer-tags'); const tags = await page.getByTestId('navigation-panel-tags');
await tags.hover(); await tags.hover();
await tags.getByTestId('explorer-bar-add-tag-button').click(); await tags.getByTestId('navigation-panel-bar-add-tag-button').click();
const input = await page.getByTestId('rename-modal-input'); const input = await page.getByTestId('rename-modal-input');
await input.focus(); await input.focus();
await input.pressSequentially(tagName); await input.pressSequentially(tagName);

View File

@@ -110,7 +110,7 @@ test('can sync collections between different browser', async ({
page page
); );
await enableCloudWorkspace(page); await enableCloudWorkspace(page);
await page.getByTestId('explorer-bar-add-collection-button').click(); await page.getByTestId('navigation-panel-bar-add-collection-button').click();
const title = page.getByTestId('prompt-modal-input'); const title = page.getByTestId('prompt-modal-input');
await title.isVisible(); await title.isVisible();
await title.fill('test collection'); await title.fill('test collection');
@@ -122,7 +122,7 @@ test('can sync collections between different browser', async ({
const page2 = await context.newPage(); const page2 = await context.newPage();
await loginUser(page2, user); await loginUser(page2, user);
await page2.goto(page.url()); await page2.goto(page.url());
const collections = page2.getByTestId('explorer-collections'); const collections = page2.getByTestId('navigation-panel-collections');
await collections.getByTestId('category-divider-collapse-button').click(); await collections.getByTestId('category-divider-collapse-button').click();
await expect(collections.getByText('test collection')).toBeVisible(); await expect(collections.getByText('test collection')).toBeVisible();
} }

View File

@@ -109,21 +109,24 @@ test('should transform local favorites data', async ({ page }) => {
}, },
page page
); );
await page.getByTestId('explorer-bar-add-favorite-button').first().click(); await page
.getByTestId('navigation-panel-bar-add-favorite-button')
.first()
.click();
await clickPageModeButton(page); await clickPageModeButton(page);
await waitForEmptyEditor(page); await waitForEmptyEditor(page);
await getBlockSuiteEditorTitle(page).fill('this is a new fav page'); await getBlockSuiteEditorTitle(page).fill('this is a new fav page');
await expect( await expect(
page page
.getByTestId('explorer-favorites') .getByTestId('navigation-panel-favorites')
.locator('[draggable] >> text=this is a new fav page') .locator('[draggable] >> text=this is a new fav page')
).toBeVisible(); ).toBeVisible();
await enableCloudWorkspace(page); await enableCloudWorkspace(page);
await expect( await expect(
page page
.getByTestId('explorer-favorites') .getByTestId('navigation-panel-favorites')
.locator('[draggable] >> text=this is a new fav page') .locator('[draggable] >> text=this is a new fav page')
).toBeVisible(); ).toBeVisible();
}); });

View File

@@ -85,14 +85,14 @@ test('New a page and add to favourites, then open info modal from sidebar', asyn
await page.getByTestId('all-pages').click(); await page.getByTestId('all-pages').click();
const favoriteListItemInSidebar = page.locator( const favoriteListItemInSidebar = page.locator(
`[data-testid="explorer-favorites"] [data-testid="explorer-doc-${newPageId}"]` `[data-testid="navigation-panel-favorites"] [data-testid="navigation-panel-doc-${newPageId}"]`
); );
expect(await favoriteListItemInSidebar.textContent()).toBe( expect(await favoriteListItemInSidebar.textContent()).toBe(
'this is a new page' 'this is a new page'
); );
await favoriteListItemInSidebar.hover(); await favoriteListItemInSidebar.hover();
await favoriteListItemInSidebar await favoriteListItemInSidebar
.getByTestId('explorer-tree-node-operation-button') .getByTestId('navigation-panel-tree-node-operation-button')
.click(); .click();
const infoBtn = page.getByText('View Info'); const infoBtn = page.getByText('View Info');
await infoBtn.click(); await infoBtn.click();

View File

@@ -27,23 +27,27 @@ const dragToFavourites = async (
id: string, id: string,
type: 'doc' | 'collection' | 'tag' | 'folder' = 'doc' type: 'doc' | 'collection' | 'tag' | 'folder' = 'doc'
) => { ) => {
const favourites = page.getByTestId('explorer-favorite-category-divider'); const favourites = page.getByTestId(
'navigation-panel-favorite-category-divider'
);
await dragTo(page, dragItem, favourites); await dragTo(page, dragItem, favourites);
const item = page const item = page
.getByTestId(`explorer-favorites`) .getByTestId(`navigation-panel-favorites`)
.locator(`[data-testid="explorer-${type}-${id}"]`); .locator(`[data-testid="navigation-panel-${type}-${id}"]`);
await expect(item).toBeVisible(); await expect(item).toBeVisible();
return item; return item;
}; };
const createCollection = async (page: Page, name: string) => { const createCollection = async (page: Page, name: string) => {
await page.getByTestId('explorer-bar-add-collection-button').click(); await page.getByTestId('navigation-panel-bar-add-collection-button').click();
const input = page.getByTestId('prompt-modal-input'); const input = page.getByTestId('prompt-modal-input');
await expect(input).toBeVisible(); await expect(input).toBeVisible();
await input.fill(name); await input.fill(name);
await page.getByTestId('prompt-modal-confirm').click(); await page.getByTestId('prompt-modal-confirm').click();
const newCollectionId = getCurrentCollectionIdFromUrl(page); const newCollectionId = getCurrentCollectionIdFromUrl(page);
const collection = page.getByTestId(`explorer-collection-${newCollectionId}`); const collection = page.getByTestId(
`navigation-panel-collection-${newCollectionId}`
);
await expect(collection).toBeVisible(); await expect(collection).toBeVisible();
return collection; return collection;
}; };
@@ -57,7 +61,9 @@ const dragToCollection = async (page: Page, dragItem: Locator) => {
await clickSideBarAllPageButton(page); await clickSideBarAllPageButton(page);
await dragTo(page, dragItem, collection); await dragTo(page, dragItem, collection);
await page.waitForTimeout(500); await page.waitForTimeout(500);
const collectionPage = collection.locator('[data-testid^="explorer-doc-"]'); const collectionPage = collection.locator(
'[data-testid^="navigation-panel-doc-"]'
);
await expect(collectionPage).toBeVisible(); await expect(collectionPage).toBeVisible();
return collectionPage; return collectionPage;
}; };
@@ -182,39 +188,45 @@ test('items in favourites can be reordered by dragging', async ({ page }) => {
// assert the order of the items in favourites // assert the order of the items in favourites
await expect( await expect(
page.getByTestId('explorer-favorites').locator('[draggable]') page.getByTestId('navigation-panel-favorites').locator('[draggable]')
).toHaveCount(3); ).toHaveCount(3);
await expect( await expect(
page.getByTestId('explorer-favorites').locator('[draggable]').first() page
.getByTestId('navigation-panel-favorites')
.locator('[draggable]')
.first()
).toHaveText('test collection'); ).toHaveText('test collection');
await expect( await expect(
page.getByTestId('explorer-favorites').locator('[draggable]').last() page.getByTestId('navigation-panel-favorites').locator('[draggable]').last()
).toHaveText(title0); ).toHaveText(title0);
// drag the first item to the last // drag the first item to the last
const firstItem = page const firstItem = page
.getByTestId('explorer-favorites') .getByTestId('navigation-panel-favorites')
.locator('[draggable]') .locator('[draggable]')
.first(); .first();
const lastItem = page const lastItem = page
.getByTestId('explorer-favorites') .getByTestId('navigation-panel-favorites')
.locator('[draggable]') .locator('[draggable]')
.last(); .last();
await dragTo(page, firstItem, lastItem, 'bottom'); await dragTo(page, firstItem, lastItem, 'bottom');
// now check the order again // now check the order again
await expect( await expect(
page.getByTestId('explorer-favorites').locator('[draggable]') page.getByTestId('navigation-panel-favorites').locator('[draggable]')
).toHaveCount(3); ).toHaveCount(3);
await expect( await expect(
page.getByTestId('explorer-favorites').locator('[draggable]').first() page
.getByTestId('navigation-panel-favorites')
.locator('[draggable]')
.first()
).toHaveText(title1); ).toHaveText(title1);
await expect( await expect(
page.getByTestId('explorer-favorites').locator('[draggable]').last() page.getByTestId('navigation-panel-favorites').locator('[draggable]').last()
).toHaveText('test collection'); ).toHaveText('test collection');
}); });
@@ -304,8 +316,8 @@ test('drag a favourite page into note on page mode', async ({ page }) => {
await page.getByTestId('pin-button').click(); await page.getByTestId('pin-button').click();
const pageId = getCurrentDocIdFromUrl(page); const pageId = getCurrentDocIdFromUrl(page);
const item = page const item = page
.getByTestId(`explorer-favorites`) .getByTestId(`navigation-panel-favorites`)
.locator(`[data-testid="explorer-doc-${pageId}"]`); .locator(`[data-testid="navigation-panel-doc-${pageId}"]`);
await expect(item).toBeVisible(); await expect(item).toBeVisible();
// drag item into blocksuite editor // drag item into blocksuite editor
@@ -327,8 +339,8 @@ test('drag a favourite page into canvas on edgeless mode', async ({ page }) => {
await page.getByTestId('pin-button').click(); await page.getByTestId('pin-button').click();
const pageId = getCurrentDocIdFromUrl(page); const pageId = getCurrentDocIdFromUrl(page);
const item = page const item = page
.getByTestId(`explorer-favorites`) .getByTestId(`navigation-panel-favorites`)
.locator(`[data-testid="explorer-doc-${pageId}"]`); .locator(`[data-testid="navigation-panel-doc-${pageId}"]`);
await expect(item).toBeVisible(); await expect(item).toBeVisible();
await clickNewPageButton(page, 'edgeless page'); await clickNewPageButton(page, 'edgeless page');
@@ -373,8 +385,8 @@ test('drag a favourite page into note on edgeless mode', async ({ page }) => {
await page.getByTestId('pin-button').click(); await page.getByTestId('pin-button').click();
const pageId = getCurrentDocIdFromUrl(page); const pageId = getCurrentDocIdFromUrl(page);
const item = page const item = page
.getByTestId(`explorer-favorites`) .getByTestId(`navigation-panel-favorites`)
.locator(`[data-testid="explorer-doc-${pageId}"]`); .locator(`[data-testid="navigation-panel-doc-${pageId}"]`);
await expect(item).toBeVisible(); await expect(item).toBeVisible();
await clickNewPageButton(page, 'edgeless page'); await clickNewPageButton(page, 'edgeless page');

View File

@@ -67,26 +67,34 @@ const createAndPinCollection = async (
test('Show collections items in sidebar', async ({ page }) => { test('Show collections items in sidebar', async ({ page }) => {
await removeOnboardingPages(page); await removeOnboardingPages(page);
await createAndPinCollection(page); await createAndPinCollection(page);
const collections = page.getByTestId('explorer-collections'); const collections = page.getByTestId('navigation-panel-collections');
await collections.getByTestId('category-divider-collapse-button').click(); await collections.getByTestId('category-divider-collapse-button').click();
const items = collections.locator('[data-testid^="explorer-collection-"]'); const items = collections.locator(
'[data-testid^="navigation-panel-collection-"]'
);
await expect(items).toHaveCount(1); await expect(items).toHaveCount(1);
const first = items.first(); const first = items.first();
expect(await first.textContent()).toBe('test collection'); expect(await first.textContent()).toBe('test collection');
await first.getByTestId('explorer-collapsed-button').click(); await first.getByTestId('navigation-panel-collapsed-button').click();
const collectionPage = first.locator('[data-testid^="explorer-doc-"]').nth(0); const collectionPage = first
.locator('[data-testid^="navigation-panel-doc-"]')
.nth(0);
expect(await collectionPage.textContent()).toBe('test page'); expect(await collectionPage.textContent()).toBe('test page');
await collectionPage.hover(); await collectionPage.hover();
await collectionPage await collectionPage
.getByTestId('explorer-tree-node-operation-button') .getByTestId('navigation-panel-tree-node-operation-button')
.click(); .click();
const deletePage = page.getByText('Move to trash'); const deletePage = page.getByText('Move to trash');
await deletePage.click(); await deletePage.click();
await page.getByTestId('confirm-modal-confirm').click(); await page.getByTestId('confirm-modal-confirm').click();
expect(await first.locator('[data-testid^="explorer-doc-"]').count()).toBe(0); expect(
await first.locator('[data-testid^="navigation-panel-doc-"]').count()
).toBe(0);
// position is a workaround for the hover issue when empty collection status's height > 26px (will cause scroll) // position is a workaround for the hover issue when empty collection status's height > 26px (will cause scroll)
await first.hover({ position: { x: 10, y: 10 } }); await first.hover({ position: { x: 10, y: 10 } });
await first.getByTestId('explorer-tree-node-operation-button').click(); await first
.getByTestId('navigation-panel-tree-node-operation-button')
.click();
const deleteCollection = page.getByText('Delete'); const deleteCollection = page.getByText('Delete');
await deleteCollection.click(); await deleteCollection.click();
await page.waitForTimeout(50); await page.waitForTimeout(50);
@@ -109,13 +117,17 @@ test('Show collections items in sidebar', async ({ page }) => {
test('edit collection', async ({ page }) => { test('edit collection', async ({ page }) => {
await removeOnboardingPages(page); await removeOnboardingPages(page);
await createAndPinCollection(page); await createAndPinCollection(page);
const collections = page.getByTestId('explorer-collections'); const collections = page.getByTestId('navigation-panel-collections');
await collections.getByTestId('category-divider-collapse-button').click(); await collections.getByTestId('category-divider-collapse-button').click();
const items = collections.locator('[data-testid^="explorer-collection-"]'); const items = collections.locator(
'[data-testid^="navigation-panel-collection-"]'
);
expect(await items.count()).toBe(1); expect(await items.count()).toBe(1);
const first = items.first(); const first = items.first();
await first.hover(); await first.hover();
await first.getByTestId('explorer-tree-node-operation-button').click(); await first
.getByTestId('navigation-panel-tree-node-operation-button')
.click();
const editCollection = page.getByText('Rename'); const editCollection = page.getByText('Rename');
await editCollection.click(); await editCollection.click();
await page.getByTestId('rename-modal-input').fill('123'); await page.getByTestId('rename-modal-input').fill('123');
@@ -127,13 +139,17 @@ test('edit collection', async ({ page }) => {
test('edit collection and change filter date', async ({ page }) => { test('edit collection and change filter date', async ({ page }) => {
await removeOnboardingPages(page); await removeOnboardingPages(page);
await createAndPinCollection(page); await createAndPinCollection(page);
const collections = page.getByTestId('explorer-collections'); const collections = page.getByTestId('navigation-panel-collections');
await collections.getByTestId('category-divider-collapse-button').click(); await collections.getByTestId('category-divider-collapse-button').click();
const items = collections.locator('[data-testid^="explorer-collection-"]'); const items = collections.locator(
'[data-testid^="navigation-panel-collection-"]'
);
expect(await items.count()).toBe(1); expect(await items.count()).toBe(1);
const first = items.first(); const first = items.first();
await first.hover(); await first.hover();
await first.getByTestId('explorer-tree-node-operation-button').click(); await first
.getByTestId('navigation-panel-tree-node-operation-button')
.click();
const editCollection = page.getByText('Rename'); const editCollection = page.getByText('Rename');
await editCollection.click(); await editCollection.click();
await page.getByTestId('rename-modal-input').fill('123'); await page.getByTestId('rename-modal-input').fill('123');
@@ -151,21 +167,23 @@ test('add collection from sidebar', async ({ page }) => {
const cell = page.getByTestId('page-list-item-title').getByText('test page'); const cell = page.getByTestId('page-list-item-title').getByText('test page');
await expect(cell).toBeVisible(); await expect(cell).toBeVisible();
await page await page
.getByTestId('explorer-collections') .getByTestId('navigation-panel-collections')
.getByTestId('category-divider-collapse-button') .getByTestId('category-divider-collapse-button')
.click(); .click();
const nullCollection = page.getByTestId( const nullCollection = page.getByTestId(
'slider-bar-collection-empty-message' 'slider-bar-collection-empty-message'
); );
await expect(nullCollection).toBeVisible(); await expect(nullCollection).toBeVisible();
await page.getByTestId('explorer-bar-add-collection-button').click(); await page.getByTestId('navigation-panel-bar-add-collection-button').click();
const title = page.getByTestId('prompt-modal-input'); const title = page.getByTestId('prompt-modal-input');
await expect(title).toBeVisible(); await expect(title).toBeVisible();
await title.fill('test collection'); await title.fill('test collection');
await page.getByTestId('prompt-modal-confirm').click(); await page.getByTestId('prompt-modal-confirm').click();
await page.waitForTimeout(100); await page.waitForTimeout(100);
const collections = page.getByTestId('explorer-collections'); const collections = page.getByTestId('navigation-panel-collections');
const items = collections.locator('[data-testid^="explorer-collection-"]'); const items = collections.locator(
'[data-testid^="navigation-panel-collection-"]'
);
expect(await items.count()).toBe(1); expect(await items.count()).toBe(1);
await expect(nullCollection).not.toBeVisible(); await expect(nullCollection).not.toBeVisible();
}); });

View File

@@ -29,7 +29,7 @@ test('Show favorite items in sidebar', async ({ page, workspace }) => {
const favoriteBtn = page.getByTestId('editor-option-menu-favorite'); const favoriteBtn = page.getByTestId('editor-option-menu-favorite');
await favoriteBtn.click(); await favoriteBtn.click();
const favoriteListItemInSidebar = page.getByTestId( const favoriteListItemInSidebar = page.getByTestId(
'explorer-doc-' + newPageId 'navigation-panel-doc-' + newPageId
); );
expect(await favoriteListItemInSidebar.textContent()).toBe( expect(await favoriteListItemInSidebar.textContent()).toBe(
'this is a new page to favorite' 'this is a new page to favorite'
@@ -58,7 +58,7 @@ test('Show favorite reference in sidebar', async ({ page, workspace }) => {
const favoriteBtn = page.getByTestId('editor-option-menu-favorite'); const favoriteBtn = page.getByTestId('editor-option-menu-favorite');
await favoriteBtn.click(); await favoriteBtn.click();
const favItemTestId = 'explorer-doc-' + newPageId; const favItemTestId = 'navigation-panel-doc-' + newPageId;
const favoriteListItemInSidebar = page.getByTestId(favItemTestId); const favoriteListItemInSidebar = page.getByTestId(favItemTestId);
expect(await favoriteListItemInSidebar.textContent()).toBe( expect(await favoriteListItemInSidebar.textContent()).toBe(
@@ -66,14 +66,14 @@ test('Show favorite reference in sidebar', async ({ page, workspace }) => {
); );
const collapseButton = favoriteListItemInSidebar.getByTestId( const collapseButton = favoriteListItemInSidebar.getByTestId(
'explorer-collapsed-button' 'navigation-panel-collapsed-button'
); );
await expect(collapseButton).toBeVisible(); await expect(collapseButton).toBeVisible();
await collapseButton.click(); await collapseButton.click();
await expect( await expect(
favoriteListItemInSidebar.locator( favoriteListItemInSidebar.locator(
'[data-testid^="explorer-doc-"]:has-text("Another page")' '[data-testid^="navigation-panel-doc-"]:has-text("Another page")'
) )
).toBeVisible(); ).toBeVisible();
const currentWorkspace = await workspace.current(); const currentWorkspace = await workspace.current();
@@ -111,7 +111,7 @@ test("Deleted page's reference will not be shown in sidebar", async ({
const anotherPageId = getCurrentDocIdFromUrl(page); const anotherPageId = getCurrentDocIdFromUrl(page);
const favItemTestId = 'explorer-doc-' + newPageId; const favItemTestId = 'navigation-panel-doc-' + newPageId;
await expect(page.getByTestId(favItemTestId)).toHaveText( await expect(page.getByTestId(favItemTestId)).toHaveText(
'this is a new page to favorite' 'this is a new page to favorite'
@@ -119,10 +119,10 @@ test("Deleted page's reference will not be shown in sidebar", async ({
await page await page
.getByTestId(favItemTestId) .getByTestId(favItemTestId)
.getByTestId('explorer-collapsed-button') .getByTestId('navigation-panel-collapsed-button')
.click(); .click();
const favItemAnotherPageTestId = 'explorer-doc-' + anotherPageId; const favItemAnotherPageTestId = 'navigation-panel-doc-' + anotherPageId;
await expect( await expect(
page.getByTestId(favItemTestId).getByTestId(favItemAnotherPageTestId) page.getByTestId(favItemTestId).getByTestId(favItemAnotherPageTestId)
@@ -146,7 +146,10 @@ test('Add new favorite page via sidebar', async ({ page }) => {
await openHomePage(page); await openHomePage(page);
await waitForEditorLoad(page); await waitForEditorLoad(page);
await page.getByTestId('explorer-bar-add-favorite-button').first().click(); await page
.getByTestId('navigation-panel-bar-add-favorite-button')
.first()
.click();
await clickPageModeButton(page); await clickPageModeButton(page);
await waitForEmptyEditor(page); await waitForEmptyEditor(page);
@@ -154,7 +157,7 @@ test('Add new favorite page via sidebar', async ({ page }) => {
await getBlockSuiteEditorTitle(page).fill('this is a new fav page'); await getBlockSuiteEditorTitle(page).fill('this is a new fav page');
// check if the page title is shown in the favorite list // check if the page title is shown in the favorite list
const favItem = page const favItem = page
.getByTestId('explorer-favorites') .getByTestId('navigation-panel-favorites')
.locator('[draggable] >> text=this is a new fav page'); .locator('[draggable] >> text=this is a new fav page');
await expect(favItem).toBeVisible(); await expect(favItem).toBeVisible();
}); });

Some files were not shown because too many files have changed in this diff Show More