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:
Saul-Mirone
2025-02-20 13:06:39 +00:00
parent 64cc99354e
commit 007bbabce4
43 changed files with 252 additions and 135 deletions

View File

@@ -1 +0,0 @@
export type NavigatorMode = 'fill' | 'fit';

View File

@@ -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';

View File

@@ -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);

View File

@@ -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,

View File

@@ -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';

View File

@@ -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';

View File

@@ -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,

View File

@@ -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();
}
}

View File

@@ -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,

View File

@@ -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;
}
}

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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(

View File

@@ -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';

View File

@@ -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';

View File

@@ -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