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


import { IChartApi } from 'lightweight-charts';
import { Observable, Subscription } from 'rxjs';

import { DataSource } from '@core/DataSource';
import { DOMObject, DOMObjectParams } from '@core/DOMObject';
import { Pane } from '@core/Pane';

import { indicatorLabelById, indicatorSeriesLabelById, IndicatorsIds } from '@src/constants';
import { SeriesFactory, SeriesStrategies } from '@src/modules/series-strategies/SeriesFactory';
import { ChartTypeOptions, IndicatorConfig, SettingsValues } from '@src/types';
import { DOMObjectSnapshot, IndicatorSnapshot, ISerializable } from '@src/types/snapshot';

type IIndicator = DOMObject;

export interface IndicatorParams extends DOMObjectParams {
  mainSymbol$: Observable<string>;
  mainSymbolName$?: Observable<string>;
  lwcChart: IChartApi;
  dataSource: DataSource;
  associatedPane: Pane;
  config: IndicatorConfig;
  type?: IndicatorsIds;
  chartOptions?: ChartTypeOptions;
}

export class Indicator extends DOMObject implements ISerializable<IndicatorSnapshot> {
  private indicatorType?: IndicatorsIds;
  private series: SeriesStrategies[] = [];
  private seriesMap: Map<string, SeriesStrategies> = new Map();
  private lwcChart: IChartApi;
  private dataSource: DataSource;
  private mainSymbol$: Observable<string>;
  private mainSymbolName$: Observable<string>;
  private associatedPane: Pane;
  private config: IndicatorConfig;
  private settings: SettingsValues = {};

  private dataChangeHandlers = new Set<() => void>();
  private seriesSubscriptions = new Subscription();

  constructor({
    id,
    type,
    lwcChart,
    dataSource,
    zIndex,
    onDelete,
    moveUp,
    moveDown,
    mainSymbol$,
    mainSymbolName$,
    associatedPane,
    paneId,
    config,
  }: IndicatorParams) {
    super({ id, name: config?.label ?? id, zIndex, onDelete, moveUp, moveDown, paneId });
    this.lwcChart = lwcChart;
    this.dataSource = dataSource;
    this.mainSymbol$ = mainSymbol$;
    this.mainSymbolName$ = mainSymbolName$ ?? mainSymbol$;
    this.indicatorType = type;
    this.config = config;
    this.name = this.getLabel();

    this.settings = this.getDefaultSettings();

    this.associatedPane = associatedPane;

    this.createSeries();

    this.associatedPane.setIndicator(this.id, this);
  }

  public subscribeDataChange(handler: () => void): Subscription {
    this.dataChangeHandlers.add(handler);

    return new Subscription(() => {
      this.dataChangeHandlers.delete(handler);
    });
  }

  public getLabel = () => {
    if (this.config.label) {
      return this.config.label;
    }

    if (this.indicatorType) {
      return indicatorLabelById()[this.indicatorType];
    }

    return this.id;
  };

  public getSeriesLabel(serieName: string): string | undefined {
    if (this.config.seriesLabels?.[serieName]) {
      return this.config.seriesLabels[serieName];
    }

    if (this.indicatorType) {
      return indicatorSeriesLabelById[this.indicatorType]?.[serieName];
    }

    return undefined;
  }

  public getId(): string {
    return this.id;
  }

  public getType(): IndicatorsIds | undefined {
    return this.indicatorType;
  }

  public getPane(): Pane {
    return this.associatedPane;
  }

  public getSeriesMap(): Map<string, SeriesStrategies> {
    return this.seriesMap;
  }

  public getConfig(): IndicatorConfig {
    return this.config;
  }

  public getIndicatorType(): IndicatorSnapshot['indicatorType'] {
    return this.indicatorType;
  }

  public getSettings(): SettingsValues {
    return { ...this.settings };
  }

  public getSettingsConfig() {
    return this.config.settings ?? [];
  }

  public hasSettings(): boolean {
    return Boolean(this.config.settings?.length);
  }

  public updateSettings(settings: SettingsValues): void {
    this.settings = settings;

    // todo: обновлять данные серий без удаления и повторного создания
    this.destroySeries();
    this.createSeries();

    this.notifyDataChanged();
  }

  public show() {
    this.series.forEach((s) => {
      s.show();
    });
    super.show();
  }

  public hide() {
    this.series.forEach((s) => {
      s.hide();
    });
    super.hide();
  }

  public override getSnapshot(): DOMObjectSnapshot & IndicatorSnapshot {
    const domSnap = super.getSnapshot();

    return {
      ...domSnap,
      dataSource: this.dataSource,
      indicatorType: this.indicatorType,
      config: this.config,
    };
  }

  public setSnapshot(snap: IndicatorSnapshot): void {}

  // destroy и delete принципиально отличаются!
  // delete вызовет destroy в конце концов. По сути - это destroy с сайд-эффектом в eventManager
  public delete() {
    super.delete();
  }

  // destroy и delete принципиально отличаются!
  public destroy() {
    this.destroySeries();
    this.dataChangeHandlers.clear();
    this.associatedPane.removeIndicator(this.id);
  }

  private createSeries(): void {
    this.config.series.forEach(({ name, id: serieId, dataFormatter, seriesOptions, priceScaleOptions }) => {
      const serie = SeriesFactory.create(name!)({
        lwcChart: this.lwcChart,
        dataSource: this.dataSource,
        customFormatter: dataFormatter
          ? (params) =>
              dataFormatter({
                ...params,
                settings: this.settings,
                indicatorReference: this,
              })
          : undefined,
        seriesOptions,
        priceScaleOptions,
        mainSymbol$: this.mainSymbol$,
        mainSymbolName$: this.mainSymbolName$,
        mainSerie$: this.associatedPane.getMainSerie(),
        showSymbolLabel: false,
        paneIndex: this.associatedPane.paneIndex(),
        indicatorReference: this,
      });

      const handleDataChanged = () => {
        this.notifyDataChanged();
      };

      serie.subscribeDataChanged(handleDataChanged);

      this.seriesSubscriptions.add(() => {
        serie.unsubscribeDataChanged(handleDataChanged);
      });

      this.seriesMap.set(serieId, serie);

      this.series.push(serie);
    });
  }

  private destroySeries(): void {
    this.seriesSubscriptions.unsubscribe();
    this.seriesSubscriptions = new Subscription();

    this.series.forEach((s) => {
      s.destroy();
    });

    this.series = [];
    this.seriesMap.clear();
  }

  private notifyDataChanged(): void {
    this.dataChangeHandlers.forEach((handler) => {
      handler();
    });
  }

  private getDefaultSettings(): SettingsValues {
    const settings: SettingsValues = {};

    this.config.settings?.forEach((field) => {
      settings[field.key] = field.defaultValue;
    });

    return settings;
  }
}