Загрузка данных
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 838190944d0d8495df324d5bd5008040e8da00a1..e0db5328422a945504ca18a7a3f86aa97b0fc04a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
# latest
- Добавлена возможность работы с дровингами на второстепенных пейнах
+- Исправлено взаимодействие с пересекающимися дровингами
# 0.1.19
diff --git a/src/core/Drawings.ts b/src/core/Drawings.ts
index 47a873bf138401e441898e5dc2667d494cd0f600..58e6e168238b230355e66ca2841c580ccfdfffcc 100644
--- a/src/core/Drawings.ts
+++ b/src/core/Drawings.ts
@@ -141,7 +141,7 @@ export class Drawing extends DOMObject {
return this.drawingName;
}
- public getLwcDrawing(): ISeriesDrawing {
+ public getSeriesDrawing(): ISeriesDrawing {
return this.lwcDrawing;
}
diff --git a/src/core/Drawings/SeriesDrawingBase.ts b/src/core/Drawings/SeriesDrawingBase.ts
index 00ab6e6c2f29c7f8f5498d4b243b1384e97a9607..2cc853902ff83a615f8dcc0053e5da402e30f2e6 100644
--- a/src/core/Drawings/SeriesDrawingBase.ts
+++ b/src/core/Drawings/SeriesDrawingBase.ts
@@ -52,6 +52,13 @@ export interface ISeriesDrawing extends ISeriesPrimitive<Time> {
subscribeSettings(callback: (settings: SettingsValues) => void): Subscription;
+ isHit(event: MouseEvent): boolean;
+ pointerDown(event: PointerEvent): void;
+ pointerMove(event: PointerEvent): void;
+ pointerUp(event: PointerEvent): void;
+ doubleClick(event: MouseEvent): void;
+ contextMenu(event: MouseEvent): void;
+
getRenderData(): unknown;
}
@@ -204,6 +211,64 @@ export abstract class SeriesDrawingBase<TSettings extends SettingsValues = Setti
};
}
+ public isHit(event: MouseEvent): boolean {
+ if (!this.isEventInside(event)) {
+ return false;
+ }
+
+ const point = this.getEventPoint(event as PointerEvent);
+
+ return this.getHoveredItem(point.x, point.y) !== null;
+ }
+
+ public pointerDown(event: PointerEvent): void {
+ if (!this.isEventInside(event)) {
+ return;
+ }
+
+ if (!this.isLocked() || this.isCreationPending() || event.button !== 0) {
+ this.handlePointerDown(event);
+
+ return;
+ }
+
+ const point = this.getEventPoint(event);
+
+ if (this.getHoveredItem(point.x, point.y)) {
+ this.select();
+
+ return;
+ }
+
+ if (this.isSelected()) {
+ this.deselect();
+ }
+ }
+
+ public pointerMove(event: PointerEvent): void {
+ this.handlePointerMove(event);
+ }
+
+ public pointerUp(event: PointerEvent): void {
+ this.handlePointerUp(event);
+ }
+
+ public doubleClick(event: MouseEvent): void {
+ if (!this.isEventInside(event)) {
+ return;
+ }
+
+ this.handleDoubleClick(event);
+ }
+
+ public contextMenu(event: MouseEvent): void {
+ if (!this.isEventInside(event)) {
+ return;
+ }
+
+ this.handleContextMenu(event);
+ }
+
public abstract getRenderData(): unknown; // todo: make proper type
public abstract getState(): unknown;
public abstract getSettingsTabs(): SettingsTab[];
@@ -267,14 +332,6 @@ export abstract class SeriesDrawingBase<TSettings extends SettingsValues = Setti
}
this.isBound = true;
-
- this.container.addEventListener('dblclick', this.handleDoubleClick);
- this.container.addEventListener('pointerdown', this.handlePointerDownEvent);
- this.container.addEventListener('contextmenu', this.handleContextMenu);
-
- window.addEventListener('pointermove', this.handlePointerMove);
- window.addEventListener('pointerup', this.handlePointerUp);
- window.addEventListener('pointercancel', this.handlePointerUp);
}
protected unbindEvents(): void {
@@ -283,14 +340,6 @@ export abstract class SeriesDrawingBase<TSettings extends SettingsValues = Setti
}
this.isBound = false;
-
- this.container.removeEventListener('dblclick', this.handleDoubleClick);
- this.container.removeEventListener('pointerdown', this.handlePointerDownEvent);
- this.container.removeEventListener('contextmenu', this.handleContextMenu);
-
- window.removeEventListener('pointermove', this.handlePointerMove);
- window.removeEventListener('pointerup', this.handlePointerUp);
- window.removeEventListener('pointercancel', this.handlePointerUp);
}
// todo: хочется общую реализацию для каждой кнопки
@@ -331,24 +380,7 @@ export abstract class SeriesDrawingBase<TSettings extends SettingsValues = Setti
);
}
- protected handlePointerDownEvent = (event: PointerEvent): void => {
- if (!this.isLocked() || this.isCreationPending() || event.button !== 0) {
- this.handlePointerDown(event);
-
- return;
- }
-
- const point = this.getEventPoint(event);
- const isDrawingHit = this.getHoveredItem(point.x, point.y) !== null;
-
- if (isDrawingHit) {
- this.select();
-
- return;
- }
-
- if (this.isSelected()) {
- this.deselect();
- }
- };
+ private isEventInside(event: MouseEvent): boolean {
+ return event.target instanceof Node && this.container.contains(event.target);
+ }
}
diff --git a/src/core/DrawingsManager.tsx b/src/core/DrawingsManager.tsx
index 653e50a3dde04df5206e2f0550aa7c71e14f8b53..d6e9ee1117ca30f8e8713e251bdb900bb86df3c1 100644
--- a/src/core/DrawingsManager.tsx
+++ b/src/core/DrawingsManager.tsx
@@ -66,6 +66,7 @@ export class DrawingsManager {
private selectedDrawing$ = new BehaviorSubject<Drawing | null>(null); // todo: переместить в DrawingsManagerCollection
private pendingSnapshot: DrawingsManagerSnapshot | null = null;
private selectedDrawingSnapshot: DrawingSnapshotItem | null = null;
+ private interactionDrawing: Drawing | null = null;
private recreateScheduled = false;
private pane: Pane;
@@ -117,10 +118,13 @@ export class DrawingsManager {
}),
);
+ window.addEventListener('pointermove', this.handlePointerMove);
window.addEventListener('pointerup', this.handlePointerUp);
window.addEventListener('pointercancel', this.handlePointerUp);
this.container.addEventListener('click', this.handleClick);
this.container.addEventListener('pointerdown', this.handlePointerDown);
+ this.container.addEventListener('dblclick', this.handleDoubleClick);
+ this.container.addEventListener('contextmenu', this.handleContextMenu);
// todo: implement ctrl+v
// hotkeys.register({
// keys: [Keys.control, Keys.v],
@@ -147,49 +151,129 @@ export class DrawingsManager {
// })
}
- private handlePointerDown = (): void => {
+ private handlePointerDown = (event: PointerEvent): void => {
this.selectedDrawingSnapshot = null;
- queueMicrotask(() => {
- const drawing = this.selectedDrawing$.value;
+ if (event.button === 0) {
+ const drawings = this.drawings$.value;
+ const selectedDrawing = this.selectedDrawing$.value;
+ const pendingDrawing =
+ this.interactionDrawing?.isCreationPending() === true
+ ? this.interactionDrawing
+ : drawings.find((drawing) => drawing.isCreationPending());
- if (!drawing || drawing.isCreationPending()) {
- return;
- }
+ const drawing =
+ pendingDrawing ??
+ (selectedDrawing?.getSeriesDrawing().isHit(event) ? selectedDrawing : null) ??
+ this.findTopDrawing(event);
- this.selectedDrawingSnapshot = this.createDrawingSnapshot(drawing);
- });
+ if (drawing) {
+ this.interactionDrawing = drawing;
+
+ if (!drawing.isCreationPending()) {
+ this.selectedDrawingSnapshot = this.createDrawingSnapshot(drawing);
+ }
+
+ drawing.getSeriesDrawing().pointerDown(event);
+ } else {
+ selectedDrawing?.getSeriesDrawing().pointerDown(event);
+ }
+ }
this.DOM.refreshEntities();
};
- private handlePointerUp = (): void => {
+ private handlePointerMove = (event: PointerEvent): void => {
+ this.interactionDrawing?.getSeriesDrawing().pointerMove(event);
+ };
+
+ private handlePointerUp = (event: PointerEvent): void => {
const previousSnapshot = this.selectedDrawingSnapshot;
+ const drawing = this.interactionDrawing;
+
+ drawing?.getSeriesDrawing().pointerUp(event);
this.selectedDrawingSnapshot = null;
- queueMicrotask(() => {
- if (!previousSnapshot) {
- return;
+ if (previousSnapshot) {
+ const currentDrawing = this.findDrawing(previousSnapshot.id);
+
+ if (currentDrawing && !currentDrawing.isCreationPending()) {
+ this.pushDrawingChange(previousSnapshot, this.createDrawingSnapshot(currentDrawing));
}
+ }
- const drawing = this.findDrawing(previousSnapshot.id);
+ if (drawing && !drawing.isCreationPending()) {
+ this.interactionDrawing = null;
+ }
- if (!drawing || drawing.isCreationPending()) {
- return;
- }
+ this.DOM.refreshEntities();
+ this.updateActiveTool();
+ };
- this.pushDrawingChange(previousSnapshot, this.createDrawingSnapshot(drawing));
- });
+ private handleDoubleClick = (event: MouseEvent): void => {
+ const drawings = this.drawings$.value;
+ const pendingDrawing =
+ this.interactionDrawing?.isCreationPending() === true
+ ? this.interactionDrawing
+ : drawings.find((drawing) => drawing.isCreationPending());
+
+ const drawing = pendingDrawing ?? this.findTopDrawing(event);
+
+ if (!drawing) {
+ return;
+ }
+
+ if (!drawing.isCreationPending() && drawing !== this.selectedDrawing$.value) {
+ this.selectedDrawing$.next(drawing);
+ }
+
+ drawing.getSeriesDrawing().doubleClick(event);
+
+ if (this.interactionDrawing && !this.interactionDrawing.isCreationPending()) {
+ this.interactionDrawing = null;
+ }
this.DOM.refreshEntities();
this.updateActiveTool();
};
+ private handleContextMenu = (event: MouseEvent): void => {
+ const drawings = this.drawings$.value;
+ const pendingDrawing =
+ this.interactionDrawing?.isCreationPending() === true
+ ? this.interactionDrawing
+ : drawings.find((drawing) => drawing.isCreationPending());
+
+ const drawing = pendingDrawing ?? this.findTopDrawing(event);
+
+ drawing?.getSeriesDrawing().contextMenu(event);
+
+ if (this.interactionDrawing && !this.interactionDrawing.isCreationPending()) {
+ this.interactionDrawing = null;
+ }
+ };
+
private handleClick = (): void => {
this.DOM.refreshEntities();
};
+ private findTopDrawing(event: MouseEvent): Drawing | null {
+ let topDrawing: Drawing | null = null;
+
+ for (const drawing of this.drawings$.value) {
+ if (!drawing.getSeriesDrawing().isHit(event)) {
+ continue;
+ }
+
+ if (!topDrawing || drawing.zIndex > topDrawing.zIndex) {
+ topDrawing = drawing;
+ }
+ }
+
+ return topDrawing;
+ }
+
private findDrawing(id: string): Drawing | undefined {
return this.drawings$.value.find((drawing) => drawing.id === id);
}
@@ -387,6 +471,10 @@ export class DrawingsManager {
this.selectedDrawing$.next(null);
}
+ if (this.interactionDrawing && drawingsToRemove.includes(this.interactionDrawing)) {
+ this.interactionDrawing = null;
+ }
+
drawingsToRemove.forEach((drawing) => {
drawing.destroy();
this.DOM.removeEntity(drawing);
@@ -447,7 +535,6 @@ export class DrawingsManager {
}
const { id, state, isLocked = false, zIndex, shouldUpdateDrawingsList = true } = options;
-
const shouldSelectAfterCreation = state === undefined;
if (shouldSelectAfterCreation && this.selectedDrawing$.value) {
@@ -576,6 +663,7 @@ export class DrawingsManager {
}
this.selectedDrawingSnapshot = null;
+ this.interactionDrawing = null;
this.removeDrawings(this.drawings$.value, false);
const restoredDrawings = snapshot.reduce<Drawing[]>((drawings, item) => {
@@ -607,6 +695,7 @@ export class DrawingsManager {
public activateCrosshair(): void {
this.removePendingDrawings(false);
+ this.interactionDrawing = null;
this.setActiveTool('crosshair');
this.DOM.refreshEntities();
}
@@ -706,19 +795,24 @@ export class DrawingsManager {
this.selectedDrawing$.next(null);
}
+ this.interactionDrawing = null;
this.drawings$.value.forEach((drawing) => drawing.hide());
this.DOM.refreshEntities();
}
public destroy(): void {
+ window.removeEventListener('pointermove', this.handlePointerMove);
window.removeEventListener('pointerup', this.handlePointerUp);
window.removeEventListener('pointercancel', this.handlePointerUp);
this.container.removeEventListener('click', this.handleClick);
this.container.removeEventListener('pointerdown', this.handlePointerDown);
+ this.container.removeEventListener('dblclick', this.handleDoubleClick);
+ this.container.removeEventListener('contextmenu', this.handleContextMenu);
this.drawings$.value.forEach((drawing) => drawing.destroy());
this.selectedDrawingSnapshot = null;
+ this.interactionDrawing = null;
this.copyPasteBuffer = null;
this.subscriptions.unsubscribe();
diff --git a/src/utils/parseTimeframe.ts b/src/utils/parseTimeframe.ts
index 38c2fa444cbf5c98f3e8757d4c5458464b1fb0f5..4517200a7d8a803a81cf3bbab236fe083b5dc6b6 100644
--- a/src/utils/parseTimeframe.ts
+++ b/src/utils/parseTimeframe.ts
@@ -20,10 +20,16 @@ export function parseTimeframe(timeframe: Timeframes, date?: number) {
}
export function getStartTime(timeframe: Timeframes, date: number) {
- const { startOfUnit } = parseTimeframe(timeframe, date);
+ const { candleWidth, startOfUnit } = parseTimeframe(timeframe, date);
+
+ if (candleWidth === 1) {
+ return startOfUnit.unix();
+ }
const sec = timeframeToSeconds(timeframe);
- return startOfUnit.unix() - (startOfUnit.unix() % sec);
+ const offsetSec = startOfUnit.utcOffset() * 60;
+
+ return startOfUnit.unix() - ((startOfUnit.unix() + offsetSec) % sec);
}
export const toDayjs: Record<string, ManipulateType> = {