mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
feat: add scroll wheel zoom setting (#9476)
### Changed Add `scroll wheel to zoom` setting option, when the option enables, user can zoom in and out with scroll wheel without pressing the cmd/ctrl key.
This commit is contained in:
@@ -2,13 +2,17 @@ import type { ExtensionType } from '@blocksuite/block-std';
|
||||
import { createIdentifier } from '@blocksuite/global/di';
|
||||
import type { DeepPartial } from '@blocksuite/global/utils';
|
||||
import type { Signal } from '@preact/signals-core';
|
||||
import type { z } from 'zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { NodePropsSchema } from '../utils/index.js';
|
||||
|
||||
export const EditorSettingSchema = NodePropsSchema;
|
||||
export const GeneralSettingSchema = z
|
||||
.object({
|
||||
edgelessScrollZoom: z.boolean().default(false),
|
||||
})
|
||||
.merge(NodePropsSchema);
|
||||
|
||||
export type EditorSetting = z.infer<typeof EditorSettingSchema>;
|
||||
export type EditorSetting = z.infer<typeof GeneralSettingSchema>;
|
||||
|
||||
export const EditorSettingProvider = createIdentifier<
|
||||
Signal<DeepPartial<EditorSetting>>
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
ShapeElementModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
EditorSettingProvider,
|
||||
EditPropsStore,
|
||||
FontLoaderService,
|
||||
ThemeProvider,
|
||||
@@ -371,8 +372,10 @@ export class EdgelessRootBlockComponent extends BlockComponent<
|
||||
private _initWheelEvent() {
|
||||
this._disposables.add(
|
||||
this.dispatcher.add('wheel', ctx => {
|
||||
const config = this.std.getOptional(EditorSettingProvider);
|
||||
const state = ctx.get('defaultState');
|
||||
const e = state.event as WheelEvent;
|
||||
const edgelessScrollZoom = config?.peek().edgelessScrollZoom ?? false;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
@@ -380,7 +383,7 @@ export class EdgelessRootBlockComponent extends BlockComponent<
|
||||
if (viewport.locked) return;
|
||||
|
||||
// zoom
|
||||
if (isTouchPadPinchEvent(e)) {
|
||||
if (isTouchPadPinchEvent(e) || edgelessScrollZoom) {
|
||||
const rect = this.getBoundingClientRect();
|
||||
// Perform zooming relative to the mouse position
|
||||
const [baseX, baseY] = this.gfx.viewport.toModelCoord(
|
||||
|
||||
@@ -3,11 +3,13 @@ import type {
|
||||
PeekViewService,
|
||||
} from '@blocksuite/affine-components/peek';
|
||||
import { PeekViewExtension } from '@blocksuite/affine-components/peek';
|
||||
import type { EditorSetting } from '@blocksuite/affine-shared/services';
|
||||
import { BlockComponent } from '@blocksuite/block-std';
|
||||
import {
|
||||
ColorScheme,
|
||||
type DocMode,
|
||||
type DocModeProvider,
|
||||
GeneralSettingSchema,
|
||||
type GenerateDocUrlService,
|
||||
matchFlavours,
|
||||
type NotificationService,
|
||||
@@ -19,7 +21,7 @@ import {
|
||||
import { Slot } from '@blocksuite/global/utils';
|
||||
import type { AffineEditorContainer } from '@blocksuite/presets';
|
||||
import { type DocCollection } from '@blocksuite/store';
|
||||
import { signal } from '@preact/signals-core';
|
||||
import { Signal, signal } from '@preact/signals-core';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
import type { AttachmentViewerPanel } from './components/attachment-viewer-panel.js';
|
||||
@@ -204,3 +206,28 @@ export function mockGenerateDocUrlService(collection: DocCollection) {
|
||||
};
|
||||
return generateDocUrlService;
|
||||
}
|
||||
|
||||
export function mockEditorSetting() {
|
||||
if (window.editorSetting$) return window.editorSetting$;
|
||||
|
||||
const initialVal = Object.entries(GeneralSettingSchema.shape).reduce(
|
||||
(pre: EditorSetting, [key, schema]) => {
|
||||
// @ts-expect-error key is EditorSetting field
|
||||
pre[key as keyof EditorSetting] = schema.parse(undefined);
|
||||
return pre;
|
||||
},
|
||||
{} as EditorSetting
|
||||
);
|
||||
|
||||
const signal = new Signal<EditorSetting>(initialVal);
|
||||
|
||||
window.editorSetting$ = signal;
|
||||
|
||||
return signal;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
editorSetting$: Signal<EditorSetting>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
CommunityCanvasTextFonts,
|
||||
DocModeExtension,
|
||||
DocModeProvider,
|
||||
EditorSettingExtension,
|
||||
FontConfigExtension,
|
||||
GenerateDocUrlExtension,
|
||||
GenerateDocUrlProvider,
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
} from '../../_common/history.js';
|
||||
import {
|
||||
mockDocModeService,
|
||||
mockEditorSetting,
|
||||
mockGenerateDocUrlService,
|
||||
mockNotificationService,
|
||||
mockParseDocUrlService,
|
||||
@@ -126,6 +128,7 @@ export async function mountDefaultDocEditor(collection: DocCollection) {
|
||||
NotificationExtension(mockNotificationService(editor)),
|
||||
FontConfigExtension(CommunityCanvasTextFonts),
|
||||
mockPeekViewExtension(attachmentViewerPanel),
|
||||
EditorSettingExtension(mockEditorSetting()),
|
||||
];
|
||||
return newSpec;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ import { effects as presetsEffects } from '@blocksuite/presets/effects';
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import * as store from '@blocksuite/store';
|
||||
|
||||
import { mockDocModeService } from '../_common/mock-services.js';
|
||||
import {
|
||||
mockDocModeService,
|
||||
mockEditorSetting,
|
||||
} from '../_common/mock-services.js';
|
||||
import { setupEdgelessTemplate } from '../_common/setup.js';
|
||||
import {
|
||||
createStarterDocCollection,
|
||||
@@ -59,6 +62,7 @@ async function main() {
|
||||
},
|
||||
defaultExtensions: (): ExtensionType[] => [
|
||||
FontConfigExtension(CommunityCanvasTextFonts),
|
||||
blocks.EditorSettingExtension(mockEditorSetting()),
|
||||
],
|
||||
extensions: {
|
||||
FontConfigExtension: FontConfigExtension(CommunityCanvasTextFonts),
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
AffineFormatBarWidget,
|
||||
CommunityCanvasTextFonts,
|
||||
DocModeProvider,
|
||||
EditorSettingExtension,
|
||||
FontConfigExtension,
|
||||
GenerateDocUrlExtension,
|
||||
NotificationExtension,
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
} from '../../_common/history.js';
|
||||
import {
|
||||
mockDocModeService,
|
||||
mockEditorSetting,
|
||||
mockGenerateDocUrlService,
|
||||
mockNotificationService,
|
||||
mockParseDocUrlService,
|
||||
@@ -79,6 +81,7 @@ export async function mountDefaultDocEditor(collection: DocCollection) {
|
||||
GenerateDocUrlExtension(mockGenerateDocUrlService(collection)),
|
||||
NotificationExtension(mockNotificationService(editor)),
|
||||
OverrideThemeExtension(themeExtension),
|
||||
EditorSettingExtension(mockEditorSetting()),
|
||||
{
|
||||
setup: di => {
|
||||
di.override(DocModeProvider, () =>
|
||||
|
||||
@@ -120,6 +120,50 @@ test('zoom by mouse', async ({ page }) => {
|
||||
await assertEdgelessSelectedModelRect(page, zoomed);
|
||||
});
|
||||
|
||||
test('zoom by mouse without ctrl pressed when edgelessScrollZoom is enabled', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await assertZoomLevel(page, 100);
|
||||
|
||||
await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]);
|
||||
|
||||
await page.mouse.click(CENTER_X, CENTER_Y);
|
||||
const original = [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT];
|
||||
await assertEdgelessSelectedModelRect(page, original);
|
||||
|
||||
// enable edgelessScrollZoom
|
||||
await page.evaluate(() => {
|
||||
window.editorSetting$.value = {
|
||||
...window.editorSetting$.value,
|
||||
edgelessScrollZoom: true,
|
||||
};
|
||||
});
|
||||
|
||||
// can zoom without ctrl pressed
|
||||
await zoomByMouseWheel(page, 0, 125, false);
|
||||
await assertZoomLevel(page, 90);
|
||||
|
||||
const zoomed = [0, 0, original[2] * 0.9, original[3] * 0.9];
|
||||
await assertEdgelessSelectedModelRect(page, zoomed);
|
||||
|
||||
// disable edgelessScrollZoom
|
||||
await page.evaluate(() => {
|
||||
window.editorSetting$.value = {
|
||||
...window.editorSetting$.value,
|
||||
edgelessScrollZoom: false,
|
||||
};
|
||||
});
|
||||
|
||||
// can't zoom without ctrl pressed
|
||||
await zoomByMouseWheel(page, 0, 125, false);
|
||||
await assertZoomLevel(page, 90);
|
||||
});
|
||||
|
||||
test('zoom by pinch', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
@@ -825,11 +825,14 @@ export async function clickComponentToolbarMoreMenuButton(
|
||||
export async function zoomByMouseWheel(
|
||||
page: Page,
|
||||
stepX: number,
|
||||
stepY: number
|
||||
stepY: number,
|
||||
pressedKey = true
|
||||
) {
|
||||
await page.keyboard.down(SHORT_KEY);
|
||||
if (pressedKey) await page.keyboard.down(SHORT_KEY);
|
||||
|
||||
await page.mouse.wheel(stepX, stepY);
|
||||
await page.keyboard.up(SHORT_KEY);
|
||||
|
||||
if (pressedKey) await page.keyboard.up(SHORT_KEY);
|
||||
}
|
||||
|
||||
// touch screen is not supported by Playwright now
|
||||
|
||||
Reference in New Issue
Block a user