Загрузка данных
import { IChartApi, ISeriesApi, SeriesType } from 'lightweight-charts';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { DOMObject, DOMObjectParams } from '@core/DOMObject';
import { Hotkeys, Keys } from '@core/Hotkeys';
import { DrawingsNames } from '@src/constants';
import { SeriesStrategies } from '@src/modules/series-strategies/SeriesFactory';
import { SettingsTab, SettingsValues, ToolbarSettingField } from '@src/types/settings';
import type { DrawingInteraction, ISeriesDrawing } from '@src/core/Drawings/SeriesDrawingBase';
interface DrawingParams extends DOMObjectParams {
drawingName: DrawingsNames;
lwcChart: IChartApi;
mainSeries: SeriesStrategies;
onDelete: (id: string) => void;
onCopy: () => void;
construct: (chart: IChartApi, series: ISeriesApi<SeriesType>, interaction: DrawingInteraction) => ISeriesDrawing;
selected$: Observable<boolean>;
isSelected: () => boolean;
select: () => void;
deselect: () => void;
isLocked?: boolean;
hotkeys: Hotkeys;
}
export class Drawing extends DOMObject {
private lwcDrawing: ISeriesDrawing;
private mainSeries: SeriesStrategies;
private drawingName: DrawingsNames;
private hotkeys: Hotkeys;
private lockedSubject: BehaviorSubject<boolean>;
private settingsSubject: BehaviorSubject<SettingsValues>;
private subscriptions = new Subscription();
private unregisterDeleteHotkey = () => {};
private unregisterCopyHotkey = () => {};
constructor({
lwcChart,
name,
mainSeries,
drawingName,
id,
onDelete,
onCopy,
zIndex,
moveUp,
moveDown,
construct,
selected$,
isSelected,
select,
deselect,
isLocked = false,
paneId,
hotkeys,
}: DrawingParams) {
super({
id,
name,
zIndex,
onDelete,
moveUp,
moveDown,
paneId,
});
this.hotkeys = hotkeys;
this.mainSeries = mainSeries;
this.drawingName = drawingName;
this.lockedSubject = new BehaviorSubject(isLocked);
const interaction: DrawingInteraction = {
selected$,
locked$: this.lockedSubject.asObservable(),
isSelected,
isLocked: () => this.lockedSubject.value,
select,
deselect,
};
this.lwcDrawing = construct(lwcChart, mainSeries, interaction);
this.settingsSubject = new BehaviorSubject(this.lwcDrawing.getSettings());
this.subscriptions.add(
this.lwcDrawing.subscribeSettings((settings) => {
this.settingsSubject.next(settings);
}),
);
this.subscriptions.add(
selected$.subscribe((isSelectedDrawing) => {
if (!isSelectedDrawing) {
this.unregisterSelectedDrawingHotkeys();
return;
}
this.unregisterDeleteHotkey = this.hotkeys.register({
keys: [Keys.delete],
callback: () => {
this.delete();
},
});
this.unregisterCopyHotkey = this.hotkeys.register({
keys: [Keys.mod, Keys.c],
callback: () => {
if (!this.isCreationPending()) {
onCopy();
}
},
});
}),
);
}
public getDrawingName(): DrawingsNames {
return this.drawingName;
}
public getSeriesDrawing(): ISeriesDrawing {
return this.lwcDrawing;
}
public show(): void {
this.lwcDrawing.show();
super.show();
}
public hide(): void {
this.lwcDrawing.hide();
super.hide();
}
public rebind = (nextMainSeries: SeriesStrategies): void => {
this.lwcDrawing.rebind(nextMainSeries);
this.mainSeries = nextMainSeries;
};
public isCreationPending(): boolean {
return this.lwcDrawing.isCreationPending();
}
public subscribeIsLocked(callback: (isLocked: boolean) => void): Subscription {
return this.lockedSubject.subscribe(callback);
}
public subscribeSettings(callback: (settings: SettingsValues) => void): Subscription {
return this.settingsSubject.subscribe(callback);
}
public isLocked(): boolean {
return this.lockedSubject.value;
}
public setLocked(isLocked: boolean): void {
if (this.lockedSubject.value === isLocked) {
return;
}
this.lockedSubject.next(isLocked);
}
public toggleLock(): void {
this.setLocked(!this.isLocked());
}
public waitForCreation(): Promise<void> {
return this.lwcDrawing.waitTillReady();
}
public shouldShowInObjectTree(): boolean {
return this.lwcDrawing.shouldShowInObjectTree();
}
public getState(): unknown {
return this.lwcDrawing.getState();
}
public setState(state: unknown): void {
this.lwcDrawing.setState(state);
this.settingsSubject.next(this.lwcDrawing.getSettings());
}
public getSettings(): SettingsValues {
return this.lwcDrawing.getSettings();
}
public updateSettings(settings: SettingsValues): void {
this.lwcDrawing.updateSettings(settings);
}
public getSettingsTabs(): SettingsTab[] {
return this.lwcDrawing.getSettingsTabs();
}
public getToolbarSettings(): ToolbarSettingField[] {
return this.getSettingsTabs()
.flatMap((tab) => tab.fields)
.filter((field): field is ToolbarSettingField => field.toolbar !== undefined);
}
public hasSettings(): boolean {
return this.getSettingsTabs().some((tab) => tab.fields.length > 0);
}
public destroy(): void {
this.subscriptions.unsubscribe();
this.unregisterSelectedDrawingHotkeys();
this.lockedSubject.complete();
this.settingsSubject.complete();
this.mainSeries.detachPrimitive(this.lwcDrawing);
this.lwcDrawing.destroy();
}
private unregisterSelectedDrawingHotkeys(): void {
this.unregisterDeleteHotkey();
this.unregisterCopyHotkey();
this.unregisterDeleteHotkey = () => {};
this.unregisterCopyHotkey = () => {};
}
}
import { BehaviorSubject, Observable } from 'rxjs';
import { DrawingsNames } from '@src/constants';
import { ActiveDrawingTool, SettingsValues } from '@src/types';
import { Drawing } from './Drawings';
import { DrawingsManager } from './DrawingsManager';
import { Hotkeys, Keys } from './Hotkeys';
import type { MouseEventParams } from 'lightweight-charts';
import type { Pane } from './Pane';
const DRAWING_HOTKEYS: { keys: readonly Keys[]; drawingName: DrawingsNames }[] = [
{
keys: [Keys.alt, Keys.t],
drawingName: DrawingsNames.trendLine,
},
{
keys: [Keys.alt, Keys.h],
drawingName: DrawingsNames.horizontalLine,
},
{
keys: [Keys.alt, Keys.v],
drawingName: DrawingsNames.verticalLine,
},
{
keys: [Keys.alt, Keys.f],
drawingName: DrawingsNames.fibonacciRetracement,
},
{
keys: [Keys.alt, Keys.shift, Keys.r],
drawingName: DrawingsNames.rectangle,
},
];
// todo: нужно дописывать класс)
export class DrawingsManagerCollection {
private managersMap: Map<number, DrawingsManager> = new Map();
private hotkeys: Hotkeys;
private activeTool$ = new BehaviorSubject<ActiveDrawingTool>('crosshair');
private endlessMode$ = new BehaviorSubject(false);
private isWaitingForDrawingStart = false;
private unregisterHotkeys: (() => void)[] = [];
constructor({ hotkeys }: { hotkeys: Hotkeys }) {
this.hotkeys = hotkeys;
for (const { keys, drawingName } of DRAWING_HOTKEYS) {
this.unregisterHotkeys.push(
this.hotkeys.register({
keys,
callback: () => {
this.addDrawingForce(drawingName);
},
}),
);
}
this.unregisterHotkeys.push(
this.hotkeys.register({
keys: [Keys.escape],
callback: this.activateCrosshair,
}),
);
}
public getIsEndlessMode(): boolean {
return this.endlessMode$.value;
}
public getActiveToolValue(): ActiveDrawingTool {
return this.activeTool$.value;
}
public setActiveTool(next: ActiveDrawingTool): void {
this.activeTool$.next(next);
if (next === 'crosshair') {
this.isWaitingForDrawingStart = false;
}
}
public removeDrawingManager(paneId: number): void {
this.managersMap.delete(paneId);
}
public addDrawingManager(manager: DrawingsManager, paneId: number): void {
this.managersMap.set(paneId, manager);
}
public addDrawingForce = (name: DrawingsNames): void => {
this.cancelPendingDrawings();
this.setActiveTool(name);
this.isWaitingForDrawingStart = true;
};
public handlePaneClick(pane: Pane, event: MouseEventParams): void {
const activeTool = this.activeTool$.value;
if (activeTool === 'crosshair') {
if (event.sourceEvent?.shiftKey) {
void pane.getDrawingManager().addDrawingForce(DrawingsNames.ruler, event, false);
}
return;
}
if (!this.isWaitingForDrawingStart) {
return;
}
this.isWaitingForDrawingStart = false;
void pane.getDrawingManager().addDrawingForce(activeTool, event);
}
public setEndlessDrawingMode = (value: boolean): void => {
if (this.endlessMode$.value === value) {
return;
}
this.endlessMode$.next(value);
};
public isEndlessDrawingsMode(): Observable<boolean> {
return this.endlessMode$.asObservable();
}
public activateCrosshair = (): void => {
this.cancelPendingDrawings();
this.setActiveTool('crosshair');
};
public getActiveTool(): Observable<ActiveDrawingTool> {
return this.activeTool$.asObservable();
}
public selectedDrawing(): Observable<Drawing | null> {
return Array.from(this.managersMap.values())[0].selectedDrawing();
}
public updateSelectedDrawingSettings = (settings: SettingsValues): void => {
for (const manager of this.managersMap.values()) {
manager.updateSelectedDrawingSettings(settings);
}
};
public toggleSelectedDrawingLock = (): void => {
Array.from(this.managersMap.values())[0].toggleSelectedDrawingLock();
};
public openSelectedDrawingSettings = (): void => {
Array.from(this.managersMap.values())[0].openSelectedDrawingSettings();
};
public deleteSelectedDrawing = (): void => {
Array.from(this.managersMap.values())[0].deleteSelectedDrawing();
};
public destroy(): void {
for (const unregisterHotkey of this.unregisterHotkeys) {
unregisterHotkey();
}
this.unregisterHotkeys = [];
this.activeTool$.complete();
this.endlessMode$.complete();
}
private cancelPendingDrawings(): void {
for (const manager of this.managersMap.values()) {
manager.cancelPendingDrawing();
}
}
}
if (config.chartCollectionPreset.undoRedoEnabled) {
const undoRedo = this.eventManager.getUndoRedo();
this.hotkeys.register({
keys: [Keys.mod, Keys.z],
callback: undoRedo.undo,
});
this.hotkeys.register({
keys: [Keys.mod, Keys.y],
callback: undoRedo.redo,
});
this.hotkeys.register({
keys: [Keys.mod, Keys.shift, Keys.z],
callback: undoRedo.redo,
});
}