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


import { DateFormat, IndicatorsIds, Locale, Timeframes } from 'moex-chart';

import type {
  ChartCollectionPreset,
  IMoexChart,
  MoexChartSnapshotInput,
} from 'moex-chart';

type ChartCollectionPresetConfig = Omit<
  ChartCollectionPreset,
  'getDataSource' | 'startRealtime'
>;

type ChartSnapshotConfig = Omit<
  MoexChartSnapshotInput['charts'][number],
  'symbolId'
>;

type MoexChartSnapshotConfig = Omit<MoexChartSnapshotInput, 'charts'> & {
  charts: ChartSnapshotConfig[];
};

type MoexChartConfig = Omit<
  IMoexChart,
  'container' | 'chartCollectionPreset' | 'snapshot'
> & {
  snapshot: MoexChartSnapshotConfig;
  chartCollectionPreset: ChartCollectionPresetConfig;
};

const MOEX_CHART_CONFIG: MoexChartConfig = {
  snapshot: {
    charts: [
      {
        chartSeriesType: 'Candlestick',
        timeframe: Timeframes['10s'],
        timeFormat: '24h',
        dateFormat: DateFormat.DD_MM_YYYY_HH_mm_ss,
        panes: [
          {
            isMain: true,
            id: 0,
            indicators: [
              {
                indicatorType: IndicatorsIds.Volume,
              },
            ],
            drawings: [],
          },
        ],
      },
    ],
  },

  chartCollectionPreset: {
    undoRedoEnabled: true,
    showMenuButton: true,
    showBottomPanel: true,
    showControlBar: true,
    showFullscreenButton: true,
    showSettingsButton: true,
    showCompareButton: true,
    tooltipConfig: {
      showTooltip: false,
      time: { visible: true, label: 'Время' },
      close: { visible: true, label: 'Закр.' },
      change: { visible: true, label: 'Изм.' },
      volume: { visible: true, label: 'Объем' },
      open: { visible: true, label: 'Откр.' },
      high: { visible: true, label: 'Макс.' },
      low: { visible: true, label: 'Мин.' },
    },

    supportedTimeframes: [
      Timeframes['1m'],
      Timeframes['5m'],
      Timeframes['10m'],
      Timeframes['15m'],
      Timeframes['30m'],
      Timeframes['45m'],
      Timeframes['1h'],
      Timeframes['4h'],
      Timeframes['1d'],
      Timeframes['1w'],
      Timeframes['1М'],
    ],
    supportedChartSeriesTypes: ['Candlestick', 'Line', 'Bar'],
    theme: 'tr',
    ohlc: {
      show: true,
      precision: 4,
    },
    mode: 'dark',
    locale: Locale.rus,
  },

  lwcInheritedChartOptions: {
    timeVisible: true,
    secondsVisible: false,
  },
};

export { MOEX_CHART_CONFIG };




import { MoexChart, Timeframes } from 'moex-chart';

import { useCallback, useEffect, useRef, useState } from 'react';

import { useChangeProperties, useSelectProperties } from '@modules/widgetProperties';
import { WidgetProperties } from '@widgets/Chart/properties/types';
import { ChartIndicativeData } from '@widgets/Chart/types';

import { MOEX_CHART_CONFIG } from '../constants';
import { DataSourceProvider } from '../dataSourceProvide';

import type {
  __CompareManager__,
  IMoexChart,
  SymbolInfoInput,
} from 'moex-chart';

interface TUseMoexChartProps {
  symbolInfo: SymbolInfoInput;
  indicativeData?: ChartIndicativeData;
}

export const useMoexChart = ({
  symbolInfo,
  indicativeData,
}: TUseMoexChartProps) => {
  const moexChartState = useSelectProperties(
    (widgetProperties: Partial<WidgetProperties>) => widgetProperties.moexChartState,
  );

  const { updateProperties } = useChangeProperties<WidgetProperties>();

  const [isCompareOpen, setIsCompareOpen] = useState(false);
  const [isSymbolSearchOpen, setIsSymbolSearchOpen] = useState(false);

  const containerRef = useRef<HTMLDivElement | null>(null);
  const chartRef = useRef<MoexChart | null>(null);
  const compareManagerRef = useRef<__CompareManager__ | null>(null);
  const currentSymbolInfoRef = useRef<SymbolInfoInput>(symbolInfo);

  const timeframeRef = useRef<Timeframes | undefined>(moexChartState?.timeframe);
  const savedDataRef = useRef<string | undefined>(moexChartState?.savedData);
  const updateTimeframeRef = useRef<((timeframe: Timeframes) => void) | null>(null);

  useEffect(() => {
    const nextSymbolInfo: SymbolInfoInput = {
      symbolId: symbolInfo.symbolId,
      symbol: symbolInfo.symbol,
      symbolName: symbolInfo.symbolName,
    };

    currentSymbolInfoRef.current = nextSymbolInfo;
    chartRef.current?.setSymbol(nextSymbolInfo);
  }, [symbolInfo.symbolId, symbolInfo.symbol, symbolInfo.symbolName]);

  useEffect(() => {
    timeframeRef.current = moexChartState?.timeframe;
    savedDataRef.current = moexChartState?.savedData;
  }, [moexChartState]);

  updateTimeframeRef.current = (timeframe: Timeframes) => {
    if (timeframeRef.current === timeframe) {
      return;
    }

    timeframeRef.current = timeframe;

    updateProperties((state) => {
      state.moexChartState = {
        ...state.moexChartState,
        timeframe,
      };
    });
  };

  const saveSnapshot = useCallback((): void => {
    const snapshot = chartRef.current?.getSnapshot();

    if (!snapshot) {
      return;
    }

    const savedData = JSON.stringify({
      ...snapshot,
      charts: snapshot.charts.map((chart) => ({
        ...chart,
        panes: chart.panes.map((pane) => ({
          ...pane,
          indicators: pane.indicators.map((indicator) => {
            const compareSeries = indicator.config?.series[0];
            const compareSymbolInfo = indicator.config?.symbolInfo;

            return {
              ...indicator,
              dataSource: undefined, // dataSource пока не среиализуем
              config:
                indicator.indicatorType === undefined &&
                compareSymbolInfo &&
                indicator.config?.label &&
                compareSeries
                  ? {
                      symbolInfo: compareSymbolInfo,
                      label: indicator.config.label,
                      newPane: indicator.config.newPane,
                      series: [
                        {
                          name: compareSeries.name,
                          seriesOptions: {
                            priceScaleId: compareSeries.seriesOptions?.priceScaleId,
                          },
                        },
                      ],
                    }
                  : undefined,
            };
          }),
        })),
      })),
    });

    savedDataRef.current = savedData;

    updateProperties((state) => {
      state.moexChartState = {
        ...state.moexChartState,
        timeframe: timeframeRef.current || Timeframes['1m'],
        savedData,
      };
    });
  }, [updateProperties]);

  const getSnapshotWithCurrentSymbol = (
    snapshot: typeof MOEX_CHART_CONFIG.snapshot | IMoexChart['snapshot'],
    timeframe: Timeframes,
  ): IMoexChart['snapshot'] => ({
    ...snapshot,
    charts: snapshot.charts.map((chartSnapshot) => ({
      ...chartSnapshot,
      ...currentSymbolInfoRef.current,
      timeframe,
    })),
  });

  const applySnapshot = (): void => {
    if (!savedDataRef.current || !chartRef.current) {
      return;
    }

    const savedSnapshot = JSON.parse(savedDataRef.current) as IMoexChart['snapshot'];
    const timeframe = timeframeRef.current || Timeframes['1m'];

    chartRef.current.setSnapshot(
      getSnapshotWithCurrentSymbol(savedSnapshot, timeframe),
    );

    compareManagerRef.current = chartRef.current.getCompareManager();
  };

  useEffect(() => {
    const container = containerRef.current;

    if (!container) {
      return undefined;
    }

    const timeframe = timeframeRef.current || Timeframes['1m'];

    const savedSnapshot = savedDataRef.current
      ? (JSON.parse(savedDataRef.current) as IMoexChart['snapshot'])
      : MOEX_CHART_CONFIG.snapshot;

    const dataProvider = new DataSourceProvider();

    const chart = new MoexChart({
      ...MOEX_CHART_CONFIG,
      container,
      snapshot: getSnapshotWithCurrentSymbol(savedSnapshot, timeframe),
      chartCollectionPreset: {
        ...MOEX_CHART_CONFIG.chartCollectionPreset,
        openCompareModal: () => setIsCompareOpen(true),
        openSymbolSearchModal: () => setIsSymbolSearchOpen(true),
        getDataSource: dataProvider.getDataSource(indicativeData, (nextTimeframe) => {
          updateTimeframeRef.current?.(nextTimeframe);
        }),
        startRealtime: (getSymbols, getTimeframe, update) =>
          dataProvider.startRealtime({
            getSymbols,
            getTimeframe,
            update,
          }),
      },
    });

    chartRef.current = chart;
    compareManagerRef.current = chart.getCompareManager();

    const intervalId = setInterval(() => {
      saveSnapshot();
    }, 1000);

    return () => {
      clearInterval(intervalId);
      saveSnapshot();

      chartRef.current = null;
      compareManagerRef.current = null;

      chart.destroy();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps -- исправим позже
  }, [indicativeData]);

  return {
    containerRef,
    isCompareOpen,
    isSymbolSearchOpen,
    compareManagerRef,
    setIsCompareOpen,
    setIsSymbolSearchOpen,
    saveSnapshot,
    applySnapshot,
    hasSavedSnapshot: Boolean(moexChartState?.savedData),
  };
};