mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 13:25:12 +00:00
refactor: move frame manager and panel to separate packages (#10324)
### TL;DR Moved frame management functionality from `blocksuite/blocks` to `@blocksuite/affine-block-frame` package. ### What changed? - Relocated `frame-manager.ts` from `blocksuite/blocks` to `@blocksuite/affine-block-frame` - Added new dependencies to block-frame package: `@blocksuite/affine-block-surface` and `yjs` - Updated imports across multiple components to reference frame manager from its new location - Moved utility functions `areSetsEqual` and `isFrameBlock` into frame-manager file - Replaced direct EdgelessRootService references with GfxController in frame panel components ### How to test? 1. Verify frame functionality works in edgeless mode 2. Test frame creation, selection, and manipulation 3. Confirm frame navigation and presentation modes operate correctly 4. Check that frame panel and toolbar interactions remain functional ### Why make this change? This refactoring improves code organization by consolidating frame-related functionality into a dedicated package, making the codebase more modular and easier to maintain. It also reduces dependencies between packages and provides clearer boundaries for frame-related features.
This commit is contained in:
@@ -1 +0,0 @@
|
||||
export type NavigatorMode = 'fill' | 'fit';
|
||||
@@ -1,11 +1,12 @@
|
||||
import { ConnectionOverlay } from '@blocksuite/affine-block-surface';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { EdgelessRootBlockSpec } from '../../root-block/edgeless/edgeless-root-spec.js';
|
||||
import {
|
||||
EdgelessFrameManager,
|
||||
FrameOverlay,
|
||||
} from '../../root-block/edgeless/frame-manager.js';
|
||||
} from '@blocksuite/affine-block-frame';
|
||||
import { ConnectionOverlay } from '@blocksuite/affine-block-surface';
|
||||
import { PresentTool } from '@blocksuite/affine-fragment-frame-panel';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { EdgelessRootBlockSpec } from '../../root-block/edgeless/edgeless-root-spec.js';
|
||||
import { BrushTool } from '../../root-block/edgeless/gfx-tool/brush-tool.js';
|
||||
import { ConnectorTool } from '../../root-block/edgeless/gfx-tool/connector-tool.js';
|
||||
import { CopilotTool } from '../../root-block/edgeless/gfx-tool/copilot-tool.js';
|
||||
@@ -13,7 +14,6 @@ import { DefaultTool } from '../../root-block/edgeless/gfx-tool/default-tool.js'
|
||||
import { MindMapIndicatorOverlay } from '../../root-block/edgeless/gfx-tool/default-tool-ext/mind-map-ext/indicator-overlay.js';
|
||||
import { EmptyTool } from '../../root-block/edgeless/gfx-tool/empty-tool.js';
|
||||
import { EraserTool } from '../../root-block/edgeless/gfx-tool/eraser-tool.js';
|
||||
import { PresentTool } from '../../root-block/edgeless/gfx-tool/frame-navigator-tool.js';
|
||||
import { FrameTool } from '../../root-block/edgeless/gfx-tool/frame-tool.js';
|
||||
import { LassoTool } from '../../root-block/edgeless/gfx-tool/lasso-tool.js';
|
||||
import { NoteTool } from '../../root-block/edgeless/gfx-tool/note-tool.js';
|
||||
|
||||
@@ -33,6 +33,7 @@ import { SmoothCorner } from '@blocksuite/affine-components/smooth-corner';
|
||||
import { effects as componentToggleButtonEffects } from '@blocksuite/affine-components/toggle-button';
|
||||
import { ToggleSwitch } from '@blocksuite/affine-components/toggle-switch';
|
||||
import { effects as componentToolbarEffects } from '@blocksuite/affine-components/toolbar';
|
||||
import { effects as fragmentFramePanelEffects } from '@blocksuite/affine-fragment-frame-panel/effects';
|
||||
import { effects as widgetDragHandleEffects } from '@blocksuite/affine-widget-drag-handle/effects';
|
||||
import { effects as widgetEdgelessAutoConnectEffects } from '@blocksuite/affine-widget-edgeless-auto-connect/effects';
|
||||
import { effects as widgetFrameTitleEffects } from '@blocksuite/affine-widget-frame-title/effects';
|
||||
@@ -185,6 +186,8 @@ export function effects() {
|
||||
stdEffects();
|
||||
inlineEffects();
|
||||
|
||||
dataViewEffects();
|
||||
|
||||
blockNoteEffects();
|
||||
blockAttachmentEffects();
|
||||
blockBookmarkEffects();
|
||||
@@ -224,7 +227,8 @@ export function effects() {
|
||||
widgetRemoteSelectionEffects();
|
||||
widgetDragHandleEffects();
|
||||
widgetEdgelessAutoConnectEffects();
|
||||
dataViewEffects();
|
||||
|
||||
fragmentFramePanelEffects();
|
||||
|
||||
customElements.define('affine-page-root', PageRootBlockComponent);
|
||||
customElements.define('affine-preview-root', PreviewRootBlockComponent);
|
||||
|
||||
@@ -7,7 +7,6 @@ import { splitElements } from './root-block/edgeless/utils/clipboard-utils.js';
|
||||
import { isCanvasElement } from './root-block/edgeless/utils/query.js';
|
||||
|
||||
export * from './_common/adapters/index.js';
|
||||
export { type NavigatorMode } from './_common/edgeless/frame/consts.js';
|
||||
export * from './_common/transformers/index.js';
|
||||
export * from './_specs/index.js';
|
||||
export { EdgelessTemplatePanel } from './root-block/edgeless/components/toolbar/template/template-panel.js';
|
||||
@@ -16,10 +15,6 @@ export type {
|
||||
TemplateCategory,
|
||||
TemplateManager,
|
||||
} from './root-block/edgeless/components/toolbar/template/template-type.js';
|
||||
export {
|
||||
EdgelessFrameManager,
|
||||
FrameOverlay,
|
||||
} from './root-block/edgeless/frame-manager.js';
|
||||
export { CopilotTool } from './root-block/edgeless/gfx-tool/copilot-tool.js';
|
||||
export * from './root-block/edgeless/gfx-tool/index.js';
|
||||
export { EditPropsMiddlewareBuilder } from './root-block/edgeless/middlewares/base.js';
|
||||
@@ -95,6 +90,7 @@ export {
|
||||
ToolbarMoreMenuConfigExtension,
|
||||
Tooltip,
|
||||
} from '@blocksuite/affine-components/toolbar';
|
||||
export * from '@blocksuite/affine-fragment-frame-panel';
|
||||
export * from '@blocksuite/affine-model';
|
||||
export {
|
||||
AttachmentAdapter,
|
||||
|
||||
@@ -6,6 +6,11 @@ import {
|
||||
SYNCED_MIN_HEIGHT,
|
||||
SYNCED_MIN_WIDTH,
|
||||
} from '@blocksuite/affine-block-embed';
|
||||
import {
|
||||
type EdgelessFrameManager,
|
||||
type FrameOverlay,
|
||||
isFrameBlock,
|
||||
} from '@blocksuite/affine-block-frame';
|
||||
import {
|
||||
CanvasElementType,
|
||||
isNoteBlock,
|
||||
@@ -65,10 +70,6 @@ import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import type { EdgelessRootBlockComponent } from '../../edgeless-root-block.js';
|
||||
import type {
|
||||
EdgelessFrameManager,
|
||||
FrameOverlay,
|
||||
} from '../../frame-manager.js';
|
||||
import {
|
||||
AI_CHAT_BLOCK_MAX_HEIGHT,
|
||||
AI_CHAT_BLOCK_MAX_WIDTH,
|
||||
@@ -90,7 +91,6 @@ import {
|
||||
isEmbedLoomBlock,
|
||||
isEmbedSyncedDocBlock,
|
||||
isEmbedYoutubeBlock,
|
||||
isFrameBlock,
|
||||
isImageBlock,
|
||||
isMindmapNode,
|
||||
} from '../../utils/query.js';
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
isFrameBlock,
|
||||
type NavigatorMode,
|
||||
} from '@blocksuite/affine-block-frame';
|
||||
import { toast } from '@blocksuite/affine-components/toast';
|
||||
import type { FrameBlockModel } from '@blocksuite/affine-model';
|
||||
import { EditPropsStore } from '@blocksuite/affine-shared/services';
|
||||
@@ -15,9 +19,7 @@ import { cssVar } from '@toeverything/theme';
|
||||
import { css, html, LitElement, nothing, type PropertyValues } from 'lit';
|
||||
import { property, state } from 'lit/decorators.js';
|
||||
|
||||
import type { NavigatorMode } from '../../../../_common/edgeless/frame/consts.js';
|
||||
import type { EdgelessRootBlockComponent } from '../../edgeless-root-block.js';
|
||||
import { isFrameBlock } from '../../utils/query.js';
|
||||
import { launchIntoFullscreen } from '../utils.js';
|
||||
import { EdgelessToolbarToolMixin } from './mixins/tool.mixin.js';
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { EdgelessFrameManager } from '@blocksuite/affine-block-frame';
|
||||
import {
|
||||
EdgelessCRUDIdentifier,
|
||||
EdgelessLegacySlotIdentifier,
|
||||
@@ -36,7 +37,6 @@ import { Bound, getCommonBound } from '@blocksuite/global/utils';
|
||||
import { effect } from '@preact/signals-core';
|
||||
|
||||
import { RootService } from '../root-service.js';
|
||||
import type { EdgelessFrameManager } from './frame-manager.js';
|
||||
import { TemplateJob } from './services/template.js';
|
||||
import {
|
||||
createInsertPlaceMiddleware,
|
||||
|
||||
@@ -1,463 +0,0 @@
|
||||
import type { SurfaceBlockModel } from '@blocksuite/affine-block-surface';
|
||||
import { Overlay } from '@blocksuite/affine-block-surface';
|
||||
import type { FrameBlockModel } from '@blocksuite/affine-model';
|
||||
import { EditPropsStore } from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
generateKeyBetweenV2,
|
||||
getTopElements,
|
||||
GfxBlockElementModel,
|
||||
type GfxController,
|
||||
GfxExtension,
|
||||
GfxExtensionIdentifier,
|
||||
type GfxModel,
|
||||
isGfxGroupCompatibleModel,
|
||||
renderableInEdgeless,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import {
|
||||
Bound,
|
||||
deserializeXYWH,
|
||||
DisposableGroup,
|
||||
type IVec,
|
||||
type SerializedXYWH,
|
||||
} from '@blocksuite/global/utils';
|
||||
import { Text } from '@blocksuite/store';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { areSetsEqual } from './utils/misc.js';
|
||||
import { isFrameBlock } from './utils/query.js';
|
||||
|
||||
const FRAME_PADDING = 40;
|
||||
|
||||
export class FrameOverlay extends Overlay {
|
||||
static override overlayName: string = 'frame';
|
||||
|
||||
private _disposable = new DisposableGroup();
|
||||
|
||||
private _frame: FrameBlockModel | null = null;
|
||||
|
||||
private _innerElements = new Set<GfxModel>();
|
||||
|
||||
private readonly _prevXYWH: SerializedXYWH | null = null;
|
||||
|
||||
private get _frameManager() {
|
||||
return this.gfx.std.get(
|
||||
GfxExtensionIdentifier('frame-manager')
|
||||
) as EdgelessFrameManager;
|
||||
}
|
||||
|
||||
constructor(gfx: GfxController) {
|
||||
super(gfx);
|
||||
}
|
||||
|
||||
private _reset() {
|
||||
this._disposable.dispose();
|
||||
this._disposable = new DisposableGroup();
|
||||
|
||||
this._frame = null;
|
||||
this._innerElements.clear();
|
||||
}
|
||||
|
||||
override clear() {
|
||||
if (this._frame === null && this._innerElements.size === 0) return;
|
||||
this._reset();
|
||||
this._renderer?.refresh();
|
||||
}
|
||||
|
||||
highlight(
|
||||
frame: FrameBlockModel,
|
||||
highlightElementsInBound = false,
|
||||
highlightOutline = true
|
||||
) {
|
||||
if (!highlightElementsInBound && !highlightOutline) return;
|
||||
|
||||
let needRefresh = false;
|
||||
|
||||
if (highlightOutline && this._prevXYWH !== frame.xywh) {
|
||||
needRefresh = true;
|
||||
}
|
||||
|
||||
let innerElements = new Set<GfxModel>();
|
||||
if (highlightElementsInBound) {
|
||||
innerElements = new Set(
|
||||
getTopElements(
|
||||
this._frameManager.getElementsInFrameBound(frame)
|
||||
).concat(
|
||||
this._frameManager.getChildElementsInFrame(frame).filter(child => {
|
||||
return frame.intersectsBound(child.elementBound);
|
||||
})
|
||||
)
|
||||
);
|
||||
if (!areSetsEqual(this._innerElements, innerElements)) {
|
||||
needRefresh = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!needRefresh) return;
|
||||
|
||||
this._reset();
|
||||
if (highlightOutline) this._frame = frame;
|
||||
if (highlightElementsInBound) this._innerElements = innerElements;
|
||||
|
||||
this._disposable.add(
|
||||
frame.deleted.once(() => {
|
||||
this.clear();
|
||||
})
|
||||
);
|
||||
this._renderer?.refresh();
|
||||
}
|
||||
|
||||
override render(ctx: CanvasRenderingContext2D): void {
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = '#1E96EB';
|
||||
ctx.lineWidth = 2 / this.gfx.viewport.zoom;
|
||||
const radius = 2 / this.gfx.viewport.zoom;
|
||||
|
||||
if (this._frame) {
|
||||
const { x, y, w, h } = this._frame.elementBound;
|
||||
ctx.roundRect(x, y, w, h, radius);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
this._innerElements.forEach(element => {
|
||||
const [x, y, w, h] = deserializeXYWH(element.xywh);
|
||||
ctx.translate(x + w / 2, y + h / 2);
|
||||
ctx.rotate(element.rotate);
|
||||
ctx.roundRect(-w / 2, -h / 2, w, h, radius);
|
||||
ctx.translate(-x - w / 2, -y - h / 2);
|
||||
ctx.stroke();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class EdgelessFrameManager extends GfxExtension {
|
||||
static override key = 'frame-manager';
|
||||
|
||||
private readonly _disposable = new DisposableGroup();
|
||||
|
||||
/**
|
||||
* Get all sorted frames by presentation orderer,
|
||||
* the legacy frame that uses `index` as presentation order
|
||||
* will be put at the beginning of the array.
|
||||
*/
|
||||
get frames() {
|
||||
return Object.values(this.gfx.doc.blocks.value)
|
||||
.map(({ model }) => model)
|
||||
.filter(isFrameBlock)
|
||||
.sort(EdgelessFrameManager.framePresentationComparator);
|
||||
}
|
||||
|
||||
constructor(gfx: GfxController) {
|
||||
super(gfx);
|
||||
this._watchElementAdded();
|
||||
}
|
||||
|
||||
static framePresentationComparator<
|
||||
T extends FrameBlockModel | { index: string; presentationIndex?: string },
|
||||
>(a: T, b: T) {
|
||||
function stringCompare(a: string, b: string) {
|
||||
if (a < b) return -1;
|
||||
if (a > b) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (
|
||||
'presentationIndex$' in a &&
|
||||
'presentationIndex$' in b &&
|
||||
a.presentationIndex$.value &&
|
||||
b.presentationIndex$.value
|
||||
) {
|
||||
return stringCompare(
|
||||
a.presentationIndex$.value,
|
||||
b.presentationIndex$.value
|
||||
);
|
||||
} else if (a.presentationIndex && b.presentationIndex) {
|
||||
return stringCompare(a.presentationIndex, b.presentationIndex);
|
||||
} else if (a.presentationIndex) {
|
||||
return -1;
|
||||
} else if (b.presentationIndex) {
|
||||
return 1;
|
||||
} else {
|
||||
return stringCompare(a.index, b.index);
|
||||
}
|
||||
}
|
||||
|
||||
private _addChildrenToLegacyFrame(frame: FrameBlockModel) {
|
||||
if (frame.childElementIds !== undefined) return;
|
||||
const elements = this.getElementsInFrameBound(frame);
|
||||
const childElements = elements.filter(
|
||||
element => this.getParentFrame(element) === null && element !== frame
|
||||
);
|
||||
|
||||
frame.addChildren(childElements);
|
||||
}
|
||||
|
||||
private _addFrameBlock(bound: Bound) {
|
||||
const surfaceModel = this.gfx.surface as SurfaceBlockModel;
|
||||
const props = this.gfx.std
|
||||
.get(EditPropsStore)
|
||||
.applyLastProps('affine:frame', {
|
||||
title: new Text(new Y.Text(`Frame ${this.frames.length + 1}`)),
|
||||
xywh: bound.serialize(),
|
||||
index: this.gfx.layer.generateIndex(true),
|
||||
presentationIndex: this.generatePresentationIndex(),
|
||||
});
|
||||
|
||||
const id = this.gfx.doc.addBlock('affine:frame', props, surfaceModel);
|
||||
const frameModel = this.gfx.getElementById(id);
|
||||
|
||||
if (!frameModel || !isFrameBlock(frameModel)) {
|
||||
throw new BlockSuiteError(
|
||||
ErrorCode.GfxBlockElementError,
|
||||
'Frame model is not found'
|
||||
);
|
||||
}
|
||||
|
||||
return frameModel;
|
||||
}
|
||||
|
||||
private _watchElementAdded() {
|
||||
if (!this.gfx.surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { surface: surfaceModel, doc } = this.gfx;
|
||||
|
||||
this._disposable.add(
|
||||
surfaceModel.elementAdded.on(({ id, local }) => {
|
||||
const element = surfaceModel.getElementById(id);
|
||||
if (element && local) {
|
||||
const frame = this.getFrameFromPoint(element.elementBound.center);
|
||||
|
||||
// if the container created with a frame, skip it.
|
||||
if (
|
||||
isGfxGroupCompatibleModel(element) &&
|
||||
frame &&
|
||||
element.hasChild(frame)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// new element may intended to be added to other group
|
||||
// so we need to wait for the next microtask to check if the element can be added to the frame
|
||||
queueMicrotask(() => {
|
||||
if (!element.group && frame) {
|
||||
this.addElementsToFrame(frame, [element]);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this._disposable.add(
|
||||
doc.slots.blockUpdated.on(payload => {
|
||||
if (
|
||||
payload.type === 'add' &&
|
||||
payload.model instanceof GfxBlockElementModel &&
|
||||
renderableInEdgeless(doc, surfaceModel, payload.model)
|
||||
) {
|
||||
const frame = this.getFrameFromPoint(
|
||||
payload.model.elementBound.center,
|
||||
isFrameBlock(payload.model) ? [payload.model] : []
|
||||
);
|
||||
if (!frame) return;
|
||||
|
||||
if (
|
||||
isFrameBlock(payload.model) &&
|
||||
payload.model.containsBound(frame.elementBound)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.addElementsToFrame(frame, [payload.model]);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset parent of elements to the frame
|
||||
*/
|
||||
addElementsToFrame(frame: FrameBlockModel, elements: GfxModel[]) {
|
||||
if (frame.isLocked()) return;
|
||||
|
||||
if (frame.childElementIds === undefined) {
|
||||
this._addChildrenToLegacyFrame(frame);
|
||||
}
|
||||
|
||||
elements = elements.filter(
|
||||
el => el !== frame && !frame.childElements.includes(el)
|
||||
);
|
||||
|
||||
if (elements.length === 0) return;
|
||||
|
||||
frame.addChildren(elements);
|
||||
}
|
||||
|
||||
createFrameOnBound(bound: Bound) {
|
||||
const frameModel = this._addFrameBlock(bound);
|
||||
|
||||
this.addElementsToFrame(
|
||||
frameModel,
|
||||
getTopElements(this.getElementsInFrameBound(frameModel))
|
||||
);
|
||||
|
||||
this.gfx.doc.captureSync();
|
||||
|
||||
this.gfx.selection.set({
|
||||
elements: [frameModel.id],
|
||||
editing: false,
|
||||
});
|
||||
|
||||
return frameModel;
|
||||
}
|
||||
|
||||
createFrameOnElements(elements: GfxModel[]) {
|
||||
// make sure all elements are in the same level
|
||||
for (const element of elements) {
|
||||
if (element.group !== elements[0].group) return;
|
||||
}
|
||||
|
||||
const parentFrameBound = this.getParentFrame(elements[0])?.elementBound;
|
||||
|
||||
let bound = this.gfx.selection.selectedBound;
|
||||
|
||||
if (parentFrameBound?.contains(bound)) {
|
||||
bound.x -= Math.min(0.5 * (bound.x - parentFrameBound.x), FRAME_PADDING);
|
||||
bound.y -= Math.min(0.5 * (bound.y - parentFrameBound.y), FRAME_PADDING);
|
||||
bound.w += Math.min(
|
||||
0.5 * (parentFrameBound.x + parentFrameBound.w - bound.x - bound.w),
|
||||
FRAME_PADDING
|
||||
);
|
||||
bound.h += Math.min(
|
||||
0.5 * (parentFrameBound.y + parentFrameBound.h - bound.y - bound.h),
|
||||
FRAME_PADDING
|
||||
);
|
||||
} else {
|
||||
bound = bound.expand(FRAME_PADDING);
|
||||
}
|
||||
|
||||
const frameModel = this._addFrameBlock(bound);
|
||||
|
||||
this.addElementsToFrame(frameModel, getTopElements(elements));
|
||||
|
||||
this.gfx.doc.captureSync();
|
||||
|
||||
this.gfx.selection.set({
|
||||
elements: [frameModel.id],
|
||||
editing: false,
|
||||
});
|
||||
|
||||
return frameModel;
|
||||
}
|
||||
|
||||
createFrameOnSelected() {
|
||||
return this.createFrameOnElements(this.gfx.selection.selectedElements);
|
||||
}
|
||||
|
||||
createFrameOnViewportCenter(wh: [number, number]) {
|
||||
const center = this.gfx.viewport.center;
|
||||
const bound = new Bound(
|
||||
center.x - wh[0] / 2,
|
||||
center.y - wh[1] / 2,
|
||||
wh[0],
|
||||
wh[1]
|
||||
);
|
||||
|
||||
this.createFrameOnBound(bound);
|
||||
}
|
||||
|
||||
generatePresentationIndex() {
|
||||
const before =
|
||||
this.frames[this.frames.length - 1]?.presentationIndex ?? null;
|
||||
|
||||
return generateKeyBetweenV2(before, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all elements in the frame, there are three cases:
|
||||
* 1. The frame doesn't have `childElements`, return all elements in the frame bound but not owned by another frame.
|
||||
* 2. Return all child elements of the frame if `childElements` exists.
|
||||
*/
|
||||
getChildElementsInFrame(frame: FrameBlockModel): GfxModel[] {
|
||||
if (frame.childElementIds === undefined) {
|
||||
return this.getElementsInFrameBound(frame).filter(
|
||||
element => this.getParentFrame(element) !== null
|
||||
);
|
||||
}
|
||||
|
||||
const childElements = frame.childIds
|
||||
.map(id => this.gfx.getElementById(id))
|
||||
.filter(element => element !== null);
|
||||
|
||||
return childElements as GfxModel[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all elements in the frame bound,
|
||||
* whatever the element already has another parent frame or not.
|
||||
*/
|
||||
getElementsInFrameBound(frame: FrameBlockModel, fullyContained = true) {
|
||||
const bound = Bound.deserialize(frame.xywh);
|
||||
const elements: GfxModel[] = this.gfx.grid
|
||||
.search(bound, { strict: fullyContained })
|
||||
.filter(element => element !== frame);
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get most top frame from the point.
|
||||
*/
|
||||
getFrameFromPoint([x, y]: IVec, ignoreFrames: FrameBlockModel[] = []) {
|
||||
for (let i = this.frames.length - 1; i >= 0; i--) {
|
||||
const frame = this.frames[i];
|
||||
if (frame.includesPoint(x, y, {}) && !ignoreFrames.includes(frame)) {
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getParentFrame(element: GfxModel) {
|
||||
const container = element.group;
|
||||
return container && isFrameBlock(container) ? container : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will populate `presentationIndex` for all legacy frames,
|
||||
* and keep the orderer of the legacy frames.
|
||||
*/
|
||||
refreshLegacyFrameOrder() {
|
||||
const frames = this.frames.splice(0, this.frames.length);
|
||||
|
||||
let splitIndex = frames.findIndex(frame => frame.presentationIndex);
|
||||
if (splitIndex === 0) return;
|
||||
|
||||
if (splitIndex === -1) splitIndex = frames.length;
|
||||
|
||||
let afterPreIndex =
|
||||
frames[splitIndex]?.presentationIndex || generateKeyBetweenV2(null, null);
|
||||
|
||||
for (let index = splitIndex - 1; index >= 0; index--) {
|
||||
const preIndex = generateKeyBetweenV2(null, afterPreIndex);
|
||||
frames[index].presentationIndex = preIndex;
|
||||
afterPreIndex = preIndex;
|
||||
}
|
||||
}
|
||||
|
||||
removeAllChildrenFromFrame(frame: FrameBlockModel) {
|
||||
this.gfx.doc.transact(() => {
|
||||
frame.childElementIds = {};
|
||||
});
|
||||
}
|
||||
|
||||
removeFromParentFrame(element: GfxModel) {
|
||||
const parentFrame = this.getParentFrame(element);
|
||||
// oxlint-disable-next-line unicorn/prefer-dom-node-remove
|
||||
parentFrame?.removeChild(element);
|
||||
}
|
||||
|
||||
override unmounted(): void {
|
||||
this._disposable.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
import { insertEdgelessTextCommand } from '@blocksuite/affine-block-edgeless-text';
|
||||
import {
|
||||
type EdgelessFrameManager,
|
||||
type FrameOverlay,
|
||||
isFrameBlock,
|
||||
} from '@blocksuite/affine-block-frame';
|
||||
import {
|
||||
ConnectorUtils,
|
||||
isNoteBlock,
|
||||
@@ -50,14 +55,9 @@ import { effect } from '@preact/signals-core';
|
||||
|
||||
import { isSingleMindMapNode } from '../../../_common/edgeless/mindmap/index.js';
|
||||
import type { EdgelessRootBlockComponent } from '../edgeless-root-block.js';
|
||||
import type { EdgelessFrameManager, FrameOverlay } from '../frame-manager.js';
|
||||
import { prepareCloneData } from '../utils/clone-utils.js';
|
||||
import { calPanDelta } from '../utils/panning-utils.js';
|
||||
import {
|
||||
isCanvasElement,
|
||||
isEdgelessTextBlock,
|
||||
isFrameBlock,
|
||||
} from '../utils/query.js';
|
||||
import { isCanvasElement, isEdgelessTextBlock } from '../utils/query.js';
|
||||
import type { EdgelessSnapManager } from '../utils/snap-manager.js';
|
||||
import {
|
||||
addText,
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { BaseTool } from '@blocksuite/block-std/gfx';
|
||||
|
||||
import type { NavigatorMode } from '../../../_common/edgeless/frame/consts.js';
|
||||
|
||||
type PresentToolOption = {
|
||||
mode?: NavigatorMode;
|
||||
};
|
||||
|
||||
export class PresentTool extends BaseTool<PresentToolOption> {
|
||||
static override toolName: string = 'frameNavigator';
|
||||
}
|
||||
|
||||
declare module '@blocksuite/block-std/gfx' {
|
||||
interface GfxToolsMap {
|
||||
frameNavigator: PresentTool;
|
||||
}
|
||||
|
||||
interface GfxToolsOption {
|
||||
frameNavigator: PresentToolOption;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
import type {
|
||||
EdgelessFrameManager,
|
||||
FrameOverlay,
|
||||
} from '@blocksuite/affine-block-frame';
|
||||
import { OverlayIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import type { FrameBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
@@ -15,8 +19,6 @@ import { Bound, Vec } from '@blocksuite/global/utils';
|
||||
import { Text } from '@blocksuite/store';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import type { EdgelessFrameManager, FrameOverlay } from '../frame-manager.js';
|
||||
|
||||
export class FrameTool extends BaseTool {
|
||||
static override toolName = 'frame';
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ export { CopilotTool } from './copilot-tool.js';
|
||||
export { DefaultTool } from './default-tool.js';
|
||||
export { EmptyTool } from './empty-tool.js';
|
||||
export { EraserTool } from './eraser-tool.js';
|
||||
export { PresentTool } from './frame-navigator-tool.js';
|
||||
export { FrameTool } from './frame-tool.js';
|
||||
export { LassoTool, type LassoToolOption } from './lasso-tool.js';
|
||||
export { NoteTool, type NoteToolOption } from './note-tool.js';
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
EdgelessFrameManager,
|
||||
isFrameBlock,
|
||||
} from '@blocksuite/affine-block-frame';
|
||||
import { isNoteBlock } from '@blocksuite/affine-block-surface';
|
||||
import type {
|
||||
EdgelessTextBlockModel,
|
||||
@@ -18,12 +22,10 @@ import { getCommonBoundWithRotation, groupBy } from '@blocksuite/global/utils';
|
||||
import { type BlockSnapshot, BlockSnapshotSchema } from '@blocksuite/store';
|
||||
|
||||
import type { EdgelessRootBlockComponent } from '../edgeless-root-block.js';
|
||||
import { EdgelessFrameManager } from '../frame-manager.js';
|
||||
import { getSortedCloneElements, prepareCloneData } from './clone-utils.js';
|
||||
import {
|
||||
isEdgelessTextBlock,
|
||||
isEmbedSyncedDocBlock,
|
||||
isFrameBlock,
|
||||
isImageBlock,
|
||||
} from './query.js';
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export function areSetsEqual<T>(setA: Set<T>, setB: Set<T>) {
|
||||
if (setA.size !== setB.size) return false;
|
||||
for (const a of setA) if (!setB.has(a)) return false;
|
||||
return true;
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
type EmbedLoomModel,
|
||||
type EmbedSyncedDocModel,
|
||||
type EmbedYoutubeModel,
|
||||
type FrameBlockModel,
|
||||
type ImageBlockModel,
|
||||
MindmapElementModel,
|
||||
ShapeElementModel,
|
||||
@@ -49,10 +48,6 @@ export function isEdgelessTextBlock(
|
||||
);
|
||||
}
|
||||
|
||||
export function isFrameBlock(element: unknown): element is FrameBlockModel {
|
||||
return !!element && (element as BlockModel).flavour === 'affine:frame';
|
||||
}
|
||||
|
||||
export function isImageBlock(
|
||||
element: BlockModel | GfxModel | null
|
||||
): element is ImageBlockModel {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { EdgelessFrameManager } from '@blocksuite/affine-block-frame';
|
||||
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import type {
|
||||
EdgelessColorPickerButton,
|
||||
@@ -36,7 +37,6 @@ import { join } from 'lit/directives/join.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js';
|
||||
import type { EdgelessFrameManager } from '../../edgeless/frame-manager.js';
|
||||
import { mountFrameTitleEditor } from '../../edgeless/utils/text.js';
|
||||
|
||||
function getMostCommonColor(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isFrameBlock } from '@blocksuite/affine-block-frame';
|
||||
import { isNoteBlock } from '@blocksuite/affine-block-surface';
|
||||
import {
|
||||
cloneGroups,
|
||||
@@ -47,7 +48,6 @@ import {
|
||||
isBookmarkBlock,
|
||||
isEdgelessTextBlock,
|
||||
isEmbeddedBlock,
|
||||
isFrameBlock,
|
||||
isImageBlock,
|
||||
} from '../../edgeless/utils/query.js';
|
||||
import { renderAddFrameButton } from './add-frame-button.js';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isFrameBlock } from '@blocksuite/affine-block-frame';
|
||||
import {
|
||||
isNoteBlock,
|
||||
type SurfaceBlockComponent,
|
||||
@@ -18,7 +19,6 @@ import {
|
||||
isEmbeddedLinkBlock,
|
||||
isEmbedLinkedDocBlock,
|
||||
isEmbedSyncedDocBlock,
|
||||
isFrameBlock,
|
||||
isImageBlock,
|
||||
} from '../../../edgeless/utils/query.js';
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isFrameBlock } from '@blocksuite/affine-block-frame';
|
||||
import { getSurfaceBlock, isNoteBlock } from '@blocksuite/affine-block-surface';
|
||||
import type { FrameBlockModel, NoteBlockModel } from '@blocksuite/affine-model';
|
||||
import { NoteDisplayMode } from '@blocksuite/affine-model';
|
||||
@@ -12,7 +13,6 @@ import {
|
||||
mapFrameIds,
|
||||
sortEdgelessElements,
|
||||
} from '../../../edgeless/utils/clone-utils.js';
|
||||
import { isFrameBlock } from '../../../edgeless/utils/query.js';
|
||||
|
||||
function addBlocksToDoc(targetDoc: Store, model: BlockModel, parentId: string) {
|
||||
// Add current block to linked doc
|
||||
|
||||
Reference in New Issue
Block a user