mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-07 01:09:54 +08:00
feat(core): persist right-sidebar open state and resize width (#10120)
This commit is contained in:
@@ -19,6 +19,7 @@ export interface ResizeHandleProps
|
|||||||
onOpen: (open: boolean) => void;
|
onOpen: (open: boolean) => void;
|
||||||
onResizing: (resizing: boolean) => void;
|
onResizing: (resizing: boolean) => void;
|
||||||
onWidthChange: (width: number) => void;
|
onWidthChange: (width: number) => void;
|
||||||
|
onWidthChanged?: (width: number) => void;
|
||||||
tooltip?: TooltipProps['content'];
|
tooltip?: TooltipProps['content'];
|
||||||
tooltipShortcut?: TooltipProps['shortcut'];
|
tooltipShortcut?: TooltipProps['shortcut'];
|
||||||
tooltipOptions?: Partial<Omit<TooltipProps, 'content' | 'shortcut'>>;
|
tooltipOptions?: Partial<Omit<TooltipProps, 'content' | 'shortcut'>>;
|
||||||
@@ -49,6 +50,7 @@ export interface ResizePanelProps
|
|||||||
onOpen: (open: boolean) => void;
|
onOpen: (open: boolean) => void;
|
||||||
onResizing: (resizing: boolean) => void;
|
onResizing: (resizing: boolean) => void;
|
||||||
onWidthChange: (width: number) => void;
|
onWidthChange: (width: number) => void;
|
||||||
|
onWidthChanged?: (width: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResizeHandle = ({
|
const ResizeHandle = ({
|
||||||
@@ -64,6 +66,7 @@ const ResizeHandle = ({
|
|||||||
onOpen,
|
onOpen,
|
||||||
onResizing,
|
onResizing,
|
||||||
onWidthChange,
|
onWidthChange,
|
||||||
|
onWidthChanged,
|
||||||
tooltip,
|
tooltip,
|
||||||
tooltipShortcut,
|
tooltipShortcut,
|
||||||
tooltipOptions,
|
tooltipOptions,
|
||||||
@@ -84,6 +87,7 @@ const ResizeHandle = ({
|
|||||||
const { left: anchorLeft, right: anchorRight } =
|
const { left: anchorLeft, right: anchorRight } =
|
||||||
panelContainer.getBoundingClientRect();
|
panelContainer.getBoundingClientRect();
|
||||||
|
|
||||||
|
let lastWidth: number;
|
||||||
function onMouseMove(e: MouseEvent) {
|
function onMouseMove(e: MouseEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!panelContainer) return;
|
if (!panelContainer) return;
|
||||||
@@ -96,6 +100,7 @@ const ResizeHandle = ({
|
|||||||
minWidth
|
minWidth
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
lastWidth = newWidth;
|
||||||
onWidthChange(newWidth);
|
onWidthChange(newWidth);
|
||||||
onResizing(true);
|
onResizing(true);
|
||||||
resized = true;
|
resized = true;
|
||||||
@@ -110,13 +115,22 @@ const ResizeHandle = ({
|
|||||||
onOpen(false);
|
onOpen(false);
|
||||||
}
|
}
|
||||||
onResizing(false);
|
onResizing(false);
|
||||||
|
lastWidth && onWidthChanged?.(lastWidth);
|
||||||
document.removeEventListener('mousemove', onMouseMove);
|
document.removeEventListener('mousemove', onMouseMove);
|
||||||
document.body.style.cursor = '';
|
document.body.style.cursor = '';
|
||||||
},
|
},
|
||||||
{ once: true }
|
{ once: true }
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[maxWidth, resizeHandlePos, minWidth, onWidthChange, onResizing, onOpen]
|
[
|
||||||
|
maxWidth,
|
||||||
|
resizeHandlePos,
|
||||||
|
minWidth,
|
||||||
|
onWidthChange,
|
||||||
|
onResizing,
|
||||||
|
onWidthChanged,
|
||||||
|
onOpen,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { dropTargetRef } = useDropTarget(dropTargetOptions, [
|
const { dropTargetRef } = useDropTarget(dropTargetOptions, [
|
||||||
@@ -173,6 +187,7 @@ export const ResizePanel = forwardRef<HTMLDivElement, ResizePanelProps>(
|
|||||||
onOpen,
|
onOpen,
|
||||||
onResizing,
|
onResizing,
|
||||||
onWidthChange,
|
onWidthChange,
|
||||||
|
onWidthChanged,
|
||||||
resizeHandlePos,
|
resizeHandlePos,
|
||||||
resizeHandleOffset,
|
resizeHandleOffset,
|
||||||
resizeHandleVerticalPadding,
|
resizeHandleVerticalPadding,
|
||||||
@@ -223,6 +238,7 @@ export const ResizePanel = forwardRef<HTMLDivElement, ResizePanelProps>(
|
|||||||
onOpen={onOpen}
|
onOpen={onOpen}
|
||||||
onResizing={onResizing}
|
onResizing={onResizing}
|
||||||
onWidthChange={onWidthChange}
|
onWidthChange={onWidthChange}
|
||||||
|
onWidthChanged={onWidthChanged}
|
||||||
open={open}
|
open={open}
|
||||||
resizing={resizing}
|
resizing={resizing}
|
||||||
dropTargetOptions={resizeHandleDropTargetOptions}
|
dropTargetOptions={resizeHandleDropTargetOptions}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { atom } from 'jotai';
|
|||||||
* @deprecated use `useSignOut` hook instated
|
* @deprecated use `useSignOut` hook instated
|
||||||
*/
|
*/
|
||||||
export const openQuotaModalAtom = atom(false);
|
export const openQuotaModalAtom = atom(false);
|
||||||
export const rightSidebarWidthAtom = atom(320);
|
|
||||||
|
|
||||||
export type AllPageFilterOption = 'docs' | 'collections' | 'tags';
|
export type AllPageFilterOption = 'docs' | 'collections' | 'tags';
|
||||||
export const allPageFilterSelectAtom = atom<AllPageFilterOption>('docs');
|
export const allPageFilterSelectAtom = atom<AllPageFilterOption>('docs');
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { type To } from 'history';
|
|||||||
import { omit } from 'lodash-es';
|
import { omit } from 'lodash-es';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
|
import type { GlobalState } from '../../storage';
|
||||||
import type { WorkbenchNewTabHandler } from '../services/workbench-new-tab-handler';
|
import type { WorkbenchNewTabHandler } from '../services/workbench-new-tab-handler';
|
||||||
import type { WorkbenchDefaultState } from '../services/workbench-view-state';
|
import type { WorkbenchDefaultState } from '../services/workbench-view-state';
|
||||||
import { View } from './view';
|
import { View } from './view';
|
||||||
@@ -18,10 +19,14 @@ export type WorkbenchOpenOptions = {
|
|||||||
show?: boolean; // only for new tab
|
show?: boolean; // only for new tab
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sidebarOpenKey = 'workbenchSidebarOpen';
|
||||||
|
const sidebarWidthKey = 'workbenchSidebarWidth';
|
||||||
|
|
||||||
export class Workbench extends Entity {
|
export class Workbench extends Entity {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly defaultState: WorkbenchDefaultState,
|
private readonly defaultState: WorkbenchDefaultState,
|
||||||
private readonly newTabHandler: WorkbenchNewTabHandler
|
private readonly newTabHandler: WorkbenchNewTabHandler,
|
||||||
|
private readonly globalState: GlobalState
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -50,7 +55,20 @@ export class Workbench extends Entity {
|
|||||||
location$ = LiveData.computed(get => {
|
location$ = LiveData.computed(get => {
|
||||||
return get(get(this.activeView$).location$);
|
return get(get(this.activeView$).location$);
|
||||||
});
|
});
|
||||||
sidebarOpen$ = new LiveData(false);
|
sidebarOpen$ = LiveData.from(
|
||||||
|
this.globalState.watch<boolean>(sidebarOpenKey),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
setSidebarOpen(open: boolean) {
|
||||||
|
this.globalState.set(sidebarOpenKey, open);
|
||||||
|
}
|
||||||
|
sidebarWidth$ = LiveData.from(
|
||||||
|
this.globalState.watch<number>(sidebarWidthKey),
|
||||||
|
320
|
||||||
|
);
|
||||||
|
setSidebarWidth(width: number) {
|
||||||
|
this.globalState.set(sidebarWidthKey, width);
|
||||||
|
}
|
||||||
|
|
||||||
active(index: number | View) {
|
active(index: number | View) {
|
||||||
if (typeof index === 'number') {
|
if (typeof index === 'number') {
|
||||||
@@ -85,15 +103,15 @@ export class Workbench extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openSidebar() {
|
openSidebar() {
|
||||||
this.sidebarOpen$.next(true);
|
this.setSidebarOpen(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
closeSidebar() {
|
closeSidebar() {
|
||||||
this.sidebarOpen$.next(false);
|
this.setSidebarOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSidebar() {
|
toggleSidebar() {
|
||||||
this.sidebarOpen$.next(!this.sidebarOpen$.value);
|
this.setSidebarOpen(!this.sidebarOpen$.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
open(to: To, option: WorkbenchOpenOptions = {}) {
|
open(to: To, option: WorkbenchOpenOptions = {}) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { type Framework } from '@toeverything/infra';
|
|||||||
|
|
||||||
import { DesktopApiService } from '../desktop-api';
|
import { DesktopApiService } from '../desktop-api';
|
||||||
import { PeekViewService } from '../peek-view';
|
import { PeekViewService } from '../peek-view';
|
||||||
import { GlobalStateService } from '../storage';
|
import { GlobalState, GlobalStateService } from '../storage';
|
||||||
import { WorkspaceScope } from '../workspace';
|
import { WorkspaceScope } from '../workspace';
|
||||||
import { SidebarTab } from './entities/sidebar-tab';
|
import { SidebarTab } from './entities/sidebar-tab';
|
||||||
import { View } from './entities/view';
|
import { View } from './entities/view';
|
||||||
@@ -39,7 +39,11 @@ export function configureWorkbenchCommonModule(services: Framework) {
|
|||||||
services
|
services
|
||||||
.scope(WorkspaceScope)
|
.scope(WorkspaceScope)
|
||||||
.service(WorkbenchService)
|
.service(WorkbenchService)
|
||||||
.entity(Workbench, [WorkbenchDefaultState, WorkbenchNewTabHandler])
|
.entity(Workbench, [
|
||||||
|
WorkbenchDefaultState,
|
||||||
|
WorkbenchNewTabHandler,
|
||||||
|
GlobalState,
|
||||||
|
])
|
||||||
.entity(View)
|
.entity(View)
|
||||||
.scope(ViewScope)
|
.scope(ViewScope)
|
||||||
.service(ViewService, [ViewScope])
|
.service(ViewService, [ViewScope])
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ResizePanel } from '@affine/component/resize-panel';
|
import { ResizePanel } from '@affine/component/resize-panel';
|
||||||
import { AffineErrorComponent } from '@affine/core/components/affine/affine-error-boundary/affine-error-fallback';
|
import { AffineErrorComponent } from '@affine/core/components/affine/affine-error-boundary/affine-error-fallback';
|
||||||
import { rightSidebarWidthAtom } from '@affine/core/components/atoms';
|
|
||||||
import { workbenchRoutes } from '@affine/core/desktop/workbench-router';
|
import { workbenchRoutes } from '@affine/core/desktop/workbench-router';
|
||||||
import {
|
import {
|
||||||
appSettingAtom,
|
appSettingAtom,
|
||||||
@@ -8,7 +7,7 @@ import {
|
|||||||
useLiveData,
|
useLiveData,
|
||||||
useService,
|
useService,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import { useAtom, useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { type RouteObject, useLocation } from 'react-router-dom';
|
import { type RouteObject, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -115,16 +114,24 @@ const MAX_SIDEBAR_WIDTH = 800;
|
|||||||
const WorkbenchSidebar = () => {
|
const WorkbenchSidebar = () => {
|
||||||
const { clientBorder } = useAtomValue(appSettingAtom);
|
const { clientBorder } = useAtomValue(appSettingAtom);
|
||||||
|
|
||||||
const [width, setWidth] = useAtom(rightSidebarWidthAtom);
|
|
||||||
const [resizing, setResizing] = useState(false);
|
const [resizing, setResizing] = useState(false);
|
||||||
|
|
||||||
const workbench = useService(WorkbenchService).workbench;
|
const workbench = useService(WorkbenchService).workbench;
|
||||||
|
const [width, setWidth] = useState(workbench.sidebarWidth$.value ?? 0);
|
||||||
|
|
||||||
const views = useLiveData(workbench.views$);
|
const views = useLiveData(workbench.views$);
|
||||||
const activeView = useLiveData(workbench.activeView$);
|
const activeView = useLiveData(workbench.activeView$);
|
||||||
const sidebarOpen = useLiveData(workbench.sidebarOpen$);
|
const sidebarOpen = useLiveData(workbench.sidebarOpen$);
|
||||||
const [floating, setFloating] = useState(false);
|
const [floating, setFloating] = useState(false);
|
||||||
|
|
||||||
|
const onWidthChanged = useCallback(
|
||||||
|
(width: number) => {
|
||||||
|
workbench.setSidebarWidth(width);
|
||||||
|
setWidth(width);
|
||||||
|
},
|
||||||
|
[workbench]
|
||||||
|
);
|
||||||
|
|
||||||
const handleOpenChange = useCallback(
|
const handleOpenChange = useCallback(
|
||||||
(open: boolean) => {
|
(open: boolean) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
@@ -155,9 +162,10 @@ const WorkbenchSidebar = () => {
|
|||||||
onResizing={setResizing}
|
onResizing={setResizing}
|
||||||
className={styles.workbenchSidebar}
|
className={styles.workbenchSidebar}
|
||||||
data-client-border={clientBorder && sidebarOpen}
|
data-client-border={clientBorder && sidebarOpen}
|
||||||
open={sidebarOpen}
|
open={sidebarOpen ?? false}
|
||||||
onOpen={handleOpenChange}
|
onOpen={handleOpenChange}
|
||||||
onWidthChange={setWidth}
|
onWidthChange={setWidth}
|
||||||
|
onWidthChanged={onWidthChanged}
|
||||||
minWidth={MIN_SIDEBAR_WIDTH}
|
minWidth={MIN_SIDEBAR_WIDTH}
|
||||||
maxWidth={MAX_SIDEBAR_WIDTH}
|
maxWidth={MAX_SIDEBAR_WIDTH}
|
||||||
unmountOnExit={false}
|
unmountOnExit={false}
|
||||||
|
|||||||
Reference in New Issue
Block a user