Загрузка данных
import { useEffect, useMemo } from 'react';
import { useAppSelect } from '@hooks/useAppSelector';
import { Contract, useContracts } from '@modules/contracts';
import { filterByUniqIssKey } from '@utils/filterByUniqIssKey';
import { DEFAULT_SYMBOL } from '@widgets/Chart/const';
import { Widget } from 'types/Widgets';
import type { SymbolInfoInput } from 'moex-chart';
interface UseChartPublicContextArg {
setCurrInstrument: (symbolInfo: SymbolInfoInput) => void;
widgetId: Widget['id'];
getMasterInstrumentFromPublicContext: () => string | number | null | undefined;
}
interface UseChartPublicContextReturn {
issKey: Contract['issKey'] | undefined;
}
export function useChartPublicContext({
widgetId,
setCurrInstrument,
getMasterInstrumentFromPublicContext,
}: UseChartPublicContextArg): UseChartPublicContextReturn {
const publicContext = useAppSelect((state) => state.publicContext.publicContext);
const widget = useAppSelect((state) => state.widgets.widgets.find(({ id }) => id === widgetId));
const { contracts } = useContracts();
const instruments = useMemo(
() => (contracts ? filterByUniqIssKey(contracts, ['issKey']) : []),
[contracts],
);
const issKey = useMemo(() => {
const fieldValue = getMasterInstrumentFromPublicContext();
return instruments.find((instrument) => instrument.issKey === fieldValue)?.issKey;
// eslint-disable-next-line react-hooks/exhaustive-deps -- Посмотреть этот момент.
}, [publicContext, instruments]);
useEffect(() => {
const fieldValue = getMasterInstrumentFromPublicContext();
if (!fieldValue && widget?.master) {
const defaultInstrument = instruments.find((instrument) => instrument.issKey === DEFAULT_SYMBOL);
setCurrInstrument({
symbolId: DEFAULT_SYMBOL,
symbol: defaultInstrument?.symbol,
symbolName: defaultInstrument?.displayName,
});
return;
}
const instrument = instruments.find((item) => item.issKey === fieldValue);
if (instrument) {
setCurrInstrument({
symbolId: instrument.issKey,
symbol: instrument.symbol,
symbolName: instrument.displayName,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- Посмотреть этот момент.
}, [publicContext, instruments, widget?.externalProperties, setCurrInstrument]);
return {
issKey,
};
}
import { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { communicator } from '@core/comm';
import { useAppSelect } from '@hooks/useAppSelector';
import { CORPACTIONS_OPEN_EXESTED_WIDGET_EVENT, HIGHLIGHT_WIDGET_EVENT } from '@modules/widgets/shared';
import { addContentPropsToWidget, unbindWidgets } from '@store/slices/widgets';
import { getState } from '@store/store';
import { useWidgetsBind } from '@utils/hooks/useWidgetsBind';
import { DEFAULT_SYMBOL } from '../const';
import { useChartPublicContext } from './useChartPublicContext';
import type { WidgetProperties } from '../properties/types';
import type { ChartContainerProps, SelectedInstrument } from '../types';
import type { SymbolInfoInput } from 'moex-chart';
import type { Dispatch, SetStateAction } from 'react';
import type { Widget } from 'types/Widgets';
interface UseChartComponentFacadeReturn {
dropDownOpen: boolean;
setDropdownOpen: Dispatch<SetStateAction<boolean>>;
currentSymbolInfo: SymbolInfoInput;
isWidgetHeaderContextMenuOpen: boolean;
setIsWidgetHeaderContextMenuOpen: Dispatch<SetStateAction<boolean>>;
onDropInstrument: (symbolId: string, withUpdate?: boolean) => void;
addInstrumentFromModal: (instruments: SelectedInstrument[]) => void;
isOver: boolean;
}
type SaveContentPropsOptions = Partial<WidgetProperties['chartState']> & {
withUpdate?: boolean;
cleanIndicativeData?: boolean;
};
export default function useChartComponentFacade(props: ChartContainerProps): UseChartComponentFacadeReturn {
const { widgetId } = props;
const dispatch = useDispatch();
const [isOver, setIsOver] = useState(false);
const [dropDownOpen, setDropdownOpen] = useState(false);
const [isWidgetHeaderContextMenuOpen, setIsWidgetHeaderContextMenuOpen] = useState(false);
const widgetProperties = useAppSelect(
(state) => state.widgets.widgets.find(({ id }) => id === widgetId)?.widgetContentProps,
) as WidgetProperties | undefined;
const currentSymbolInfoRef = useRef<SymbolInfoInput>({
symbolId: widgetProperties?.chartState?.savedInstrument ?? DEFAULT_SYMBOL,
symbol: widgetProperties?.chartState?.savedSymbol,
symbolName: widgetProperties?.chartState?.savedSymbolName,
});
const [currentSymbolInfo, setCurrentSymbolInfo] = useState<SymbolInfoInput>(
currentSymbolInfoRef.current,
);
const { triggerRelatedWidgetsToUpdate, getMasterInstrumentFromPublicContext } = useWidgetsBind({
widgetId,
});
const triggerRelatedWidgetsToUpdateRef = useRef(triggerRelatedWidgetsToUpdate);
useEffect(() => {
triggerRelatedWidgetsToUpdateRef.current = triggerRelatedWidgetsToUpdate;
}, [triggerRelatedWidgetsToUpdate]);
const saveContentProps = useCallback(
(value: SaveContentPropsOptions): void => {
const { withUpdate, cleanIndicativeData, ...chartStateValue } = value;
const oldProps = (getState().widgets.widgets.find(({ id }) => id === widgetId) as Widget | undefined)
?.widgetContentProps;
if (!oldProps) {
return;
}
dispatch(
addContentPropsToWidget({
id: widgetId,
withoutSend: !withUpdate,
widgetContentProps: {
chartState: {
...oldProps.chartState,
...chartStateValue,
},
// при смене инструмента очищаем индикативные данные, переданные из виджета Индикативные котировки
indicativeData: cleanIndicativeData ? undefined : oldProps.indicativeData,
},
}),
);
},
[dispatch, widgetId],
);
const onInstrumentChange = useCallback(
(
symbolInfo: SymbolInfoInput,
withUpdate?: boolean,
unbind = true,
): void => {
const symbolId = symbolInfo.symbolId.trim();
if (!symbolId) {
return;
}
setIsWidgetHeaderContextMenuOpen(false);
const currentValue = currentSymbolInfoRef.current;
const symbolIdChanged = currentValue.symbolId !== symbolId;
const symbol = symbolInfo.symbol?.trim() || (symbolIdChanged ? undefined : currentValue.symbol);
const symbolName =
symbolInfo.symbolName?.trim() || (symbolIdChanged ? undefined : currentValue.symbolName);
const nextSymbolInfo: SymbolInfoInput = {
symbolId,
symbol,
symbolName,
};
const symbolChanged = currentValue.symbol !== symbol;
const symbolNameChanged = currentValue.symbolName !== symbolName;
if (!symbolIdChanged && !symbolChanged && !symbolNameChanged) {
return;
}
currentSymbolInfoRef.current = nextSymbolInfo;
setCurrentSymbolInfo(nextSymbolInfo);
if (symbolIdChanged && unbind) {
dispatch(unbindWidgets({ widgetId }));
}
// при смене инструмента очищаем индикативные данные виджета график
// т.к. логика для графика индикатива построена на наличии в widgetContentProps данных indicativeData
saveContentProps({
savedInstrument: symbolId,
savedSymbol: symbol,
savedSymbolName: symbolName,
withUpdate,
cleanIndicativeData: symbolIdChanged,
});
if (symbolIdChanged) {
triggerRelatedWidgetsToUpdateRef.current(symbolId);
}
},
[dispatch, saveContentProps, widgetId],
);
const addInstrumentFromModal = useCallback(
(instruments: SelectedInstrument[]): void => {
const instrument = instruments[0];
if (!instrument?.issKey) {
return;
}
onInstrumentChange({
symbolId: instrument.issKey,
symbol: instrument.symbol,
symbolName: instrument.displayName,
});
},
[onInstrumentChange],
);
const onInstrumentChangeFromBind = useCallback(
(symbolInfo: SymbolInfoInput): void => {
onInstrumentChange(symbolInfo, true, false);
},
[onInstrumentChange],
);
const onDropInstrument = useCallback(
(symbolId: string, withUpdate?: boolean): void => {
onInstrumentChange({ symbolId }, withUpdate);
},
[onInstrumentChange],
);
useChartPublicContext({
setCurrInstrument: onInstrumentChangeFromBind,
widgetId,
getMasterInstrumentFromPublicContext,
});
useEffect(() => {
triggerRelatedWidgetsToUpdateRef.current(currentSymbolInfoRef.current.symbolId);
}, []);
useEffect(() => {
const unsubscribe = communicator.listen(
{
messageType: CORPACTIONS_OPEN_EXESTED_WIDGET_EVENT,
},
(message) => {
const symbolId = (message as Record<number, string>)[widgetId];
if (symbolId) {
onInstrumentChange({ symbolId });
}
},
);
const unsubscribeHighlighter = communicator.listen(
{
messageType: HIGHLIGHT_WIDGET_EVENT,
},
(message) => {
const typedMessage = message as Record<number, boolean>;
if (Object.keys(typedMessage).includes(String(widgetId))) {
setIsOver(typedMessage[widgetId]);
}
},
);
return () => {
unsubscribe();
unsubscribeHighlighter();
};
}, [onInstrumentChange, widgetId]);
return {
dropDownOpen,
setDropdownOpen,
currentSymbolInfo,
isWidgetHeaderContextMenuOpen,
setIsWidgetHeaderContextMenuOpen,
onDropInstrument,
addInstrumentFromModal,
isOver,
};
}