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


import { IPriceScaleApi, PriceScaleMode } from 'lightweight-charts';

import { PriceScaleControls } from '@components/PriceScaleControls';
import { ReactRenderer } from '@core/ReactRenderer';
import { Direction } from '@src/types';

import type { PriceScaleTarget } from './controller';

interface PriceScaleControlsControllerParams {
  chartContainer: HTMLElement;
  getPaneElement: () => HTMLElement | null;
  getPriceScale: (priceScaleId: Direction) => IPriceScaleApi;
  getPriceScaleTarget: (priceScaleId: Direction) => PriceScaleTarget;
  hasPriceScaleSeries: (priceScaleId: Direction) => boolean;
  getPriceScaleMode: (target: PriceScaleTarget) => PriceScaleMode;
  togglePriceScaleMode: (
    target: PriceScaleTarget,
    mode: PriceScaleMode,
  ) => void;
}

export class PriceScaleControlsController {
  private readonly chartContainer: HTMLElement;
  private readonly getPaneElement: () => HTMLElement | null;
  private readonly getPriceScale: (
    priceScaleId: Direction,
  ) => IPriceScaleApi;
  private readonly getPriceScaleTarget: (
    priceScaleId: Direction,
  ) => PriceScaleTarget;
  private readonly hasPriceScaleSeries: (
    priceScaleId: Direction,
  ) => boolean;
  private readonly getPriceScaleMode: (
    target: PriceScaleTarget,
  ) => PriceScaleMode;
  private readonly togglePriceScaleMode: (
    target: PriceScaleTarget,
    mode: PriceScaleMode,
  ) => void;

  private readonly container: HTMLElement;
  private readonly renderer: ReactRenderer;

  private paneElement: HTMLElement | null = null;
  private hoveredPriceScaleId: Direction | null = null;

  constructor({
    chartContainer,
    getPaneElement,
    getPriceScale,
    getPriceScaleTarget,
    hasPriceScaleSeries,
    getPriceScaleMode,
    togglePriceScaleMode,
  }: PriceScaleControlsControllerParams) {
    this.chartContainer = chartContainer;
    this.getPaneElement = getPaneElement;
    this.getPriceScale = getPriceScale;
    this.getPriceScaleTarget = getPriceScaleTarget;
    this.hasPriceScaleSeries = hasPriceScaleSeries;
    this.getPriceScaleMode = getPriceScaleMode;
    this.togglePriceScaleMode = togglePriceScaleMode;

    this.container = document.createElement('div');
    this.container.className = 'moex-price-scale-controls';
    this.container.style.position = 'absolute';
    this.container.style.zIndex = '10';
    this.container.style.pointerEvents = 'none';

    this.renderer = new ReactRenderer(this.container);
  }

  public mount(): void {
    const paneElement = this.getPaneElement();

    if (!paneElement) {
      return;
    }

    if (
      this.paneElement === paneElement &&
      this.container.parentElement === this.chartContainer
    ) {
      this.update();
      return;
    }

    this.unmount();

    this.paneElement = paneElement;

    this.chartContainer.appendChild(this.container);
    this.chartContainer.addEventListener(
      'pointermove',
      this.handlePointerMove,
    );
    this.chartContainer.addEventListener(
      'pointerleave',
      this.handlePointerLeave,
    );

    this.update();
  }

  public update(): void {
    if (!this.hoveredPriceScaleId) {
      this.hide();
      return;
    }

    this.render(this.hoveredPriceScaleId);
  }

  public resetAutoScale(): void {
    this.setAutoScale(Direction.Left, true);
    this.setAutoScale(Direction.Right, true);
    this.update();
  }

  public destroy(): void {
    this.unmount();
    this.renderer.destroy();
    this.container.remove();
  }

  private handlePointerMove = (event: PointerEvent): void => {
    if (!this.paneElement) {
      return;
    }

    const chartRect =
      this.chartContainer.getBoundingClientRect();
    const paneRect =
      this.paneElement.getBoundingClientRect();

    const isInsidePane =
      event.clientY >= paneRect.top &&
      event.clientY < paneRect.bottom;

    const nextPriceScaleId = isInsidePane
      ? this.getHoveredPriceScaleId(
          event.clientX - chartRect.left,
          chartRect.width,
        )
      : null;

    if (this.hoveredPriceScaleId === nextPriceScaleId) {
      if (nextPriceScaleId) {
        this.updatePosition(nextPriceScaleId);
      }

      return;
    }

    this.hoveredPriceScaleId = nextPriceScaleId;
    this.update();
  };

  private handlePointerLeave = (): void => {
    this.hoveredPriceScaleId = null;
    this.update();
  };

  private render(priceScaleId: Direction): void {
    if (!this.isPriceScaleAvailable(priceScaleId)) {
      this.hide();
      return;
    }

    this.updatePosition(priceScaleId);

    const target = this.getPriceScaleTarget(priceScaleId);
    const priceScaleMode = this.getPriceScaleMode(target);

    this.renderer.renderComponent(
      <PriceScaleControls
        isAutoScale={this.isAutoScale(priceScaleId)}
        isLogarithmic={
          priceScaleMode === PriceScaleMode.Logarithmic
        }
        onToggleAutoScale={() => {
          this.toggleAutoScale(priceScaleId);
          this.update();
        }}
        onToggleLogarithmic={() => {
          this.togglePriceScaleMode(
            target,
            PriceScaleMode.Logarithmic,
          );
          this.update();
        }}
      />,
    );

    this.container.classList.add(
      'moex-price-scale-controls_visible',
    );
  }

  private hide(): void {
    this.container.classList.remove(
      'moex-price-scale-controls_visible',
    );
  }

  private getHoveredPriceScaleId(
    pointerX: number,
    chartWidth: number,
  ): Direction | null {
    const leftWidth = this.getPriceScaleWidth(Direction.Left);
    const rightWidth = this.getPriceScaleWidth(
      Direction.Right,
    );

    if (
      this.isPriceScaleAvailable(Direction.Left) &&
      pointerX >= 0 &&
      pointerX <= leftWidth
    ) {
      return Direction.Left;
    }

    if (
      this.isPriceScaleAvailable(Direction.Right) &&
      pointerX >= chartWidth - rightWidth &&
      pointerX <= chartWidth
    ) {
      return Direction.Right;
    }

    return null;
  }

  private updatePosition(priceScaleId: Direction): void {
    if (!this.paneElement) {
      return;
    }

    const chartRect =
      this.chartContainer.getBoundingClientRect();
    const paneRect =
      this.paneElement.getBoundingClientRect();

    const bottomOffset = Math.max(
      0,
      chartRect.bottom - paneRect.bottom,
    );

    this.container.style.width = `${this.getPriceScaleWidth(
      priceScaleId,
    )}px`;
    this.container.style.bottom = `${bottomOffset}px`;

    if (priceScaleId === Direction.Left) {
      this.container.style.left = '0';
      this.container.style.right = '';
      return;
    }

    this.container.style.left = '';
    this.container.style.right = '0';
  }

  private toggleAutoScale(priceScaleId: Direction): void {
    this.setAutoScale(
      priceScaleId,
      !this.isAutoScale(priceScaleId),
    );
  }

  private setAutoScale(
    priceScaleId: Direction,
    autoScale: boolean,
  ): void {
    this.getPriceScale(priceScaleId).setAutoScale(autoScale);
  }

  private isAutoScale(priceScaleId: Direction): boolean {
    return (
      this.getPriceScale(priceScaleId).options().autoScale ?? true
    );
  }

  private isPriceScaleAvailable(
    priceScaleId: Direction,
  ): boolean {
    const priceScale = this.getPriceScale(priceScaleId);
    const options = priceScale.options();

    return (
      options.visible !== false &&
      priceScale.width() > 0 &&
      this.hasPriceScaleSeries(priceScaleId)
    );
  }

  private getPriceScaleWidth(
    priceScaleId: Direction,
  ): number {
    return this.getPriceScale(priceScaleId).width();
  }

  private unmount(): void {
    this.chartContainer.removeEventListener(
      'pointermove',
      this.handlePointerMove,
    );
    this.chartContainer.removeEventListener(
      'pointerleave',
      this.handlePointerLeave,
    );

    this.paneElement = null;
    this.hoveredPriceScaleId = null;
    this.hide();
  }
}


this.priceScaleControlsController =
  new PriceScaleControlsController({
    chartContainer,
    getPaneElement: () => this.lwcPane.getHTMLElement(),
    getPriceScale: (priceScaleId) =>
      this.getPriceScale(priceScaleId),
    getPriceScaleTarget: (priceScaleId) =>
      this.getPriceScaleTarget(priceScaleId),
    hasPriceScaleSeries: (priceScaleId) =>
      this.hasPriceScaleSeries(priceScaleId),
    getPriceScaleMode,
    togglePriceScaleMode,
  });


getPriceScaleMode: (target) =>
  this.priceScaleModeController.getActiveMode(target),
togglePriceScaleMode: (target, mode) =>
  this.priceScaleModeController.toggleSavedMode(target, mode),