mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-17 22:37:04 +08:00
refactor(core): rename explorer to navigation-panel (#11876)
This commit is contained in:
@@ -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']()}
|
||||
|
||||
@@ -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';
|
||||
@@ -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$);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -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={
|
||||
@@ -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
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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',
|
||||
}}
|
||||
/>
|
||||
));
|
||||
@@ -19,7 +19,7 @@ import { useCallback, useMemo } from 'react';
|
||||
|
||||
import type { NodeOperation } from '../../tree/types';
|
||||
|
||||
export const useExplorerTagNodeOperations = (
|
||||
export const useNavigationPanelTagNodeOperations = (
|
||||
tagId: string,
|
||||
{
|
||||
openNodeCollapsed,
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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);
|
||||
@@ -0,0 +1,7 @@
|
||||
export { DropEffect } from './drop-effect';
|
||||
export type {
|
||||
NavigationPanelTreeNodeDropEffect,
|
||||
NavigationPanelTreeNodeDropEffectData,
|
||||
} from './node';
|
||||
export { NavigationPanelTreeNode } from './node';
|
||||
export { NavigationPanelTreeRoot } from './root';
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
@@ -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 (
|
||||
@@ -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(
|
||||
@@ -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={
|
||||
@@ -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
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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));
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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} />
|
||||
</>
|
||||
@@ -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(
|
||||
() => [
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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} />
|
||||
);
|
||||
};
|
||||
@@ -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}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const placeholder = style({
|
||||
display: 'none',
|
||||
selectors: {
|
||||
'&:only-child': {
|
||||
display: 'initial',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# explorer
|
||||
|
||||
file manager in app left sidebar
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
});
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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);
|
||||
@@ -1,7 +0,0 @@
|
||||
export { DropEffect } from './drop-effect';
|
||||
export type {
|
||||
ExplorerTreeNodeDropEffect,
|
||||
ExplorerTreeNodeDropEffectData,
|
||||
} from './node';
|
||||
export { ExplorerTreeNode } from './node';
|
||||
export { ExplorerTreeRoot } from './root';
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
15
packages/frontend/core/src/modules/navigation-panel/index.ts
Normal file
15
packages/frontend/core/src/modules/navigation-panel/index.ts
Normal 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]);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user