Загрузка данных


import { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, UTCTimestamp } from 'lightweight-charts';
import { Observable } from 'rxjs';

import {
  CustomPriceAxisPaneView,
  CustomPriceAxisView,
  CustomTimeAxisPaneView,
  CustomTimeAxisView,
} from '@core/Drawings/axis';
import { SeriesDrawingBase } from '@core/Drawings/common';
import {
  getAnchorFromPoint,
  getPriceDelta as getPriceDeltaFromCoordinates,
  getXCoordinateFromTime,
  getYCoordinateFromPrice,
  isNearPoint,
  shiftTimeByPixels,
} from '@core/Drawings/helpers';
import { updateViews } from '@core/Drawings/utils';

import { getThemeStore } from '@src/theme';
import { Defaults } from '@src/types/defaults';
import { formatPrice } from '@src/utils';
import { formatDate } from '@src/utils/formatter';

import { TrendLinePaneView } from './paneView';

import {
  createDefaultSettings,
  getTrendLineSettingsTabs,
  TrendLineSettings,
  TrendLineStyle,
  TrendLineTextStyle,
} from './settings';

import type { DrawingInteraction, ISeriesDrawing } from '@core/Drawings/common';
import type { Anchor, AxisLabel, AxisSegment, Point, SeriesApi } from '@core/Drawings/types';
import type { ChartOptionsModel, SettingsTab } from '@src/types';

type TrendLineMode = 'idle' | 'drawing' | 'ready' | 'dragging-start' | 'dragging-end' | 'dragging-body';
type TimeLabelKind = 'start' | 'end';
type PriceLabelKind = 'start' | 'end';

interface TrendLineParams {
  container: HTMLElement;
  interaction: DrawingInteraction;
  formatObservable?: Observable<ChartOptionsModel>;
  removeSelf?: () => void;
  openSettings?: () => void;
}

interface TrendLineState {
  hidden: boolean;
  mode: TrendLineMode;
  startAnchor: Anchor | null;
  endAnchor: Anchor | null;
  settings: TrendLineSettings;
}

interface TrendLineGeometry {
  startPoint: Point;
  endPoint: Point;
  left: number;
  right: number;
  top: number;
  bottom: number;
}

export interface TrendLineRenderData extends TrendLineGeometry, TrendLineStyle, TrendLineTextStyle {
  showHandles: boolean;
}

const LINE_HIT_TOLERANCE = 6;
const MIN_LINE_SIZE = 4;

export class TrendLine extends SeriesDrawingBase<TrendLineSettings> implements ISeriesDrawing {
  private removeSelf?: () => void;
  private openSettings?: () => void;

  protected settings: TrendLineSettings = createDefaultSettings();
  protected mode: TrendLineMode = 'idle';

  private startAnchor: Anchor | null = null;
  private endAnchor: Anchor | null = null;

  private dragPointerId: number | null = null;
  private dragStartPoint: Point | null = null;
  private dragStateSnapshot: TrendLineState | null = null;

  private displayFormat: ChartOptionsModel = {
    dateFormat: Defaults.dateFormat,
    timeFormat: Defaults.timeFormat,
    showTime: Defaults.showTime,
  };

  private readonly paneView: TrendLinePaneView;
  private readonly timeAxisPaneView: CustomTimeAxisPaneView;
  private readonly priceAxisPaneView: CustomPriceAxisPaneView;
  private readonly startTimeAxisView: CustomTimeAxisView;
  private readonly endTimeAxisView: CustomTimeAxisView;
  private readonly startPriceAxisView: CustomPriceAxisView;
  private readonly endPriceAxisView: CustomPriceAxisView;

  constructor(
    chart: IChartApi,
    series: SeriesApi,
    { container, interaction, formatObservable, removeSelf, openSettings }: TrendLineParams,
  ) {
    super({ chart, series, container, interaction });

    this.removeSelf = removeSelf;
    this.openSettings = openSettings;

    this.paneView = new TrendLinePaneView(this);

    this.timeAxisPaneView = new CustomTimeAxisPaneView({
      getAxisSegments: () => this.getTimeAxisSegments(),
    });

    this.priceAxisPaneView = new CustomPriceAxisPaneView({
      getAxisSegments: () => this.getPriceAxisSegments(),
    });

    this.startTimeAxisView = new CustomTimeAxisView({
      getAxisLabel: (kind) => this.getTimeAxisLabel(kind),
      labelKind: 'start',
    });

    this.endTimeAxisView = new CustomTimeAxisView({
      getAxisLabel: (kind) => this.getTimeAxisLabel(kind),
      labelKind: 'end',
    });

    this.startPriceAxisView = new CustomPriceAxisView({
      getAxisLabel: (kind) => this.getPriceAxisLabel(kind),
      labelKind: 'start',
    });

    this.endPriceAxisView = new CustomPriceAxisView({
      getAxisLabel: (kind) => this.getPriceAxisLabel(kind),
      labelKind: 'end',
    });

    if (formatObservable) {
      this.subscriptions.add(
        formatObservable.subscribe((format) => {
          this.displayFormat = format;
          this.render();
        }),
      );
    }

    this.series.attachPrimitive(this);
  }

  public isCreationPending(): boolean {
    return this.mode === 'idle' || this.mode === 'drawing';
  }

  public getState(): TrendLineState {
    return {
      hidden: this.hidden,
      mode: this.mode,
      startAnchor: this.startAnchor,
      endAnchor: this.endAnchor,
      settings: { ...this.settings },
    };
  }

  public setState(state: unknown): void {
    if (!state || typeof state !== 'object') {
      return;
    }

    const nextState = state as Partial<TrendLineState>;

    if ('hidden' in nextState && typeof nextState.hidden === 'boolean') {
      this.hidden = nextState.hidden;
    }

    if ('mode' in nextState && nextState.mode) {
      this.mode = nextState.mode;
    }

    if ('startAnchor' in nextState) {
      this.startAnchor = nextState.startAnchor ?? null;
    }

    if ('endAnchor' in nextState) {
      this.endAnchor = nextState.endAnchor ?? null;
    }

    if ('settings' in nextState && nextState.settings) {
      this.settings = {
        ...createDefaultSettings(),
        ...nextState.settings,
      };
    }

    this.render();
  }

  public getSettingsTabs(): SettingsTab[] {
    return getTrendLineSettingsTabs(this.settings);
  }

  public updateAllViews(): void {
    updateViews([
      this.paneView,
      this.timeAxisPaneView,
      this.priceAxisPaneView,
      this.startTimeAxisView,
      this.endTimeAxisView,
      this.startPriceAxisView,
      this.endPriceAxisView,
    ]);
  }

  public paneViews(): readonly IPrimitivePaneView[] {
    return [this.paneView];
  }

  public timeAxisPaneViews(): readonly IPrimitivePaneView[] {
    return [this.timeAxisPaneView];
  }

  public priceAxisPaneViews(): readonly IPrimitivePaneView[] {
    return [this.priceAxisPaneView];
  }

  public timeAxisViews() {
    return [this.startTimeAxisView, this.endTimeAxisView];
  }

  public priceAxisViews() {
    return [this.startPriceAxisView, this.endPriceAxisView];
  }

  public getRenderData(): TrendLineRenderData | null {
    if (this.hidden) {
      return null;
    }

    const geometry = this.getGeometry();

    if (!geometry) {
      return null;
    }

    return {
      ...geometry,
      showHandles: this.shouldShowHandles(),
      ...this.settings,
    };
  }

  protected getHoveredItem(x: number, y: number): PrimitiveHoveredItem | null {
    if (this.hidden || this.mode === 'idle' || this.mode === 'drawing') {
      return null;
    }

    const point = { x, y };

    if (this.getPointTarget(point)) {
      return {
        cursorStyle: 'move',
        externalId: 'trend-line',
        zOrder: 'top',
      };
    }

    if (!this.isPointNearLine(point)) {
      return null;
    }

    return {
      cursorStyle: 'grab',
      externalId: 'trend-line',
      zOrder: 'top',
    };
  }

  protected getTimeAxisSegments(): AxisSegment[] {
    if (!this.isSelected() && !this.isCreationPending()) {
      return [];
    }

    const geometry = this.getGeometry();

    if (!geometry) {
      return [];
    }

    const { colors } = getThemeStore();

    return [
      {
        from: geometry.left,
        to: geometry.right,
        color: colors.axisMarkerAreaFill,
      },
    ];
  }

  protected getPriceAxisSegments(): AxisSegment[] {
    if (!this.isSelected() && !this.isCreationPending()) {
      return [];
    }

    const geometry = this.getGeometry();

    if (!geometry) {
      return [];
    }

    const { colors } = getThemeStore();

    return [
      {
        from: geometry.top,
        to: geometry.bottom,
        color: colors.axisMarkerAreaFill,
      },
    ];
  }

  protected getTimeAxisLabel(kind: string): AxisLabel | null {
    if ((!this.isSelected() && !this.isCreationPending()) || (kind !== 'start' && kind !== 'end')) {
      return null;
    }

    const coordinate = this.getTimeCoordinate(kind);
    const text = this.getTimeText(kind);

    if (coordinate === null || !text) {
      return null;
    }

    const { colors } = getThemeStore();

    return {
      coordinate,
      text,
      textColor: colors.chartPriceLineText,
      backgroundColor: colors.axisMarkerLabelFill,
    };
  }

  protected getPriceAxisLabel(kind: string): AxisLabel | null {
    if ((!this.isSelected() && !this.isCreationPending()) || (kind !== 'start' && kind !== 'end')) {
      return null;
    }

    const coordinate = this.getPriceCoordinate(kind);
    const text = this.getPriceText(kind);

    if (coordinate === null || !text) {
      return null;
    }

    const { colors } = getThemeStore();

    return {
      coordinate,
      text,
      textColor: colors.chartPriceLineText,
      backgroundColor: colors.axisMarkerLabelFill,
    };
  }

  protected handleDoubleClick = (event: MouseEvent): void => {
    if (this.hidden || this.mode === 'idle' || this.mode === 'drawing') {
      return;
    }

    const rect = this.container.getBoundingClientRect();
    const point = {
      x: event.clientX - rect.left,
      y: event.clientY - rect.top,
    };

    if (!this.getPointTarget(point) && !this.isPointNearLine(point)) {
      return;
    }

    event.preventDefault();
    event.stopPropagation();

    this.openSettings?.();
  };

  protected handlePointerDown = (event: PointerEvent): void => {
    if (this.hidden || event.button !== 0) {
      return;
    }

    const point = this.getEventPoint(event);

    if (this.mode === 'idle') {
      event.preventDefault();
      event.stopPropagation();

      this.startDrawing(point);
      return;
    }

    if (this.mode === 'drawing') {
      event.preventDefault();
      event.stopPropagation();

      this.updateDrawing(point);
      this.finishDrawing();
      return;
    }

    if (this.mode !== 'ready') {
      return;
    }

    const pointTarget = this.getPointTarget(point);
    const isNearLine = this.isPointNearLine(point);
    const isDrawingHit = pointTarget !== null || isNearLine;

    if (!this.isSelected()) {
      if (!isDrawingHit) {
        return;
      }

      event.preventDefault();
      event.stopPropagation();

      this.select();
      return;
    }

    if (pointTarget === 'start') {
      event.preventDefault();
      event.stopPropagation();

      this.startDragging('dragging-start', point, event.pointerId);
      return;
    }

    if (pointTarget === 'end') {
      event.preventDefault();
      event.stopPropagation();

      this.startDragging('dragging-end', point, event.pointerId);
      return;
    }

    if (isNearLine) {
      event.preventDefault();
      event.stopPropagation();

      this.startDragging('dragging-body', point, event.pointerId);
      return;
    }

    this.deselect();
  };

  protected handlePointerMove = (event: PointerEvent): void => {
    const point = this.getEventPoint(event);

    if (this.mode === 'drawing') {
      this.updateDrawing(point);
      return;
    }

    if (this.dragPointerId !== event.pointerId) {
      return;
    }

    if (this.mode === 'dragging-start' || this.mode === 'dragging-end') {
      event.preventDefault();
      event.stopPropagation();

      this.movePoint(point);
      this.render();
      return;
    }

    if (this.mode === 'dragging-body') {
      event.preventDefault();
      event.stopPropagation();

      this.moveBody(point);
      this.render();
    }
  };

  protected handlePointerUp = (event: PointerEvent): void => {
    if (this.dragPointerId !== event.pointerId) {
      return;
    }

    if (this.mode === 'dragging-start' || this.mode === 'dragging-end' || this.mode === 'dragging-body') {
      this.finishDragging();
    }
  };

  private startDrawing(point: Point): void {
    const anchor = this.createAnchor(point);

    if (!anchor) {
      return;
    }

    this.startAnchor = anchor;
    this.endAnchor = anchor;
    this.mode = 'drawing';

    this.render();
  }

  private updateDrawing(point: Point): void {
    const anchor = this.createAnchor(point);

    if (!anchor) {
      return;
    }

    this.endAnchor = anchor;
    this.render();
  }

  private finishDrawing(): void {
    const geometry = this.getGeometry();

    if (!geometry) {
      return;
    }

    const lineSize = Math.hypot(
      geometry.endPoint.x - geometry.startPoint.x,
      geometry.endPoint.y - geometry.startPoint.y,
    );

    if (lineSize < MIN_LINE_SIZE) {
      this.removeSelf?.();
      return;
    }

    this.mode = 'ready';
    this.resolveReady?.();

    this.render();
  }

  private startDragging(mode: TrendLineMode, point: Point, pointerId: number): void {
    this.mode = mode;

    this.dragPointerId = pointerId;
    this.dragStartPoint = point;
    this.dragStateSnapshot = this.getState();

    this.hideCrosshair();
    this.render();
  }

  private finishDragging(): void {
    this.mode = 'ready';
    this.resolveReady?.();

    this.dragPointerId = null;
    this.dragStartPoint = null;
    this.dragStateSnapshot = null;

    this.showCrosshair();
    this.render();
  }

  private movePoint(point: Point): void {
    const anchor = this.createAnchor(point);

    if (!anchor) {
      return;
    }

    if (this.mode === 'dragging-start') {
      this.startAnchor = anchor;
    }

    if (this.mode === 'dragging-end') {
      this.endAnchor = anchor;
    }
  }

  private moveBody(point: Point): void {
    const snapshot = this.dragStateSnapshot;

    if (!snapshot?.startAnchor || !snapshot.endAnchor || !this.dragStartPoint) {
      return;
    }

    const offsetX = point.x - this.dragStartPoint.x;
    const priceOffset = getPriceDeltaFromCoordinates(this.series, this.dragStartPoint.y, point.y);

    const nextStartTime = shiftTimeByPixels(this.chart, snapshot.startAnchor.time, offsetX, this.series);
    const nextEndTime = shiftTimeByPixels(this.chart, snapshot.endAnchor.time, offsetX, this.series);

    if (nextStartTime === null || nextEndTime === null) {
      return;
    }

    this.startAnchor = {
      time: nextStartTime,
      price: snapshot.startAnchor.price + priceOffset,
    };

    this.endAnchor = {
      time: nextEndTime,
      price: snapshot.endAnchor.price + priceOffset,
    };
  }

  private createAnchor(point: Point): Anchor | null {
    return getAnchorFromPoint(this.chart, this.series, point);
  }

  protected getGeometry(): TrendLineGeometry | null {
    if (!this.startAnchor || !this.endAnchor) {
      return null;
    }

    const startX = getXCoordinateFromTime(this.chart, this.startAnchor.time, this.series);
    const endX = getXCoordinateFromTime(this.chart, this.endAnchor.time, this.series);
    const startY = getYCoordinateFromPrice(this.series, this.startAnchor.price);
    const endY = getYCoordinateFromPrice(this.series, this.endAnchor.price);

    if (startX === null || endX === null || startY === null || endY === null) {
      return null;
    }

    const startPoint = {
      x: Math.round(Number(startX)),
      y: Math.round(Number(startY)),
    };

    const endPoint = {
      x: Math.round(Number(endX)),
      y: Math.round(Number(endY)),
    };

    return {
      startPoint,
      endPoint,
      left: Math.min(startPoint.x, endPoint.x),
      right: Math.max(startPoint.x, endPoint.x),
      top: Math.min(startPoint.y, endPoint.y),
      bottom: Math.max(startPoint.y, endPoint.y),
    };
  }

  private getPointTarget(point: Point): 'start' | 'end' | null {
    const geometry = this.getGeometry();

    if (!geometry) {
      return null;
    }

    if (isNearPoint(point, geometry.startPoint.x, geometry.startPoint.y, 8)) {
      return 'start';
    }

    if (isNearPoint(point, geometry.endPoint.x, geometry.endPoint.y, 8)) {
      return 'end';
    }

    return null;
  }

  private isPointNearLine(point: Point): boolean {
    const geometry = this.getGeometry();

    if (!geometry) {
      return false;
    }

    return this.getDistanceToSegment(point, geometry.startPoint, geometry.endPoint) <= LINE_HIT_TOLERANCE;
  }

  private getDistanceToSegment(point: Point, startPoint: Point, endPoint: Point): number {
    const dx = endPoint.x - startPoint.x;
    const dy = endPoint.y - startPoint.y;

    if (dx === 0 && dy === 0) {
      return Math.hypot(point.x - startPoint.x, point.y - startPoint.y);
    }

    const t = Math.max(
      0,
      Math.min(1, ((point.x - startPoint.x) * dx + (point.y - startPoint.y) * dy) / (dx * dx + dy * dy)),
    );

    const projectionX = startPoint.x + t * dx;
    const projectionY = startPoint.y + t * dy;

    return Math.hypot(point.x - projectionX, point.y - projectionY);
  }

  private getTimeCoordinate(kind: TimeLabelKind): number | null {
    const anchor = kind === 'start' ? this.startAnchor : this.endAnchor;

    if (!anchor) {
      return null;
    }

    const coordinate = getXCoordinateFromTime(this.chart, anchor.time, this.series);

    return coordinate === null ? null : Number(coordinate);
  }

  private getPriceCoordinate(kind: PriceLabelKind): number | null {
    const anchor = kind === 'start' ? this.startAnchor : this.endAnchor;

    if (!anchor) {
      return null;
    }

    const coordinate = getYCoordinateFromPrice(this.series, anchor.price);

    return coordinate === null ? null : Number(coordinate);
  }

  private getTimeText(kind: TimeLabelKind): string {
    const anchor = kind === 'start' ? this.startAnchor : this.endAnchor;

    if (!anchor || typeof anchor.time !== 'number') {
      return '';
    }

    return formatDate(
      anchor.time as UTCTimestamp,
      this.displayFormat.dateFormat,
      this.displayFormat.timeFormat,
      this.displayFormat.showTime,
    );
  }

  private getPriceText(kind: PriceLabelKind): string {
    const anchor = kind === 'start' ? this.startAnchor : this.endAnchor;

    if (!anchor) {
      return '';
    }

    return formatPrice(anchor.price) ?? '';
  }
}



import { getThemeStore } from '@src/theme';
import { t } from '@src/translations';

import { SettingField, SettingsTab, SettingsValues } from '@src/types';

export interface TrendLineStyle {
  lineColor: string;
}

export interface TrendLineTextStyle {
  fontSize: number;
  text: string;
  isBold: boolean;
  isItalic: boolean;
  textColor: string;
}

export type TrendLineSettings = TrendLineStyle & TrendLineTextStyle & SettingsValues;

export function createDefaultSettings(): TrendLineSettings {
  const { colors } = getThemeStore();

  return {
    lineColor: colors.chartLineColor,
    fontSize: 14,
    text: '',
    isBold: false,
    isItalic: false,
    textColor: colors.chartLineColor,
  };
}

export function getTrendLineSettingsTabs(settings: TrendLineSettings): SettingsTab[] {
  const styleFields: SettingField[] = [
    {
      key: 'lineColor',
      label: t('Line'),
      type: 'color',
      defaultValue: settings.lineColor,
      toolbar: {
        control: 'color',
        role: 'line',
      },
    },
  ];

  const textFields: SettingField[] = [
    {
      key: 'fontSize',
      label: t('Font size'),
      type: 'number',
      defaultValue: settings.fontSize,
      min: 8,
      max: 24,
    },
    {
      key: 'text',
      label: t('Text'),
      type: 'textarea',
      defaultValue: settings.text,
      placeholder: t('Enter text'),
    },
    {
      key: 'isBold',
      label: t('Bold'),
      type: 'boolean',
      defaultValue: settings.isBold,
    },
    {
      key: 'isItalic',
      label: t('Italic'),
      type: 'boolean',
      defaultValue: settings.isItalic,
    },
    {
      key: 'textColor',
      label: t('Text'),
      type: 'color',
      defaultValue: settings.textColor,
      toolbar: {
        control: 'color',
        role: 'text',
      },
    },
  ];

  return [
    {
      key: 'style',
      label: t('Style'),
      fields: styleFields,
    },
    {
      key: 'text',
      label: t('Text'),
      fields: textFields,
    },
  ];
}


import { IPrimitivePaneRenderer, IPrimitivePaneView, PrimitivePaneViewZOrder } from 'lightweight-charts';

import { TrendLinePaneRenderer } from './paneRenderer';
import { TrendLine } from './trendLine';

export class TrendLinePaneView implements IPrimitivePaneView {
  private readonly trendLine: TrendLine;

  constructor(trendLine: TrendLine) {
    this.trendLine = trendLine;
  }

  public update(): void {}

  public renderer(): IPrimitivePaneRenderer {
    return new TrendLinePaneRenderer(this.trendLine);
  }

  public zOrder(): PrimitivePaneViewZOrder {
    return 'top';
  }
}


import { CanvasRenderingTarget2D } from 'fancy-canvas';
import { IPrimitivePaneRenderer } from 'lightweight-charts';

import { getThemeStore } from '@src/theme';

import { TrendLine } from './trendLine';

const UI = {
  lineWidth: 2,
  handleRadius: 5,
  handleBorderWidth: 2,
  textLineHeightMultiplier: 1.2,
  textOffset: 4,
};

export class TrendLinePaneRenderer implements IPrimitivePaneRenderer {
  private readonly trendLine: TrendLine;

  constructor(trendLine: TrendLine) {
    this.trendLine = trendLine;
  }

  public draw(target: CanvasRenderingTarget2D): void {
    const data = this.trendLine.getRenderData();

    if (!data) {
      return;
    }

    const { colors } = getThemeStore();

    target.useBitmapCoordinateSpace(({ context, horizontalPixelRatio, verticalPixelRatio }) => {
      const pixelRatio = Math.max(horizontalPixelRatio, verticalPixelRatio);

      const startX = data.startPoint.x * horizontalPixelRatio;
      const startY = data.startPoint.y * verticalPixelRatio;
      const endX = data.endPoint.x * horizontalPixelRatio;
      const endY = data.endPoint.y * verticalPixelRatio;

      context.save();

      context.lineWidth = UI.lineWidth * pixelRatio;
      context.strokeStyle = data.lineColor;
      context.beginPath();
      context.moveTo(startX, startY);
      context.lineTo(endX, endY);
      context.stroke();

      if (data.text.trim()) {
        drawTextAlongLine(context, {
          startX,
          startY,
          endX,
          endY,
          text: data.text,
          fontSize: data.fontSize,
          isBold: data.isBold,
          isItalic: data.isItalic,
          textColor: data.textColor,
          horizontalPixelRatio,
          verticalPixelRatio,
        });
      }

      if (data.showHandles) {
        context.fillStyle = colors.chartBackground;
        context.strokeStyle = colors.chartLineColor;
        context.lineWidth = UI.handleBorderWidth * pixelRatio;

        drawHandle(context, startX, startY, UI.handleRadius * pixelRatio);
        drawHandle(context, endX, endY, UI.handleRadius * pixelRatio);
      }

      context.restore();
    });
  }
}

function drawHandle(context: CanvasRenderingContext2D, x: number, y: number, radius: number): void {
  context.beginPath();
  context.arc(x, y, radius, 0, Math.PI * 2);
  context.fill();
  context.stroke();
}

function drawTextAlongLine(
  context: CanvasRenderingContext2D,
  params: {
    startX: number;
    startY: number;
    endX: number;
    endY: number;
    text: string;
    fontSize: number;
    isBold: boolean;
    isItalic: boolean;
    textColor: string;
    horizontalPixelRatio: number;
    verticalPixelRatio: number;
  },
): void {
  const { startX, startY, endX, endY, text, fontSize, isBold, isItalic, textColor, verticalPixelRatio } = params;

  const lines = text.split('\n');
  const safeFontSize = Math.max(1, fontSize);
  const fontSizePx = safeFontSize * verticalPixelRatio;
  const lineHeight = safeFontSize * UI.textLineHeightMultiplier * verticalPixelRatio;

  const dx = endX - startX;
  const dy = endY - startY;

  let angle = Math.atan2(dy, dx);

  if (angle > Math.PI / 2 || angle < -Math.PI / 2) {
    angle += Math.PI;
  }

  const centerX = (startX + endX) / 2;
  const centerY = (startY + endY) / 2;

  const fontWeight = isBold ? '700 ' : '';
  const fontStyle = isItalic ? 'italic ' : '';

  context.save();
  context.translate(centerX, centerY);
  context.rotate(angle);

  context.font = `${fontStyle}${fontWeight}${fontSizePx}px Inter, sans-serif`;
  context.fillStyle = textColor;
  context.textAlign = 'center';
  context.textBaseline = 'middle';

  const blockHeight = lines.length * lineHeight;
  const textOffset = UI.textOffset * verticalPixelRatio;
  const textCenterY = -(blockHeight / 2 + textOffset);
  const startLineY = textCenterY - blockHeight / 2 + lineHeight / 2;

  lines.forEach((line, index) => {
    context.fillText(line, 0, startLineY + index * lineHeight);
  });

  context.restore();
}


import { AxisLine } from '@src/core/Drawings/axisLine';
import { Diapson } from '@src/core/Drawings/diapson';
import { FibonacciRetracement } from '@src/core/Drawings/fibonacciRetracement';
import { ParallelChannel } from '@src/core/Drawings/parallelChannel';
import { Ray } from '@src/core/Drawings/ray';
import { Rectangle } from '@src/core/Drawings/rectangle';
import { RegressionTrend } from '@src/core/Drawings/regressionTrend';
import { Ruler } from '@src/core/Drawings/ruler';
import { SliderPosition } from '@src/core/Drawings/sliderPosition';
import { Text } from '@src/core/Drawings/text';
import { Traectory } from '@src/core/Drawings/traectory';
import { TrendLine } from '@src/core/Drawings/trendLine';
import { VolumeProfile } from '@src/core/Drawings/volumeProfile';
import { t } from '@src/translations';
import { DrawingConfig } from '@src/types';

export enum DrawingsNames {
  'trendLine' = 'trendLine',
  'parallelChannel' = 'parallelChannel',
  'regressionTrend' = 'regressionTrend',
  'ray' = 'ray',
  'horizontalLine' = 'horizontalLine',
  'horizontalRay' = 'horizontalRay',
  'verticalLine' = 'verticalLine',
  'ruler' = 'ruler',

  'fibonacciRetracement' = 'fibonacciRetracement',

  'sliderLong' = 'sliderLong',
  'sliderShort' = 'sliderShort',
  'diapsonDates' = 'diapsonDates',
  'diapsonPrices' = 'diapsonPrices',
  'fixedRangeProfile' = 'fixedRangeProfile',
  'visibleRangeProfile' = 'visibleRangeProfile',

  'rectangle' = 'rectangle',
  'traectory' = 'traectory',

  'text' = 'text',
}

export const drawingLabelById = (): Record<DrawingsNames, string> => ({
  [DrawingsNames.trendLine]: t('Trend line'),
  [DrawingsNames.parallelChannel]: t('Parallel channel'),
  [DrawingsNames.regressionTrend]: t('Regression trend'),
  [DrawingsNames.ray]: t('Ray'),
  [DrawingsNames.horizontalLine]: t('Horizontal line'),
  [DrawingsNames.horizontalRay]: t('Horizontal ray'),
  [DrawingsNames.verticalLine]: t('Vertical line'),
  [DrawingsNames.fibonacciRetracement]: t('Fibonacci retracement'),
  [DrawingsNames.ruler]: t('Ruler'),
  [DrawingsNames.sliderLong]: t('Long position'),
  [DrawingsNames.sliderShort]: t('Short position'),
  [DrawingsNames.diapsonDates]: t('Dates range'),
  [DrawingsNames.diapsonPrices]: t('Prices range'),
  [DrawingsNames.fixedRangeProfile]: t('Fixed range volume profile'),
  [DrawingsNames.visibleRangeProfile]: t('Anchored volume profile'),
  [DrawingsNames.rectangle]: t('Rectangle'),
  [DrawingsNames.traectory]: t('Traectory'),
  [DrawingsNames.text]: t('Text'),
});

export const drawingsMap: Record<DrawingsNames, DrawingConfig> = {
  [DrawingsNames.trendLine]: {
    construct: ({ chart, series, container, eventManager, interaction, removeSelf, openSettings }) => {
      return new TrendLine(chart, series, {
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        removeSelf,
        openSettings,
      });
    },
  },
  [DrawingsNames.parallelChannel]: {
    construct: ({ chart, series, container, eventManager, interaction, openSettings }) => {
      return new ParallelChannel(chart, series, {
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        openSettings,
      });
    },
  },
  [DrawingsNames.regressionTrend]: {
    construct: ({ chart, series, container, eventManager, interaction, openSettings }) => {
      return new RegressionTrend(chart, series, {
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        openSettings,
      });
    },
  },
  [DrawingsNames.ray]: {
    construct: ({ chart, series, container, eventManager, interaction, removeSelf, openSettings }) => {
      return new Ray(chart, series, {
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        removeSelf,
        openSettings,
      });
    },
  },
  [DrawingsNames.horizontalLine]: {
    construct: ({ chart, series, eventManager, container, interaction, removeSelf, openSettings }) => {
      return new AxisLine(chart, series, {
        direction: 'horizontal',
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        removeSelf,
        openSettings,
      });
    },
  },
  [DrawingsNames.horizontalRay]: {
    construct: ({ chart, series, container, eventManager, interaction, removeSelf, openSettings }) => {
      return new TrendLine(chart, series, {
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        removeSelf,
        openSettings,
      });
    },
  },
  [DrawingsNames.verticalLine]: {
    construct: ({ chart, series, eventManager, container, interaction, removeSelf, openSettings }) => {
      return new AxisLine(chart, series, {
        direction: 'vertical',
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        removeSelf,
        openSettings,
      });
    },
  },
  [DrawingsNames.sliderLong]: {
    construct: ({ chart, series, eventManager, container, interaction, removeSelf, openSettings }) => {
      return new SliderPosition(chart, series, {
        side: 'long',
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        removeSelf,
        openSettings,
      });
    },
  },
  [DrawingsNames.fibonacciRetracement]: {
    construct: ({ chart, series, eventManager, container, interaction, removeSelf, openSettings }) => {
      return new FibonacciRetracement(chart, series, {
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        removeSelf,
        openSettings,
      });
    },
  },
  [DrawingsNames.sliderShort]: {
    construct: ({ chart, series, eventManager, container, interaction, removeSelf, openSettings }) => {
      return new SliderPosition(chart, series, {
        side: 'short',
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        removeSelf,
        openSettings,
      });
    },
  },
  [DrawingsNames.diapsonDates]: {
    construct: ({ chart, series, container, eventManager, interaction, removeSelf, openSettings }) => {
      return new Diapson(chart, series, {
        rangeMode: 'date',
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        removeSelf,
        openSettings,
      });
    },
  },
  [DrawingsNames.diapsonPrices]: {
    construct: ({ chart, series, container, eventManager, interaction, removeSelf, openSettings }) => {
      return new Diapson(chart, series, {
        rangeMode: 'price',
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        removeSelf,
        openSettings,
      });
    },
  },
  [DrawingsNames.fixedRangeProfile]: {
    construct: ({ chart, series, container, eventManager, interaction, removeSelf, openSettings }) => {
      return new VolumeProfile(chart, series, {
        profileKind: 'fixedRange',
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        removeSelf,
        openSettings,
      });
    },
  },
  [DrawingsNames.visibleRangeProfile]: {
    singleInstance: true,
    construct: ({ chart, series, container, eventManager, interaction, removeSelf, openSettings }) => {
      return new VolumeProfile(chart, series, {
        profileKind: 'visibleRange',
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        removeSelf,
        openSettings,
      });
    },
  },
  [DrawingsNames.rectangle]: {
    construct: ({ chart, series, eventManager, container, interaction, removeSelf, openSettings }) => {
      return new Rectangle(chart, series, {
        container,
        interaction,
        formatObservable: eventManager.getChartOptionsModel(),
        removeSelf,
        openSettings,
      });
    },
  },
  [DrawingsNames.ruler]: {
    singleInstance: true,
    construct: ({ chart, series, eventManager, container, interaction, removeSelf }) => {
      return new Ruler(chart, series, {
        formatObservable: eventManager.getChartOptionsModel(),
        container,
        interaction,
        resetTriggers: [eventManager.getTimeframeObs(), eventManager.getInterval()],
        removeSelf,
      });
    },
  },
  [DrawingsNames.traectory]: {
    construct: ({ chart, series, eventManager, container, interaction, removeSelf, openSettings }) => {
      return new Traectory(chart, series, {
        formatObservable: eventManager.getChartOptionsModel(),
        container,
        interaction,
        removeSelf,
        openSettings,
      });
    },
  },
  [DrawingsNames.text]: {
    construct: ({ chart, series, eventManager, container, interaction, removeSelf, openSettings }) => {
      return new Text(chart, series, {
        formatObservable: eventManager.getChartOptionsModel(),
        container,
        interaction,
        removeSelf,
        openSettings,
      });
    },
  },
};


import {
  DiapsonDatesIcon,
  DiapsonPricesIcon,
  FibonacciRetracementIcon,
  FixedProfileIcon,
  HorizontalLineIcon,
  ParallelChannelIcon,
  RayIcon,
  RectangleIcon,
  RegressionTrendIcon,
  SlidersHorizontalLongDashedIcon,
  SlidersHorizontalShortDashedIcon,
  SplineStraightIcon,
  TraectoryIcon,
  VerticalLineIcon,
  VisibleRangeProfileIcon,
} from '@src/components/Icon';
import { MenuOption } from '@src/components/Menu/MenuList';
import { DrawingsNames } from '@src/constants';
import { t } from '@src/translations';

export const trendLines = (): MenuOption<DrawingsNames>[] => [
  { value: DrawingsNames.trendLine, icon: <SplineStraightIcon />, label: t('Trend line') },
  { value: DrawingsNames.ray, icon: <RayIcon />, label: t('Ray') },
  { value: DrawingsNames.horizontalLine, icon: <HorizontalLineIcon />, label: t('Horizontal line') },
  // { value: DrawingsNames.horizontalRay, icon: <HorizontalRayIcon />, label: t('Horizontal ray') },
  { value: DrawingsNames.verticalLine, icon: <VerticalLineIcon />, label: t('Vertical ray') },
  { value: DrawingsNames.parallelChannel, icon: <ParallelChannelIcon />, label: t('Parallel channel') },
  { value: DrawingsNames.regressionTrend, icon: <RegressionTrendIcon />, label: t('Regression trend') },
];

export const measurementTools = (): MenuOption<DrawingsNames>[] => [
  { value: DrawingsNames.sliderLong, icon: <SlidersHorizontalLongDashedIcon />, label: t('Long position') },
  { value: DrawingsNames.sliderShort, icon: <SlidersHorizontalShortDashedIcon />, label: t('Short position') },
  { value: DrawingsNames.diapsonDates, icon: <DiapsonDatesIcon />, label: t('Dates range') },
  { value: DrawingsNames.diapsonPrices, icon: <DiapsonPricesIcon />, label: t('Prices range') },
  { value: DrawingsNames.fixedRangeProfile, icon: <FixedProfileIcon />, label: t('Fixed range volume profile') },
  {
    value: DrawingsNames.visibleRangeProfile,
    icon: <VisibleRangeProfileIcon />,
    label: t('Anchored volume profile'),
  },
];

export const gannAndFibonacciTools = (): MenuOption<DrawingsNames>[] => [
  { value: DrawingsNames.fibonacciRetracement, icon: <FibonacciRetracementIcon />, label: t('Fibonacci retracement') },
];

export const geometricShapes = (): MenuOption<DrawingsNames>[] => [
  { value: DrawingsNames.rectangle, icon: <RectangleIcon />, label: t('Rectangle') },
  { value: DrawingsNames.traectory, icon: <TraectoryIcon />, label: t('Traectory') },
];


import classNames from 'classnames';

import { Button, Divider, Tooltip } from 'exchange-elements/v2';

import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react';

import { Observable } from 'rxjs';

import { Hotkeys, Keys } from '@core/Hotkeys';

import { MenuList } from '@src/components/Menu';
import SplitDropdown from '@src/components/SplitDropdown';

import {
  gannAndFibonacciTools,
  geometricShapes,
  measurementTools,
  trendLines,
} from '@src/components/Toolbar/constants';

import { DrawingsNames } from '@src/constants';
import { t } from '@src/translations';
import { ActiveDrawingTool } from '@src/types';
import { ensureDefined, useObservable } from '@src/utils';

import {
  CrossIcon,
  EyeBrushIcon,
  HeartIcon,
  LayersIcon,
  MagnetIcon,
  PencilLockerIcon,
  RulerIcon,
  SplineCurveIcon,
  TrashIcon,
  TypeIcon,
  UnlockIcon,
  ZoomInIcon,
} from '../Icon';

import styles from './index.module.scss';

interface ToolbarProps {
  toggleDOM: () => void;
  addDrawing: (name: DrawingsNames) => Promise<void>;
  setEndlessDrawingsMode: (value: boolean) => void;
  isEndlessDrawingsMode$: Observable<boolean>;
  activateCrosshair: () => void;
  activeTool$: Observable<ActiveDrawingTool>;
  hotkeys: Hotkeys;
}

const implemented = {
  crosshair: true,
  lines: true,
  fib: true,
  rectangle: true,
  text: true,
  XABCD: false,
  position: true,
  icons: false,
};

const TOOLTIP_CLASSNAME = 'moex-chart-drawing-tooltip';

export default function Toolbar({
  toggleDOM,
  addDrawing,
  setEndlessDrawingsMode,
  isEndlessDrawingsMode$,
  activateCrosshair,
  activeTool$,
  hotkeys,
}: ToolbarProps) {
  const [selectedLineType, setSelectedLineType] = useState<DrawingsNames>(DrawingsNames.trendLine);
  const [selectedMeasurementTool, setSelectedMeasurementTool] = useState(DrawingsNames.fixedRangeProfile);
  const [selectedGeometricShape, setSelectedGeometricShape] = useState(DrawingsNames.rectangle);
  const [selectedGannAndFibonacci, setSelectedGannAndFibonacci] = useState(DrawingsNames.fibonacciRetracement);

  const isEndlessDrawingsMode = useObservable(isEndlessDrawingsMode$);

  const activeTool = useObservable(activeTool$, 'crosshair');

  const toolbarRef = useRef<HTMLDivElement | null>(null);

  const createDrawingHandler = (setter: Dispatch<SetStateAction<DrawingsNames>>) => async (value: DrawingsNames) => {
    setter(value);
    await addDrawing(value);
  };

  const findOptionByValue = <T extends { value: string }>(options: T[], selectedValue: T['value']): T | undefined =>
    options.find((item) => item.value === selectedValue);

  const selectedTrendLineOption = findOptionByValue(trendLines(), selectedLineType);
  const selectedMeasurementToolOption = findOptionByValue(measurementTools(), selectedMeasurementTool);
  const selectedGeometricShapeOption = findOptionByValue(geometricShapes(), selectedGeometricShape);
  const selectedGannAndFibonacciOption = findOptionByValue(gannAndFibonacciTools(), selectedGannAndFibonacci);

  const getButtonClassName = (tool: ActiveDrawingTool) =>
    classNames(styles.button, {
      [styles.pressed]: activeTool === tool,
    });

  const getTooltipClassName = () => classNames(styles.tooltipHint, TOOLTIP_CLASSNAME);

  useEffect(() => {
    const hashT = hotkeys.register({
      keys: [Keys.alt, Keys.t],
      callback: async () => {
        await createDrawingHandler(setSelectedLineType)(DrawingsNames.trendLine);
      },
    });
    const hashH = hotkeys.register({
      keys: [Keys.alt, Keys.h],
      callback: async () => {
        await createDrawingHandler(setSelectedLineType)(DrawingsNames.horizontalLine);
      },
    });
    const hashV = hotkeys.register({
      keys: [Keys.alt, Keys.v],
      callback: async () => {
        await createDrawingHandler(setSelectedLineType)(DrawingsNames.verticalLine);
      },
    });
    const hashF = hotkeys.register({
      keys: [Keys.alt, Keys.f],
      callback: async () => {
        await createDrawingHandler(setSelectedGannAndFibonacci)(DrawingsNames.fibonacciRetracement);
      },
    });
    // const hashShift = hotkeys.register({
    //   keys: [Keys.shift],
    //   pressHoldRequired: true,
    //   callback: async () => {
    //     await addDrawing(DrawingsNames.ruler);
    //   },
    // });

    return () => {
      hotkeys.unregister({
        keys: [Keys.alt, Keys.t],
        hash: hashT,
      });
      hotkeys.unregister({
        keys: [Keys.alt, Keys.h],
        hash: hashH,
      });
      hotkeys.unregister({
        keys: [Keys.alt, Keys.v],
        hash: hashV,
      });
      hotkeys.unregister({
        keys: [Keys.alt, Keys.f],
        hash: hashF,
      });
      // hotkeys.unregister({
      //   keys: [Keys.shift],
      //   hash: hashShift
      // });
    };
  }, []);

  return (
    <div
      ref={toolbarRef}
      className={styles.toolbar}
    >
      <div className={classNames(styles.group)}>
        {implemented.crosshair && (
          <Tooltip
            tooltipClassName={getTooltipClassName()}
            showMessageOnFocus
            label={t('Crosshair')}
            location="right"
          >
            <Button
              size="sm"
              onClick={activateCrosshair}
              className={getButtonClassName('crosshair')}
              label={<CrossIcon />}
            />
          </Tooltip>
        )}
        {implemented.lines && (
          <SplitDropdown
            anchorRef={toolbarRef}
            mainContent={ensureDefined(selectedTrendLineOption?.icon)}
            mainTooltip={ensureDefined(selectedTrendLineOption).label}
            onMainClick={() => addDrawing(selectedLineType)}
            menuTooltip={t('Trend line')}
            mainButtonClassName={getButtonClassName(selectedLineType)}
            tooltipClassName={getTooltipClassName()}
          >
            <MenuList
              mode="single"
              value={selectedLineType}
              options={trendLines()}
              onClick={createDrawingHandler(setSelectedLineType)}
            />
          </SplitDropdown>
        )}
        {implemented.fib && (
          <SplitDropdown
            anchorRef={toolbarRef}
            mainContent={ensureDefined(selectedGannAndFibonacciOption?.icon)}
            mainTooltip={ensureDefined(selectedGannAndFibonacciOption).label}
            onMainClick={() => addDrawing(selectedGannAndFibonacci)}
            menuTooltip={t('Gann and Fibonacci')}
            mainButtonClassName={getButtonClassName(selectedGannAndFibonacci)}
            tooltipClassName={getTooltipClassName()}
          >
            <MenuList
              mode="single"
              value={selectedGannAndFibonacci}
              options={gannAndFibonacciTools()}
              onClick={createDrawingHandler(setSelectedGannAndFibonacci)}
            />
          </SplitDropdown>
        )}
        {implemented.rectangle && (
          <SplitDropdown
            anchorRef={toolbarRef}
            mainContent={ensureDefined(selectedGeometricShapeOption?.icon)}
            mainTooltip={ensureDefined(selectedGeometricShapeOption).label}
            onMainClick={() => addDrawing(selectedGeometricShape)}
            menuTooltip={t('Geometric shapes')}
            mainButtonClassName={getButtonClassName(selectedGeometricShape)}
            tooltipClassName={getTooltipClassName()}
          >
            <MenuList
              mode="single"
              value={selectedGeometricShape}
              options={geometricShapes()}
              onClick={createDrawingHandler(setSelectedGeometricShape)}
            />
          </SplitDropdown>
        )}
        {implemented.text && (
          <Tooltip
            tooltipClassName={getTooltipClassName()}
            showMessageOnFocus
            label={t('Text')}
            location="right"
          >
            <Button
              size="sm"
              className={getButtonClassName(DrawingsNames.text)}
              onClick={() => addDrawing(DrawingsNames.text)}
              label={<TypeIcon />}
            />
          </Tooltip>
        )}
        {implemented.XABCD && (
          <Tooltip
            tooltipClassName={getTooltipClassName()}
            showMessageOnFocus
            label={t('XABCD template')}
            location="right"
          >
            <Button
              size="sm"
              className={styles.button}
              onClick={() => {}}
              label={<SplineCurveIcon />}
            />
          </Tooltip>
        )}
        {implemented.position && (
          <SplitDropdown
            anchorRef={toolbarRef}
            mainContent={ensureDefined(selectedMeasurementToolOption?.icon)}
            mainTooltip={ensureDefined(selectedMeasurementToolOption).label}
            menuTooltip={t('Measurement tools')}
            onMainClick={() => addDrawing(selectedMeasurementTool)}
            mainButtonClassName={getButtonClassName(selectedMeasurementTool)}
            tooltipClassName={getTooltipClassName()}
          >
            <MenuList
              mode="single"
              value={selectedMeasurementTool}
              options={measurementTools()}
              onClick={createDrawingHandler(setSelectedMeasurementTool)}
            />
          </SplitDropdown>
        )}
        {implemented.icons && (
          <Tooltip
            label={t('Pin')}
            location="right"
            tooltipClassName={getTooltipClassName()}
            showMessageOnFocus
          >
            <Button
              size="sm"
              className={styles.button}
              onClick={() => {}}
              label={<HeartIcon />}
            />
          </Tooltip>
        )}
      </div>
      <Divider
        direction="horizontal"
        pt={{ divider: { className: styles.divider } }}
      />
      <div className={classNames(styles.group)}>
        <Tooltip
          tooltipClassName={getTooltipClassName()}
          showMessageOnFocus
          label={t('Ruler')}
          location="right"
        >
          <Button
            size="sm"
            className={getButtonClassName(DrawingsNames.ruler)}
            onClick={() => addDrawing(DrawingsNames.ruler)}
            label={<RulerIcon />}
          />
        </Tooltip>

        <Tooltip
          tooltipClassName={getTooltipClassName()}
          showMessageOnFocus
          label={t('Upscale')}
          location="right"
          className={styles.notImplemented}
        >
          <Button
            size="sm"
            className={styles.button}
            onClick={() => {}}
            label={<ZoomInIcon />}
          />
        </Tooltip>
      </div>
      <Divider
        direction="horizontal"
        pt={{ divider: { className: classNames(styles.divider) } }}
      />
      <div className={classNames(styles.group)}>
        <Tooltip
          tooltipClassName={getTooltipClassName()}
          showMessageOnFocus
          label={t('Magnet allows you to attract objects points to the nearest bar prices')}
          location="right"
          className={styles.notImplemented}
        >
          <Button
            size="sm"
            className={styles.button}
            onClick={() => {}}
            label={<MagnetIcon />}
          />
        </Tooltip>

        <Tooltip
          tooltipClassName={getTooltipClassName()}
          showMessageOnFocus
          label={t('Endless drawing mode')}
          location="right"
        >
          <Button
            size="sm"
            className={`${styles.button} ${isEndlessDrawingsMode ? styles.pressed : ''}`}
            onClick={() => setEndlessDrawingsMode(!isEndlessDrawingsMode)}
            label={<PencilLockerIcon />}
          />
        </Tooltip>

        <Tooltip
          tooltipClassName={getTooltipClassName()}
          showMessageOnFocus
          label={t('Fix all objects')}
          location="right"
          className={styles.notImplemented}
        >
          <Button
            size="sm"
            className={styles.button}
            onClick={() => {}}
            label={<UnlockIcon />}
          />
        </Tooltip>

        <Tooltip
          tooltipClassName={getTooltipClassName()}
          showMessageOnFocus
          label={t('Hide all drawing objects')}
          location="right"
          className={styles.notImplemented}
        >
          <Button
            size="sm"
            className={styles.button}
            onClick={() => {}}
            label={<EyeBrushIcon />}
          />
        </Tooltip>
      </div>
      <Divider
        direction="horizontal"
        pt={{ divider: { className: classNames(styles.divider) } }}
      />
      <div className={classNames(styles.group)}>
        <Tooltip
          tooltipClassName={getTooltipClassName()}
          showMessageOnFocus
          label={t('Clear objects')}
          location="right"
          className={styles.notImplemented}
        >
          <Button
            size="sm"
            className={styles.button}
            onClick={() => {}}
            label={<TrashIcon />}
          />
        </Tooltip>

        <Tooltip
          tooltipClassName={getTooltipClassName()}
          showMessageOnFocus
          label={t('DOM tree')}
          location="right"
        >
          <Button
            size="sm"
            className={styles.button}
            onClick={() => toggleDOM()}
            label={<LayersIcon />}
          />
        </Tooltip>
      </div>
    </div>
  );
}