Skip to content

Commit

Permalink
refactor(core): improve scroll anchoring logic (#8269)
Browse files Browse the repository at this point in the history
  • Loading branch information
fundon committed Sep 19, 2024
1 parent 03ac9bc commit 6921c30
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 110 deletions.
12 changes: 6 additions & 6 deletions packages/frontend/core/src/modules/editor/entities/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,12 @@ export class Editor extends Entity {
(a, b) => a?.id === b?.id && a?.refreshKey === b?.refreshKey
)
.subscribe(params => {
if (params?.id) {
const std = editorContainer.host?.std;
if (std) {
scrollAnchoring(std, this.mode$.value, params.id);
}
}
if (!params?.id) return;

const std = editorContainer.host?.std;
if (!std) return;

scrollAnchoring(std, this.mode$.value, params.id);
});
unsubs.push(subscription.unsubscribe.bind(subscription));

Expand Down
134 changes: 31 additions & 103 deletions packages/frontend/core/src/modules/editor/utils/scroll-anchoring.ts
Original file line number Diff line number Diff line change
@@ -1,115 +1,43 @@
import { notify } from '@affine/component';
import { I18n } from '@affine/i18n';
import type { BlockStdScope, SelectionManager } from '@blocksuite/block-std';
import type {
DocMode,
EdgelessRootService,
PageRootService,
} from '@blocksuite/blocks';
import { Bound, deserializeXYWH } from '@blocksuite/global/utils';
import type { BlockStdScope } from '@blocksuite/block-std';
import {
GfxControllerIdentifier,
type GfxModel,
} from '@blocksuite/block-std/gfx';
import type { DocMode } from '@blocksuite/blocks';

function scrollAnchoringInEdgelessMode(
service: EdgelessRootService,
id: string
) {
requestAnimationFrame(() => {
let bounds: Bound | null = null;
let pageSelection: SelectionManager | null = null;

const blockComponent = service.std.view.getBlock(id);
const parentComponent = blockComponent?.parentComponent;
if (parentComponent && parentComponent.flavour === 'affine:note') {
pageSelection = parentComponent.std.selection;
if (!pageSelection) return;

const { left: x, width: w } = parentComponent.getBoundingClientRect();
const { top: y, height: h } = blockComponent.getBoundingClientRect();
const coord = service.viewport.toModelCoordFromClientCoord([x, y]);
bounds = new Bound(
coord[0],
coord[1],
w / service.viewport.zoom,
h / service.viewport.zoom
);
} else {
if (!service.getElementById) return;
const model = service.getElementById(id);
if (!model) return;

bounds = Bound.fromXYWH(deserializeXYWH(model.xywh));
}

if (!bounds) {
notify.error({ title: I18n['Block not found']() });
return;
}

const { zoom, centerX, centerY } = service.getFitToScreenData(
[20, 20, 100, 20],
[bounds]
);

service.viewport.setCenter(centerX, centerY);
service.viewport.setZoom(zoom);

if (pageSelection) {
pageSelection.setGroup('note', [
pageSelection.create('block', {
blockId: id,
}),
]);
} else {
service.selection.set({
elements: [id],
editing: false,
});
// TODO(@fundon): it should be a command
export function scrollAnchoring(std: BlockStdScope, mode: DocMode, id: string) {
let key = 'blockIds';
let exists = false;

if (mode === 'page') {
exists = std.doc.hasBlock(id);
} else {
const controller = std.getOptional(GfxControllerIdentifier);
if (!controller) return;

exists = !!controller.getElementById<GfxModel>(id)?.xywh;
if (controller.surface?.hasElementById(id)) {
key = 'elementIds';
}
}

// const surfaceComponent = service.std.view.getBlock(service.surface.id);
// if (!surfaceComponent) return;
// (surfaceComponent as SurfaceBlockComponent).refresh();

// TODO(@fundon): toolbar should be hidden
});
}

function scrollAnchoringInPageMode(service: PageRootService, id: string) {
const blockComponent = service.std.view.getBlock(id);
if (!blockComponent) {
notify.error({ title: I18n['Block not found']() });
if (!exists) {
notify.error({
title: I18n['Block not found'](),
message: I18n['Block not found description'](),
});
return;
}

blockComponent.scrollIntoView({
behavior: 'instant',
block: 'center',
});

const selection = service.std.selection;
if (!selection) return;
const selection = std.selection;

selection.setGroup('note', [
selection.create('block', {
blockId: id,
selection.setGroup('scene', [
selection.create('highlight', {
mode,
[key]: [id],
}),
]);

// TODO(@fundon): toolbar should be hidden
}

// TODO(@fundon): it should be a command
export function scrollAnchoring(std: BlockStdScope, mode: DocMode, id: string) {
if (mode === 'edgeless') {
const service = std.getService<EdgelessRootService>('affine:page');
if (!service) return;

scrollAnchoringInEdgelessMode(service, id);

return;
}

const service = std.getService<PageRootService>('affine:page');
if (!service) return;

scrollAnchoringInPageMode(service, id);
}
1 change: 1 addition & 0 deletions packages/frontend/i18n/src/resources/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"Back to Quick Search": "Back to quick search",
"Back to all": "Back to all",
"Block not found": "Block not found",
"Block not found description": "The block you are trying to access does not exist or has been removed.",
"Body text": "Body text",
"Bold": "Bold",
"Cancel": "Cancel",
Expand Down
2 changes: 1 addition & 1 deletion tests/affine-local/e2e/quick-search.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ test('can use cmdk to search page content and scroll to it, then the block will
);
expect(isVisitable).toBe(true);
const selectionElement = page.locator(
'affine-block-selection[style*="display: block;"]'
'affine-scroll-anchoring-widget div.highlight'
);
await expect(selectionElement).toBeVisible();
});
Expand Down

0 comments on commit 6921c30

Please sign in to comment.