mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-27 02:42:25 +08:00
fix(editor): mid button drag in presentation mode (#12309)
Fixes https://linear.app/affine-design/issue/BS-3448 Before this PR, presentation mode would force quit if user either: 1. Press space 2. Drag with mouse middle button Unfixed behavior: https://github.com/user-attachments/assets/8ff4e13a-69a8-4de6-8994-bf36e6e3eb49 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved presentation mode to preserve your current panned view when exiting pan mode or toggling fullscreen, preventing unwanted viewport resets. - Spacebar actions are now correctly disabled when using the frame navigator tool, avoiding accidental tool switches. - **New Features** - Enhanced presentation controls for smoother transitions and better handling of user navigation states. - Added a one-time toast notification for presentations without frames, shown only once per session for better user guidance. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -185,9 +185,33 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen().catch(console.error);
|
||||
}
|
||||
|
||||
// Reset the flag when fully exiting presentation mode
|
||||
this.edgeless.std
|
||||
.get(EditPropsStore)
|
||||
.setStorage('presentNoFrameToastShown', false);
|
||||
}
|
||||
|
||||
private _moveToCurrentFrame() {
|
||||
private _moveToCurrentFrame(forceMove = false) {
|
||||
const currentToolOption = this.gfx.tool.currentToolOption$.value;
|
||||
const toolOptions = currentToolOption?.options;
|
||||
|
||||
// If PresentTool is being activated after a temporary pan (indicated by restoredAfterPan)
|
||||
// and a forced move isn't explicitly requested, skip moving to the current frame.
|
||||
// This preserves the user's panned position instead of resetting to the frame's default view.
|
||||
if (
|
||||
currentToolOption?.toolType === PresentTool &&
|
||||
toolOptions?.restoredAfterPan &&
|
||||
!forceMove
|
||||
) {
|
||||
// Clear the flag so future navigations behave normally
|
||||
this.gfx.tool.setTool(PresentTool, {
|
||||
...toolOptions,
|
||||
restoredAfterPan: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const current = this._currentFrameIndex;
|
||||
const viewport = this.gfx.viewport;
|
||||
const frame = this._frames[current];
|
||||
@@ -263,28 +287,56 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
|
||||
_disposables.add(
|
||||
effect(() => {
|
||||
const currentTool = this.gfx.tool.currentToolOption$.value;
|
||||
const selection = this.gfx.selection;
|
||||
const currentToolOption = this.gfx.tool.currentToolOption$.value;
|
||||
|
||||
if (currentTool?.toolType === PresentTool) {
|
||||
this._cachedIndex = this._currentFrameIndex;
|
||||
this._navigatorMode =
|
||||
(currentTool.options as ToolOptions<PresentTool>)?.mode ??
|
||||
this._navigatorMode;
|
||||
if (isFrameBlock(selection.selectedElements[0])) {
|
||||
this._cachedIndex = this._frames.findIndex(
|
||||
frame => frame.id === selection.selectedElements[0].id
|
||||
);
|
||||
if (currentToolOption?.toolType === PresentTool) {
|
||||
const opts = currentToolOption.options as
|
||||
| ToolOptions<PresentTool>
|
||||
| undefined;
|
||||
|
||||
const isAlreadyFullscreen = !!document.fullscreenElement;
|
||||
|
||||
if (!isAlreadyFullscreen) {
|
||||
this._toggleFullScreen();
|
||||
} else {
|
||||
this._fullScreenMode = true;
|
||||
}
|
||||
if (this._frames.length === 0)
|
||||
toast(
|
||||
this.host,
|
||||
'The presentation requires at least 1 frame. You can firstly create a frame.',
|
||||
5000
|
||||
);
|
||||
this._toggleFullScreen();
|
||||
}
|
||||
|
||||
this._cachedIndex = this._currentFrameIndex;
|
||||
this._navigatorMode = opts?.mode ?? this._navigatorMode;
|
||||
|
||||
const selection = this.gfx.selection;
|
||||
if (
|
||||
selection.selectedElements.length > 0 &&
|
||||
isFrameBlock(selection.selectedElements[0])
|
||||
) {
|
||||
const selectedFrameId = selection.selectedElements[0].id;
|
||||
const indexOfSelectedFrame = this._frames.findIndex(
|
||||
frame => frame.id === selectedFrameId
|
||||
);
|
||||
if (indexOfSelectedFrame !== -1) {
|
||||
this._cachedIndex = indexOfSelectedFrame;
|
||||
}
|
||||
}
|
||||
|
||||
const store = this.edgeless.std.get(EditPropsStore);
|
||||
if (this._frames.length === 0) {
|
||||
if (!store.getStorage('presentNoFrameToastShown')) {
|
||||
toast(
|
||||
this.host,
|
||||
'The presentation requires at least 1 frame. You can firstly create a frame.',
|
||||
5000
|
||||
);
|
||||
store.setStorage('presentNoFrameToastShown', true);
|
||||
}
|
||||
} else {
|
||||
// If frames exist, and the flag was set, reset it.
|
||||
// This allows the toast to show again if all frames are subsequently deleted.
|
||||
if (store.getStorage('presentNoFrameToastShown')) {
|
||||
store.setStorage('presentNoFrameToastShown', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.requestUpdate();
|
||||
})
|
||||
);
|
||||
@@ -305,12 +357,10 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
|
||||
_disposables.addFromEvent(document, 'fullscreenchange', () => {
|
||||
if (document.fullscreenElement) {
|
||||
// When enter fullscreen, we need to set current frame to the cached index
|
||||
this._timer = setTimeout(() => {
|
||||
this._currentFrameIndex = this._cachedIndex;
|
||||
}, 400);
|
||||
} else {
|
||||
// When exit fullscreen, we need to clear the timer
|
||||
clearTimeout(this._timer);
|
||||
if (
|
||||
this.edgelessTool.toolType === PresentTool &&
|
||||
@@ -324,7 +374,7 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => this._moveToCurrentFrame(), 400);
|
||||
setTimeout(() => this._moveToCurrentFrame(true), 400);
|
||||
this.slots.fullScreenToggled.next();
|
||||
});
|
||||
|
||||
@@ -430,11 +480,29 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
}
|
||||
|
||||
protected override updated(changedProperties: PropertyValues) {
|
||||
if (
|
||||
changedProperties.has('_currentFrameIndex') &&
|
||||
this.edgelessTool.toolType === PresentTool
|
||||
) {
|
||||
this._moveToCurrentFrame();
|
||||
const currentToolOption = this.gfx.tool.currentToolOption$.value;
|
||||
const isPresentToolActive = currentToolOption?.toolType === PresentTool;
|
||||
const toolOptions = currentToolOption?.options;
|
||||
const isRestoredAfterPan = !!(
|
||||
isPresentToolActive && toolOptions?.restoredAfterPan
|
||||
);
|
||||
|
||||
if (changedProperties.has('_currentFrameIndex') && isPresentToolActive) {
|
||||
// When the current frame index changes (e.g., user navigates), a viewport update is needed.
|
||||
// However, if PresentTool is merely being restored after a pan (isRestoredAfterPan = true)
|
||||
// without an explicit index change in this update cycle, we avoid forcing a move to preserve the panned position.
|
||||
// Thus, `forceMove` is true unless it's a pan restoration.
|
||||
const shouldForceMove = !isRestoredAfterPan;
|
||||
this._moveToCurrentFrame(shouldForceMove);
|
||||
} else if (isPresentToolActive && changedProperties.has('edgelessTool')) {
|
||||
// Handles cases where the tool is set/switched to PresentTool (e.g., initial activation or returning from another tool).
|
||||
// Similar to frame index changes, avoid forcing a viewport move if restoring after a pan.
|
||||
const currentToolIsPresentTool =
|
||||
this.edgelessTool.toolType === PresentTool;
|
||||
if (currentToolIsPresentTool) {
|
||||
const shouldForceMoveOnToolChange = !isRestoredAfterPan;
|
||||
this._moveToCurrentFrame(shouldForceMoveOnToolChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { NavigatorMode } from './frame-manager';
|
||||
|
||||
export type PresentToolOption = {
|
||||
mode?: NavigatorMode;
|
||||
restoredAfterPan?: boolean;
|
||||
};
|
||||
|
||||
export class PresentTool extends BaseTool<PresentToolOption> {
|
||||
|
||||
Reference in New Issue
Block a user