feat(core): adjust explorer section style, persist collapsable state (#7679)
close AF-1124,AF-1129,AF-1134,AF-1144
This commit is contained in:
@@ -1,25 +1,37 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const actions = style({
|
||||||
|
display: 'flex',
|
||||||
|
gap: 8,
|
||||||
|
});
|
||||||
export const root = style({
|
export const root = style({
|
||||||
fontSize: cssVar('fontXs'),
|
fontSize: cssVar('fontXs'),
|
||||||
minHeight: '16px',
|
height: 20,
|
||||||
width: 'calc(100% + 6px)',
|
width: 'calc(100%)',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
marginBottom: '4px',
|
|
||||||
padding: '0 8px',
|
padding: '0 8px',
|
||||||
gap: '8px',
|
borderRadius: 4,
|
||||||
selectors: {
|
selectors: {
|
||||||
'&:not(:first-of-type)': {
|
[`&[data-collapsible="true"]`]: {
|
||||||
marginTop: '16px',
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
[`&[data-collapsible="true"]:hover`]: {
|
||||||
|
backgroundColor: cssVarV2('layer/background/hoverOverlay'),
|
||||||
|
},
|
||||||
|
[`&[data-collapsible="true"]:hover:has(${actions}:hover)`]: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
export const label = style({
|
export const label = style({
|
||||||
color: cssVar('black30'),
|
color: cssVarV2('text/tertiary'),
|
||||||
|
fontWeight: 500,
|
||||||
|
lineHeight: '20px',
|
||||||
flexGrow: '0',
|
flexGrow: '0',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -30,8 +42,17 @@ export const label = style({
|
|||||||
export const collapseButton = style({
|
export const collapseButton = style({
|
||||||
selectors: {
|
selectors: {
|
||||||
[`${label} > &`]: {
|
[`${label} > &`]: {
|
||||||
color: cssVar('black30'),
|
color: cssVarV2('icon/tertiary'),
|
||||||
transform: 'translateY(1px)',
|
transform: 'translateY(1px)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
export const collapseIcon = style({
|
||||||
|
transform: 'rotate(90deg)',
|
||||||
|
transition: 'transform 0.2s',
|
||||||
|
selectors: {
|
||||||
|
[`${root}[data-collapsed="true"] &`]: {
|
||||||
|
transform: 'rotate(0deg)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IconButton } from '@affine/component';
|
import { IconButton } from '@affine/component';
|
||||||
import { ToggleCollapseIcon, ToggleExpandIcon } from '@blocksuite/icons/rc';
|
import { ToggleCollapseIcon } from '@blocksuite/icons/rc';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { type ForwardedRef, forwardRef, type PropsWithChildren } from 'react';
|
import { type ForwardedRef, forwardRef, type PropsWithChildren } from 'react';
|
||||||
|
|
||||||
@@ -28,26 +28,33 @@ export const CategoryDivider = forwardRef(
|
|||||||
}: CategoryDividerProps,
|
}: CategoryDividerProps,
|
||||||
ref: ForwardedRef<HTMLDivElement>
|
ref: ForwardedRef<HTMLDivElement>
|
||||||
) => {
|
) => {
|
||||||
|
const collapsible = collapsed !== undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx([styles.root, className])} ref={ref} {...otherProps}>
|
<div
|
||||||
<div
|
className={clsx([styles.root, className])}
|
||||||
className={styles.label}
|
ref={ref}
|
||||||
onClick={() => setCollapsed?.(!collapsed)}
|
onClick={() => setCollapsed?.(!collapsed)}
|
||||||
>
|
data-collapsed={collapsed}
|
||||||
|
data-collapsible={collapsible}
|
||||||
|
{...otherProps}
|
||||||
|
>
|
||||||
|
<div className={styles.label}>
|
||||||
{label}
|
{label}
|
||||||
{collapsed !== undefined && (
|
{collapsible ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
withoutHoverStyle
|
withoutHoverStyle
|
||||||
className={styles.collapseButton}
|
className={styles.collapseButton}
|
||||||
size="small"
|
size="small"
|
||||||
data-testid="category-divider-collapse-button"
|
data-testid="category-divider-collapse-button"
|
||||||
>
|
>
|
||||||
{collapsed ? <ToggleCollapseIcon /> : <ToggleExpandIcon />}
|
<ToggleCollapseIcon className={styles.collapseIcon} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className={styles.actions} onClick={e => e.stopPropagation()}>
|
||||||
|
{children}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1 }}></div>
|
|
||||||
{children}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ export const scrollableContainer = style([
|
|||||||
baseContainer,
|
baseContainer,
|
||||||
{
|
{
|
||||||
height: '100%',
|
height: '100%',
|
||||||
padding: '4px 8px',
|
padding: '0px 8px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 8,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
export const scrollbar = style({
|
export const scrollbar = style({
|
||||||
|
|||||||
@@ -188,14 +188,10 @@ export const RootAppSidebar = (): ReactElement => {
|
|||||||
{runtimeConfig.enableNewFavorite && <ExplorerFavorites />}
|
{runtimeConfig.enableNewFavorite && <ExplorerFavorites />}
|
||||||
{runtimeConfig.enableOrganize && <ExplorerOrganize />}
|
{runtimeConfig.enableOrganize && <ExplorerOrganize />}
|
||||||
{runtimeConfig.enableNewFavorite && <ExplorerMigrationFavorites />}
|
{runtimeConfig.enableNewFavorite && <ExplorerMigrationFavorites />}
|
||||||
{runtimeConfig.enableOldFavorite && (
|
{runtimeConfig.enableOldFavorite && <ExplorerOldFavorites />}
|
||||||
<ExplorerOldFavorites defaultCollapsed />
|
<ExplorerCollections />
|
||||||
)}
|
<ExplorerTags />
|
||||||
<ExplorerCollections defaultCollapsed />
|
|
||||||
<ExplorerTags defaultCollapsed />
|
|
||||||
<CategoryDivider label={t['com.affine.rootAppSidebar.others']()} />
|
<CategoryDivider label={t['com.affine.rootAppSidebar.others']()} />
|
||||||
{/* fixme: remove the following spacer */}
|
|
||||||
<div style={{ height: '4px' }} />
|
|
||||||
<div style={{ padding: '0 8px' }}>
|
<div style={{ padding: '0 8px' }}>
|
||||||
<TrashButton />
|
<TrashButton />
|
||||||
<ImportPage docCollection={docCollection} />
|
<ImportPage docCollection={docCollection} />
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import type { GlobalCache } from '@toeverything/infra';
|
||||||
|
import { Entity, LiveData } from '@toeverything/infra';
|
||||||
|
import { map } from 'rxjs';
|
||||||
|
|
||||||
|
import type { CollapsibleSectionName } from '../types';
|
||||||
|
|
||||||
|
const DEFAULT_COLLAPSABLE_STATE: Record<CollapsibleSectionName, boolean> = {
|
||||||
|
favorites: false,
|
||||||
|
organize: false,
|
||||||
|
collections: true,
|
||||||
|
tags: true,
|
||||||
|
favoritesOld: true,
|
||||||
|
migrationFavorites: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ExplorerSection extends Entity<{ name: CollapsibleSectionName }> {
|
||||||
|
name: CollapsibleSectionName = this.props.name;
|
||||||
|
key = `explorer.section.${this.name}`;
|
||||||
|
defaultValue = DEFAULT_COLLAPSABLE_STATE[this.name];
|
||||||
|
|
||||||
|
constructor(private readonly globalCache: GlobalCache) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
collapsed$ = LiveData.from(
|
||||||
|
this.globalCache
|
||||||
|
.watch<boolean>(this.key)
|
||||||
|
.pipe(map(v => v ?? this.defaultValue)),
|
||||||
|
this.defaultValue
|
||||||
|
);
|
||||||
|
|
||||||
|
setCollapsed(collapsed: boolean) {
|
||||||
|
this.globalCache.set(this.key, collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,22 @@
|
|||||||
|
import {
|
||||||
|
type Framework,
|
||||||
|
GlobalCache,
|
||||||
|
WorkspaceScope,
|
||||||
|
} from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { ExplorerSection } from './entities/explore-section';
|
||||||
|
import { ExplorerService } from './services/explorer';
|
||||||
|
export { ExplorerService } from './services/explorer';
|
||||||
|
export type { CollapsibleSectionName } from './types';
|
||||||
export { ExplorerCollections } from './views/sections/collections';
|
export { ExplorerCollections } from './views/sections/collections';
|
||||||
export { ExplorerFavorites } from './views/sections/favorites';
|
export { ExplorerFavorites } from './views/sections/favorites';
|
||||||
export { ExplorerMigrationFavorites } from './views/sections/migration-favorites';
|
export { ExplorerMigrationFavorites } from './views/sections/migration-favorites';
|
||||||
export { ExplorerOldFavorites } from './views/sections/old-favorites';
|
export { ExplorerOldFavorites } from './views/sections/old-favorites';
|
||||||
export { ExplorerOrganize } from './views/sections/organize';
|
export { ExplorerOrganize } from './views/sections/organize';
|
||||||
|
|
||||||
|
export function configureExplorerModule(framework: Framework) {
|
||||||
|
framework
|
||||||
|
.scope(WorkspaceScope)
|
||||||
|
.service(ExplorerService)
|
||||||
|
.entity(ExplorerSection, [GlobalCache]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Service } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { ExplorerSection } from '../entities/explore-section';
|
||||||
|
import type { CollapsibleSectionName } from '../types';
|
||||||
|
|
||||||
|
const allSectionName: Array<CollapsibleSectionName> = [
|
||||||
|
'favorites',
|
||||||
|
'organize',
|
||||||
|
'collections',
|
||||||
|
'tags',
|
||||||
|
'favoritesOld',
|
||||||
|
'migrationFavorites',
|
||||||
|
];
|
||||||
|
|
||||||
|
export class ExplorerService extends Service {
|
||||||
|
readonly sections = allSectionName.reduce(
|
||||||
|
(prev, name) =>
|
||||||
|
Object.assign(prev, {
|
||||||
|
[name]: this.framework.createEntity(ExplorerSection, { name }),
|
||||||
|
}),
|
||||||
|
{} as Record<CollapsibleSectionName, ExplorerSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
packages/frontend/core/src/modules/explorer/types.ts
Normal file
7
packages/frontend/core/src/modules/explorer/types.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export type CollapsibleSectionName =
|
||||||
|
| 'collections'
|
||||||
|
| 'favorites'
|
||||||
|
| 'tags'
|
||||||
|
| 'organize'
|
||||||
|
| 'favoritesOld'
|
||||||
|
| 'migrationFavorites';
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const root = style({});
|
||||||
|
export const content = style({
|
||||||
|
paddingTop: 6,
|
||||||
|
});
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { CategoryDivider } from '@affine/core/components/app-sidebar';
|
||||||
|
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||||
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import {
|
||||||
|
type PropsWithChildren,
|
||||||
|
type ReactNode,
|
||||||
|
type RefObject,
|
||||||
|
useCallback,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { ExplorerService } from '../../services/explorer';
|
||||||
|
import type { CollapsibleSectionName } from '../../types';
|
||||||
|
import { content, root } from './collapsible-section.css';
|
||||||
|
|
||||||
|
interface CollapsibleSectionProps extends PropsWithChildren {
|
||||||
|
name: CollapsibleSectionName;
|
||||||
|
title: string;
|
||||||
|
actions?: ReactNode;
|
||||||
|
|
||||||
|
className?: string;
|
||||||
|
testId?: string;
|
||||||
|
|
||||||
|
headerRef?: RefObject<HTMLDivElement>;
|
||||||
|
headerTestId?: string;
|
||||||
|
headerClassName?: string;
|
||||||
|
|
||||||
|
contentClassName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CollapsibleSection = ({
|
||||||
|
name,
|
||||||
|
title,
|
||||||
|
actions,
|
||||||
|
children,
|
||||||
|
|
||||||
|
className,
|
||||||
|
testId,
|
||||||
|
|
||||||
|
headerRef,
|
||||||
|
headerTestId,
|
||||||
|
headerClassName,
|
||||||
|
|
||||||
|
contentClassName,
|
||||||
|
}: CollapsibleSectionProps) => {
|
||||||
|
const section = useService(ExplorerService).sections[name];
|
||||||
|
|
||||||
|
const collapsed = useLiveData(section.collapsed$);
|
||||||
|
|
||||||
|
const setCollapsed = useCallback(
|
||||||
|
(v: boolean) => {
|
||||||
|
section.setCollapsed(v);
|
||||||
|
},
|
||||||
|
[section]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Collapsible.Root
|
||||||
|
data-collapsed={collapsed}
|
||||||
|
className={clsx(root, className)}
|
||||||
|
open={!collapsed}
|
||||||
|
data-testid={testId}
|
||||||
|
>
|
||||||
|
<CategoryDivider
|
||||||
|
data-testid={headerTestId}
|
||||||
|
label={title}
|
||||||
|
setCollapsed={setCollapsed}
|
||||||
|
collapsed={collapsed}
|
||||||
|
ref={headerRef}
|
||||||
|
className={headerClassName}
|
||||||
|
>
|
||||||
|
{actions}
|
||||||
|
</CategoryDivider>
|
||||||
|
<Collapsible.Content className={clsx(content, contentClassName)}>
|
||||||
|
{children}
|
||||||
|
</Collapsible.Content>
|
||||||
|
</Collapsible.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { IconButton } from '@affine/component';
|
import { IconButton } from '@affine/component';
|
||||||
import { CategoryDivider } from '@affine/core/components/app-sidebar';
|
|
||||||
import { useEditCollectionName } from '@affine/core/components/page-list';
|
import { useEditCollectionName } from '@affine/core/components/page-list';
|
||||||
import { createEmptyCollection } from '@affine/core/components/page-list/use-collection-manager';
|
import { createEmptyCollection } from '@affine/core/components/page-list/use-collection-manager';
|
||||||
import { mixpanel } from '@affine/core/mixpanel';
|
import { mixpanel } from '@affine/core/mixpanel';
|
||||||
@@ -8,26 +7,23 @@ import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
|
|||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { ExplorerService } from '../../../services/explorer';
|
||||||
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
import { ExplorerCollectionNode } from '../../nodes/collection';
|
import { ExplorerCollectionNode } from '../../nodes/collection';
|
||||||
import { RootEmpty } from './empty';
|
import { RootEmpty } from './empty';
|
||||||
import * as styles from './styles.css';
|
|
||||||
|
|
||||||
export const ExplorerCollections = ({
|
export const ExplorerCollections = () => {
|
||||||
defaultCollapsed = false,
|
|
||||||
}: {
|
|
||||||
defaultCollapsed?: boolean;
|
|
||||||
}) => {
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { collectionService, workbenchService } = useServices({
|
const { collectionService, workbenchService, explorerService } = useServices({
|
||||||
CollectionService,
|
CollectionService,
|
||||||
WorkbenchService,
|
WorkbenchService,
|
||||||
|
ExplorerService,
|
||||||
});
|
});
|
||||||
const [collapsed, setCollapsed] = useState(defaultCollapsed);
|
const explorerSection = explorerService.sections.collections;
|
||||||
const collections = useLiveData(collectionService.collections$);
|
const collections = useLiveData(collectionService.collections$);
|
||||||
const { node, open: openCreateCollectionModel } = useEditCollectionName({
|
const { node, open: openCreateCollectionModel } = useEditCollectionName({
|
||||||
title: t['com.affine.editCollection.createCollection'](),
|
title: t['com.affine.editCollection.createCollection'](),
|
||||||
@@ -45,25 +41,25 @@ export const ExplorerCollections = ({
|
|||||||
control: 'new collection button',
|
control: 'new collection button',
|
||||||
});
|
});
|
||||||
workbenchService.workbench.openCollection(id);
|
workbenchService.workbench.openCollection(id);
|
||||||
setCollapsed(false);
|
explorerSection.setCollapsed(false);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}, [collectionService, openCreateCollectionModel, workbenchService]);
|
}, [
|
||||||
|
collectionService,
|
||||||
|
explorerSection,
|
||||||
|
openCreateCollectionModel,
|
||||||
|
workbenchService.workbench,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Collapsible.Root
|
<CollapsibleSection
|
||||||
className={styles.container}
|
name="collections"
|
||||||
data-testid="explorer-collections"
|
testId="explorer-collections"
|
||||||
open={!collapsed}
|
title={t['com.affine.rootAppSidebar.collections']()}
|
||||||
>
|
actions={
|
||||||
<CategoryDivider
|
|
||||||
label={t['com.affine.rootAppSidebar.collections']()}
|
|
||||||
setCollapsed={setCollapsed}
|
|
||||||
collapsed={collapsed}
|
|
||||||
>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
data-testid="explorer-bar-add-collection-button"
|
data-testid="explorer-bar-add-collection-button"
|
||||||
onClick={handleCreateCollection}
|
onClick={handleCreateCollection}
|
||||||
@@ -71,24 +67,23 @@ export const ExplorerCollections = ({
|
|||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</CategoryDivider>
|
}
|
||||||
<Collapsible.Content>
|
>
|
||||||
<ExplorerTreeRoot
|
<ExplorerTreeRoot
|
||||||
placeholder={<RootEmpty onClickCreate={handleCreateCollection} />}
|
placeholder={<RootEmpty onClickCreate={handleCreateCollection} />}
|
||||||
>
|
>
|
||||||
{collections.map(collection => (
|
{collections.map(collection => (
|
||||||
<ExplorerCollectionNode
|
<ExplorerCollectionNode
|
||||||
key={collection.id}
|
key={collection.id}
|
||||||
collectionId={collection.id}
|
collectionId={collection.id}
|
||||||
reorderable={false}
|
reorderable={false}
|
||||||
location={{
|
location={{
|
||||||
at: 'explorer:collection:list',
|
at: 'explorer:collection:list',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ExplorerTreeRoot>
|
</ExplorerTreeRoot>
|
||||||
</Collapsible.Content>
|
</CollapsibleSection>
|
||||||
</Collapsible.Root>
|
|
||||||
{node}
|
{node}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { style } from '@vanilla-extract/css';
|
|
||||||
|
|
||||||
export const container = style({
|
|
||||||
marginTop: '8px',
|
|
||||||
});
|
|
||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
useDropTarget,
|
useDropTarget,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { CategoryDivider } from '@affine/core/components/app-sidebar';
|
|
||||||
import { mixpanel } from '@affine/core/mixpanel';
|
import { mixpanel } from '@affine/core/mixpanel';
|
||||||
import {
|
import {
|
||||||
DropEffect,
|
DropEffect,
|
||||||
@@ -20,10 +19,11 @@ import { WorkbenchService } from '@affine/core/modules/workbench';
|
|||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
|
||||||
import { DocsService, useLiveData, useServices } from '@toeverything/infra';
|
import { DocsService, useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { ExplorerService } from '../../../services/explorer';
|
||||||
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
import { ExplorerCollectionNode } from '../../nodes/collection';
|
import { ExplorerCollectionNode } from '../../nodes/collection';
|
||||||
import { ExplorerDocNode } from '../../nodes/doc';
|
import { ExplorerDocNode } from '../../nodes/doc';
|
||||||
import { ExplorerFolderNode } from '../../nodes/folder';
|
import { ExplorerFolderNode } from '../../nodes/folder';
|
||||||
@@ -31,17 +31,16 @@ import { ExplorerTagNode } from '../../nodes/tag';
|
|||||||
import { RootEmpty } from './empty';
|
import { RootEmpty } from './empty';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
export const ExplorerFavorites = ({
|
export const ExplorerFavorites = () => {
|
||||||
defaultCollapsed = false,
|
const { favoriteService, docsService, workbenchService, explorerService } =
|
||||||
}: {
|
useServices({
|
||||||
defaultCollapsed?: boolean;
|
FavoriteService,
|
||||||
}) => {
|
DocsService,
|
||||||
const { favoriteService, docsService, workbenchService } = useServices({
|
WorkbenchService,
|
||||||
FavoriteService,
|
ExplorerService,
|
||||||
DocsService,
|
});
|
||||||
WorkbenchService,
|
|
||||||
});
|
const explorerSection = explorerService.sections.favorites;
|
||||||
const [collapsed, setCollapsed] = useState(defaultCollapsed);
|
|
||||||
|
|
||||||
const favorites = useLiveData(favoriteService.favoriteList.sortedList$);
|
const favorites = useLiveData(favoriteService.favoriteList.sortedList$);
|
||||||
|
|
||||||
@@ -65,10 +64,10 @@ export const ExplorerFavorites = ({
|
|||||||
type: data.source.data.entity.type,
|
type: data.source.data.entity.type,
|
||||||
id: data.source.data.entity.id,
|
id: data.source.data.entity.id,
|
||||||
});
|
});
|
||||||
setCollapsed(false);
|
explorerSection.setCollapsed(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[favoriteService]
|
[explorerSection, favoriteService.favoriteList]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDropEffect = useCallback<ExplorerTreeNodeDropEffect>(data => {
|
const handleDropEffect = useCallback<ExplorerTreeNodeDropEffect>(data => {
|
||||||
@@ -110,8 +109,13 @@ export const ExplorerFavorites = ({
|
|||||||
id: newDoc.id,
|
id: newDoc.id,
|
||||||
});
|
});
|
||||||
workbenchService.workbench.openDoc(newDoc.id);
|
workbenchService.workbench.openDoc(newDoc.id);
|
||||||
setCollapsed(false);
|
explorerSection.setCollapsed(false);
|
||||||
}, [docsService, favoriteService, workbenchService]);
|
}, [
|
||||||
|
docsService,
|
||||||
|
explorerSection,
|
||||||
|
favoriteService.favoriteList,
|
||||||
|
workbenchService.workbench,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleOnChildrenDrop = useCallback(
|
const handleOnChildrenDrop = useCallback(
|
||||||
(
|
(
|
||||||
@@ -221,61 +225,57 @@ export const ExplorerFavorites = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible.Root
|
<CollapsibleSection
|
||||||
className={styles.container}
|
name="favorites"
|
||||||
data-testid="explorer-favorites"
|
title={t['com.affine.rootAppSidebar.favorites']()}
|
||||||
open={!collapsed}
|
headerRef={dropTargetRef}
|
||||||
|
testId="explorer-favorites"
|
||||||
|
headerTestId="explorer-favorite-category-divider"
|
||||||
|
headerClassName={styles.draggedOverHighlight}
|
||||||
|
actions={
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
data-testid="explorer-bar-add-favorite-button"
|
||||||
|
onClick={handleCreateNewFavoriteDoc}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<PlusIcon />
|
||||||
|
</IconButton>
|
||||||
|
{draggedOverDraggable && (
|
||||||
|
<DropEffect
|
||||||
|
position={{
|
||||||
|
x: draggedOverPosition.relativeX,
|
||||||
|
y: draggedOverPosition.relativeY,
|
||||||
|
}}
|
||||||
|
dropEffect={handleDropEffect({
|
||||||
|
source: draggedOverDraggable,
|
||||||
|
treeInstruction: null,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<CategoryDivider
|
<ExplorerTreeRoot
|
||||||
className={styles.draggedOverHighlight}
|
placeholder={
|
||||||
label={t['com.affine.rootAppSidebar.favorites']()}
|
<RootEmpty
|
||||||
ref={dropTargetRef}
|
onDrop={handleDrop}
|
||||||
data-testid="explorer-favorite-category-divider"
|
canDrop={handleCanDrop}
|
||||||
setCollapsed={setCollapsed}
|
dropEffect={handleDropEffect}
|
||||||
collapsed={collapsed}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
data-testid="explorer-bar-add-favorite-button"
|
|
||||||
onClick={handleCreateNewFavoriteDoc}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<PlusIcon />
|
|
||||||
</IconButton>
|
|
||||||
{draggedOverDraggable && (
|
|
||||||
<DropEffect
|
|
||||||
position={{
|
|
||||||
x: draggedOverPosition.relativeX,
|
|
||||||
y: draggedOverPosition.relativeY,
|
|
||||||
}}
|
|
||||||
dropEffect={handleDropEffect({
|
|
||||||
source: draggedOverDraggable,
|
|
||||||
treeInstruction: null,
|
|
||||||
})}
|
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
</CategoryDivider>
|
>
|
||||||
<Collapsible.Content>
|
{favorites.map(favorite => (
|
||||||
<ExplorerTreeRoot
|
<ExplorerFavoriteNode
|
||||||
placeholder={
|
key={favorite.id}
|
||||||
<RootEmpty
|
favorite={favorite}
|
||||||
onDrop={handleDrop}
|
onDrop={handleOnChildrenDrop}
|
||||||
canDrop={handleCanDrop}
|
dropEffect={handleChildrenDropEffect}
|
||||||
dropEffect={handleDropEffect}
|
canDrop={handleChildrenCanDrop}
|
||||||
/>
|
/>
|
||||||
}
|
))}
|
||||||
>
|
</ExplorerTreeRoot>
|
||||||
{favorites.map(favorite => (
|
</CollapsibleSection>
|
||||||
<ExplorerFavoriteNode
|
|
||||||
key={favorite.id}
|
|
||||||
favorite={favorite}
|
|
||||||
onDrop={handleOnChildrenDrop}
|
|
||||||
dropEffect={handleChildrenDropEffect}
|
|
||||||
canDrop={handleChildrenCanDrop}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ExplorerTreeRoot>
|
|
||||||
</Collapsible.Content>
|
|
||||||
</Collapsible.Root>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
export const container = style({
|
|
||||||
marginTop: '8px',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const draggedOverHighlight = style({
|
export const draggedOverHighlight = style({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
selectors: {
|
selectors: {
|
||||||
|
|||||||
@@ -1,25 +1,18 @@
|
|||||||
import { IconButton, useConfirmModal } from '@affine/component';
|
import { IconButton, useConfirmModal } from '@affine/component';
|
||||||
import { CategoryDivider } from '@affine/core/components/app-sidebar';
|
|
||||||
import { mixpanel } from '@affine/core/mixpanel';
|
import { mixpanel } from '@affine/core/mixpanel';
|
||||||
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
|
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
|
||||||
import { FavoriteItemsAdapter } from '@affine/core/modules/properties';
|
import { FavoriteItemsAdapter } from '@affine/core/modules/properties';
|
||||||
import { Trans, useI18n } from '@affine/i18n';
|
import { Trans, useI18n } from '@affine/i18n';
|
||||||
import { BroomIcon, HelpIcon } from '@blocksuite/icons/rc';
|
import { BroomIcon, HelpIcon } from '@blocksuite/icons/rc';
|
||||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
|
||||||
import { DocsService, useLiveData, useServices } from '@toeverything/infra';
|
import { DocsService, useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
import { ExplorerCollectionNode } from '../../nodes/collection';
|
import { ExplorerCollectionNode } from '../../nodes/collection';
|
||||||
import { ExplorerDocNode } from '../../nodes/doc';
|
import { ExplorerDocNode } from '../../nodes/doc';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
export const ExplorerMigrationFavorites = ({
|
export const ExplorerMigrationFavorites = () => {
|
||||||
defaultCollapsed = false,
|
|
||||||
}: {
|
|
||||||
defaultCollapsed?: boolean;
|
|
||||||
}) => {
|
|
||||||
const [collapsed, setCollapsed] = useState(defaultCollapsed);
|
|
||||||
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
const { favoriteItemsAdapter, docsService } = useServices({
|
const { favoriteItemsAdapter, docsService } = useServices({
|
||||||
@@ -112,38 +105,38 @@ export const ExplorerMigrationFavorites = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible.Root className={styles.container} open={!collapsed}>
|
<CollapsibleSection
|
||||||
<CategoryDivider
|
name="migrationFavorites"
|
||||||
label={t['com.affine.rootAppSidebar.migration-data']()}
|
className={styles.container}
|
||||||
setCollapsed={setCollapsed}
|
title={t['com.affine.rootAppSidebar.migration-data']()}
|
||||||
collapsed={collapsed}
|
actions={
|
||||||
>
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
data-testid="explorer-bar-favorite-migration-clear-button"
|
data-testid="explorer-bar-favorite-migration-clear-button"
|
||||||
onClick={handleClickClear}
|
onClick={handleClickClear}
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
<BroomIcon />
|
<BroomIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
data-testid="explorer-bar-favorite-migration-help-button"
|
data-testid="explorer-bar-favorite-migration-help-button"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={handleClickHelp}
|
onClick={handleClickHelp}
|
||||||
>
|
>
|
||||||
<HelpIcon />
|
<HelpIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</CategoryDivider>
|
</>
|
||||||
<Collapsible.Content>
|
}
|
||||||
<ExplorerTreeRoot>
|
>
|
||||||
{favorites.map((favorite, i) => (
|
<ExplorerTreeRoot>
|
||||||
<ExplorerMigrationFavoriteNode
|
{favorites.map((favorite, i) => (
|
||||||
key={favorite.id + ':' + i}
|
<ExplorerMigrationFavoriteNode
|
||||||
favorite={favorite}
|
key={favorite.id + ':' + i}
|
||||||
/>
|
favorite={favorite}
|
||||||
))}
|
/>
|
||||||
</ExplorerTreeRoot>
|
))}
|
||||||
</Collapsible.Content>
|
</ExplorerTreeRoot>
|
||||||
</Collapsible.Root>
|
</CollapsibleSection>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import { cssVar } from '@toeverything/theme';
|
|||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
export const container = style({
|
export const container = style({
|
||||||
marginTop: '8px',
|
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
selectors: {
|
selectors: {
|
||||||
'&:after': {
|
'&[data-collapsed="false"]:after': {
|
||||||
display: 'block',
|
display: 'block',
|
||||||
content: '""',
|
content: '""',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
type DropTargetOptions,
|
type DropTargetOptions,
|
||||||
IconButton,
|
IconButton,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { CategoryDivider } from '@affine/core/components/app-sidebar';
|
|
||||||
import {
|
import {
|
||||||
type ExplorerTreeNodeDropEffect,
|
type ExplorerTreeNodeDropEffect,
|
||||||
ExplorerTreeRoot,
|
ExplorerTreeRoot,
|
||||||
@@ -13,29 +12,23 @@ import { WorkbenchService } from '@affine/core/modules/workbench';
|
|||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
|
||||||
import { DocsService, useLiveData, useServices } from '@toeverything/infra';
|
import { DocsService, useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
import { ExplorerCollectionNode } from '../../nodes/collection';
|
import { ExplorerCollectionNode } from '../../nodes/collection';
|
||||||
import { ExplorerDocNode } from '../../nodes/doc';
|
import { ExplorerDocNode } from '../../nodes/doc';
|
||||||
import { RootEmpty } from './empty';
|
import { RootEmpty } from './empty';
|
||||||
import * as styles from './styles.css';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated remove this after 0.17 released
|
* @deprecated remove this after 0.17 released
|
||||||
*/
|
*/
|
||||||
export const ExplorerOldFavorites = ({
|
export const ExplorerOldFavorites = () => {
|
||||||
defaultCollapsed = false,
|
|
||||||
}: {
|
|
||||||
defaultCollapsed?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { favoriteItemsAdapter, docsService, workbenchService } = useServices({
|
const { favoriteItemsAdapter, docsService, workbenchService } = useServices({
|
||||||
FavoriteItemsAdapter,
|
FavoriteItemsAdapter,
|
||||||
DocsService,
|
DocsService,
|
||||||
WorkbenchService,
|
WorkbenchService,
|
||||||
});
|
});
|
||||||
const [collapsed, setCollapsed] = useState(defaultCollapsed);
|
|
||||||
|
|
||||||
const docs = useLiveData(docsService.list.docs$);
|
const docs = useLiveData(docsService.list.docs$);
|
||||||
const trashDocs = useLiveData(docsService.list.trashDocs$);
|
const trashDocs = useLiveData(docsService.list.trashDocs$);
|
||||||
@@ -180,17 +173,14 @@ export const ExplorerOldFavorites = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible.Root className={styles.container} open={!collapsed}>
|
<CollapsibleSection
|
||||||
<CategoryDivider
|
name="favoritesOld"
|
||||||
className={styles.draggedOverHighlight}
|
title={
|
||||||
label={
|
runtimeConfig.enableNewFavorite
|
||||||
runtimeConfig.enableNewFavorite
|
? `${t['com.affine.rootAppSidebar.favorites']()} (OLD)`
|
||||||
? `${t['com.affine.rootAppSidebar.favorites']()} (OLD)`
|
: t['com.affine.rootAppSidebar.favorites']()
|
||||||
: t['com.affine.rootAppSidebar.favorites']()
|
}
|
||||||
}
|
actions={
|
||||||
setCollapsed={setCollapsed}
|
|
||||||
collapsed={collapsed}
|
|
||||||
>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
data-testid="explorer-bar-add-old-favorite-button"
|
data-testid="explorer-bar-add-old-favorite-button"
|
||||||
onClick={handleCreateNewFavoriteDoc}
|
onClick={handleCreateNewFavoriteDoc}
|
||||||
@@ -198,29 +188,28 @@ export const ExplorerOldFavorites = ({
|
|||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</CategoryDivider>
|
}
|
||||||
<Collapsible.Content>
|
>
|
||||||
<ExplorerTreeRoot
|
<ExplorerTreeRoot
|
||||||
placeholder={
|
placeholder={
|
||||||
<RootEmpty
|
<RootEmpty
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
canDrop={handleCanDrop}
|
canDrop={handleCanDrop}
|
||||||
dropEffect={handleDropEffect}
|
dropEffect={handleDropEffect}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{favorites.map((favorite, i) => (
|
{favorites.map((favorite, i) => (
|
||||||
<ExplorerFavoriteNode
|
<ExplorerFavoriteNode
|
||||||
key={favorite.id + ':' + i}
|
key={favorite.id + ':' + i}
|
||||||
favorite={favorite}
|
favorite={favorite}
|
||||||
onDrop={handleOnChildrenDrop}
|
onDrop={handleOnChildrenDrop}
|
||||||
dropEffect={handleChildrenDropEffect}
|
dropEffect={handleChildrenDropEffect}
|
||||||
canDrop={handleChildrenCanDrop}
|
canDrop={handleChildrenCanDrop}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ExplorerTreeRoot>
|
</ExplorerTreeRoot>
|
||||||
</Collapsible.Content>
|
</CollapsibleSection>
|
||||||
</Collapsible.Root>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
|
||||||
import { style } from '@vanilla-extract/css';
|
|
||||||
|
|
||||||
export const container = style({
|
|
||||||
marginTop: '8px',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const draggedOverHighlight = style({
|
|
||||||
selectors: {
|
|
||||||
'&[data-dragged-over="true"]': {
|
|
||||||
background: cssVar('--affine-hover-color'),
|
|
||||||
borderRadius: '4px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
toast,
|
toast,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { CategoryDivider } from '@affine/core/components/app-sidebar';
|
|
||||||
import { mixpanel } from '@affine/core/mixpanel';
|
import { mixpanel } from '@affine/core/mixpanel';
|
||||||
import {
|
import {
|
||||||
type ExplorerTreeNodeDropEffect,
|
type ExplorerTreeNodeDropEffect,
|
||||||
@@ -17,22 +16,23 @@ import {
|
|||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { ExplorerService } from '../../../services/explorer';
|
||||||
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
import { ExplorerFolderNode } from '../../nodes/folder';
|
import { ExplorerFolderNode } from '../../nodes/folder';
|
||||||
import { RootEmpty } from './empty';
|
import { RootEmpty } from './empty';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
export const ExplorerOrganize = ({
|
export const ExplorerOrganize = () => {
|
||||||
defaultCollapsed = false,
|
const { organizeService, explorerService } = useServices({
|
||||||
}: {
|
OrganizeService,
|
||||||
defaultCollapsed?: boolean;
|
ExplorerService,
|
||||||
}) => {
|
});
|
||||||
const { organizeService } = useServices({ OrganizeService });
|
const explorerSection = explorerService.sections.organize;
|
||||||
|
const collapsed = useLiveData(explorerSection.collapsed$);
|
||||||
const [newFolderId, setNewFolderId] = useState<string | null>(null);
|
const [newFolderId, setNewFolderId] = useState<string | null>(null);
|
||||||
const [collapsed, setCollapsed] = useState(defaultCollapsed);
|
|
||||||
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
@@ -51,8 +51,8 @@ export const ExplorerOrganize = ({
|
|||||||
control: 'new folder',
|
control: 'new folder',
|
||||||
});
|
});
|
||||||
setNewFolderId(newFolderId);
|
setNewFolderId(newFolderId);
|
||||||
setCollapsed(false);
|
explorerSection.setCollapsed(false);
|
||||||
}, [rootFolder]);
|
}, [explorerSection, rootFolder]);
|
||||||
|
|
||||||
const handleOnChildrenDrop = useCallback(
|
const handleOnChildrenDrop = useCallback(
|
||||||
(data: DropTargetDropEvent<AffineDNDData>, node?: FolderNode) => {
|
(data: DropTargetDropEvent<AffineDNDData>, node?: FolderNode) => {
|
||||||
@@ -108,23 +108,16 @@ export const ExplorerOrganize = ({
|
|||||||
DropTargetOptions<AffineDNDData>['canDrop']
|
DropTargetOptions<AffineDNDData>['canDrop']
|
||||||
>(() => args => args.source.data.entity?.type === 'folder', []);
|
>(() => args => args.source.data.entity?.type === 'folder', []);
|
||||||
|
|
||||||
const handleCollapsedChange = useCallback((collapsed: boolean) => {
|
useEffect(() => {
|
||||||
if (collapsed) {
|
if (collapsed) setNewFolderId(null); // reset new folder id to clear the renaming state
|
||||||
setNewFolderId(null); // reset new folder id to clear the renaming state
|
}, [collapsed]);
|
||||||
setCollapsed(true);
|
|
||||||
} else {
|
|
||||||
setCollapsed(false);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible.Root className={styles.container} open={!collapsed}>
|
<CollapsibleSection
|
||||||
<CategoryDivider
|
name="organize"
|
||||||
className={styles.draggedOverHighlight}
|
headerClassName={styles.draggedOverHighlight}
|
||||||
label={t['com.affine.rootAppSidebar.organize']()}
|
title={t['com.affine.rootAppSidebar.organize']()}
|
||||||
setCollapsed={handleCollapsedChange}
|
actions={
|
||||||
collapsed={collapsed}
|
|
||||||
>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
data-testid="explorer-bar-add-organize-button"
|
data-testid="explorer-bar-add-organize-button"
|
||||||
onClick={handleCreateFolder}
|
onClick={handleCreateFolder}
|
||||||
@@ -132,27 +125,26 @@ export const ExplorerOrganize = ({
|
|||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</CategoryDivider>
|
}
|
||||||
<Collapsible.Content>
|
>
|
||||||
<ExplorerTreeRoot
|
<ExplorerTreeRoot
|
||||||
placeholder={<RootEmpty onClickCreate={handleCreateFolder} />}
|
placeholder={<RootEmpty onClickCreate={handleCreateFolder} />}
|
||||||
>
|
>
|
||||||
{folders.map(child => (
|
{folders.map(child => (
|
||||||
<ExplorerFolderNode
|
<ExplorerFolderNode
|
||||||
key={child.id}
|
key={child.id}
|
||||||
nodeId={child.id as string}
|
nodeId={child.id as string}
|
||||||
defaultRenaming={child.id === newFolderId}
|
defaultRenaming={child.id === newFolderId}
|
||||||
onDrop={handleOnChildrenDrop}
|
onDrop={handleOnChildrenDrop}
|
||||||
dropEffect={handleChildrenDropEffect}
|
dropEffect={handleChildrenDropEffect}
|
||||||
canDrop={handleChildrenCanDrop}
|
canDrop={handleChildrenCanDrop}
|
||||||
location={{
|
location={{
|
||||||
at: 'explorer:organize:folder-node',
|
at: 'explorer:organize:folder-node',
|
||||||
nodeId: child.id as string,
|
nodeId: child.id as string,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ExplorerTreeRoot>
|
</ExplorerTreeRoot>
|
||||||
</Collapsible.Content>
|
</CollapsibleSection>
|
||||||
</Collapsible.Root>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
export const container = style({
|
|
||||||
marginTop: '8px',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const draggedOverHighlight = style({
|
export const draggedOverHighlight = style({
|
||||||
selectors: {
|
selectors: {
|
||||||
'&[data-dragged-over="true"]': {
|
'&[data-dragged-over="true"]': {
|
||||||
|
|||||||
@@ -1,30 +1,27 @@
|
|||||||
import { IconButton } from '@affine/component';
|
import { IconButton } from '@affine/component';
|
||||||
import { CategoryDivider } from '@affine/core/components/app-sidebar';
|
|
||||||
import { mixpanel } from '@affine/core/mixpanel';
|
import { mixpanel } from '@affine/core/mixpanel';
|
||||||
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
|
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
|
||||||
import type { Tag } from '@affine/core/modules/tag';
|
import type { Tag } from '@affine/core/modules/tag';
|
||||||
import { TagService } from '@affine/core/modules/tag';
|
import { TagService } from '@affine/core/modules/tag';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { ExplorerService } from '../../../services/explorer';
|
||||||
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
import { ExplorerTagNode } from '../../nodes/tag';
|
import { ExplorerTagNode } from '../../nodes/tag';
|
||||||
import { RootEmpty } from './empty';
|
import { RootEmpty } from './empty';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
export const ExplorerTags = ({
|
export const ExplorerTags = () => {
|
||||||
defaultCollapsed = false,
|
const { tagService, explorerService } = useServices({
|
||||||
}: {
|
|
||||||
defaultCollapsed?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { tagService } = useServices({
|
|
||||||
TagService,
|
TagService,
|
||||||
|
ExplorerService,
|
||||||
});
|
});
|
||||||
|
const explorerSection = explorerService.sections.tags;
|
||||||
|
const collapsed = useLiveData(explorerSection.collapsed$);
|
||||||
const [createdTag, setCreatedTag] = useState<Tag | null>(null);
|
const [createdTag, setCreatedTag] = useState<Tag | null>(null);
|
||||||
|
|
||||||
const [collapsed, setCollapsed] = useState(defaultCollapsed);
|
|
||||||
const tags = useLiveData(tagService.tagList.tags$);
|
const tags = useLiveData(tagService.tagList.tags$);
|
||||||
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
@@ -40,26 +37,19 @@ export const ExplorerTags = ({
|
|||||||
module: 'tags',
|
module: 'tags',
|
||||||
control: 'new tag button',
|
control: 'new tag button',
|
||||||
});
|
});
|
||||||
setCollapsed(false);
|
explorerSection.setCollapsed(false);
|
||||||
}, [t, tagService]);
|
}, [explorerSection, t, tagService]);
|
||||||
|
|
||||||
const handleCollapsedChange = useCallback((collapsed: boolean) => {
|
useEffect(() => {
|
||||||
if (collapsed) {
|
if (collapsed) setCreatedTag(null); // reset created tag to clear the renaming state
|
||||||
setCreatedTag(null); // reset created tag to clear the renaming state
|
}, [collapsed]);
|
||||||
setCollapsed(true);
|
|
||||||
} else {
|
|
||||||
setCollapsed(false);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible.Root className={styles.container} open={!collapsed}>
|
<CollapsibleSection
|
||||||
<CategoryDivider
|
name="tags"
|
||||||
className={styles.draggedOverHighlight}
|
headerClassName={styles.draggedOverHighlight}
|
||||||
label={t['com.affine.rootAppSidebar.tags']()}
|
title={t['com.affine.rootAppSidebar.tags']()}
|
||||||
setCollapsed={handleCollapsedChange}
|
actions={
|
||||||
collapsed={collapsed}
|
|
||||||
>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
data-testid="explorer-bar-add-favorite-button"
|
data-testid="explorer-bar-add-favorite-button"
|
||||||
onClick={handleCreateNewFavoriteDoc}
|
onClick={handleCreateNewFavoriteDoc}
|
||||||
@@ -67,22 +57,21 @@ export const ExplorerTags = ({
|
|||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</CategoryDivider>
|
}
|
||||||
<Collapsible.Content>
|
>
|
||||||
<ExplorerTreeRoot placeholder={<RootEmpty />}>
|
<ExplorerTreeRoot placeholder={<RootEmpty />}>
|
||||||
{tags.map(tag => (
|
{tags.map(tag => (
|
||||||
<ExplorerTagNode
|
<ExplorerTagNode
|
||||||
key={tag.id}
|
key={tag.id}
|
||||||
tagId={tag.id}
|
tagId={tag.id}
|
||||||
reorderable={false}
|
reorderable={false}
|
||||||
location={{
|
location={{
|
||||||
at: 'explorer:tags:list',
|
at: 'explorer:tags:list',
|
||||||
}}
|
}}
|
||||||
defaultRenaming={createdTag?.id === tag.id}
|
defaultRenaming={createdTag?.id === tag.id}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ExplorerTreeRoot>
|
</ExplorerTreeRoot>
|
||||||
</Collapsible.Content>
|
</CollapsibleSection>
|
||||||
</Collapsible.Root>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
export const container = style({
|
|
||||||
marginTop: '8px',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const draggedOverHighlight = style({
|
export const draggedOverHighlight = style({
|
||||||
selectors: {
|
selectors: {
|
||||||
'&[data-dragged-over="true"]': {
|
'&[data-dragged-over="true"]': {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import { createVar, keyframes, style } from '@vanilla-extract/css';
|
import { createVar, keyframes, style } from '@vanilla-extract/css';
|
||||||
export const levelIndent = createVar();
|
export const levelIndent = createVar();
|
||||||
export const linkItemRoot = style({
|
export const linkItemRoot = style({
|
||||||
@@ -41,6 +42,7 @@ export const itemContent = style({
|
|||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
color: cssVarV2('text/primary'),
|
||||||
});
|
});
|
||||||
export const postfix = style({
|
export const postfix = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -59,7 +61,7 @@ export const postfix = style({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
export const icon = style({
|
export const icon = style({
|
||||||
color: cssVar('iconColor'),
|
color: cssVarV2('icon/primary'),
|
||||||
fontSize: '20px',
|
fontSize: '20px',
|
||||||
});
|
});
|
||||||
export const collapsedIconContainer = style({
|
export const collapsedIconContainer = style({
|
||||||
@@ -70,7 +72,7 @@ export const collapsedIconContainer = style({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
borderRadius: '2px',
|
borderRadius: '2px',
|
||||||
transition: 'transform 0.2s',
|
transition: 'transform 0.2s',
|
||||||
color: 'inherit',
|
color: cssVarV2('icon/primary'),
|
||||||
selectors: {
|
selectors: {
|
||||||
'&[data-collapsed="true"]': {
|
'&[data-collapsed="true"]': {
|
||||||
transform: 'rotate(-90deg)',
|
transform: 'rotate(-90deg)',
|
||||||
@@ -131,6 +133,7 @@ const draggedOverAnimation = keyframes({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const contentContainer = style({
|
export const contentContainer = style({
|
||||||
|
marginTop: 2,
|
||||||
paddingLeft: levelIndent,
|
paddingLeft: levelIndent,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { configureCloudModule } from './cloud';
|
|||||||
import { configureCollectionModule } from './collection';
|
import { configureCollectionModule } from './collection';
|
||||||
import { configureDocLinksModule } from './doc-link';
|
import { configureDocLinksModule } from './doc-link';
|
||||||
import { configureDocsSearchModule } from './docs-search';
|
import { configureDocsSearchModule } from './docs-search';
|
||||||
|
import { configureExplorerModule } from './explorer';
|
||||||
import { configureFavoriteModule } from './favorite';
|
import { configureFavoriteModule } from './favorite';
|
||||||
import { configureFindInPageModule } from './find-in-page';
|
import { configureFindInPageModule } from './find-in-page';
|
||||||
import { configureNavigationModule } from './navigation';
|
import { configureNavigationModule } from './navigation';
|
||||||
@@ -35,4 +36,5 @@ export function configureCommonModules(framework: Framework) {
|
|||||||
configureDocLinksModule(framework);
|
configureDocLinksModule(framework);
|
||||||
configureOrganizeModule(framework);
|
configureOrganizeModule(framework);
|
||||||
configureFavoriteModule(framework);
|
configureFavoriteModule(framework);
|
||||||
|
configureExplorerModule(framework);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user