fix: drag block issue (#9902)

### Changed
- Added support for changing the preview offset during dragging.
- Fixed the preview rendering for embed block and surface-ref block
- Resolved an issue where the host element might be reused in certain cases, which could cause unexpected behavior
- Moved viewport-related constants and methods to a more appropriate location
This commit is contained in:
doouding
2025-02-05 07:25:53 +00:00
parent abeff8bb1a
commit 02122098c7
22 changed files with 177 additions and 138 deletions

View File

@@ -30,7 +30,10 @@ import {
BlockStdScope, BlockStdScope,
type EditorHost, type EditorHost,
} from '@blocksuite/block-std'; } from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx'; import {
GfxControllerIdentifier,
GfxExtension,
} from '@blocksuite/block-std/gfx';
import { assertExists, Bound, getCommonBound } from '@blocksuite/global/utils'; import { assertExists, Bound, getCommonBound } from '@blocksuite/global/utils';
import { type GetBlocksOptions, type Query, Text } from '@blocksuite/store'; import { type GetBlocksOptions, type Query, Text } from '@blocksuite/store';
import { computed, signal } from '@preact/signals-core'; import { computed, signal } from '@preact/signals-core';
@@ -145,8 +148,17 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
} }
} }
class GfxViewportInitializer extends GfxExtension {
static override key = 'gfx-viewport-initializer';
override mounted(): void {
this.gfx.fitToScreen();
}
}
previewSpecBuilder.extend([ previewSpecBuilder.extend([
EmbedSyncedDocWatcher, EmbedSyncedDocWatcher,
GfxViewportInitializer,
EditorSettingExtension(editorSetting), EditorSettingExtension(editorSetting),
]); ]);

View File

@@ -18,7 +18,10 @@ import {
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { import {
DocModeProvider, DocModeProvider,
EditorSettingExtension,
EditorSettingProvider,
EditPropsStore, EditPropsStore,
GeneralSettingSchema,
ThemeProvider, ThemeProvider,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { import {
@@ -37,6 +40,7 @@ import {
import { import {
GfxBlockElementModel, GfxBlockElementModel,
GfxControllerIdentifier, GfxControllerIdentifier,
GfxExtension,
} from '@blocksuite/block-std/gfx'; } from '@blocksuite/block-std/gfx';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { import {
@@ -47,6 +51,7 @@ import {
type SerializedXYWH, type SerializedXYWH,
} from '@blocksuite/global/utils'; } from '@blocksuite/global/utils';
import type { BaseSelection, Store } from '@blocksuite/store'; import type { BaseSelection, Store } from '@blocksuite/store';
import { signal } from '@preact/signals-core';
import { css, html, nothing, type TemplateResult } from 'lit'; import { css, html, nothing, type TemplateResult } from 'lit';
import { query, state } from 'lit/decorators.js'; import { query, state } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js'; import { styleMap } from 'lit/directives/style-map.js';
@@ -424,6 +429,11 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
private _initSpec() { private _initSpec() {
const refreshViewport = this._refreshViewport.bind(this); const refreshViewport = this._refreshViewport.bind(this);
// oxlint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
const editorSetting =
this.std.getOptional(EditorSettingProvider) ??
signal(GeneralSettingSchema.parse({}));
class PageViewWatcher extends BlockServiceWatcher { class PageViewWatcher extends BlockServiceWatcher {
static override readonly flavour = 'affine:page'; static override readonly flavour = 'affine:page';
@@ -444,7 +454,23 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
); );
} }
} }
this._previewSpec.extend([PageViewWatcher]);
class ViewportInitializer extends GfxExtension {
static override readonly key = 'surface-ref-viewport-initializer';
override mounted() {
this.gfx.viewport.setViewportByBound(
Bound.deserialize(self._referenceXYWH!)
);
refreshViewport();
}
}
this._previewSpec.extend([
ViewportInitializer,
PageViewWatcher,
EditorSettingExtension(editorSetting),
]);
const referenceId = this.model.reference; const referenceId = this.model.reference;
const setReferenceXYWH = (xywh: typeof this._referenceXYWH) => { const setReferenceXYWH = (xywh: typeof this._referenceXYWH) => {

View File

@@ -1,3 +1,4 @@
import { requestConnectedFrame } from '@blocksuite/affine-shared/utils';
import { assertExists } from '@blocksuite/global/utils'; import { assertExists } from '@blocksuite/global/utils';
import { import {
arrow, arrow,
@@ -188,9 +189,9 @@ export class Tooltip extends LitElement {
assertExists(parent, 'Tooltip must have a parent element'); assertExists(parent, 'Tooltip must have a parent element');
// Wait for render // Wait for render
setTimeout(() => { requestConnectedFrame(() => {
this._hoverController.setReference(parent); this._hoverController.setReference(parent);
}, 0); }, this);
}; };
private _getStyles() { private _getStyles() {

View File

@@ -211,6 +211,7 @@ export class DatabaseColumnStatsCell extends SignalWatcher(
this.subscriptionMap.forEach(unsub => { this.subscriptionMap.forEach(unsub => {
unsub(); unsub();
}); });
this.subscriptionMap.clear();
}); });
} }

View File

@@ -617,7 +617,7 @@ export class DragEventWatcher {
onDrop: () => { onDrop: () => {
this._cleanup(); this._cleanup();
}, },
setDragPreview: ({ source, container }) => { setDragPreview: ({ source, container, setOffset }) => {
if (!source.data?.bsEntity?.modelIds.length) { if (!source.data?.bsEntity?.modelIds.length) {
return; return;
} }
@@ -626,6 +626,9 @@ export class DragEventWatcher {
source.data?.bsEntity?.modelIds, source.data?.bsEntity?.modelIds,
container container
); );
const rect = container.getBoundingClientRect();
setOffset({ x: rect.width / 2, y: rect.height / 2 });
}, },
setDragData: () => { setDragData: () => {
const { snapshot } = this._getSnapshotFromHoveredBlocks(); const { snapshot } = this._getSnapshotFromHoveredBlocks();

View File

@@ -47,7 +47,6 @@ import { EdgelessPageKeyboardManager } from './edgeless-keyboard.js';
import type { EdgelessRootService } from './edgeless-root-service.js'; import type { EdgelessRootService } from './edgeless-root-service.js';
import { getBackgroundGrid, isCanvasElement } from './utils/query.js'; import { getBackgroundGrid, isCanvasElement } from './utils/query.js';
import { mountShapeTextEditor } from './utils/text.js'; import { mountShapeTextEditor } from './utils/text.js';
import { fitToScreen } from './utils/viewport.js';
export class EdgelessRootBlockComponent extends BlockComponent< export class EdgelessRootBlockComponent extends BlockComponent<
RootBlockModel, RootBlockModel,
@@ -341,9 +340,7 @@ export class EdgelessRootBlockComponent extends BlockComponent<
const storedViewport = std.get(EditPropsStore).getStorage('viewport'); const storedViewport = std.get(EditPropsStore).getStorage('viewport');
if (!storedViewport) { if (!storedViewport) {
fitToScreen(this.gfx.gfxElements, gfx.viewport, { this.gfx.fitToScreen();
smooth: false,
});
return; return;
} }

View File

@@ -23,7 +23,6 @@ import { query, state } from 'lit/decorators.js';
import type { EdgelessRootBlockWidgetName } from '../types.js'; import type { EdgelessRootBlockWidgetName } from '../types.js';
import type { EdgelessRootService } from './edgeless-root-service.js'; import type { EdgelessRootService } from './edgeless-root-service.js';
import { getBackgroundGrid, isCanvasElement } from './utils/query.js'; import { getBackgroundGrid, isCanvasElement } from './utils/query.js';
import { fitToScreen } from './utils/viewport.js';
export class EdgelessRootPreviewBlockComponent export class EdgelessRootPreviewBlockComponent
extends BlockComponent< extends BlockComponent<
@@ -178,14 +177,6 @@ export class EdgelessRootPreviewBlockComponent
); );
} }
private _initViewport() {
const gfx = this.service.gfx;
fitToScreen(gfx.gfxElements, gfx.viewport, {
smooth: false,
});
}
private get _disableScheduleUpdate() { private get _disableScheduleUpdate() {
const editorSetting = this.std.getOptional(EditorSettingProvider); const editorSetting = this.std.getOptional(EditorSettingProvider);
@@ -195,7 +186,6 @@ export class EdgelessRootPreviewBlockComponent
override connectedCallback() { override connectedCallback() {
super.connectedCallback(); super.connectedCallback();
this._initViewport();
this.handleEvent('selectionChange', () => { this.handleEvent('selectionChange', () => {
const surface = this.host.selection.value.find( const surface = this.host.selection.value.find(
(sel): sel is SurfaceSelection => sel.is(SurfaceSelection) (sel): sel is SurfaceSelection => sel.is(SurfaceSelection)

View File

@@ -27,6 +27,9 @@ import {
GfxControllerIdentifier, GfxControllerIdentifier,
GfxExtensionIdentifier, GfxExtensionIdentifier,
isGfxGroupCompatibleModel, isGfxGroupCompatibleModel,
ZOOM_MAX,
ZOOM_MIN,
ZOOM_STEP,
} from '@blocksuite/block-std/gfx'; } from '@blocksuite/block-std/gfx';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { Bound, getCommonBound } from '@blocksuite/global/utils'; import { Bound, getCommonBound } from '@blocksuite/global/utils';
@@ -41,15 +44,7 @@ import {
createStickerMiddleware, createStickerMiddleware,
replaceIdMiddleware, replaceIdMiddleware,
} from './services/template-middlewares.js'; } from './services/template-middlewares.js';
import { FIT_TO_SCREEN_PADDING } from './utils/consts.js';
import { getCursorMode } from './utils/query.js'; import { getCursorMode } from './utils/query.js';
import {
ZOOM_INITIAL,
ZOOM_MAX,
ZOOM_MIN,
ZOOM_STEP,
type ZoomAction,
} from './utils/zoom.js';
export class EdgelessRootService extends RootService implements SurfaceContext { export class EdgelessRootService extends RootService implements SurfaceContext {
static override readonly flavour = RootBlockSchema.model.flavour; static override readonly flavour = RootBlockSchema.model.flavour;
@@ -288,34 +283,6 @@ export class EdgelessRootService extends RootService implements SurfaceContext {
return this.surface.getConnectors(id) as ConnectorElementModel[]; return this.surface.getConnectors(id) as ConnectorElementModel[];
} }
getFitToScreenData(
padding: [number, number, number, number] = [0, 0, 0, 0],
inputBounds?: Bound[]
) {
let bounds = [];
if (inputBounds && inputBounds.length) {
bounds = inputBounds;
} else {
this.blocks.forEach(block => {
bounds.push(Bound.deserialize(block.xywh));
});
const surfaceElementsBound = getCommonBound(this.elements);
if (surfaceElementsBound) {
bounds.push(surfaceElementsBound);
}
}
const bound = getCommonBound(bounds);
return this.viewport.getFitToScreenData(
bound,
padding,
ZOOM_INITIAL,
FIT_TO_SCREEN_PADDING
);
}
override mounted() { override mounted() {
super.mounted(); super.mounted();
this._initSlotEffects(); this._initSlotEffects();
@@ -372,12 +339,12 @@ export class EdgelessRootService extends RootService implements SurfaceContext {
} }
} }
setZoomByAction(action: ZoomAction) { setZoomByAction(action: 'fit' | 'out' | 'reset' | 'in') {
if (this.locked) return; if (this.locked) return;
switch (action) { switch (action) {
case 'fit': case 'fit':
this.zoomToFit(); this.gfx.fitToScreen();
break; break;
case 'reset': case 'reset':
this.viewport.smoothZoom(1.0); this.viewport.smoothZoom(1.0);
@@ -439,9 +406,4 @@ export class EdgelessRootService extends RootService implements SurfaceContext {
this.selectionManager.set([]); this.selectionManager.set([]);
this.disposables.dispose(); this.disposables.dispose();
} }
zoomToFit() {
const { centerX, centerY, zoom } = this.getFitToScreenData();
this.viewport.setViewport(zoom, [centerX, centerY], true);
}
} }

View File

@@ -67,7 +67,6 @@ import {
mountShapeTextEditor, mountShapeTextEditor,
mountTextElementEditor, mountTextElementEditor,
} from '../utils/text.js'; } from '../utils/text.js';
import { fitToScreen } from '../utils/viewport.js';
import { CanvasElementEventExt } from './default-tool-ext/event-ext.js'; import { CanvasElementEventExt } from './default-tool-ext/event-ext.js';
import type { DefaultToolExt } from './default-tool-ext/ext.js'; import type { DefaultToolExt } from './default-tool-ext/ext.js';
import { DefaultModeDragType } from './default-tool-ext/ext.js'; import { DefaultModeDragType } from './default-tool-ext/ext.js';
@@ -766,11 +765,7 @@ export class DefaultTool extends BaseTool {
if (this.doc.readonly) { if (this.doc.readonly) {
const viewport = this.gfx.viewport; const viewport = this.gfx.viewport;
if (viewport.zoom === 1) { if (viewport.zoom === 1) {
// Fit to Screen this.gfx.fitToScreen();
fitToScreen(
[...this.gfx.layer.blocks, ...this.gfx.layer.canvasElements],
this.gfx.viewport
);
} else { } else {
// Zoom to 100% and Center // Zoom to 100% and Center
const [x, y] = viewport.toModelCoord(e.x, e.y); const [x, y] = viewport.toModelCoord(e.x, e.y);

View File

@@ -1,28 +0,0 @@
import type { GfxModel, Viewport } from '@blocksuite/block-std/gfx';
import { Bound, getCommonBound } from '@blocksuite/global/utils';
import { FIT_TO_SCREEN_PADDING } from './consts.js';
import { ZOOM_INITIAL } from './zoom.js';
export function fitToScreen(
elements: GfxModel[],
viewport: Viewport,
options: {
padding?: [number, number, number, number];
smooth?: boolean;
} = {
padding: [0, 0, 0, 0],
smooth: true,
}
) {
const elemBounds = elements.map(element => Bound.deserialize(element.xywh));
const commonBound = getCommonBound(elemBounds);
const { zoom, centerX, centerY } = viewport.getFitToScreenData(
commonBound,
options.padding,
ZOOM_INITIAL,
FIT_TO_SCREEN_PADDING
);
viewport.setViewport(zoom, [centerX, centerY], options.smooth);
}

View File

@@ -1,5 +0,0 @@
export type ZoomAction = 'fit' | 'out' | 'reset' | 'in';
export const ZOOM_MAX = 6.0;
export const ZOOM_MIN = 0.1;
export const ZOOM_STEP = 0.25;
export const ZOOM_INITIAL = 1.0;

View File

@@ -4,6 +4,7 @@ import {
ViewBarIcon, ViewBarIcon,
} from '@blocksuite/affine-components/icons'; } from '@blocksuite/affine-components/icons';
import { stopPropagation } from '@blocksuite/affine-shared/utils'; import { stopPropagation } from '@blocksuite/affine-shared/utils';
import { ZOOM_STEP } from '@blocksuite/block-std/gfx';
import { WithDisposable } from '@blocksuite/global/utils'; import { WithDisposable } from '@blocksuite/global/utils';
import { effect } from '@preact/signals-core'; import { effect } from '@preact/signals-core';
import { baseTheme } from '@toeverything/theme'; import { baseTheme } from '@toeverything/theme';
@@ -11,7 +12,6 @@ import { css, html, LitElement, nothing, unsafeCSS } from 'lit';
import { property } from 'lit/decorators.js'; import { property } from 'lit/decorators.js';
import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js'; import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
import { ZOOM_STEP } from '../../edgeless/utils/zoom.js';
export class EdgelessZoomToolbar extends WithDisposable(LitElement) { export class EdgelessZoomToolbar extends WithDisposable(LitElement) {
static override styles = css` static override styles = css`
@@ -87,6 +87,10 @@ export class EdgelessZoomToolbar extends WithDisposable(LitElement) {
return this.edgeless.service; return this.edgeless.service;
} }
get gfx() {
return this.edgeless.gfx;
}
get edgelessTool() { get edgelessTool() {
return this.edgeless.gfx.tool.currentToolOption$.peek(); return this.edgeless.gfx.tool.currentToolOption$.peek();
} }
@@ -162,7 +166,7 @@ export class EdgelessZoomToolbar extends WithDisposable(LitElement) {
.tooltip=${'Fit to screen'} .tooltip=${'Fit to screen'}
.tipPosition=${this._isVerticalBar() ? 'right' : 'top-end'} .tipPosition=${this._isVerticalBar() ? 'right' : 'top-end'}
.arrow=${!this._isVerticalBar()} .arrow=${!this._isVerticalBar()}
@click=${() => this.edgelessService.zoomToFit()} @click=${() => this.gfx.fitToScreen()}
.iconContainerPadding=${4} .iconContainerPadding=${4}
.disabled=${locked} .disabled=${locked}
> >

View File

@@ -4,7 +4,10 @@ import {
type ElementGetFeedbackArgs, type ElementGetFeedbackArgs,
monitorForElements, monitorForElements,
} from '@atlaskit/pragmatic-drag-and-drop/element/adapter'; } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import { centerUnderPointer } from '@atlaskit/pragmatic-drag-and-drop/element/center-under-pointer';
import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview'; import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
import { pointerOutsideOfPreview } from '@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview';
import { preserveOffsetOnSource } from '@atlaskit/pragmatic-drag-and-drop/element/preserve-offset-on-source';
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview'; import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
import type { DropTargetRecord } from '@atlaskit/pragmatic-drag-and-drop/types'; import type { DropTargetRecord } from '@atlaskit/pragmatic-drag-and-drop/types';
import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element'; import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
@@ -89,7 +92,7 @@ export type DraggableOption<
* If you want to completely disable the drag preview, just set `setDragPreview` to `false`. * If you want to completely disable the drag preview, just set `setDragPreview` to `false`.
* *
* @example * @example
* dnd.draggable{ * dnd.draggable({
* // ... * // ...
* setDragPreview: ({ container }) => { * setDragPreview: ({ container }) => {
* const preview = document.createElement('div'); * const preview = document.createElement('div');
@@ -98,8 +101,12 @@ export type DraggableOption<
* preview.style.backgroundColor = 'red'; * preview.style.backgroundColor = 'red';
* preview.innerText = 'Custom Drag Preview'; * preview.innerText = 'Custom Drag Preview';
* container.appendChild(preview); * container.appendChild(preview);
*
* return () => {
* // do some cleanup
* }
* } * }
* } * })
* *
* @param callback - callback to set custom drag preview * @param callback - callback to set custom drag preview
* @returns * @returns
@@ -116,8 +123,11 @@ export type DraggableOption<
*/ */
nativeSetDragImage: DataTransfer['setDragImage'] | null; nativeSetDragImage: DataTransfer['setDragImage'] | null;
container: HTMLElement; container: HTMLElement;
setOffset: (
offset: 'preserve' | 'center' | { x: number; y: number }
) => void;
} }
) => void); ) => void | (() => void));
} & ElementDragEventMap<DragPayload<PayloadEntity, PayloadFrom>, DropPayload>; } & ElementDragEventMap<DragPayload<PayloadEntity, PayloadFrom>, DropPayload>;
export type DropTargetOption< export type DropTargetOption<
@@ -228,9 +238,55 @@ export class DndController extends LifeCycleWatcher {
dragHandle, dragHandle,
onGenerateDragPreview: options => { onGenerateDragPreview: options => {
if (setDragPreview) { if (setDragPreview) {
let state: typeof centerUnderPointer | { x: number; y: number };
const setOffset = (
offset: 'preserve' | 'center' | { x: number; y: number }
) => {
if (offset === 'center') {
state = centerUnderPointer;
} else if (offset === 'preserve') {
state = preserveOffsetOnSource({
element: options.source.element,
input: options.location.current.input,
});
} else if (typeof offset === 'object') {
if (
offset.x < 0 ||
offset.y < 0 ||
typeof offset.x === 'string' ||
typeof offset.y === 'string'
) {
state = pointerOutsideOfPreview({
x:
typeof offset.x === 'number'
? `${Math.abs(offset.x)}px`
: offset.x,
y:
typeof offset.y === 'number'
? `${Math.abs(offset.y)}px`
: offset.y,
});
}
state = offset;
}
};
setCustomNativeDragPreview({ setCustomNativeDragPreview({
getOffset: (...args) => {
if (!state) {
setOffset('center');
}
if (typeof state === 'function') {
return state(...args);
}
return state;
},
render: renderOption => { render: renderOption => {
setDragPreview({ setDragPreview({
setOffset,
...options, ...options,
...renderOption, ...renderOption,
}); });

View File

@@ -2,6 +2,7 @@ import {
assertType, assertType,
Bound, Bound,
DisposableGroup, DisposableGroup,
getCommonBound,
getCommonBoundWithRotation, getCommonBoundWithRotation,
type IBound, type IBound,
last, last,
@@ -30,7 +31,7 @@ import {
GfxPrimitiveElementModel, GfxPrimitiveElementModel,
} from './model/surface/element-model.js'; } from './model/surface/element-model.js';
import type { SurfaceBlockModel } from './model/surface/surface-model.js'; import type { SurfaceBlockModel } from './model/surface/surface-model.js';
import { Viewport } from './viewport.js'; import { FIT_TO_SCREEN_PADDING, Viewport, ZOOM_INITIAL } from './viewport.js';
export class GfxController extends LifeCycleWatcher { export class GfxController extends LifeCycleWatcher {
static override key = gfxControllerKey; static override key = gfxControllerKey;
@@ -300,4 +301,28 @@ export class GfxController extends LifeCycleWatcher {
block && this.doc.updateBlock(block.model, props); block && this.doc.updateBlock(block.model, props);
} }
} }
fitToScreen(
options: {
bounds?: Bound[];
smooth?: boolean;
padding?: [number, number, number, number];
} = {
smooth: false,
padding: [0, 0, 0, 0],
}
) {
const elemBounds =
options.bounds ??
this.gfxElements.map(element => Bound.deserialize(element.xywh));
const commonBound = getCommonBound(elemBounds);
const { zoom, centerX, centerY } = this.viewport.getFitToScreenData(
commonBound,
options.padding,
ZOOM_INITIAL,
FIT_TO_SCREEN_PADDING
);
this.viewport.setViewport(zoom, [centerX, centerY], options.smooth);
}
} }

View File

@@ -60,9 +60,12 @@ export class ViewManager extends GfxExtension {
this._disposable.add( this._disposable.add(
surface.elementAdded.on(payload => { surface.elementAdded.on(payload => {
const model = surface.getElementById(payload.id)!; const model = surface.getElementById(payload.id)!;
const View = this._viewCtorMap.get(model.type) ?? GfxElementModelView; const ViewCtor =
this._viewCtorMap.get(model.type) ?? GfxElementModelView;
const view = new ViewCtor(model, this.gfx);
this._viewMap.set(model.id, new View(model, this.gfx)); this._viewMap.set(model.id, view);
view.onCreated();
}) })
); );

View File

@@ -72,7 +72,6 @@ export class GfxElementModelView<
readonly gfx: GfxController readonly gfx: GfxController
) { ) {
this.model = model; this.model = model;
this.onCreated();
} }
static setup(di: Container): void { static setup(di: Container): void {

View File

@@ -15,6 +15,10 @@ function cutoff(value: number, ref: number, sign: number) {
export const ZOOM_MAX = 6.0; export const ZOOM_MAX = 6.0;
export const ZOOM_MIN = 0.1; export const ZOOM_MIN = 0.1;
export const ZOOM_STEP = 0.25;
export const ZOOM_INITIAL = 1.0;
export const FIT_TO_SCREEN_PADDING = 100;
export class Viewport { export class Viewport {
private _cachedBoundingClientRect: DOMRect | null = null; private _cachedBoundingClientRect: DOMRect | null = null;

View File

@@ -51,8 +51,6 @@ const internalExtensions = [
export class BlockStdScope { export class BlockStdScope {
static internalExtensions = internalExtensions; static internalExtensions = internalExtensions;
private _getHost: () => EditorHost;
readonly container: Container; readonly container: Container;
readonly store: Store; readonly store: Store;
@@ -65,6 +63,8 @@ export class BlockStdScope {
return this.provider.getAll(LifeCycleWatcherIdentifier); return this.provider.getAll(LifeCycleWatcherIdentifier);
} }
private _host!: EditorHost;
get dnd() { get dnd() {
return this.get(DndController); return this.get(DndController);
} }
@@ -94,7 +94,14 @@ export class BlockStdScope {
} }
get host() { get host() {
return this._getHost(); if (!this._host) {
throw new BlockSuiteError(
ErrorCode.ValueNotExists,
'Host is not ready to use, the `render` method should be called first'
);
}
return this._host;
} }
get range() { get range() {
@@ -110,12 +117,6 @@ export class BlockStdScope {
} }
constructor(options: BlockStdOptions) { constructor(options: BlockStdOptions) {
this._getHost = () => {
throw new BlockSuiteError(
ErrorCode.ValueNotExists,
'Host is not ready to use, the `render` method should be called first'
);
};
this.store = options.store; this.store = options.store;
this.userExtensions = options.extensions; this.userExtensions = options.extensions;
this.container = new Container(); this.container = new Container();
@@ -190,7 +191,7 @@ export class BlockStdScope {
const element = new EditorHost(); const element = new EditorHost();
element.std = this; element.std = this;
element.doc = this.store; element.doc = this.store;
this._getHost = () => element; this._host = element;
this._lifeCycleWatchers.forEach(watcher => { this._lifeCycleWatchers.forEach(watcher => {
watcher.rendered.call(watcher); watcher.rendered.call(watcher);
}); });
@@ -202,7 +203,6 @@ export class BlockStdScope {
this._lifeCycleWatchers.forEach(watcher => { this._lifeCycleWatchers.forEach(watcher => {
watcher.unmounted.call(watcher); watcher.unmounted.call(watcher);
}); });
this._getHost = () => null as unknown as EditorHost;
} }
} }

View File

@@ -255,13 +255,13 @@ export class Transformer {
this._flattenSnapshot(tmpRootSnapshot, flatSnapshots, parent, index); this._flattenSnapshot(tmpRootSnapshot, flatSnapshots, parent, index);
const blockTree = await this._convertFlatSnapshots(flatSnapshots); const blockTree = await this._convertFlatSnapshots(flatSnapshots);
const first = content[0]; const first = content[0];
// check if the slice is already in the doc // check if the slice is already in the doc
if (first && doc.hasBlock(first.id)) { if (first && doc.hasBlock(first.id)) {
// if the slice is already in the doc, we need to move the blocks instead of adding them // if the slice is already in the doc, we need to move the blocks instead of adding them
const models = flatSnapshots const models = content
.map(flat => doc.getBlock(flat.snapshot.id)?.model) .map(block => doc.getBlock(block.id)?.model)
.filter(Boolean) as BlockModel[]; .filter(Boolean) as BlockModel[];
const parentModel = parent ? doc.getBlock(parent)?.model : undefined; const parentModel = parent ? doc.getBlock(parent)?.model : undefined;
if (!parentModel) { if (!parentModel) {

View File

@@ -117,13 +117,11 @@ function createNewNote(host: EditorHost): AIItemConfig {
const newNote = doc.getBlock(noteBlockId)?.model; const newNote = doc.getBlock(noteBlockId)?.model;
if (!newNote || !matchFlavours(newNote, ['affine:note'])) return; if (!newNote || !matchFlavours(newNote, ['affine:note'])) return;
const newNoteBound = Bound.deserialize(newNote.xywh); const newNoteBound = Bound.deserialize(newNote.xywh);
const bounds = [bound, newNoteBound]; const bounds = [bound, newNoteBound];
const { zoom, centerX, centerY } = service.getFitToScreenData( service.gfx.fitToScreen({
[20, 20, 20, 20], bounds,
bounds padding: [20, 20, 20, 20],
); });
service.viewport.setViewport(zoom, [centerX, centerY]);
}) })
.catch(err => { .catch(err => {
console.error(err); console.error(err);

View File

@@ -62,8 +62,7 @@ export const PPTBuilder = (host: EditorHost) => {
const block = snapshot.snapshot.content[0]; const block = snapshot.snapshot.content[0];
for (const child of block.children) { for (const child of block.children) {
await addDoc(child); await addDoc(child);
const { centerX, centerY, zoom } = service.getFitToScreenData(); service.gfx.fitToScreen();
service.viewport.setViewport(zoom, [centerX, centerY]);
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);

View File

@@ -64,12 +64,9 @@ function fitViewport(
false false
); );
} else { } else {
const data = rootService.getFitToScreenData(); rootService.gfx.fitToScreen({
rootService.viewport.setViewport( smooth: false,
data.zoom, });
[data.centerX, data.centerY],
false
);
} }
} catch (e) { } catch (e) {
logger.warn('failed to fitViewPort', e); logger.warn('failed to fitViewPort', e);