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 { AuthService } from '@affine/core/modules/cloud';
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 type { Workspace } from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n';
@@ -35,6 +27,14 @@ import { useLiveData, useService, useServices } from '@toeverything/infra';
import type { ReactElement } 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 { WorkspaceNavigator } from '../workspace-selector';
import {
@@ -180,11 +180,11 @@ export const RootAppSidebar = memo((): ReactElement => {
</MenuItem>
</SidebarContainer>
<SidebarScrollableContainer>
<ExplorerFavorites />
<ExplorerOrganize />
<ExplorerMigrationFavorites />
<ExplorerCollections />
<ExplorerTags />
<NavigationPanelFavorites />
<NavigationPanelOrganize />
<NavigationPanelMigrationFavorites />
<NavigationPanelCollections />
<NavigationPanelTags />
<CollapsibleSection
name="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 {
type CollapsibleSectionName,
NavigationPanelService,
} from '@affine/core/modules/navigation-panel';
import * as Collapsible from '@radix-ui/react-collapsible';
import { useLiveData, useService } from '@toeverything/infra';
import clsx from 'clsx';
@@ -10,8 +14,6 @@ import {
useCallback,
} from 'react';
import { ExplorerService } from '../../services/explorer';
import type { CollapsibleSectionName } from '../../types';
import { content, header, root } from './collapsible-section.css';
interface CollapsibleSectionProps extends PropsWithChildren {
@@ -46,7 +48,7 @@ export const CollapsibleSection = ({
contentClassName,
contentStyle,
}: CollapsibleSectionProps) => {
const section = useService(ExplorerService).sections[name];
const section = useService(NavigationPanelService).sections[name];
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';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { ExplorerTreeNode, type ExplorerTreeNodeDropEffect } from '../../tree';
import type { ExplorerTreeNodeIcon } from '../../tree/node';
import { ExplorerDocNode } from '../doc';
import type { GenericExplorerNode } from '../types';
import {
NavigationPanelTreeNode,
type NavigationPanelTreeNodeDropEffect,
} from '../../tree';
import type { NavigationPanelTreeNodeIcon } from '../../tree/node';
import { NavigationPanelDocNode } from '../doc';
import type { GenericNavigationPanelNode } from '../types';
import { Empty } from './empty';
import { useExplorerCollectionNodeOperations } from './operations';
import { useNavigationPanelCollectionNodeOperations } from './operations';
const CollectionIcon: ExplorerTreeNodeIcon = ({
const CollectionIcon: NavigationPanelTreeNodeIcon = ({
className,
draggedOver,
treeInstruction,
@@ -45,7 +48,7 @@ const CollectionIcon: ExplorerTreeNodeIcon = ({
/>
);
export const ExplorerCollectionNode = ({
export const NavigationPanelCollectionNode = ({
collectionId,
onDrop,
location,
@@ -55,7 +58,7 @@ export const ExplorerCollectionNode = ({
dropEffect,
}: {
collectionId: string;
} & GenericExplorerNode) => {
} & GenericNavigationPanelNode) => {
const t = useI18n();
const { globalContextService, workspaceDialogService } = useServices({
GlobalContextService,
@@ -79,7 +82,7 @@ export const ExplorerCollectionNode = ({
from: location,
},
dropTarget: {
at: 'explorer:doc',
at: 'navigation-panel:doc',
},
} satisfies AffineDNDData;
}, [collectionId, location]);
@@ -136,19 +139,20 @@ export const ExplorerCollectionNode = ({
[collection, onDrop, handleAddDocToCollection]
);
const handleDropEffectOnCollection = useCallback<ExplorerTreeNodeDropEffect>(
data => {
if (collection && data.treeInstruction?.type === 'make-child') {
if (data.source.data.entity?.type === 'doc') {
return 'link';
const handleDropEffectOnCollection =
useCallback<NavigationPanelTreeNodeDropEffect>(
data => {
if (collection && data.treeInstruction?.type === 'make-child') {
if (data.source.data.entity?.type === 'doc') {
return 'link';
}
} else {
return dropEffect?.(data);
}
} else {
return dropEffect?.(data);
}
return;
},
[collection, dropEffect]
);
return;
},
[collection, dropEffect]
);
const handleDropOnPlaceholder = useCallback(
(data: DropTargetDropEvent<AffineDNDData>) => {
@@ -176,7 +180,7 @@ export const ExplorerCollectionNode = ({
});
}, [collection, workspaceDialogService]);
const collectionOperations = useExplorerCollectionNodeOperations(
const collectionOperations = useNavigationPanelCollectionNodeOperations(
collectionId,
handleOpenCollapsed,
handleEditCollection
@@ -204,7 +208,7 @@ export const ExplorerCollectionNode = ({
}
return (
<ExplorerTreeNode
<NavigationPanelTreeNode
icon={CollectionIcon}
name={collection.name || t['Untitled']()}
dndData={dndData}
@@ -220,14 +224,14 @@ export const ExplorerCollectionNode = ({
childrenPlaceholder={<Empty onDrop={handleDropOnPlaceholder} />}
operations={finalOperations}
dropEffect={handleDropEffectOnCollection}
data-testid={`explorer-collection-${collectionId}`}
data-testid={`navigation-panel-collection-${collectionId}`}
>
<ExplorerCollectionNodeChildren collection={collection} />
</ExplorerTreeNode>
<NavigationPanelCollectionNodeChildren collection={collection} />
</NavigationPanelTreeNode>
);
};
const ExplorerCollectionNodeChildren = ({
const NavigationPanelCollectionNodeChildren = ({
collection,
}: {
collection: Collection;
@@ -294,12 +298,12 @@ const ExplorerCollectionNodeChildren = ({
});
return filtered.map(doc => (
<ExplorerDocNode
<NavigationPanelDocNode
key={doc.id}
docId={doc.id}
reorderable={false}
location={{
at: 'explorer:collection:filtered-docs',
at: 'navigation-panel:collection:filtered-docs',
collectionId: collection.id,
}}
operations={

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
import type { DropTargetDropEvent, DropTargetOptions } from '@affine/component';
import type { AffineDNDData } from '@affine/core/types/dnd';
import type { ExplorerTreeNodeDropEffect } from '../tree';
import type { NavigationPanelTreeNodeDropEffect } from '../tree';
import type { NodeOperation } from '../tree/types';
/**
* The interface for a generic explorer node.
* The interface for a generic navigation panel node.
*
* # 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.
*/
export interface GenericExplorerNode {
export interface GenericNavigationPanelNode {
/**
* 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.
*/
dropEffect?: ExplorerTreeNodeDropEffect;
dropEffect?: NavigationPanelTreeNodeDropEffect;
}

View File

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

View File

@@ -1,7 +1,7 @@
import { IconButton, usePromptModal } from '@affine/component';
import { createEmptyCollection } from '@affine/core/components/page-list/use-collection-manager';
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 { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
@@ -10,20 +10,21 @@ import { useLiveData, useServices } from '@toeverything/infra';
import { nanoid } from 'nanoid';
import { useCallback } from 'react';
import { ExplorerService } from '../../../services/explorer';
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 * as styles from './index.css';
export const ExplorerCollections = () => {
export const NavigationPanelCollections = () => {
const t = useI18n();
const { collectionService, workbenchService, explorerService } = useServices({
CollectionService,
WorkbenchService,
ExplorerService,
});
const explorerSection = explorerService.sections.collections;
const { collectionService, workbenchService, navigationPanelService } =
useServices({
CollectionService,
WorkbenchService,
NavigationPanelService,
});
const navigationPanelSection = navigationPanelService.sections.collections;
const collections = useLiveData(collectionService.collections$);
const { openPromptModal } = usePromptModal();
@@ -51,12 +52,12 @@ export const ExplorerCollections = () => {
type: 'collection',
});
workbenchService.workbench.openCollection(id);
explorerSection.setCollapsed(false);
navigationPanelSection.setCollapsed(false);
},
});
}, [
collectionService,
explorerSection,
navigationPanelSection,
openPromptModal,
t,
workbenchService.workbench,
@@ -65,11 +66,11 @@ export const ExplorerCollections = () => {
return (
<CollapsibleSection
name="collections"
testId="explorer-collections"
testId="navigation-panel-collections"
title={t['com.affine.rootAppSidebar.collections']()}
actions={
<IconButton
data-testid="explorer-bar-add-collection-button"
data-testid="navigation-panel-bar-add-collection-button"
onClick={handleCreateCollection}
size="16"
tooltip={t[
@@ -80,20 +81,20 @@ export const ExplorerCollections = () => {
</IconButton>
}
>
<ExplorerTreeRoot
<NavigationPanelTreeRoot
placeholder={<RootEmpty onClickCreate={handleCreateCollection} />}
>
{collections.map(collection => (
<ExplorerCollectionNode
<NavigationPanelCollectionNode
key={collection.id}
collectionId={collection.id}
reorderable={false}
location={{
at: 'explorer:collection:list',
at: 'navigation-panel:collection:list',
}}
/>
))}
</ExplorerTreeRoot>
</NavigationPanelTreeRoot>
</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 { FavoriteIcon } from '@blocksuite/icons/rc';
import { ExplorerEmptySection } from '../../layouts/empty-section';
import { NavigationPanelEmptySection } from '../../layouts/empty-section';
import { DropEffect } from '../../tree';
import { favoriteRootCanDrop, favoriteRootDropEffect } from './dnd';
@@ -26,7 +26,7 @@ const RootEmptyReady = ({ onDrop }: Omit<RootEmptyProps, 'isLoading'>) => {
useDropTarget<AffineDNDData>(
() => ({
data: {
at: 'explorer:favorite:root',
at: 'navigation-panel:favorite:root',
},
onDrop: onDrop,
canDrop: favoriteRootCanDrop,
@@ -36,7 +36,7 @@ const RootEmptyReady = ({ onDrop }: Omit<RootEmptyProps, 'isLoading'>) => {
);
return (
<ExplorerEmptySection
<NavigationPanelEmptySection
ref={dropTargetRef}
icon={FavoriteIcon}
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,
} from '@affine/component';
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 {
FavoriteService,
isFavoriteSupportType,
} from '@affine/core/modules/favorite';
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
import { WorkspaceService } from '@affine/core/modules/workspace';
import type { AffineDNDData } from '@affine/core/types/dnd';
import { inferOpenMode } from '@affine/core/utils';
@@ -22,12 +19,12 @@ import { PlusIcon } from '@blocksuite/icons/rc';
import { useLiveData, useServices } from '@toeverything/infra';
import { type MouseEventHandler, useCallback } from 'react';
import { ExplorerService } from '../../../services/explorer';
import { CollapsibleSection } from '../../layouts/collapsible-section';
import { ExplorerCollectionNode } from '../../nodes/collection';
import { ExplorerDocNode } from '../../nodes/doc';
import { ExplorerFolderNode } from '../../nodes/folder';
import { ExplorerTagNode } from '../../nodes/tag';
import { NavigationPanelCollectionNode } from '../../nodes/collection';
import { NavigationPanelDocNode } from '../../nodes/doc';
import { NavigationPanelFolderNode } from '../../nodes/folder';
import { NavigationPanelTagNode } from '../../nodes/tag';
import { DropEffect, NavigationPanelTreeRoot } from '../../tree';
import {
favoriteChildrenCanDrop,
favoriteChildrenDropEffect,
@@ -36,14 +33,15 @@ import {
} from './dnd';
import { RootEmpty } from './empty';
export const ExplorerFavorites = () => {
const { favoriteService, workspaceService, explorerService } = useServices({
FavoriteService,
WorkspaceService,
ExplorerService,
});
export const NavigationPanelFavorites = () => {
const { favoriteService, workspaceService, navigationPanelService } =
useServices({
FavoriteService,
WorkspaceService,
NavigationPanelService,
});
const explorerSection = explorerService.sections.favorites;
const navigationPanelSection = navigationPanelService.sections.favorites;
const favorites = useLiveData(favoriteService.favoriteList.sortedList$);
@@ -73,10 +71,10 @@ export const ExplorerFavorites = () => {
track.$.navigationPanel.favorites.drop({
type: data.source.data.entity.type,
});
explorerSection.setCollapsed(false);
navigationPanelSection.setCollapsed(false);
}
},
[explorerSection, favoriteService.favoriteList]
[navigationPanelSection, favoriteService.favoriteList]
);
const handleCreateNewFavoriteDoc: MouseEventHandler = useCallback(
@@ -87,9 +85,9 @@ export const ExplorerFavorites = () => {
newDoc.id,
favoriteService.favoriteList.indexAt('before')
);
explorerSection.setCollapsed(false);
navigationPanelSection.setCollapsed(false);
},
[createPage, explorerSection, favoriteService.favoriteList]
[createPage, navigationPanelSection, favoriteService.favoriteList]
);
const handleOnChildrenDrop = useCallback(
@@ -102,7 +100,7 @@ export const ExplorerFavorites = () => {
data.treeInstruction?.type === 'reorder-below'
) {
if (
data.source.data.from?.at === 'explorer:favorite:list' &&
data.source.data.from?.at === 'navigation-panel:favorite:list' &&
data.source.data.entity?.type &&
isFavoriteSupportType(data.source.data.entity.type)
) {
@@ -153,7 +151,7 @@ export const ExplorerFavorites = () => {
useDropTarget<AffineDNDData>(
() => ({
data: {
at: 'explorer:favorite:root',
at: 'navigation-panel:favorite:root',
},
onDrop: handleDrop,
canDrop: favoriteRootCanDrop,
@@ -167,12 +165,12 @@ export const ExplorerFavorites = () => {
name="favorites"
title={t['com.affine.rootAppSidebar.favorites']()}
headerRef={dropTargetRef}
testId="explorer-favorites"
headerTestId="explorer-favorite-category-divider"
testId="navigation-panel-favorites"
headerTestId="navigation-panel-favorite-category-divider"
actions={
<>
<IconButton
data-testid="explorer-bar-add-favorite-button"
data-testid="navigation-panel-bar-add-favorite-button"
data-event-props="$.navigationPanel.favorites.createDoc"
data-event-args-control="addFavorite"
onClick={handleCreateNewFavoriteDoc}
@@ -196,25 +194,25 @@ export const ExplorerFavorites = () => {
</>
}
>
<ExplorerTreeRoot
<NavigationPanelTreeRoot
placeholder={<RootEmpty onDrop={handleDrop} isLoading={isLoading} />}
>
{favorites.map(favorite => (
<ExplorerFavoriteNode
<NavigationPanelFavoriteNode
key={favorite.id}
favorite={favorite}
onDrop={handleOnChildrenDrop}
/>
))}
</ExplorerTreeRoot>
</NavigationPanelTreeRoot>
</CollapsibleSection>
);
};
const childLocation = {
at: 'explorer:favorite:list' as const,
at: 'navigation-panel:favorite:list' as const,
};
const ExplorerFavoriteNode = ({
const NavigationPanelFavoriteNode = ({
favorite,
onDrop,
}: {
@@ -237,7 +235,7 @@ const ExplorerFavoriteNode = ({
[favorite, onDrop]
);
return favorite.type === 'doc' ? (
<ExplorerDocNode
<NavigationPanelDocNode
key={favorite.id}
docId={favorite.id}
location={childLocation}
@@ -246,7 +244,7 @@ const ExplorerFavoriteNode = ({
canDrop={favoriteChildrenCanDrop}
/>
) : favorite.type === 'tag' ? (
<ExplorerTagNode
<NavigationPanelTagNode
key={favorite.id}
tagId={favorite.id}
location={childLocation}
@@ -255,7 +253,7 @@ const ExplorerFavoriteNode = ({
canDrop={favoriteChildrenCanDrop}
/>
) : favorite.type === 'folder' ? (
<ExplorerFolderNode
<NavigationPanelFolderNode
key={favorite.id}
nodeId={favorite.id}
location={childLocation}
@@ -264,7 +262,7 @@ const ExplorerFavoriteNode = ({
canDrop={favoriteChildrenCanDrop}
/>
) : (
<ExplorerCollectionNode
<NavigationPanelCollectionNode
key={favorite.id}
collectionId={favorite.id}
location={childLocation}

View File

@@ -1,6 +1,5 @@
import { IconButton, useConfirmModal } from '@affine/component';
import { DocsService } from '@affine/core/modules/doc';
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
import { MigrationFavoriteItemsAdapter } from '@affine/core/modules/favorite';
import { Trans, useI18n } from '@affine/i18n';
import { track } from '@affine/track';
@@ -9,11 +8,12 @@ import { useLiveData, useServices } from '@toeverything/infra';
import { useCallback } from 'react';
import { CollapsibleSection } from '../../layouts/collapsible-section';
import { ExplorerCollectionNode } from '../../nodes/collection';
import { ExplorerDocNode } from '../../nodes/doc';
import { NavigationPanelCollectionNode } from '../../nodes/collection';
import { NavigationPanelDocNode } from '../../nodes/doc';
import { NavigationPanelTreeRoot } from '../../tree';
import * as styles from './styles.css';
export const ExplorerMigrationFavorites = () => {
export const NavigationPanelMigrationFavorites = () => {
const t = useI18n();
const { migrationFavoriteItemsAdapter, docsService } = useServices({
@@ -105,14 +105,14 @@ export const ExplorerMigrationFavorites = () => {
actions={
<>
<IconButton
data-testid="explorer-bar-favorite-migration-clear-button"
data-testid="navigation-panel-bar-favorite-migration-clear-button"
onClick={handleClickClear}
size="16"
>
<BroomIcon />
</IconButton>
<IconButton
data-testid="explorer-bar-favorite-migration-help-button"
data-testid="navigation-panel-bar-favorite-migration-help-button"
size="16"
onClick={handleClickHelp}
>
@@ -121,22 +121,22 @@ export const ExplorerMigrationFavorites = () => {
</>
}
>
<ExplorerTreeRoot>
<NavigationPanelTreeRoot>
{favorites.map((favorite, i) => (
<ExplorerMigrationFavoriteNode
<NavigationPanelMigrationFavoriteNode
key={favorite.id + ':' + i}
favorite={favorite}
/>
))}
</ExplorerTreeRoot>
</NavigationPanelTreeRoot>
</CollapsibleSection>
);
};
const childLocation = {
at: 'explorer:migration-data:list' as const,
at: 'navigation-panel:migration-data:list' as const,
};
const ExplorerMigrationFavoriteNode = ({
const NavigationPanelMigrationFavoriteNode = ({
favorite,
}: {
favorite: {
@@ -145,7 +145,7 @@ const ExplorerMigrationFavoriteNode = ({
};
}) => {
return favorite.type === 'doc' ? (
<ExplorerDocNode
<NavigationPanelDocNode
key={favorite.id}
docId={favorite.id}
location={childLocation}
@@ -153,7 +153,7 @@ const ExplorerMigrationFavoriteNode = ({
canDrop={false}
/>
) : (
<ExplorerCollectionNode
<NavigationPanelCollectionNode
key={favorite.id}
collectionId={favorite.id}
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 { useI18n } from '@affine/i18n';
import { ExplorerEmptySection } from '../../layouts/empty-section';
import { NavigationPanelEmptySection } from '../../layouts/empty-section';
import { DropEffect } from '../../tree';
import { organizeEmptyDropEffect, organizeEmptyRootCanDrop } from './dnd';
@@ -30,7 +30,7 @@ export const RootEmptyReady = ({
const { dropTargetRef, draggedOverDraggable, draggedOverPosition } =
useDropTarget<AffineDNDData>(
() => ({
data: { at: 'explorer:organize:root' },
data: { at: 'navigation-panel:organize:root' },
onDrop,
canDrop: organizeEmptyRootCanDrop,
}),
@@ -38,7 +38,7 @@ export const RootEmptyReady = ({
);
return (
<ExplorerEmptySection
<NavigationPanelEmptySection
ref={dropTargetRef}
icon={<AnimatedFolderIcon open={!!draggedOverDraggable} />}
message={t['com.affine.rootAppSidebar.organize.empty']()}
@@ -57,7 +57,7 @@ export const RootEmptyReady = ({
})}
/>
)}
</ExplorerEmptySection>
</NavigationPanelEmptySection>
);
};

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
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 { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
@@ -7,20 +7,20 @@ import { AddTagIcon } from '@blocksuite/icons/rc';
import { useLiveData, useServices } from '@toeverything/infra';
import { useCallback, useEffect, useState } from 'react';
import { ExplorerService } from '../../../services/explorer';
import { CollapsibleSection } from '../../layouts/collapsible-section';
import { ExplorerTagNode } from '../../nodes/tag';
import { ExplorerTreeNodeRenameModal as CreateTagModal } from '../../tree/node';
import { NavigationPanelTagNode } from '../../nodes/tag';
import { NavigationPanelTreeRoot } from '../../tree';
import { NavigationPanelTreeNodeRenameModal as CreateTagModal } from '../../tree/node';
import { RootEmpty } from './empty';
import * as styles from './styles.css';
export const ExplorerTags = () => {
const { tagService, explorerService } = useServices({
export const NavigationPanelTags = () => {
const { tagService, navigationPanelService } = useServices({
TagService,
ExplorerService,
NavigationPanelService,
});
const explorerSection = explorerService.sections.tags;
const collapsed = useLiveData(explorerSection.collapsed$);
const navigationPanelSection = navigationPanelService.sections.tags;
const collapsed = useLiveData(navigationPanelSection.collapsed$);
const [creating, setCreating] = useState(false);
const tags = useLiveData(tagService.tagList.tags$);
@@ -30,9 +30,9 @@ export const ExplorerTags = () => {
(name: string) => {
tagService.tagList.createTag(name, tagService.randomTagColor());
track.$.navigationPanel.organize.createOrganizeItem({ type: 'tag' });
explorerSection.setCollapsed(false);
navigationPanelSection.setCollapsed(false);
},
[explorerSection, tagService]
[navigationPanelSection, tagService]
);
useEffect(() => {
@@ -46,13 +46,13 @@ export const ExplorerTags = () => {
return (
<CollapsibleSection
name="tags"
testId="explorer-tags"
testId="navigation-panel-tags"
headerClassName={styles.draggedOverHighlight}
title={t['com.affine.rootAppSidebar.tags']()}
actions={
<div className={styles.iconContainer}>
<IconButton
data-testid="explorer-bar-add-tag-button"
data-testid="navigation-panel-bar-add-tag-button"
onClick={handleOpenCreateModal}
size="16"
tooltip={t[
@@ -72,18 +72,18 @@ export const ExplorerTags = () => {
</div>
}
>
<ExplorerTreeRoot placeholder={<RootEmpty />}>
<NavigationPanelTreeRoot placeholder={<RootEmpty />}>
{tags.map(tag => (
<ExplorerTagNode
<NavigationPanelTagNode
key={tag.id}
tagId={tag.id}
reorderable={false}
location={{
at: 'explorer:tags:list',
at: 'navigation-panel:tags:list',
}}
/>
))}
</ExplorerTreeRoot>
</NavigationPanelTreeRoot>
</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,
} from 'react';
import { ExplorerTreeContext } from './context';
import { NavigationPanelTreeContext } from './context';
import { DropEffect } from './drop-effect';
import * as styles from './node.css';
import type { NodeOperation } from './types';
export type ExplorerTreeNodeDropEffectData = {
export type NavigationPanelTreeNodeDropEffectData = {
source: { data: AffineDNDData['draggable'] };
treeInstruction: DropTargetTreeInstruction | null;
};
export type ExplorerTreeNodeDropEffect = (
data: ExplorerTreeNodeDropEffectData
export type NavigationPanelTreeNodeDropEffect = (
data: NavigationPanelTreeNodeDropEffectData
) => 'copy' | 'move' | 'link' | undefined;
export type ExplorerTreeNodeIcon = React.ComponentType<{
export type NavigationPanelTreeNodeIcon = React.ComponentType<{
className?: string;
draggedOver?: boolean;
treeInstruction?: DropTargetTreeInstruction | null;
collapsed?: boolean;
}>;
export interface BaseExplorerTreeNodeProps {
export interface BaseNavigationPanelTreeNodeProps {
name?: string;
icon?: ExplorerTreeNodeIcon;
icon?: NavigationPanelTreeNodeIcon;
children?: React.ReactNode;
active?: boolean;
extractEmojiAsIcon?: boolean;
@@ -81,7 +81,8 @@ export interface BaseExplorerTreeNodeProps {
[key: `data-${string}`]: any;
}
interface WebExplorerTreeNodeProps extends BaseExplorerTreeNodeProps {
interface WebNavigationPanelTreeNodeProps
extends BaseNavigationPanelTreeNodeProps {
renameable?: boolean;
onRename?: (newName: string) => void;
renameableGuard?: { docId: string; action: DocPermissionActions };
@@ -91,14 +92,14 @@ interface WebExplorerTreeNodeProps extends BaseExplorerTreeNodeProps {
reorderable?: boolean;
dndData?: AffineDNDData;
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.
*/
export const ExplorerTreeNodeRenameModal = ({
export const NavigationPanelTreeNodeRenameModal = ({
setRenaming,
handleRename,
rawName,
@@ -124,7 +125,7 @@ export const ExplorerTreeNodeRenameModal = ({
);
};
export const ExplorerTreeNode = ({
export const NavigationPanelTreeNode = ({
children,
icon: Icon,
name: rawName,
@@ -150,10 +151,10 @@ export const ExplorerTreeNode = ({
onDrop,
dropEffect,
...otherProps
}: WebExplorerTreeNodeProps) => {
}: WebNavigationPanelTreeNodeProps) => {
const t = useI18n();
const cid = useId();
const context = useContext(ExplorerTreeContext);
const context = useContext(NavigationPanelTreeContext);
const level = context?.level ?? 0;
// If no onClick or to is provided, clicking on the node will toggle the collapse state
const clickForCollapse = !onClick && !to && !disabled;
@@ -293,7 +294,7 @@ export const ExplorerTreeNode = ({
>
{can => (
<MenuItem
key={'explorer-tree-rename'}
key={'navigation-panel-tree-rename'}
type={'default'}
prefixIcon={<EditIcon />}
onClick={() => setRenaming(true)}
@@ -305,7 +306,7 @@ export const ExplorerTreeNode = ({
</Guard>
) : (
<MenuItem
key={'explorer-tree-rename'}
key={'navigation-panel-tree-rename'}
type={'default'}
prefixIcon={<EditIcon />}
onClick={() => setRenaming(true)}
@@ -381,7 +382,7 @@ export const ExplorerTreeNode = ({
<div
data-disabled={disabled}
onClick={handleCollapsedChange}
data-testid="explorer-collapsed-button"
data-testid="navigation-panel-collapsed-button"
className={styles.collapsedIconContainer}
>
<ArrowDownSmallIcon
@@ -421,7 +422,7 @@ export const ExplorerTreeNode = ({
>
<IconButton
size="16"
data-testid="explorer-tree-node-operation-button"
data-testid="navigation-panel-tree-node-operation-button"
style={{ marginLeft: 4 }}
>
<MoreHorizontalIcon />
@@ -432,7 +433,7 @@ export const ExplorerTreeNode = ({
</div>
{renameable && renaming && (
<ExplorerTreeNodeRenameModal
<NavigationPanelTreeNodeRenameModal
setRenaming={setRenaming}
handleRename={handleRename}
rawName={rawName}
@@ -497,9 +498,9 @@ export const ExplorerTreeNode = ({
<div className={styles.collapseContentPlaceholder}>
{childCount === 0 && !collapsed ? childrenPlaceholder : null}
</div>
<ExplorerTreeContext.Provider value={contextValue}>
<NavigationPanelTreeContext.Provider value={contextValue}>
{collapsed ? null : children}
</ExplorerTreeContext.Provider>
</NavigationPanelTreeContext.Provider>
</Collapsible.Content>
</Collapsible.Root>
);

View File

@@ -1,10 +1,10 @@
import { useMemo, useState } from 'react';
import { ExplorerTreeContext } from './context';
import { NavigationPanelTreeContext } from './context';
import * as styles from './root.css';
import type { NodeOperation } from './types';
export const ExplorerTreeRoot = ({
export const NavigationPanelTreeRoot = ({
children,
childrenOperations = [],
placeholder,
@@ -33,9 +33,9 @@ export const ExplorerTreeRoot = ({
<div className={styles.placeholder}>
{childCount === 0 && placeholder}
</div>
<ExplorerTreeContext.Provider value={contextValue}>
<NavigationPanelTreeContext.Provider value={contextValue}>
{children}
</ExplorerTreeContext.Provider>
</NavigationPanelTreeContext.Provider>
</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 { assignInlineVars } from '@vanilla-extract/dynamic';
import clsx from 'clsx';
@@ -21,7 +21,7 @@ export const AddItemPlaceholder = ({
className,
...attrs
}: AddItemPlaceholderProps) => {
const context = useContext(ExplorerTreeContext);
const context = useContext(NavigationPanelTreeContext);
const level = context?.level ?? 0;
return (

View File

@@ -1,7 +1,7 @@
import {
type CollapsibleSectionName,
ExplorerService,
} from '@affine/core/modules/explorer';
NavigationPanelService,
} from '@affine/core/modules/navigation-panel';
import { ToggleRightIcon } from '@blocksuite/icons/rc';
import * as Collapsible from '@radix-ui/react-collapsible';
import { useLiveData, useService } from '@toeverything/infra';
@@ -86,7 +86,7 @@ export const CollapsibleSection = ({
children,
...attrs
}: CollapsibleSectionProps) => {
const section = useService(ExplorerService).sections[name];
const section = useService(NavigationPanelService).sections[name];
const collapsed = useLiveData(section.collapsed$);
const setCollapsed = useCallback(

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
import { Loading } from '@affine/component';
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 { DocsService } from '@affine/core/modules/doc';
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
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 { GlobalContextService } from '@affine/core/modules/global-context';
import { useI18n } from '@affine/i18n';
@@ -18,14 +18,14 @@ import {
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
import { ExplorerTreeNode } from '../../tree/node';
import { NavigationPanelTreeNode } from '../../tree/node';
import {
useExplorerDocNodeOperations,
useExplorerDocNodeOperationsMenu,
useNavigationPanelDocNodeOperations,
useNavigationPanelDocNodeOperationsMenu,
} from './operations';
import * as styles from './styles.css';
export const ExplorerDocNode = ({
export const NavigationPanelDocNode = ({
docId,
isLinked,
operations: additionalOperations,
@@ -105,8 +105,11 @@ export const ExplorerDocNode = ({
}),
[docId, workspaceDialogService]
);
const operations = useExplorerDocNodeOperationsMenu(docId, option);
const { handleAddLinkedPage } = useExplorerDocNodeOperations(docId, option);
const operations = useNavigationPanelDocNodeOperationsMenu(docId, option);
const { handleAddLinkedPage } = useNavigationPanelDocNodeOperations(
docId,
option
);
const finalOperations = useMemo(() => {
if (additionalOperations) {
@@ -120,7 +123,7 @@ export const ExplorerDocNode = ({
}
return (
<ExplorerTreeNode
<NavigationPanelTreeNode
icon={Icon}
name={t.t(docTitle)}
extractEmojiAsIcon={enableEmojiIcon}
@@ -137,13 +140,13 @@ export const ExplorerDocNode = ({
)
}
operations={finalOperations}
data-testid={`explorer-doc-${docId}`}
data-testid={`navigation-panel-doc-${docId}`}
>
<Guard docId={docId} permission="Doc_Read">
{canRead =>
canRead
? children?.map((child, index) => (
<ExplorerDocNode
<NavigationPanelDocNode
key={`${child.docId}-${index}`}
docId={child.docId}
isLinked
@@ -162,6 +165,6 @@ export const ExplorerDocNode = ({
) : null
}
</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 { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
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 type { NodeOperation } from '@affine/core/modules/explorer';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { WorkspaceService } from '@affine/core/modules/workspace';
@@ -31,7 +31,7 @@ import { useCallback, useMemo } from 'react';
import { DocFrameScope, DocInfoSheet } from '../../../doc-info';
import { DocRenameSubMenu } from './dialog';
export const useExplorerDocNodeOperations = (
export const useNavigationPanelDocNodeOperations = (
docId: string,
options: {
openNodeCollapsed: () => void;
@@ -160,7 +160,7 @@ export const useExplorerDocNodeOperations = (
);
};
export const useExplorerDocNodeOperationsMenu = (
export const useNavigationPanelDocNodeOperationsMenu = (
docId: string,
options: {
openInfoModal: () => void;
@@ -176,7 +176,7 @@ export const useExplorerDocNodeOperationsMenu = (
handleOpenInNewTab,
handleMoveToTrash,
handleRename,
} = useExplorerDocNodeOperations(docId, options);
} = useNavigationPanelDocNodeOperations(docId, options);
const docService = useService(DocsService);
const docRecord = useLiveData(docService.list.doc$(docId));

View File

@@ -7,11 +7,11 @@ import {
notify,
} from '@affine/component';
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import type {
ExplorerTreeNodeIcon,
NavigationPanelTreeNodeIcon,
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 { FeatureFlagService } from '@affine/core/modules/feature-flag';
import {
@@ -36,14 +36,14 @@ import { difference } from 'lodash-es';
import { useCallback, useMemo, useState } from 'react';
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
import { ExplorerTreeNode } from '../../tree/node';
import { ExplorerCollectionNode } from '../collection';
import { ExplorerDocNode } from '../doc';
import { ExplorerTagNode } from '../tag';
import { NavigationPanelTreeNode } from '../../tree/node';
import { NavigationPanelCollectionNode } from '../collection';
import { NavigationPanelDocNode } from '../doc';
import { NavigationPanelTagNode } from '../tag';
import { FolderCreateTip, FolderRenameSubMenu } from './dialog';
import { FavoriteFolderOperation } from './operations';
export const ExplorerFolderNode = ({
export const NavigationPanelFolderNode = ({
nodeId,
operations,
}: {
@@ -75,27 +75,34 @@ export const ExplorerFolderNode = ({
if (type === 'folder') {
return (
<ExplorerFolderNodeFolder node={node} operations={additionalOperations} />
<NavigationPanelFolderNodeFolder
node={node}
operations={additionalOperations}
/>
);
}
if (!data) return null;
if (type === 'doc') {
return <ExplorerDocNode docId={data} operations={additionalOperations} />;
return (
<NavigationPanelDocNode docId={data} operations={additionalOperations} />
);
} else if (type === 'collection') {
return (
<ExplorerCollectionNode
<NavigationPanelCollectionNode
collectionId={data}
operations={additionalOperations}
/>
);
} else if (type === 'tag') {
return <ExplorerTagNode tagId={data} operations={additionalOperations} />;
return (
<NavigationPanelTagNode tagId={data} operations={additionalOperations} />
);
}
return;
};
const ExplorerFolderIcon: ExplorerTreeNodeIcon = ({
const NavigationPanelFolderIcon: NavigationPanelTreeNodeIcon = ({
collapsed,
className,
draggedOver,
@@ -109,7 +116,7 @@ const ExplorerFolderIcon: ExplorerTreeNodeIcon = ({
/>
);
const ExplorerFolderNodeFolder = ({
const NavigationPanelFolderNodeFolder = ({
node,
operations: additionalOperations,
}: {
@@ -390,19 +397,19 @@ const ExplorerFolderNodeFolder = ({
}, []);
return (
<ExplorerTreeNode
icon={ExplorerFolderIcon}
<NavigationPanelTreeNode
icon={NavigationPanelFolderIcon}
name={name}
extractEmojiAsIcon={enableEmojiIcon}
collapsed={collapsed}
setCollapsed={handleCollapsedChange}
operations={finalOperations}
data-testid={`explorer-folder-${node.id}`}
data-testid={`navigation-panel-folder-${node.id}`}
aria-label={name}
data-role="explorer-folder"
data-role="navigation-panel-folder"
>
{children.map(child => (
<ExplorerFolderNode
<NavigationPanelFolderNode
key={child.id}
nodeId={child.id as string}
operations={childrenOperations}
@@ -413,6 +420,6 @@ const ExplorerFolderNodeFolder = ({
onClick={handleNewDoc}
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 type { Tag } 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 { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
import { ExplorerTreeNode } from '../../tree/node';
import { ExplorerDocNode } from '../doc';
import { NavigationPanelTreeNode } from '../../tree/node';
import { NavigationPanelDocNode } from '../doc';
import {
useExplorerTagNodeOperations,
useExplorerTagNodeOperationsMenu,
useNavigationPanelTagNodeOperations,
useNavigationPanelTagNodeOperationsMenu,
} from './operations';
import * as styles from './styles.css';
export const ExplorerTagNode = ({
export const NavigationPanelTagNode = ({
tagId,
operations: additionalOperations,
}: {
@@ -41,7 +41,7 @@ export const ExplorerTagNode = ({
return (
<div className={clsx(styles.tagIconContainer, className)}>
<div
data-testid="explorer-tag-icon-dot"
data-testid="navigation-panel-tag-icon-dot"
className={styles.tagIcon}
style={{
backgroundColor: tagColor,
@@ -59,8 +59,8 @@ export const ExplorerTagNode = ({
}),
[]
);
const operations = useExplorerTagNodeOperationsMenu(tagId, option);
const { handleNewDoc } = useExplorerTagNodeOperations(tagId, option);
const operations = useNavigationPanelTagNodeOperationsMenu(tagId, option);
const { handleNewDoc } = useNavigationPanelTagNodeOperations(tagId, option);
const finalOperations = useMemo(() => {
if (additionalOperations) {
@@ -74,7 +74,7 @@ export const ExplorerTagNode = ({
}
return (
<ExplorerTreeNode
<NavigationPanelTreeNode
icon={Icon}
name={tagName || t['Untitled']()}
collapsed={collapsed}
@@ -82,12 +82,12 @@ export const ExplorerTagNode = ({
to={`/tag/${tagId}`}
active={active}
operations={finalOperations}
data-testid={`explorer-tag-${tagId}`}
data-testid={`navigation-panel-tag-${tagId}`}
aria-label={tagName}
data-role="explorer-tag"
data-role="navigation-panel-tag"
>
<ExplorerTagNodeDocs tag={tagRecord} onNewDoc={handleNewDoc} />
</ExplorerTreeNode>
<NavigationPanelTagNodeDocs tag={tagRecord} onNewDoc={handleNewDoc} />
</NavigationPanelTreeNode>
);
};
@@ -96,7 +96,7 @@ export const ExplorerTagNode = ({
* so we split the tag node children into a separate component,
* so it won't be rendered when the tag node is collapsed.
*/
export const ExplorerTagNodeDocs = ({
export const NavigationPanelTagNodeDocs = ({
tag,
onNewDoc,
}: {
@@ -109,7 +109,7 @@ export const ExplorerTagNodeDocs = ({
return (
<>
{tagDocIds.map(docId => (
<ExplorerDocNode key={docId} docId={docId} />
<NavigationPanelDocNode key={docId} docId={docId} />
))}
<AddItemPlaceholder label={t['New Page']()} onClick={onNewDoc} />
</>

View File

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

View File

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

View File

@@ -1,10 +1,8 @@
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
import {
ExplorerService,
ExplorerTreeRoot,
} from '@affine/core/modules/explorer';
import { NavigationPanelTreeRoot } from '@affine/core/desktop/components/navigation-panel';
import type { FavoriteSupportTypeUnion } 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 { useI18n } from '@affine/i18n';
import { useLiveData, useServices } from '@toeverything/infra';
@@ -12,20 +10,21 @@ import { useCallback } from 'react';
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
import { CollapsibleSection } from '../../layouts/collapsible-section';
import { ExplorerCollectionNode } from '../../nodes/collection';
import { ExplorerDocNode } from '../../nodes/doc';
import { ExplorerFolderNode } from '../../nodes/folder';
import { ExplorerTagNode } from '../../nodes/tag';
import { NavigationPanelCollectionNode } from '../../nodes/collection';
import { NavigationPanelDocNode } from '../../nodes/doc';
import { NavigationPanelFolderNode } from '../../nodes/folder';
import { NavigationPanelTagNode } from '../../nodes/tag';
export const ExplorerFavorites = () => {
const { favoriteService, workspaceService, explorerService } = useServices({
FavoriteService,
WorkspaceService,
ExplorerService,
});
export const NavigationPanelFavorites = () => {
const { favoriteService, workspaceService, navigationPanelService } =
useServices({
FavoriteService,
WorkspaceService,
NavigationPanelService,
});
const t = useI18n();
const explorerSection = explorerService.sections.favorites;
const navigationPanelSection = navigationPanelService.sections.favorites;
const favorites = useLiveData(favoriteService.favoriteList.sortedList$);
const isLoading = useLiveData(favoriteService.favoriteList.isLoading$);
const { createPage } = usePageHelper(
@@ -39,28 +38,28 @@ export const ExplorerFavorites = () => {
newDoc.id,
favoriteService.favoriteList.indexAt('before')
);
explorerSection.setCollapsed(false);
}, [createPage, explorerSection, favoriteService.favoriteList]);
navigationPanelSection.setCollapsed(false);
}, [createPage, navigationPanelSection, favoriteService.favoriteList]);
return (
<CollapsibleSection
name="favorites"
title={t['com.affine.rootAppSidebar.favorites']()}
testId="explorer-favorites"
headerTestId="explorer-favorite-category-divider"
testId="navigation-panel-favorites"
headerTestId="navigation-panel-favorite-category-divider"
>
<ExplorerTreeRoot placeholder={isLoading ? 'Loading' : null}>
<NavigationPanelTreeRoot placeholder={isLoading ? 'Loading' : null}>
{favorites.map(favorite => (
<FavoriteNode key={favorite.id} favorite={favorite} />
))}
<AddItemPlaceholder
data-testid="explorer-bar-add-favorite-button"
data-testid="navigation-panel-bar-add-favorite-button"
data-event-props="$.navigationPanel.favorites.createDoc"
data-event-args-control="addFavorite"
onClick={handleCreateNewFavoriteDoc}
label={t['New Page']()}
/>
</ExplorerTreeRoot>
</NavigationPanelTreeRoot>
</CollapsibleSection>
);
};
@@ -74,12 +73,12 @@ export const FavoriteNode = ({
};
}) => {
return favorite.type === 'doc' ? (
<ExplorerDocNode docId={favorite.id} />
<NavigationPanelDocNode docId={favorite.id} />
) : favorite.type === 'tag' ? (
<ExplorerTagNode tagId={favorite.id} />
<NavigationPanelTagNode tagId={favorite.id} />
) : 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 {
ExplorerService,
ExplorerTreeRoot,
} from '@affine/core/modules/explorer';
import { NavigationPanelTreeRoot } from '@affine/core/desktop/components/navigation-panel';
import { NavigationPanelService } from '@affine/core/modules/navigation-panel';
import { OrganizeService } from '@affine/core/modules/organize';
import { useI18n } from '@affine/i18n';
import track from '@affine/track';
@@ -12,15 +10,15 @@ import { useCallback, useState } from 'react';
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
import { CollapsibleSection } from '../../layouts/collapsible-section';
import { ExplorerFolderNode } from '../../nodes/folder';
import { NavigationPanelFolderNode } from '../../nodes/folder';
import { FolderCreateTip, FolderRenameDialog } from '../../nodes/folder/dialog';
export const ExplorerOrganize = () => {
const { organizeService, explorerService } = useServices({
export const NavigationPanelOrganize = () => {
const { organizeService, navigationPanelService } = useServices({
OrganizeService,
ExplorerService,
NavigationPanelService,
});
const explorerSection = explorerService.sections.organize;
const navigationPanelSection = navigationPanelService.sections.organize;
const [openNewFolderDialog, setOpenNewFolderDialog] = useState(false);
const t = useI18n();
@@ -38,10 +36,10 @@ export const ExplorerOrganize = () => {
rootFolder.indexAt('before')
);
track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
explorerSection.setCollapsed(false);
navigationPanelSection.setCollapsed(false);
return newFolderId;
},
[explorerSection, rootFolder]
[navigationPanelSection, rootFolder]
);
return (
@@ -50,17 +48,20 @@ export const ExplorerOrganize = () => {
title={t['com.affine.rootAppSidebar.organize']()}
>
{/* TODO(@CatsJuice): Organize loading UI */}
<ExplorerTreeRoot placeholder={isLoading ? <Skeleton /> : null}>
<NavigationPanelTreeRoot placeholder={isLoading ? <Skeleton /> : null}>
{folders.map(child => (
<ExplorerFolderNode key={child.id} nodeId={child.id as string} />
<NavigationPanelFolderNode
key={child.id}
nodeId={child.id as string}
/>
))}
<AddItemPlaceholder
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']()}
onClick={() => setOpenNewFolderDialog(true)}
/>
</ExplorerTreeRoot>
</NavigationPanelTreeRoot>
<FolderRenameDialog
open={openNewFolderDialog}
onConfirm={handleCreateFolder}

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import { useService } from '@toeverything/infra';
import { useMemo } from 'react';
import { DocCard } from '../../components/doc-card';
import { CollapsibleSection } from '../../components/explorer';
import { CollapsibleSection } from '../../components/navigation';
import * as styles from './styles.css';
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 { configureEditorModule } from './editor';
import { configureEditorSettingModule } from './editor-setting';
import { configureExplorerModule } from './explorer';
import { configureFavoriteModule } from './favorite';
import { configureFeatureFlagModule } from './feature-flag';
import { configureGlobalContextModule } from './global-context';
@@ -32,6 +31,7 @@ import { configureJournalModule } from './journal';
import { configureLifecycleModule } from './lifecycle';
import { configureMediaModule } from './media';
import { configureNavigationModule } from './navigation';
import { configureNavigationPanelModule } from './navigation-panel';
import { configureNotificationModule } from './notification';
import { configureOpenInApp } from './open-in-app';
import { configureOrganizeModule } from './organize';
@@ -82,7 +82,7 @@ export function configureCommonModules(framework: Framework) {
configureDocLinksModule(framework);
configureOrganizeModule(framework);
configureFavoriteModule(framework);
configureExplorerModule(framework);
configureNavigationPanelModule(framework);
configureThemeEditorModule(framework);
configureEditorModule(framework);
configureSystemFontFamilyModule(framework);

View File

@@ -15,7 +15,9 @@ const DEFAULT_COLLAPSABLE_STATE: Record<CollapsibleSectionName, boolean> = {
others: false,
};
export class ExplorerSection extends Entity<{ name: CollapsibleSectionName }> {
export class NavigationPanelSection extends Entity<{
name: CollapsibleSectionName;
}> {
name: CollapsibleSectionName = this.props.name;
key = `explorer.section.${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 { ExplorerSection } from '../entities/explore-section';
import { NavigationPanelSection } from '../entities/navigation-panel-section';
import type { CollapsibleSectionName } from '../types';
const allSectionName: Array<CollapsibleSectionName> = [
@@ -14,12 +14,12 @@ const allSectionName: Array<CollapsibleSectionName> = [
'others',
];
export class ExplorerService extends Service {
export class NavigationPanelService extends Service {
readonly sections = allSectionName.reduce(
(prev, name) =>
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;
from?:
| {
at: 'explorer:organize:folder-node';
at: 'navigation-panel:organize:folder-node';
nodeId: string;
}
| {
at: 'explorer:collection:list';
at: 'navigation-panel:collection:list';
}
| {
at: 'explorer:doc:linked-docs';
at: 'navigation-panel:doc:linked-docs';
docId: string;
}
| {
at: 'explorer:collection:filtered-docs';
at: 'navigation-panel:collection:filtered-docs';
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';
@@ -60,10 +60,10 @@ export interface AffineDNDData extends DNDData {
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';
@@ -103,28 +103,28 @@ export interface AffineDNDData extends DNDData {
};
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: 'explorer:tag';
at: 'navigation-panel:tag';
}
| {
at: 'app-header:tabs';