Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(edgeless): incorrect presentation order #8840

Merged
merged 1 commit into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/affine/model/src/blocks/frame/frame-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
import {
canSafeAddToContainer,
descendantElementsImpl,
generateKeyBetweenV2,
GfxCompatible,
gfxGroupCompatibleSymbol,
hasDescendantElementImpl,
Expand All @@ -23,6 +24,7 @@ export type FrameBlockProps = {
title: Text;
background: Color;
childElementIds?: Record<string, boolean>;
presentationIndex?: string;
} & GfxCompatibleProps;

export const FrameBlockSchema = defineBlockSchema({
Expand All @@ -33,6 +35,7 @@ export const FrameBlockSchema = defineBlockSchema({
xywh: `[0,0,100,100]`,
index: 'a0',
childElementIds: Object.create(null),
presentationIndex: generateKeyBetweenV2(null, null),
}),
metadata: {
version: 1,
Expand Down
77 changes: 48 additions & 29 deletions packages/blocks/src/root-block/edgeless/clipboard/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {
encodeClipboardBlobs,
} from '../../clipboard/utils.js';
import { edgelessElementsBoundFromRawData } from '../utils/bound-utils.js';
import { createNewPresentationIndexes } from '../utils/clipboard-utils.js';
import {
getSortedCloneElements,
serializeElement,
Expand All @@ -88,9 +89,25 @@ const IMAGE_PNG = 'image/png';
const { GROUP, MINDMAP, CONNECTOR } = CanvasElementType;
const IMAGE_PADDING = 5; // for rotated shapes some padding is needed

type CreationContext = {
/**
* element old id to new id
*/
oldToNewIdMap: Map<string, string>;
/**
* element old id to new layer index
*/
originalIndexes: Map<string, string>;

/**
* frame old id to new presentation index
*/
newPresentationIndexes: Map<string, string>;
};

type BlockCreationFunction = (
snapshot: BlockSnapshot,
oldToNewIdsMap: Map<string, string>
context: CreationContext
) => Promise<string | null> | string | null; // new Id

interface CanvasExportOptions {
Expand Down Expand Up @@ -496,15 +513,15 @@ export class EdgelessClipboardController extends PageClipboard {

private _createCanvasElement(
clipboardData: SerializedElement,
oldToNewIdsMap: Map<string, string>,
context: CreationContext,
newXYWH: SerializedXYWH
) {
if (clipboardData.type === GROUP) {
const yMap = new DocCollection.Y.Map();
const children = clipboardData.children ?? {};

for (const [key, value] of Object.entries(children)) {
const newKey = oldToNewIdsMap.get(key);
const newKey = context.oldToNewIdMap.get(key);
assertExists(
newKey,
'Copy failed: cannot find the copied child in group'
Expand All @@ -518,7 +535,7 @@ export class EdgelessClipboardController extends PageClipboard {
const children = clipboardData.children ?? {};

for (const [oldKey, oldValue] of Object.entries(children)) {
const newKey = oldToNewIdsMap.get(oldKey);
const newKey = context.oldToNewIdMap.get(oldKey);
const newValue = {
...oldValue,
};
Expand All @@ -528,7 +545,7 @@ export class EdgelessClipboardController extends PageClipboard {
);

if (oldValue.parent) {
const newParent = oldToNewIdsMap.get(oldValue.parent);
const newParent = context.oldToNewIdMap.get(oldValue.parent);
assertExists(
newParent,
'Copy failed: cannot find the copied node in mind map'
Expand All @@ -551,13 +568,13 @@ export class EdgelessClipboardController extends PageClipboard {
);

if (source.id) {
source.id = oldToNewIdsMap.get(source.id) ?? source.id;
source.id = context.oldToNewIdMap.get(source.id) ?? source.id;
} else if (source.position) {
source.position = Vec.add(source.position, offset);
}

if (target.id) {
target.id = oldToNewIdsMap.get(target.id) ?? target.id;
target.id = context.oldToNewIdMap.get(target.id) ?? target.id;
} else if (target.position) {
target.position = Vec.add(target.position, offset);
}
Expand Down Expand Up @@ -623,10 +640,8 @@ export class EdgelessClipboardController extends PageClipboard {
return embedFigmaId;
}

private _createFrameBlock(
frame: BlockSnapshot,
oldToNewIdMap: Map<string, string>
) {
private _createFrameBlock(frame: BlockSnapshot, context: CreationContext) {
const { oldToNewIdMap, newPresentationIndexes } = context;
const { xywh, title, background, childElementIds } = frame.props;

const newChildElementIds: Record<string, boolean> = {};
Expand All @@ -647,6 +662,7 @@ export class EdgelessClipboardController extends PageClipboard {
background,
title: fromJSON(title),
childElementIds: newChildElementIds,
presentationIndex: newPresentationIndexes.get(frame.id),
},
this.surface.model.id
);
Expand Down Expand Up @@ -1234,8 +1250,15 @@ export class EdgelessClipboardController extends PageClipboard {
};

// create blocks and canvas elements
const originalIndexes = new Map<string, string>();
const oldIdToNewIdMap = new Map<string, string>();

const context: CreationContext = {
oldToNewIdMap: new Map<string, string>(),
originalIndexes: new Map<string, string>(),
newPresentationIndexes: createNewPresentationIndexes(
elementsRawData,
this.edgeless
),
};

const blockModels: BlockSuite.EdgelessBlockModelType[] = [];
const canvasElements: BlockSuite.SurfaceModel[] = [];
Expand Down Expand Up @@ -1265,10 +1288,7 @@ export class EdgelessClipboardController extends PageClipboard {
blockSnapshot.props.xywh = getNewXYWH(
blockSnapshot.props.xywh as SerializedXYWH
);
const newId = await config.createFunction(
blockSnapshot,
oldIdToNewIdMap
);
const newId = await config.createFunction(blockSnapshot, context);
if (!newId) continue;

const block = this.doc.getBlock(newId);
Expand All @@ -1277,39 +1297,38 @@ export class EdgelessClipboardController extends PageClipboard {
assertType<BlockSuite.EdgelessBlockModelType>(block.model);
blockModels.push(block.model);
allElements.push(block.model);
oldIdToNewIdMap.set(oldId, newId);

originalIndexes.set(oldId, originalIndex);
context.oldToNewIdMap.set(oldId, newId);
context.originalIndexes.set(oldId, originalIndex);
} else {
assertType<SerializedElement>(data);
const oldId = data.id;

const element = this._createCanvasElement(
data,
oldIdToNewIdMap,
context,
getNewXYWH(data.xywh)
);

canvasElements.push(element);
allElements.push(element);

oldIdToNewIdMap.set(oldId, element.id);
originalIndexes.set(oldId, element.index);
context.oldToNewIdMap.set(oldId, element.id);
context.originalIndexes.set(oldId, element.index);
}
}

// remap old id to new id for the original index
const oldIds = [...originalIndexes.keys()];
const oldIds = [...context.originalIndexes.keys()];
oldIds.forEach(oldId => {
const newId = oldIdToNewIdMap.get(oldId);
const originalIndex = originalIndexes.get(oldId);
const newId = context.oldToNewIdMap.get(oldId);
const originalIndex = context.originalIndexes.get(oldId);
if (newId && originalIndex) {
originalIndexes.set(newId, originalIndex);
originalIndexes.delete(oldId);
context.originalIndexes.set(newId, originalIndex);
context.originalIndexes.delete(oldId);
}
});

this._updatePastedElementsIndex(allElements, originalIndexes);
this._updatePastedElementsIndex(allElements, context.originalIndexes);

return {
canvasElements: canvasElements,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,14 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {

const edgeless = this.edgeless;
const { service, surfaceBlockModel } = edgeless;
const frameMgr = service.frame;
const frameIndex = service.frames.length + 1;
const id = service.addBlock(
'affine:frame',
{
title: new DocCollection.Y.Text(`Frame ${frameIndex}`),
xywh: serializeXYWH(...xywh),
presentationIndex: frameMgr.generatePresentationIndex(),
},
surfaceBlockModel
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,7 @@ export class EdgelessFrameOrderButton extends WithDisposable(LitElement) {
>
${FrameOrderAdjustmentIcon}
</edgeless-tool-icon-button>
<edgeless-frame-order-menu
.edgeless=${this.edgeless}
.frames=${this.frames}
>
<edgeless-frame-order-menu .edgeless=${this.edgeless}>
</edgeless-frame-order-menu>
`;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import type { FrameBlockModel } from '@blocksuite/affine-model';

import { CommonUtils } from '@blocksuite/affine-block-surface';
import { DisposableGroup, WithDisposable } from '@blocksuite/global/utils';
import { generateKeyBetweenV2 } from '@blocksuite/block-std/gfx';
import {
DisposableGroup,
SignalWatcher,
WithDisposable,
} from '@blocksuite/global/utils';
import { css, html, LitElement, nothing } from 'lit';
import { property, query, state } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';

import type { EdgelessRootBlockComponent } from '../../../edgeless-root-block.js';

export class EdgelessFrameOrderMenu extends WithDisposable(LitElement) {
export class EdgelessFrameOrderMenu extends SignalWatcher(
WithDisposable(LitElement)
) {
static override styles = css`
:host {
position: relative;
Expand Down Expand Up @@ -94,6 +98,10 @@ export class EdgelessFrameOrderMenu extends WithDisposable(LitElement) {
}
`;

private get _frames() {
return this.edgeless.service.frames;
}

private _bindEvent() {
const { _disposables } = this;

Expand Down Expand Up @@ -161,17 +169,21 @@ export class EdgelessFrameOrderMenu extends WithDisposable(LitElement) {
indicatorLine.style.visibility = 'hidden';
if (
newIndex >= 0 &&
newIndex <= this.frames.length &&
newIndex <= this._frames.length &&
newIndex !== index &&
newIndex !== index + 1
) {
const before = this.frames[newIndex - 1]?.index || null;
const after = this.frames[newIndex]?.index || null;
const frameMgr = this.edgeless.service.frame;
// Legacy compatibility
frameMgr.refreshLegacyFrameOrder();

const frame = this.frames[index];
const before = this._frames[newIndex - 1]?.presentationIndex || null;
const after = this._frames[newIndex]?.presentationIndex || null;

const frame = this._frames[index];

this.edgeless.service.updateElement(frame.id, {
index: CommonUtils.generateKeyBetween(before, after),
presentationIndex: generateKeyBetweenV2(before, after),
});
this.edgeless.doc.captureSync();

Expand All @@ -194,7 +206,7 @@ export class EdgelessFrameOrderMenu extends WithDisposable(LitElement) {
}

override render() {
const frame = this.frames[this._curIndex];
const frame = this._frames[this._curIndex];

return html`
<div
Expand All @@ -204,7 +216,7 @@ export class EdgelessFrameOrderMenu extends WithDisposable(LitElement) {
@click=${(e: MouseEvent) => e.stopPropagation()}
>
${repeat(
this.frames,
this._frames,
frame => frame.id,
(frame, index) => html`
<div class="item draggable" id=${frame.id} index=${index}>
Expand Down Expand Up @@ -242,9 +254,6 @@ export class EdgelessFrameOrderMenu extends WithDisposable(LitElement) {

@property({ attribute: false })
accessor embed = false;

@property({ attribute: false })
accessor frames!: FrameBlockModel[];
}

declare global {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { FrameBlockModel } from '@blocksuite/affine-model';

import { NavigatorSettingsIcon } from '@blocksuite/affine-components/icons';
import { EditPropsStore } from '@blocksuite/affine-shared/services';
import { createButtonPopper } from '@blocksuite/affine-shared/utils';
Expand Down Expand Up @@ -161,7 +159,6 @@ export class EdgelessNavigatorSettingButton extends WithDisposable(LitElement) {

<edgeless-frame-order-menu
.edgeless=${this.edgeless}
.frames=${this.frames}
.embed=${true}
></edgeless-frame-order-menu>`
: nothing}
Expand All @@ -181,9 +178,6 @@ export class EdgelessNavigatorSettingButton extends WithDisposable(LitElement) {
@property({ attribute: false })
accessor edgeless!: EdgelessRootBlockComponent;

@property({ attribute: false })
accessor frames!: FrameBlockModel[];

@property({ attribute: false })
accessor hideToolbar = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from '@blocksuite/affine-components/icons';
import { toast } from '@blocksuite/affine-components/toast';
import { EditPropsStore } from '@blocksuite/affine-shared/services';
import { Bound } from '@blocksuite/global/utils';
import { Bound, SignalWatcher } from '@blocksuite/global/utils';
import { effect } from '@preact/signals-core';
import { cssVar } from '@toeverything/theme';
import { css, html, LitElement, nothing, type PropertyValues } from 'lit';
Expand All @@ -26,7 +26,9 @@ import { EdgelessToolbarToolMixin } from './mixins/tool.mixin.js';

const { clamp } = CommonUtils;

export class PresentationToolbar extends EdgelessToolbarToolMixin(LitElement) {
export class PresentationToolbar extends EdgelessToolbarToolMixin(
SignalWatcher(LitElement)
) {
static override styles = css`
:host {
align-items: inherit;
Expand Down Expand Up @@ -378,7 +380,6 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(LitElement) {
: html`<edgeless-frame-order-button
.popperShow=${this.frameMenuShow}
.setPopperShow=${this.setFrameMenuShow}
.frames=${this._frames}
.edgeless=${this.edgeless}
>
</edgeless-frame-order-button>`}
Expand All @@ -391,7 +392,6 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(LitElement) {
}}
.popperShow=${this.settingMenuShow}
.setPopperShow=${this.setSettingMenuShow}
.frames=${this._frames}
.includeFrameOrder=${this.dense}
>
</edgeless-navigator-setting-button>
Expand Down
Loading
Loading