feat(core): support save and restore display preference in all docs (#12315)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Display preferences and selected collections are now saved and restored across sessions, providing a persistent and personalized experience in the All Documents page. - **Refactor** - Display settings menus and related components have been updated to use a controlled component pattern, allowing preferences to be managed externally for improved consistency and flexibility. - Preference state management has been consolidated, simplifying how display options are handled throughout the interface. - Various headers and detail views now accept display preferences and update callbacks as props, enabling external control of display settings. - Components previously relying on internal context and reactive streams were refactored to receive explicit props and callbacks for state management. - **Bug Fixes** - Improved collection activation logic to prevent unnecessary updates when the selected collection is already active. - Added fallback default view to ensure consistent display in document list items. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -12,9 +12,7 @@ export function useFramework(): FrameworkProvider {
|
|||||||
return useContext(FrameworkProviderContext); // never null, because the default value
|
return useContext(FrameworkProviderContext); // never null, because the default value
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useService<T extends Service>(
|
export function useService<T>(identifier: GeneralIdentifier<T>): T {
|
||||||
identifier: GeneralIdentifier<T>
|
|
||||||
): T {
|
|
||||||
return useContext(FrameworkProviderContext).get(identifier);
|
return useContext(FrameworkProviderContext).get(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import { LiveData } from '@toeverything/infra';
|
import { LiveData } from '@toeverything/infra';
|
||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
|
||||||
import type { DocListItemView } from './docs-view/doc-list-item';
|
import type { ExplorerDisplayPreference } from './types';
|
||||||
import type { ExplorerPreference } from './types';
|
|
||||||
|
|
||||||
export type DocExplorerContextType = {
|
export type DocExplorerContextType = {
|
||||||
view$: LiveData<DocListItemView>;
|
|
||||||
groups$: LiveData<Array<{ key: string; items: string[] }>>;
|
groups$: LiveData<Array<{ key: string; items: string[] }>>;
|
||||||
collapsedGroups$: LiveData<string[]>;
|
collapsedGroups$: LiveData<string[]>;
|
||||||
selectMode$?: LiveData<boolean>;
|
selectMode$?: LiveData<boolean>;
|
||||||
selectedDocIds$: LiveData<string[]>;
|
selectedDocIds$: LiveData<string[]>;
|
||||||
prevCheckAnchorId$?: LiveData<string | null>;
|
prevCheckAnchorId$?: LiveData<string | null>;
|
||||||
|
displayPreference$: LiveData<ExplorerDisplayPreference>;
|
||||||
} & {
|
} & {
|
||||||
[K in keyof Omit<ExplorerPreference, 'filters'> as `${K}$`]: LiveData<
|
[K in keyof ExplorerDisplayPreference as `${K}$`]: LiveData<
|
||||||
ExplorerPreference[K]
|
ExplorerDisplayPreference[K]
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -21,24 +20,51 @@ export const DocExplorerContext = createContext<DocExplorerContextType>(
|
|||||||
{} as any
|
{} as any
|
||||||
);
|
);
|
||||||
|
|
||||||
export const createDocExplorerContext = () =>
|
export const createDocExplorerContext = (
|
||||||
({
|
initialState?: ExplorerDisplayPreference
|
||||||
view$: new LiveData<DocListItemView>('list'),
|
) => {
|
||||||
|
const displayPreference$ = new LiveData<ExplorerDisplayPreference>(
|
||||||
|
initialState ?? {}
|
||||||
|
);
|
||||||
|
return {
|
||||||
groups$: new LiveData<Array<{ key: string; items: string[] }>>([]),
|
groups$: new LiveData<Array<{ key: string; items: string[] }>>([]),
|
||||||
collapsedGroups$: new LiveData<string[]>([]),
|
collapsedGroups$: new LiveData<string[]>([]),
|
||||||
selectMode$: new LiveData<boolean>(false),
|
selectMode$: new LiveData<boolean>(false),
|
||||||
selectedDocIds$: new LiveData<string[]>([]),
|
selectedDocIds$: new LiveData<string[]>([]),
|
||||||
prevCheckAnchorId$: new LiveData<string | null>(null),
|
prevCheckAnchorId$: new LiveData<string | null>(null),
|
||||||
groupBy$: new LiveData<ExplorerPreference['groupBy']>(undefined),
|
displayPreference$: displayPreference$,
|
||||||
orderBy$: new LiveData<ExplorerPreference['orderBy']>(undefined),
|
view$: displayPreference$.selector(
|
||||||
displayProperties$: new LiveData<ExplorerPreference['displayProperties']>(
|
displayPreference => displayPreference.view
|
||||||
[]
|
|
||||||
),
|
),
|
||||||
showDocIcon$: new LiveData<ExplorerPreference['showDocIcon']>(true),
|
groupBy$: displayPreference$.selector(
|
||||||
showDocPreview$: new LiveData<ExplorerPreference['showDocPreview']>(true),
|
displayPreference => displayPreference.groupBy
|
||||||
quickFavorite$: new LiveData<ExplorerPreference['quickFavorite']>(false),
|
),
|
||||||
quickSelect$: new LiveData<ExplorerPreference['quickSelect']>(false),
|
orderBy$: displayPreference$.selector(
|
||||||
quickSplit$: new LiveData<ExplorerPreference['quickSplit']>(false),
|
displayPreference => displayPreference.orderBy
|
||||||
quickTrash$: new LiveData<ExplorerPreference['quickTrash']>(false),
|
),
|
||||||
quickTab$: new LiveData<ExplorerPreference['quickTab']>(false),
|
displayProperties$: displayPreference$.selector(
|
||||||
}) satisfies DocExplorerContextType;
|
displayPreference => displayPreference.displayProperties
|
||||||
|
),
|
||||||
|
showDocIcon$: displayPreference$.selector(
|
||||||
|
displayPreference => displayPreference.showDocIcon
|
||||||
|
),
|
||||||
|
showDocPreview$: displayPreference$.selector(
|
||||||
|
displayPreference => displayPreference.showDocPreview
|
||||||
|
),
|
||||||
|
quickFavorite$: displayPreference$.selector(
|
||||||
|
displayPreference => displayPreference.quickFavorite
|
||||||
|
),
|
||||||
|
quickSelect$: displayPreference$.selector(
|
||||||
|
displayPreference => displayPreference.quickSelect
|
||||||
|
),
|
||||||
|
quickSplit$: displayPreference$.selector(
|
||||||
|
displayPreference => displayPreference.quickSplit
|
||||||
|
),
|
||||||
|
quickTrash$: displayPreference$.selector(
|
||||||
|
displayPreference => displayPreference.quickTrash
|
||||||
|
),
|
||||||
|
quickTab$: displayPreference$.selector(
|
||||||
|
displayPreference => displayPreference.quickTab
|
||||||
|
),
|
||||||
|
} satisfies DocExplorerContextType;
|
||||||
|
};
|
||||||
|
|||||||
@@ -11,63 +11,87 @@ import type {
|
|||||||
} from '@affine/core/modules/collection-rules/types';
|
} from '@affine/core/modules/collection-rules/types';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { ArrowDownSmallIcon } from '@blocksuite/icons/rc';
|
import { ArrowDownSmallIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData } from '@toeverything/infra';
|
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useCallback, useContext } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { DocExplorerContext } from '../context';
|
import type { ExplorerDisplayPreference } from '../types';
|
||||||
import { GroupByList, GroupByName } from './group';
|
import { GroupByList, GroupByName } from './group';
|
||||||
import { OrderByList, OrderByName } from './order';
|
import { OrderByList, OrderByName } from './order';
|
||||||
import { DisplayProperties } from './properties';
|
import { DisplayProperties } from './properties';
|
||||||
import { QuickActionsConfig } from './quick-actions';
|
import { QuickActionsConfig } from './quick-actions';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
const ExplorerDisplayMenu = () => {
|
const ExplorerDisplayMenu = ({
|
||||||
|
displayPreference,
|
||||||
|
onDisplayPreferenceChange,
|
||||||
|
}: {
|
||||||
|
displayPreference: ExplorerDisplayPreference;
|
||||||
|
onDisplayPreferenceChange: (
|
||||||
|
displayPreference: ExplorerDisplayPreference
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const explorerContextValue = useContext(DocExplorerContext);
|
|
||||||
const groupBy = useLiveData(explorerContextValue.groupBy$);
|
|
||||||
const orderBy = useLiveData(explorerContextValue.orderBy$);
|
|
||||||
|
|
||||||
const handleGroupByChange = useCallback(
|
const handleGroupByChange = useCallback(
|
||||||
(groupBy: GroupByParams) => {
|
(groupBy: GroupByParams) => {
|
||||||
explorerContextValue.groupBy$?.next(groupBy);
|
onDisplayPreferenceChange({ ...displayPreference, groupBy });
|
||||||
},
|
},
|
||||||
[explorerContextValue.groupBy$]
|
[displayPreference, onDisplayPreferenceChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOrderByChange = useCallback(
|
const handleOrderByChange = useCallback(
|
||||||
(orderBy: OrderByParams) => {
|
(orderBy: OrderByParams) => {
|
||||||
explorerContextValue.orderBy$?.next(orderBy);
|
onDisplayPreferenceChange({ ...displayPreference, orderBy });
|
||||||
},
|
},
|
||||||
[explorerContextValue.orderBy$]
|
[displayPreference, onDisplayPreferenceChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.displayMenuContainer}>
|
<div className={styles.displayMenuContainer}>
|
||||||
<MenuSub
|
<MenuSub
|
||||||
items={<GroupByList groupBy={groupBy} onChange={handleGroupByChange} />}
|
items={
|
||||||
|
<GroupByList
|
||||||
|
groupBy={displayPreference.groupBy}
|
||||||
|
onChange={handleGroupByChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div className={styles.subMenuSelectorContainer}>
|
<div className={styles.subMenuSelectorContainer}>
|
||||||
<span>{t['com.affine.explorer.display-menu.grouping']()}</span>
|
<span>{t['com.affine.explorer.display-menu.grouping']()}</span>
|
||||||
<span className={styles.subMenuSelectorSelected}>
|
<span className={styles.subMenuSelectorSelected}>
|
||||||
{groupBy ? <GroupByName groupBy={groupBy} /> : null}
|
{displayPreference.groupBy ? (
|
||||||
|
<GroupByName groupBy={displayPreference.groupBy} />
|
||||||
|
) : null}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</MenuSub>
|
</MenuSub>
|
||||||
<MenuSub
|
<MenuSub
|
||||||
items={<OrderByList orderBy={orderBy} onChange={handleOrderByChange} />}
|
items={
|
||||||
|
<OrderByList
|
||||||
|
orderBy={displayPreference.orderBy}
|
||||||
|
onChange={handleOrderByChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div className={styles.subMenuSelectorContainer}>
|
<div className={styles.subMenuSelectorContainer}>
|
||||||
<span>{t['com.affine.explorer.display-menu.ordering']()}</span>
|
<span>{t['com.affine.explorer.display-menu.ordering']()}</span>
|
||||||
<span className={styles.subMenuSelectorSelected}>
|
<span className={styles.subMenuSelectorSelected}>
|
||||||
{orderBy ? <OrderByName orderBy={orderBy} /> : null}
|
{displayPreference.orderBy ? (
|
||||||
|
<OrderByName orderBy={displayPreference.orderBy} />
|
||||||
|
) : null}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</MenuSub>
|
</MenuSub>
|
||||||
<Divider size="thinner" />
|
<Divider size="thinner" />
|
||||||
<DisplayProperties />
|
<DisplayProperties
|
||||||
|
displayPreference={displayPreference}
|
||||||
|
onDisplayPreferenceChange={onDisplayPreferenceChange}
|
||||||
|
/>
|
||||||
<Divider size="thinner" />
|
<Divider size="thinner" />
|
||||||
<QuickActionsConfig />
|
<QuickActionsConfig
|
||||||
|
displayPreference={displayPreference}
|
||||||
|
onDisplayPreferenceChange={onDisplayPreferenceChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -76,14 +100,28 @@ export const ExplorerDisplayMenuButton = ({
|
|||||||
style,
|
style,
|
||||||
className,
|
className,
|
||||||
menuProps,
|
menuProps,
|
||||||
|
displayPreference,
|
||||||
|
onDisplayPreferenceChange,
|
||||||
}: {
|
}: {
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
className?: string;
|
className?: string;
|
||||||
menuProps?: Omit<MenuProps, 'items' | 'children'>;
|
menuProps?: Omit<MenuProps, 'items' | 'children'>;
|
||||||
|
displayPreference: ExplorerDisplayPreference;
|
||||||
|
onDisplayPreferenceChange: (
|
||||||
|
displayPreference: ExplorerDisplayPreference
|
||||||
|
) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
return (
|
return (
|
||||||
<Menu items={<ExplorerDisplayMenu />} {...menuProps}>
|
<Menu
|
||||||
|
items={
|
||||||
|
<ExplorerDisplayMenu
|
||||||
|
displayPreference={displayPreference}
|
||||||
|
onDisplayPreferenceChange={onDisplayPreferenceChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{...menuProps}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
className={className}
|
className={className}
|
||||||
style={style}
|
style={style}
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import {
|
|||||||
} from '@affine/core/modules/workspace-property';
|
} from '@affine/core/modules/workspace-property';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback, useContext, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { WorkspacePropertyName } from '../../properties';
|
import { WorkspacePropertyName } from '../../properties';
|
||||||
import { WorkspacePropertyTypes } from '../../workspace-property-types';
|
import { WorkspacePropertyTypes } from '../../workspace-property-types';
|
||||||
import { DocExplorerContext } from '../context';
|
import type { ExplorerDisplayPreference } from '../types';
|
||||||
import * as styles from './properties.css';
|
import * as styles from './properties.css';
|
||||||
|
|
||||||
export const filterDisplayProperties = <
|
export const filterDisplayProperties = <
|
||||||
@@ -29,17 +29,22 @@ export const filterDisplayProperties = <
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DisplayProperties = () => {
|
export const DisplayProperties = ({
|
||||||
|
displayPreference,
|
||||||
|
onDisplayPreferenceChange,
|
||||||
|
}: {
|
||||||
|
displayPreference: ExplorerDisplayPreference;
|
||||||
|
onDisplayPreferenceChange: (
|
||||||
|
displayPreference: ExplorerDisplayPreference
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const explorerContextValue = useContext(DocExplorerContext);
|
|
||||||
const workspacePropertyService = useService(WorkspacePropertyService);
|
const workspacePropertyService = useService(WorkspacePropertyService);
|
||||||
const propertyList = useLiveData(workspacePropertyService.properties$);
|
const propertyList = useLiveData(workspacePropertyService.properties$);
|
||||||
|
|
||||||
const displayProperties = useLiveData(
|
const displayProperties = displayPreference.displayProperties;
|
||||||
explorerContextValue.displayProperties$
|
const showIcon = displayPreference.showDocIcon ?? false;
|
||||||
);
|
const showBody = displayPreference.showDocPreview ?? false;
|
||||||
const showIcon = useLiveData(explorerContextValue.showDocIcon$);
|
|
||||||
const showBody = useLiveData(explorerContextValue.showDocPreview$);
|
|
||||||
|
|
||||||
const propertiesGroups = useMemo(
|
const propertiesGroups = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@@ -57,9 +62,9 @@ export const DisplayProperties = () => {
|
|||||||
|
|
||||||
const handleDisplayPropertiesChange = useCallback(
|
const handleDisplayPropertiesChange = useCallback(
|
||||||
(displayProperties: string[]) => {
|
(displayProperties: string[]) => {
|
||||||
explorerContextValue.displayProperties$?.next(displayProperties);
|
onDisplayPreferenceChange({ ...displayPreference, displayProperties });
|
||||||
},
|
},
|
||||||
[explorerContextValue.displayProperties$]
|
[displayPreference, onDisplayPreferenceChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePropertyClick = useCallback(
|
const handlePropertyClick = useCallback(
|
||||||
@@ -74,12 +79,18 @@ export const DisplayProperties = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const toggleIcon = useCallback(() => {
|
const toggleIcon = useCallback(() => {
|
||||||
explorerContextValue.showDocIcon$?.next(!showIcon);
|
onDisplayPreferenceChange({
|
||||||
}, [explorerContextValue.showDocIcon$, showIcon]);
|
...displayPreference,
|
||||||
|
showDocIcon: !showIcon,
|
||||||
|
});
|
||||||
|
}, [displayPreference, onDisplayPreferenceChange, showIcon]);
|
||||||
|
|
||||||
const toggleBody = useCallback(() => {
|
const toggleBody = useCallback(() => {
|
||||||
explorerContextValue.showDocPreview$?.next(!showBody);
|
onDisplayPreferenceChange({
|
||||||
}, [explorerContextValue.showDocPreview$, showBody]);
|
...displayPreference,
|
||||||
|
showDocPreview: !showBody,
|
||||||
|
});
|
||||||
|
}, [displayPreference, onDisplayPreferenceChange, showBody]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
@@ -94,7 +105,9 @@ export const DisplayProperties = () => {
|
|||||||
<Button
|
<Button
|
||||||
key={property.id}
|
key={property.id}
|
||||||
data-show={
|
data-show={
|
||||||
displayProperties && displayProperties.includes(property.id)
|
displayProperties
|
||||||
|
? displayProperties.includes(property.id)
|
||||||
|
: false
|
||||||
}
|
}
|
||||||
onClick={() => handlePropertyClick(property.id)}
|
onClick={() => handlePropertyClick(property.id)}
|
||||||
className={styles.property}
|
className={styles.property}
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import { Checkbox, MenuItem, MenuSub } from '@affine/component';
|
import { Checkbox, MenuItem, MenuSub } from '@affine/component';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData } from '@toeverything/infra';
|
import { useCallback } from 'react';
|
||||||
import { useCallback, useContext } from 'react';
|
|
||||||
|
|
||||||
import { DocExplorerContext } from '../context';
|
|
||||||
import { type QuickAction, quickActions } from '../quick-actions.constants';
|
import { type QuickAction, quickActions } from '../quick-actions.constants';
|
||||||
|
import type { ExplorerDisplayPreference } from '../types';
|
||||||
|
|
||||||
export const QuickActionsConfig = () => {
|
export const QuickActionsConfig = ({
|
||||||
|
displayPreference,
|
||||||
|
onDisplayPreferenceChange,
|
||||||
|
}: {
|
||||||
|
displayPreference: ExplorerDisplayPreference;
|
||||||
|
onDisplayPreferenceChange: (
|
||||||
|
displayPreference: ExplorerDisplayPreference
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -14,7 +21,19 @@ export const QuickActionsConfig = () => {
|
|||||||
items={quickActions.map(action => {
|
items={quickActions.map(action => {
|
||||||
if (action.disabled) return null;
|
if (action.disabled) return null;
|
||||||
|
|
||||||
return <QuickActionItem key={action.key} action={action} />;
|
return (
|
||||||
|
<QuickActionItem
|
||||||
|
key={action.key}
|
||||||
|
action={action}
|
||||||
|
active={displayPreference[`${action.key}`] ?? false}
|
||||||
|
onClick={() => {
|
||||||
|
onDisplayPreferenceChange({
|
||||||
|
...displayPreference,
|
||||||
|
[action.key]: !displayPreference[action.key],
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{t['com.affine.all-docs.quick-actions']()}
|
{t['com.affine.all-docs.quick-actions']()}
|
||||||
@@ -22,24 +41,28 @@ export const QuickActionsConfig = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const QuickActionItem = ({ action }: { action: QuickAction }) => {
|
const QuickActionItem = ({
|
||||||
|
action,
|
||||||
|
active,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
action: QuickAction;
|
||||||
|
active: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const explorerContextValue = useContext(DocExplorerContext);
|
|
||||||
|
|
||||||
const value = useLiveData(explorerContextValue[`${action.key}$`]);
|
|
||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(e: React.MouseEvent) => {
|
(e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const value = explorerContextValue[`${action.key}$`]?.value;
|
onClick();
|
||||||
explorerContextValue[`${action.key}$`]?.next(!value);
|
|
||||||
},
|
},
|
||||||
[action.key, explorerContextValue]
|
[onClick]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem prefixIcon={<Checkbox checked={!!value} />} onClick={handleClick}>
|
<MenuItem prefixIcon={<Checkbox checked={active} />} onClick={handleClick}>
|
||||||
{t.t(action.name)}
|
{t.t(action.name)}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { RadioGroup, type RadioItem } from '@affine/component';
|
import { RadioGroup, type RadioItem } from '@affine/component';
|
||||||
import { useLiveData } from '@toeverything/infra';
|
import { useCallback } from 'react';
|
||||||
import { useCallback, useContext } from 'react';
|
|
||||||
|
|
||||||
import { DocExplorerContext } from '../context';
|
|
||||||
import {
|
import {
|
||||||
type DocListItemView,
|
type DocListItemView,
|
||||||
DocListViewIcon,
|
DocListViewIcon,
|
||||||
@@ -27,16 +25,18 @@ const views = [
|
|||||||
},
|
},
|
||||||
] satisfies RadioItem[];
|
] satisfies RadioItem[];
|
||||||
|
|
||||||
export const ViewToggle = () => {
|
export const ViewToggle = ({
|
||||||
const explorerContextValue = useContext(DocExplorerContext);
|
view,
|
||||||
|
onViewChange,
|
||||||
const view = useLiveData(explorerContextValue.view$);
|
}: {
|
||||||
|
view: DocListItemView;
|
||||||
|
onViewChange: (view: DocListItemView) => void;
|
||||||
|
}) => {
|
||||||
const handleViewChange = useCallback(
|
const handleViewChange = useCallback(
|
||||||
(view: DocListItemView) => {
|
(view: DocListItemView) => {
|
||||||
explorerContextValue.view$?.next(view);
|
onViewChange(view);
|
||||||
},
|
},
|
||||||
[explorerContextValue.view$]
|
[onViewChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class MixId {
|
|||||||
}
|
}
|
||||||
export const DocListItem = ({ ...props }: DocListItemProps) => {
|
export const DocListItem = ({ ...props }: DocListItemProps) => {
|
||||||
const contextValue = useContext(DocExplorerContext);
|
const contextValue = useContext(DocExplorerContext);
|
||||||
const view = useLiveData(contextValue.view$);
|
const view = useLiveData(contextValue.view$) ?? 'list';
|
||||||
const groups = useLiveData(contextValue.groups$);
|
const groups = useLiveData(contextValue.groups$);
|
||||||
const selectMode = useLiveData(contextValue.selectMode$);
|
const selectMode = useLiveData(contextValue.selectMode$);
|
||||||
const selectedDocIds = useLiveData(contextValue.selectedDocIds$);
|
const selectedDocIds = useLiveData(contextValue.selectedDocIds$);
|
||||||
@@ -255,7 +255,7 @@ const Select = memo(function Select({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
// Different with RawDocIcon, refer to `ExplorerPreference.showDocIcon`
|
// Different with RawDocIcon, refer to `ExplorerDisplayPreference.showDocIcon`
|
||||||
const DocIcon = memo(function DocIcon({
|
const DocIcon = memo(function DocIcon({
|
||||||
id,
|
id,
|
||||||
...props
|
...props
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
QuickSplit,
|
QuickSplit,
|
||||||
QuickTab,
|
QuickTab,
|
||||||
} from './docs-view/quick-actions';
|
} from './docs-view/quick-actions';
|
||||||
import type { ExplorerPreference } from './types';
|
import type { ExplorerDisplayPreference } from './types';
|
||||||
|
|
||||||
interface QuickActionItem {
|
interface QuickActionItem {
|
||||||
name: I18nString;
|
name: I18nString;
|
||||||
@@ -20,7 +20,10 @@ type ExtractPrefixKeys<Obj extends object, Prefix extends string> = {
|
|||||||
[Key in keyof Obj]-?: Key extends `${Prefix}${string}` ? Key : never;
|
[Key in keyof Obj]-?: Key extends `${Prefix}${string}` ? Key : never;
|
||||||
}[keyof Obj];
|
}[keyof Obj];
|
||||||
|
|
||||||
export type QuickActionKey = ExtractPrefixKeys<ExplorerPreference, 'quick'>;
|
export type QuickActionKey = ExtractPrefixKeys<
|
||||||
|
ExplorerDisplayPreference,
|
||||||
|
'quick'
|
||||||
|
>;
|
||||||
|
|
||||||
const QUICK_ACTION_MAP: Record<QuickActionKey, QuickActionItem> = {
|
const QUICK_ACTION_MAP: Record<QuickActionKey, QuickActionItem> = {
|
||||||
quickFavorite: {
|
quickFavorite: {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { FilterParams } from '@affine/core/modules/collection-rules';
|
|
||||||
import type {
|
import type {
|
||||||
GroupByParams,
|
GroupByParams,
|
||||||
OrderByParams,
|
OrderByParams,
|
||||||
@@ -6,8 +5,10 @@ import type {
|
|||||||
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
|
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
|
||||||
import type { DocRecord } from '@affine/core/modules/doc';
|
import type { DocRecord } from '@affine/core/modules/doc';
|
||||||
|
|
||||||
export interface ExplorerPreference {
|
import type { DocListItemView } from './docs-view/doc-list-item';
|
||||||
filters?: FilterParams[];
|
|
||||||
|
export interface ExplorerDisplayPreference {
|
||||||
|
view?: DocListItemView;
|
||||||
groupBy?: GroupByParams;
|
groupBy?: GroupByParams;
|
||||||
orderBy?: OrderByParams;
|
orderBy?: OrderByParams;
|
||||||
displayProperties?: string[];
|
displayProperties?: string[];
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { type MenuProps } from '@affine/component';
|
import { type MenuProps } from '@affine/component';
|
||||||
import { ExplorerDisplayMenuButton } from '@affine/core/components/explorer/display-menu';
|
import { ExplorerDisplayMenuButton } from '@affine/core/components/explorer/display-menu';
|
||||||
import { ViewToggle } from '@affine/core/components/explorer/display-menu/view-toggle';
|
import { ViewToggle } from '@affine/core/components/explorer/display-menu/view-toggle';
|
||||||
|
import type { DocListItemView } from '@affine/core/components/explorer/docs-view/doc-list-item';
|
||||||
import { ExplorerNavigation } from '@affine/core/components/explorer/header/navigation';
|
import { ExplorerNavigation } from '@affine/core/components/explorer/header/navigation';
|
||||||
|
import type { ExplorerDisplayPreference } from '@affine/core/components/explorer/types';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import * as styles from './all-page-header.css';
|
import * as styles from './all-page-header.css';
|
||||||
|
|
||||||
@@ -13,14 +16,36 @@ const menuProps: Partial<MenuProps> = {
|
|||||||
sideOffset: 8,
|
sideOffset: 8,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
export const AllDocsHeader = () => {
|
export const AllDocsHeader = ({
|
||||||
|
displayPreference,
|
||||||
|
onDisplayPreferenceChange,
|
||||||
|
}: {
|
||||||
|
displayPreference: ExplorerDisplayPreference;
|
||||||
|
onDisplayPreferenceChange: (
|
||||||
|
displayPreference: ExplorerDisplayPreference
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
|
const handleViewChange = useCallback(
|
||||||
|
(view: DocListItemView) => {
|
||||||
|
onDisplayPreferenceChange({ ...displayPreference, view });
|
||||||
|
},
|
||||||
|
[displayPreference, onDisplayPreferenceChange]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<ExplorerNavigation active="docs" />
|
<ExplorerNavigation active="docs" />
|
||||||
|
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
<ViewToggle />
|
<ViewToggle
|
||||||
<ExplorerDisplayMenuButton menuProps={menuProps} />
|
view={displayPreference.view ?? 'list'}
|
||||||
|
onViewChange={handleViewChange}
|
||||||
|
/>
|
||||||
|
<ExplorerDisplayMenuButton
|
||||||
|
menuProps={menuProps}
|
||||||
|
displayPreference={displayPreference}
|
||||||
|
onDisplayPreferenceChange={onDisplayPreferenceChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
DocExplorerContext,
|
DocExplorerContext,
|
||||||
} from '@affine/core/components/explorer/context';
|
} from '@affine/core/components/explorer/context';
|
||||||
import { DocsExplorer } from '@affine/core/components/explorer/docs-view/docs-list';
|
import { DocsExplorer } from '@affine/core/components/explorer/docs-view/docs-list';
|
||||||
|
import type { ExplorerDisplayPreference } from '@affine/core/components/explorer/types';
|
||||||
import { Filters } from '@affine/core/components/filter';
|
import { Filters } from '@affine/core/components/filter';
|
||||||
import {
|
import {
|
||||||
CollectionService,
|
CollectionService,
|
||||||
@@ -12,9 +13,10 @@ import {
|
|||||||
import { CollectionRulesService } from '@affine/core/modules/collection-rules';
|
import { CollectionRulesService } from '@affine/core/modules/collection-rules';
|
||||||
import type { FilterParams } from '@affine/core/modules/collection-rules/types';
|
import type { FilterParams } from '@affine/core/modules/collection-rules/types';
|
||||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
|
import { WorkspaceLocalState } from '@affine/core/modules/workspace';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ViewBody,
|
ViewBody,
|
||||||
@@ -29,11 +31,22 @@ import { AllDocsHeader } from './all-page-header';
|
|||||||
import { MigrationAllDocsDataNotification } from './migration-data';
|
import { MigrationAllDocsDataNotification } from './migration-data';
|
||||||
import { PinnedCollections } from './pinned-collections';
|
import { PinnedCollections } from './pinned-collections';
|
||||||
|
|
||||||
|
interface AllDocsStateSave extends ExplorerDisplayPreference {
|
||||||
|
selectedCollectionId: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export const AllPage = () => {
|
export const AllPage = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
const collectionService = useService(CollectionService);
|
const collectionService = useService(CollectionService);
|
||||||
const pinnedCollectionService = useService(PinnedCollectionService);
|
const pinnedCollectionService = useService(PinnedCollectionService);
|
||||||
|
const workspaceLocalState = useService(WorkspaceLocalState);
|
||||||
|
|
||||||
|
const [initialState] = useState(() => {
|
||||||
|
return workspaceLocalState.get<AllDocsStateSave>(
|
||||||
|
'allDocsDisplayPreference'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const isCollectionDataReady = useLiveData(
|
const isCollectionDataReady = useLiveData(
|
||||||
collectionService.collectionDataReady$
|
collectionService.collectionDataReady$
|
||||||
@@ -49,7 +62,7 @@ export const AllPage = () => {
|
|||||||
|
|
||||||
const [selectedCollectionId, setSelectedCollectionId] = useState<
|
const [selectedCollectionId, setSelectedCollectionId] = useState<
|
||||||
string | null
|
string | null
|
||||||
>(null);
|
>(initialState?.selectedCollectionId ?? null);
|
||||||
const selectedCollection = useLiveData(
|
const selectedCollection = useLiveData(
|
||||||
selectedCollectionId
|
selectedCollectionId
|
||||||
? collectionService.collection$(selectedCollectionId)
|
? collectionService.collection$(selectedCollectionId)
|
||||||
@@ -80,10 +93,26 @@ export const AllPage = () => {
|
|||||||
|
|
||||||
const [tempFilters, setTempFilters] = useState<FilterParams[] | null>(null);
|
const [tempFilters, setTempFilters] = useState<FilterParams[] | null>(null);
|
||||||
|
|
||||||
const [explorerContextValue] = useState(createDocExplorerContext);
|
const [explorerContextValue] = useState(() =>
|
||||||
|
createDocExplorerContext(initialState)
|
||||||
|
);
|
||||||
|
|
||||||
const groupBy = useLiveData(explorerContextValue.groupBy$);
|
const groupBy = useLiveData(explorerContextValue.groupBy$);
|
||||||
const orderBy = useLiveData(explorerContextValue.orderBy$);
|
const orderBy = useLiveData(explorerContextValue.orderBy$);
|
||||||
|
const displayPreference = useLiveData(
|
||||||
|
explorerContextValue.displayPreference$
|
||||||
|
);
|
||||||
|
|
||||||
|
const allDocsStateSave = useMemo(() => {
|
||||||
|
return {
|
||||||
|
...displayPreference,
|
||||||
|
selectedCollectionId,
|
||||||
|
};
|
||||||
|
}, [displayPreference, selectedCollectionId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
workspaceLocalState.set('allDocsDisplayPreference', allDocsStateSave);
|
||||||
|
}, [allDocsStateSave, workspaceLocalState]);
|
||||||
|
|
||||||
const { openPromptModal } = usePromptModal();
|
const { openPromptModal } = usePromptModal();
|
||||||
|
|
||||||
@@ -187,6 +216,11 @@ export const AllPage = () => {
|
|||||||
setTempFilters(null);
|
setTempFilters(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleSelectAll = useCallback(() => {
|
||||||
|
setSelectedCollectionId(null);
|
||||||
|
setTempFilters(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleEditCollection = useCallback(
|
const handleEditCollection = useCallback(
|
||||||
(collectionId: string) => {
|
(collectionId: string) => {
|
||||||
const collection = collectionService.collection$(collectionId).value;
|
const collection = collectionService.collection$(collectionId).value;
|
||||||
@@ -250,12 +284,22 @@ export const AllPage = () => {
|
|||||||
setTempFilters([params]);
|
setTempFilters([params]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleDisplayPreferenceChange = useCallback(
|
||||||
|
(displayPreference: ExplorerDisplayPreference) => {
|
||||||
|
explorerContextValue.displayPreference$.next(displayPreference);
|
||||||
|
},
|
||||||
|
[explorerContextValue]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DocExplorerContext.Provider value={explorerContextValue}>
|
<DocExplorerContext.Provider value={explorerContextValue}>
|
||||||
<ViewTitle title={t['All pages']()} />
|
<ViewTitle title={t['All pages']()} />
|
||||||
<ViewIcon icon="allDocs" />
|
<ViewIcon icon="allDocs" />
|
||||||
<ViewHeader>
|
<ViewHeader>
|
||||||
<AllDocsHeader />
|
<AllDocsHeader
|
||||||
|
displayPreference={displayPreference}
|
||||||
|
onDisplayPreferenceChange={handleDisplayPreferenceChange}
|
||||||
|
/>
|
||||||
</ViewHeader>
|
</ViewHeader>
|
||||||
<ViewBody>
|
<ViewBody>
|
||||||
<div className={styles.body}>
|
<div className={styles.body}>
|
||||||
@@ -263,7 +307,7 @@ export const AllPage = () => {
|
|||||||
<div className={styles.pinnedCollection}>
|
<div className={styles.pinnedCollection}>
|
||||||
<PinnedCollections
|
<PinnedCollections
|
||||||
activeCollectionId={selectedCollectionId}
|
activeCollectionId={selectedCollectionId}
|
||||||
onActiveAll={() => setSelectedCollectionId(null)}
|
onActiveAll={handleSelectAll}
|
||||||
onActiveCollection={handleSelectCollection}
|
onActiveCollection={handleSelectCollection}
|
||||||
onAddFilter={handleNewTempFilter}
|
onAddFilter={handleNewTempFilter}
|
||||||
onEditCollection={handleEditCollection}
|
onEditCollection={handleEditCollection}
|
||||||
|
|||||||
@@ -108,7 +108,10 @@ export const PinnedCollections = ({
|
|||||||
<div
|
<div
|
||||||
className={styles.item}
|
className={styles.item}
|
||||||
data-active={activeCollectionId === null ? 'true' : undefined}
|
data-active={activeCollectionId === null ? 'true' : undefined}
|
||||||
onClick={onActiveAll}
|
onClick={() =>
|
||||||
|
// only fire onActiveAll if the collection is not already active
|
||||||
|
activeCollectionId !== null ? onActiveAll() : undefined
|
||||||
|
}
|
||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
{t['com.affine.all-docs.pinned-collection.all']()}
|
{t['com.affine.all-docs.pinned-collection.all']()}
|
||||||
@@ -118,7 +121,12 @@ export const PinnedCollections = ({
|
|||||||
key={record.collectionId}
|
key={record.collectionId}
|
||||||
record={record}
|
record={record}
|
||||||
isActive={activeCollectionId === record.collectionId}
|
isActive={activeCollectionId === record.collectionId}
|
||||||
onClick={() => onActiveCollection(record.collectionId)}
|
onClick={() =>
|
||||||
|
// only fire onActiveCollection if the collection is not already active
|
||||||
|
activeCollectionId !== record.collectionId
|
||||||
|
? onActiveCollection(record.collectionId)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
onClickEdit={() => onEditCollection(record.collectionId)}
|
onClickEdit={() => onEditCollection(record.collectionId)}
|
||||||
onClickRemove={() => {
|
onClickRemove={() => {
|
||||||
const nextCollectionId = pinnedCollections[index - 1]?.collectionId;
|
const nextCollectionId = pinnedCollections[index - 1]?.collectionId;
|
||||||
|
|||||||
@@ -2,15 +2,32 @@ import { FlexWrapper } from '@affine/component';
|
|||||||
import { ExplorerDisplayMenuButton } from '@affine/core/components/explorer/display-menu';
|
import { ExplorerDisplayMenuButton } from '@affine/core/components/explorer/display-menu';
|
||||||
import { ViewToggle } from '@affine/core/components/explorer/display-menu/view-toggle';
|
import { ViewToggle } from '@affine/core/components/explorer/display-menu/view-toggle';
|
||||||
import { ExplorerNavigation } from '@affine/core/components/explorer/header/navigation';
|
import { ExplorerNavigation } from '@affine/core/components/explorer/header/navigation';
|
||||||
|
import type { ExplorerDisplayPreference } from '@affine/core/components/explorer/types';
|
||||||
import { Header } from '@affine/core/components/pure/header';
|
import { Header } from '@affine/core/components/pure/header';
|
||||||
|
|
||||||
export const CollectionDetailHeader = () => {
|
export const CollectionDetailHeader = ({
|
||||||
|
displayPreference,
|
||||||
|
onDisplayPreferenceChange,
|
||||||
|
}: {
|
||||||
|
displayPreference: ExplorerDisplayPreference;
|
||||||
|
onDisplayPreferenceChange: (
|
||||||
|
displayPreference: ExplorerDisplayPreference
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Header
|
<Header
|
||||||
right={
|
right={
|
||||||
<FlexWrapper gap={16}>
|
<FlexWrapper gap={16}>
|
||||||
<ViewToggle />
|
<ViewToggle
|
||||||
<ExplorerDisplayMenuButton />
|
view={displayPreference.view ?? 'list'}
|
||||||
|
onViewChange={view => {
|
||||||
|
onDisplayPreferenceChange({ ...displayPreference, view });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ExplorerDisplayMenuButton
|
||||||
|
displayPreference={displayPreference}
|
||||||
|
onDisplayPreferenceChange={onDisplayPreferenceChange}
|
||||||
|
/>
|
||||||
</FlexWrapper>
|
</FlexWrapper>
|
||||||
}
|
}
|
||||||
left={<ExplorerNavigation active="collections" />}
|
left={<ExplorerNavigation active="collections" />}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
DocExplorerContext,
|
DocExplorerContext,
|
||||||
} from '@affine/core/components/explorer/context';
|
} from '@affine/core/components/explorer/context';
|
||||||
import { DocsExplorer } from '@affine/core/components/explorer/docs-view/docs-list';
|
import { DocsExplorer } from '@affine/core/components/explorer/docs-view/docs-list';
|
||||||
|
import type { ExplorerDisplayPreference } from '@affine/core/components/explorer/types';
|
||||||
import {
|
import {
|
||||||
type Collection,
|
type Collection,
|
||||||
CollectionService,
|
CollectionService,
|
||||||
@@ -45,11 +46,21 @@ export const CollectionDetail = ({
|
|||||||
const isAdmin = useLiveData(permissionService.permission.isAdmin$);
|
const isAdmin = useLiveData(permissionService.permission.isAdmin$);
|
||||||
const isOwner = useLiveData(permissionService.permission.isOwner$);
|
const isOwner = useLiveData(permissionService.permission.isOwner$);
|
||||||
|
|
||||||
|
const displayPreference = useLiveData(
|
||||||
|
explorerContextValue.displayPreference$
|
||||||
|
);
|
||||||
const groupBy = useLiveData(explorerContextValue.groupBy$);
|
const groupBy = useLiveData(explorerContextValue.groupBy$);
|
||||||
const orderBy = useLiveData(explorerContextValue.orderBy$);
|
const orderBy = useLiveData(explorerContextValue.orderBy$);
|
||||||
const rules = useLiveData(collection.rules$);
|
const rules = useLiveData(collection.rules$);
|
||||||
const allowList = useLiveData(collection.allowList$);
|
const allowList = useLiveData(collection.allowList$);
|
||||||
|
|
||||||
|
const handleDisplayPreferenceChange = useCallback(
|
||||||
|
(displayPreference: ExplorerDisplayPreference) => {
|
||||||
|
explorerContextValue.displayPreference$.next(displayPreference);
|
||||||
|
},
|
||||||
|
[explorerContextValue]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = collectionRulesService
|
const subscription = collectionRulesService
|
||||||
.watch({
|
.watch({
|
||||||
@@ -95,7 +106,10 @@ export const CollectionDetail = ({
|
|||||||
return (
|
return (
|
||||||
<DocExplorerContext.Provider value={explorerContextValue}>
|
<DocExplorerContext.Provider value={explorerContextValue}>
|
||||||
<ViewHeader>
|
<ViewHeader>
|
||||||
<CollectionDetailHeader />
|
<CollectionDetailHeader
|
||||||
|
displayPreference={displayPreference}
|
||||||
|
onDisplayPreferenceChange={handleDisplayPreferenceChange}
|
||||||
|
/>
|
||||||
</ViewHeader>
|
</ViewHeader>
|
||||||
<ViewBody>
|
<ViewBody>
|
||||||
<FlexWrapper flexDirection="column" alignItems="stretch" width="100%">
|
<FlexWrapper flexDirection="column" alignItems="stretch" width="100%">
|
||||||
|
|||||||
@@ -1,12 +1,26 @@
|
|||||||
import { ExplorerDisplayMenuButton } from '@affine/core/components/explorer/display-menu';
|
import { ExplorerDisplayMenuButton } from '@affine/core/components/explorer/display-menu';
|
||||||
import { ExplorerNavigation } from '@affine/core/components/explorer/header/navigation';
|
import { ExplorerNavigation } from '@affine/core/components/explorer/header/navigation';
|
||||||
|
import type { ExplorerDisplayPreference } from '@affine/core/components/explorer/types';
|
||||||
import { Header } from '@affine/core/components/pure/header';
|
import { Header } from '@affine/core/components/pure/header';
|
||||||
|
|
||||||
export const TagDetailHeader = () => {
|
export const TagDetailHeader = ({
|
||||||
|
displayPreference,
|
||||||
|
onDisplayPreferenceChange,
|
||||||
|
}: {
|
||||||
|
displayPreference: ExplorerDisplayPreference;
|
||||||
|
onDisplayPreferenceChange: (
|
||||||
|
displayPreference: ExplorerDisplayPreference
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Header
|
<Header
|
||||||
left={<ExplorerNavigation active={'tags'} />}
|
left={<ExplorerNavigation active={'tags'} />}
|
||||||
right={<ExplorerDisplayMenuButton />}
|
right={
|
||||||
|
<ExplorerDisplayMenuButton
|
||||||
|
displayPreference={displayPreference}
|
||||||
|
onDisplayPreferenceChange={onDisplayPreferenceChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
DocExplorerContext,
|
DocExplorerContext,
|
||||||
} from '@affine/core/components/explorer/context';
|
} from '@affine/core/components/explorer/context';
|
||||||
import { DocsExplorer } from '@affine/core/components/explorer/docs-view/docs-list';
|
import { DocsExplorer } from '@affine/core/components/explorer/docs-view/docs-list';
|
||||||
|
import type { ExplorerDisplayPreference } from '@affine/core/components/explorer/types';
|
||||||
import { CollectionRulesService } from '@affine/core/modules/collection-rules';
|
import { CollectionRulesService } from '@affine/core/modules/collection-rules';
|
||||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||||
@@ -15,7 +16,7 @@ import {
|
|||||||
ViewTitle,
|
ViewTitle,
|
||||||
} from '@affine/core/modules/workbench';
|
} from '@affine/core/modules/workbench';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { PageNotFound } from '../../404';
|
import { PageNotFound } from '../../404';
|
||||||
@@ -36,6 +37,9 @@ export const TagDetail = ({ tagId }: { tagId?: string }) => {
|
|||||||
const tagList = useService(TagService).tagList;
|
const tagList = useService(TagService).tagList;
|
||||||
const currentTag = useLiveData(tagList.tagByTagId$(tagId));
|
const currentTag = useLiveData(tagList.tagByTagId$(tagId));
|
||||||
|
|
||||||
|
const displayPreference = useLiveData(
|
||||||
|
explorerContextValue.displayPreference$
|
||||||
|
);
|
||||||
const groupBy = useLiveData(explorerContextValue.groupBy$);
|
const groupBy = useLiveData(explorerContextValue.groupBy$);
|
||||||
const orderBy = useLiveData(explorerContextValue.orderBy$);
|
const orderBy = useLiveData(explorerContextValue.orderBy$);
|
||||||
const groups = useLiveData(explorerContextValue.groups$);
|
const groups = useLiveData(explorerContextValue.groups$);
|
||||||
@@ -109,12 +113,23 @@ export const TagDetail = ({ tagId }: { tagId?: string }) => {
|
|||||||
return <PageNotFound />;
|
return <PageNotFound />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
const handleDisplayPreferenceChange = useCallback(
|
||||||
|
(displayPreference: ExplorerDisplayPreference) => {
|
||||||
|
explorerContextValue.displayPreference$.next(displayPreference);
|
||||||
|
},
|
||||||
|
[explorerContextValue]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DocExplorerContext.Provider value={explorerContextValue}>
|
<DocExplorerContext.Provider value={explorerContextValue}>
|
||||||
<ViewTitle title={tagName ?? 'Untitled'} />
|
<ViewTitle title={tagName ?? 'Untitled'} />
|
||||||
<ViewIcon icon="tag" />
|
<ViewIcon icon="tag" />
|
||||||
<ViewHeader>
|
<ViewHeader>
|
||||||
<TagDetailHeader />
|
<TagDetailHeader
|
||||||
|
displayPreference={displayPreference}
|
||||||
|
onDisplayPreferenceChange={handleDisplayPreferenceChange}
|
||||||
|
/>
|
||||||
</ViewHeader>
|
</ViewHeader>
|
||||||
<ViewBody>
|
<ViewBody>
|
||||||
<div className={styles.body}>
|
<div className={styles.body}>
|
||||||
|
|||||||
Reference in New Issue
Block a user