mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-27 19:02:23 +08:00
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:
@@ -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),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ export class DatabaseColumnStatsCell extends SignalWatcher(
|
|||||||
this.subscriptionMap.forEach(unsub => {
|
this.subscriptionMap.forEach(unsub => {
|
||||||
unsub();
|
unsub();
|
||||||
});
|
});
|
||||||
|
this.subscriptionMap.clear();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user