fix: connector label editing (#12282)

Fixes [BS-3373](https://linear.app/affine-design/issue/BS-3373/connector%E7%9A%84%E5%8F%8C%E5%87%BB%E6%B7%BB%E5%8A%A0note%E8%A1%8C%E4%B8%BA%E5%8F%97%E5%88%B0%E4%BA%86%E8%A6%86%E7%9B%96%E8%8C%83%E5%9B%B4%E7%9A%84%E5%BD%B1%E5%93%8D)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Connector label elements now include identity and creator metadata.

- **Bug Fixes**
  - Improved hit-testing for pointer interactions, resulting in more accurate detection of hovered elements.

- **Refactor**
  - Enhanced internal comparison logic for elements, improving sorting and ordering consistency.
  - Strengthened type definitions for search filters, improving result accuracy and clarity.

- **Tests**
  - Added end-to-end tests to verify correct label entry and retrieval for multiple connectors.
  - Introduced utility functions to fetch connector labels and improved connector creation in test actions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
doouding
2025-05-15 10:31:55 +00:00
parent 6c9f28e08b
commit e98ec93af1
7 changed files with 136 additions and 15 deletions

View File

@@ -65,6 +65,14 @@ const typeFilters = {
model instanceof GfxLocalElementModel,
};
export type BuiltInFilterModelMap = {
block: GfxBlockElementModel;
canvas: GfxPrimitiveElementModel;
local: GfxLocalElementModel;
};
export type BuiltInFilterType = keyof typeof typeFilters;
type FilterFunc = (model: GfxModel | GfxLocalElementModel) => boolean;
export class GridManager extends GfxExtension {
@@ -280,7 +288,7 @@ export class GridManager extends GfxExtension {
* @param bound
* @param options
*/
search<T extends keyof typeof typeFilters>(
search<T extends BuiltInFilterType = 'canvas' | 'block'>(
bound: IBound,
options?: {
/**
@@ -297,16 +305,16 @@ export class GridManager extends GfxExtension {
*/
filter?: (T | FilterFunc)[] | FilterFunc;
}
): T extends 'local'[] ? (GfxModel | GfxLocalElementModel)[] : GfxModel[];
search<T extends keyof typeof typeFilters>(
): BuiltInFilterModelMap[T][];
search<T extends BuiltInFilterType = 'canvas' | 'block'>(
bound: IBound,
options: {
strict?: boolean | undefined;
useSet: true;
filter?: (T | FilterFunc)[] | FilterFunc;
}
): T extends 'local'[] ? Set<GfxModel | GfxLocalElementModel> : Set<GfxModel>;
search<T extends keyof typeof typeFilters>(
): Set<BuiltInFilterModelMap[T]>;
search<T extends BuiltInFilterType = 'canvas' | 'block'>(
bound: IBound,
options: {
strict?: boolean;

View File

@@ -66,7 +66,29 @@ export class GfxViewEventManager {
.search(new Bound(x - 5, y - 5, 10, 10), {
filter: ['canvas', 'local'],
})
.map(model => this.gfx.view.get(model)) as GfxElementModelView[];
.reduce((pre, model) => {
if (
model.includesPoint(
x,
y,
{
hitThreshold: 10,
responsePadding: [5, 5],
},
this.gfx.std.host
) ||
('externalBound' in model
? model.externalBound?.isPointInBound([x, y])
: false)
) {
const view = this.gfx.view.get(model) as GfxElementModelView | null;
view && pre.push(view);
}
return pre;
}, [] as GfxElementModelView[]);
const currentStackedViews = new Set(this._hoveredElementsStack);
const visited = new Set<GfxElementModelView>();

View File

@@ -13,7 +13,7 @@ import { mutex } from 'lib0';
import type { EditorHost } from '../../../view/index.js';
import type { GfxCompatibleInterface, PointTestOptions } from '../base.js';
import type { GfxGroupModel } from '../model.js';
import type { GfxGroupModel, GfxModel } from '../model.js';
import type { SurfaceBlockModel } from './surface-model.js';
export function prop<V, T extends GfxLocalElementModel>() {
@@ -61,6 +61,8 @@ export abstract class GfxLocalElementModel implements GfxCompatibleInterface {
abstract readonly type: string;
creator: GfxModel | null = null;
get deserializedXYWH() {
if (!this._local.has('deserializedXYWH')) {
const xywh = this.xywh;

View File

@@ -1,13 +1,13 @@
import type { Store } from '@blocksuite/store';
import type { GfxLocalElementModel } from '../gfx/index.js';
import type { Layer } from '../gfx/layer.js';
import {
type GfxGroupCompatibleInterface,
isGfxGroupCompatibleModel,
} from '../gfx/model/base.js';
import type { GfxBlockElementModel } from '../gfx/model/gfx-block-model.js';
import { type GfxBlockElementModel } from '../gfx/model/gfx-block-model.js';
import type { GfxModel } from '../gfx/model/model.js';
import { GfxLocalElementModel } from '../gfx/model/surface/local-element-model.js';
import type { SurfaceBlockModel } from '../gfx/model/surface/surface-model.js';
export function getLayerEndZIndex(layers: Layer[], layerIndex: number) {
@@ -90,6 +90,39 @@ export function renderableInEdgeless(
return parent === doc.root || parent === surface;
}
export function compareIndex(aIndex: string, bIndex: string) {
return aIndex === bIndex
? SortOrder.SAME
: aIndex < bIndex
? SortOrder.BEFORE
: SortOrder.AFTER;
}
function compareLocal(
a: GfxModel | GfxLocalElementModel,
b: GfxModel | GfxLocalElementModel
) {
const isALocal = a instanceof GfxLocalElementModel;
const isBLocal = b instanceof GfxLocalElementModel;
if (isALocal && a.creator && a.creator === b) {
return SortOrder.AFTER;
}
if (isBLocal && b.creator && b.creator === a) {
return SortOrder.BEFORE;
}
if (isALocal && isBLocal && a.creator && a.creator === b.creator) {
return compareIndex(a.index, b.index);
}
return {
a: isALocal && a.creator ? a.creator : a,
b: isBLocal && b.creator ? b.creator : b,
};
}
/**
* A comparator function for sorting elements in the surface.
* SortOrder.AFTER means a should be rendered after b and so on.
@@ -99,6 +132,15 @@ export function compare(
a: GfxModel | GfxLocalElementModel,
b: GfxModel | GfxLocalElementModel
) {
const result = compareLocal(a, b);
if (typeof result === 'number') {
return result;
}
a = result.a;
b = result.b;
if (isGfxGroupCompatibleModel(a) && b.groups.includes(a)) {
return SortOrder.BEFORE;
} else if (isGfxGroupCompatibleModel(b) && a.groups.includes(b)) {
@@ -128,10 +170,6 @@ export function compare(
aGroup = aGroup ?? a;
bGroup = bGroup ?? b;
return aGroup.index === bGroup.index
? SortOrder.SAME
: aGroup.index < bGroup.index
? SortOrder.BEFORE
: SortOrder.AFTER;
return compareIndex(aGroup.index, bGroup.index);
}
}