Skip to content
Draft
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
1 change: 1 addition & 0 deletions README-ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ graph.zoomTo("center", { padding: 100 });
| Раздел | Описание | Документация |
|--------|-----------|--------------|
| Жизненный цикл компонентов | Инициализация, обновление, отрисовка и удаление компонентов | [Подробнее](docs/system/component-lifecycle.md) |
| Система перетаскивания | Управление drag-операциями, модификаторы позиций, поддержка множественного выбора | [Подробнее](docs/system/drag-system.md) |
| Механизм отрисовки | Процесс отрисовки и оптимизации | [Подробнее](docs/rendering/rendering-mechanism.md) |
| Система событий | Обработка и распространение событий | [Подробнее](docs/system/events.md) |

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ graph.zoomTo("center", { padding: 100 });

1. System
- [Component Lifecycle](docs/system/component-lifecycle.md)
- [Drag System](docs/system/drag-system.md)
- [Events](docs/system/events.md)
- [Graph Settings](docs/system/graph-settings.md)
- [Public API](docs/system/public_api.md)
Expand Down
1,290 changes: 1,290 additions & 0 deletions docs/system/drag-system.md

Large diffs are not rendered by default.

13 changes: 8 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/components/canvas/EventedComponent/EventedComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

const listeners = new WeakMap<Component, Map<string, Set<TEventedComponentListener>>>();

const parents = new WeakMap<Event, EventedComponent>();

export class EventedComponent<
Props extends TComponentProps = TComponentProps,
State extends TComponentState = TComponentState,
Expand Down Expand Up @@ -50,12 +52,19 @@
}
}

protected getTargetComponent(event: Event): EventedComponent {
return parents.get(event);
}

public _fireEvent(cmp: Component, event: Event) {
if (!parents.has(event)) {
parents.set(event, this);
}
const handlers = listeners.get(cmp)?.get?.(event.type);

handlers?.forEach((cb) => {
if (typeof cb === "function") {
cb(event);

Check warning on line 67 in src/components/canvas/EventedComponent/EventedComponent.ts

View workflow job for this annotation

GitHub Actions / Verify Files

Expected return with your callback function
} else if (cb instanceof Component && "handleEvent" in cb && typeof cb.handleEvent === "function") {
cb.handleEvent?.(event);
}
Expand Down
60 changes: 31 additions & 29 deletions src/components/canvas/GraphComponent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
import { TComponentContext, TComponentProps, TComponentState } from "../../../lib/Component";
import { HitBox, HitBoxData } from "../../../services/HitTest";
import { getXY } from "../../../utils/functions";
import { dragListener } from "../../../utils/functions/dragListener";
import { EVENTS } from "../../../utils/types/events";
import { EventedComponent } from "../EventedComponent/EventedComponent";
import { TGraphLayerContext } from "../layers/graphLayer/GraphLayer";

Expand All @@ -22,7 +20,7 @@
> extends EventedComponent<Props, State, Context> {
public hitBox: HitBox;

private unsubscribe: (() => void)[] = [];
protected unsubscribe: (() => void)[] = [];

constructor(props: Props, parent: Component) {
super(props, parent);
Expand Down Expand Up @@ -54,32 +52,36 @@
return;
}
event.stopPropagation();
dragListener(this.context.ownerDocument)
.on(EVENTS.DRAG_START, (event: MouseEvent) => {
if (onDragStart?.(event) === false) {
return;
}
this.context.graph.getGraphLayer().captureEvents(this);
const xy = getXY(this.context.canvas, event);
startDragCoords = this.context.camera.applyToPoint(xy[0], xy[1]);
})
.on(EVENTS.DRAG_UPDATE, (event: MouseEvent) => {
if (!startDragCoords.length) return;

const [canvasX, canvasY] = getXY(this.context.canvas, event);
const currentCoords = this.context.camera.applyToPoint(canvasX, canvasY);

const diffX = (startDragCoords[0] - currentCoords[0]) | 0;
const diffY = (startDragCoords[1] - currentCoords[1]) | 0;

onDragUpdate?.({ prevCoords: startDragCoords, currentCoords, diffX, diffY }, event);
startDragCoords = currentCoords;
})
.on(EVENTS.DRAG_END, (_event: MouseEvent) => {
this.context.graph.getGraphLayer().releaseCapture();
startDragCoords = undefined;
onDrop?.(_event);
});
this.context.graph.dragController.start(
{
onDragStart: (event) => {

Check warning on line 57 in src/components/canvas/GraphComponent/index.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

'event' is already declared in the upper scope on line 50 column 48
if (onDragStart?.(event) === false) {
return;
}
this.context.graph.getGraphLayer().captureEvents(this);
const xy = getXY(this.context.canvas, event);
startDragCoords = this.context.camera.applyToPoint(xy[0], xy[1]);
},
onDragUpdate: (event) => {

Check warning on line 65 in src/components/canvas/GraphComponent/index.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

'event' is already declared in the upper scope on line 50 column 48
if (!startDragCoords.length) return;

const [canvasX, canvasY] = getXY(this.context.canvas, event);
const currentCoords = this.context.camera.applyToPoint(canvasX, canvasY);

const diffX = (startDragCoords[0] - currentCoords[0]) | 0;
const diffY = (startDragCoords[1] - currentCoords[1]) | 0;

onDragUpdate?.({ prevCoords: startDragCoords, currentCoords, diffX, diffY }, event);
startDragCoords = currentCoords;
},
onDragEnd: (event) => {

Check warning on line 77 in src/components/canvas/GraphComponent/index.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

'event' is already declared in the upper scope on line 50 column 48
this.context.graph.getGraphLayer().releaseCapture();
startDragCoords = undefined;
onDrop?.(event);
},
},
event
);
});
}

Expand Down
86 changes: 26 additions & 60 deletions src/components/canvas/blocks/Block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,21 @@ import { signal } from "@preact/signals-core";
import cloneDeep from "lodash/cloneDeep";
import isObject from "lodash/isObject";

import { DragInfo } from "../../../services/Drag/DragInfo";
import { ECameraScaleLevel } from "../../../services/camera/CameraService";
import { TGraphSettingsConfig } from "../../../store";
import { EAnchorType } from "../../../store/anchor/Anchor";
import { BlockState, IS_BLOCK_TYPE, TBlockId } from "../../../store/block/Block";
import { selectBlockById } from "../../../store/block/selectors";
import { getXY } from "../../../utils/functions";
import { ECanChangeBlockGeometry } from "../../../store/settings";
import { isAllowChangeBlockGeometry } from "../../../utils/functions";
import { TMeasureTextOptions } from "../../../utils/functions/text";
import { TTExtRect, renderText } from "../../../utils/renderers/text";
import { EVENTS } from "../../../utils/types/events";
import { TPoint, TRect } from "../../../utils/types/shapes";
import { GraphComponent } from "../GraphComponent";
import { Anchor, TAnchor } from "../anchors";
import { GraphLayer, TGraphLayerContext } from "../layers/graphLayer/GraphLayer";

import { BlockController } from "./controllers/BlockController";

export type TBlockSettings = {
/** Phantom blocks are blocks whose dimensions and position
* are not taken into account when calculating the usable rect. */
Expand Down Expand Up @@ -98,9 +97,7 @@ export class Block<T extends TBlock = TBlock, Props extends TBlockProps = TBlock

public connectedState: BlockState<T>;

protected lastDragEvent?: MouseEvent;

protected startDragCoords: number[] = [];
protected startDragCoords?: [number, number];

protected shouldRenderText: boolean;

Expand All @@ -114,18 +111,12 @@ export class Block<T extends TBlock = TBlock, Props extends TBlockProps = TBlock
return this.connectedState.$state.value;
}

protected blockController = new BlockController(this);

public $viewState = signal<BlockViewState>({ zIndex: 0, order: 0 });

constructor(props: Props, parent: GraphLayer) {
super(props, parent);

this.subscribe(props.id);

this.addEventListener(EVENTS.DRAG_START, this);
this.addEventListener(EVENTS.DRAG_UPDATE, this);
this.addEventListener(EVENTS.DRAG_END, this);
}

public isRendered() {
Expand All @@ -151,6 +142,13 @@ export class Block<T extends TBlock = TBlock, Props extends TBlockProps = TBlock
};
}

public isDraggable() {
return isAllowChangeBlockGeometry(
this.getConfigFlag("canChangeBlockGeometry") as ECanChangeBlockGeometry,
this.state.selected
);
}

public getGeometry(): TRect {
return {
x: this.state.x,
Expand Down Expand Up @@ -209,7 +207,7 @@ export class Block<T extends TBlock = TBlock, Props extends TBlockProps = TBlock
}

protected calcZIndex() {
const raised = this.connectedState.selected || this.lastDragEvent ? 1 : 0;
const raised = this.connectedState.selected || this.startDragCoords ? 1 : 0;
return this.context.constants.block.DEFAULT_Z_INDEX + raised;
}

Expand All @@ -236,74 +234,43 @@ export class Block<T extends TBlock = TBlock, Props extends TBlockProps = TBlock
this.setState({ x, y });
}

public handleEvent(event: CustomEvent) {
switch (event.type) {
case EVENTS.DRAG_START: {
this.onDragStart(event.detail.sourceEvent);
break;
}
case EVENTS.DRAG_UPDATE: {
this.onDragUpdate(event.detail.sourceEvent);
break;
}
case EVENTS.DRAG_END: {
this.onDragEnd(event.detail.sourceEvent);
break;
}
}
}

protected onDragStart(event: MouseEvent) {
public onDragStart(event: MouseEvent) {
this.context.graph.executеDefaultEventAction(
"block-drag-start",
{
nativeEvent: event,
block: this.connectedState.asTBlock(),
},
() => {
this.lastDragEvent = event;
const xy = getXY(this.context.canvas, event);
this.startDragCoords = this.context.camera.applyToPoint(xy[0], xy[1]).concat([this.state.x, this.state.y]);
this.startDragCoords = [this.state.x, this.state.y];
this.raiseBlock();
}
);
}

protected onDragUpdate(event: MouseEvent) {
public onDragUpdate(event: MouseEvent, dragInfo: DragInfo) {
if (!this.startDragCoords) return;

this.lastDragEvent = event;

const [canvasX, canvasY] = getXY(this.context.canvas, event);
const [cameraSpaceX, cameraSpaceY] = this.context.camera.applyToPoint(canvasX, canvasY);

const [x, y] = this.calcNextDragPosition(cameraSpaceX, cameraSpaceY);
// Применяем скорректированную дельту к начальной позиции этого блока
const newPos = dragInfo.applyAdjustedDelta(this.startDragCoords[0], this.startDragCoords[1]);

this.context.graph.executеDefaultEventAction(
"block-drag",
{
nativeEvent: event,
block: this.connectedState.asTBlock(),
x,
y,
x: newPos.x,
y: newPos.y,
},
() => this.applyNextPosition(x, y)
() => this.applyNextPosition(newPos.x, newPos.y)
);
}

protected calcNextDragPosition(x: number, y: number) {
const diffX = (this.startDragCoords[0] - x) | 0;
const diffY = (this.startDragCoords[1] - y) | 0;

let nextX = this.startDragCoords[2] - diffX;
let nextY = this.startDragCoords[3] - diffY;
protected calcNextDragPosition(dragInfo: DragInfo) {
const diff = dragInfo.worldDelta;

const spanGridSize = this.context.constants.block.SNAPPING_GRID_SIZE;

if (spanGridSize > 1) {
nextX = Math.round(nextX / spanGridSize) * spanGridSize;
nextY = Math.round(nextY / spanGridSize) * spanGridSize;
}
const nextX = this.startDragCoords[0] + diff.x;
const nextY = this.startDragCoords[1] + diff.y;

return [nextX, nextY];
}
Expand All @@ -312,15 +279,14 @@ export class Block<T extends TBlock = TBlock, Props extends TBlockProps = TBlock
this.updatePosition(x, y);
}

protected onDragEnd(event: MouseEvent) {
public onDragEnd(event: MouseEvent) {
if (!this.startDragCoords) return;

this.context.graph.emit("block-drag-end", {
nativeEvent: event,
block: this.connectedState.asTBlock(),
});

this.lastDragEvent = undefined;
this.startDragCoords = [];
this.updateHitBox(this.state);
}

Expand Down
Loading
Loading