Загрузка данных
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 {
Dispatch,
SetStateAction,
} from 'react';
import type { Widget } from 'types/Widgets';
interface UseChartComponentFacadeReturn {
dropDownOpen: boolean;
setDropdownOpen: Dispatch<SetStateAction<boolean>>;
currentInstrument: string;
currentInstrumentName: string;
currentInstrumentTicker: string;
isWidgetHeaderContextMenuOpen: boolean;
setIsWidgetHeaderContextMenuOpen: Dispatch<
SetStateAction<boolean>
>;
onDropInstruments: (
instrument: SelectedInstrument,
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 initialInstrument =
widgetProperties?.chartState?.savedInstrument ??
DEFAULT_SYMBOL;
const initialInstrumentName =
widgetProperties?.chartState?.savedInstrumentName ??
initialInstrument;
const initialInstrumentTicker =
widgetProperties?.chartState?.savedInstrumentTicker ??
initialInstrument;
// TODO временно реф из-за непредсказуемого изменения если используется useState,
// нужен рефакторинг и вернуть обратно useState
const currentInstrumentRef =
useRef(initialInstrument);
const currentInstrumentNameRef =
useRef(initialInstrumentName);
const currentInstrumentTickerRef =
useRef(initialInstrumentTicker);
const [
currentInstrument,
setCurrentInstrument,
] = useState(initialInstrument);
const [
currentInstrumentName,
setCurrentInstrumentName,
] = useState(initialInstrumentName);
const [
currentInstrumentTicker,
setCurrentInstrumentTicker,
] = useState(initialInstrumentTicker);
const {
triggerRelatedWidgetsToUpdate,
getMasterInstrumentFromPublicContext,
} = useWidgetsBind({
widgetId,
});
const triggerRelatedWidgetsToUpdateRef = useRef(
triggerRelatedWidgetsToUpdate,
);
useEffect(() => {
triggerRelatedWidgetsToUpdateRef.current =
triggerRelatedWidgetsToUpdate;
}, [triggerRelatedWidgetsToUpdate]);
const saveContentProps = useCallback(
(val: SaveContentPropsOptions): void => {
const {
withUpdate,
cleanIndicativeData,
...chartStateVal
} = val;
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,
...chartStateVal,
},
// при смене инструмента очищаем индикативные данные, переданные из виджета Индикативные котировки
indicativeData: cleanIndicativeData
? undefined
: oldProps.indicativeData,
},
}),
);
},
[dispatch, widgetId],
);
const onInstrumentChange = useCallback(
(
newVal: string,
newName?: string,
newTicker?: string,
withUpdate?: boolean,
unbind = true,
): void => {
if (!newVal) {
return;
}
setIsWidgetHeaderContextMenuOpen(false);
const symbolChanged =
currentInstrumentRef.current !== newVal;
const normalizedName = newName?.trim();
const normalizedTicker = newTicker?.trim();
const nextInstrumentName =
normalizedName ||
(symbolChanged
? newVal
: currentInstrumentNameRef.current);
const nextInstrumentTicker =
normalizedTicker ||
(symbolChanged
? newVal
: currentInstrumentTickerRef.current);
const nameChanged =
currentInstrumentNameRef.current !==
nextInstrumentName;
const tickerChanged =
currentInstrumentTickerRef.current !==
nextInstrumentTicker;
if (
!symbolChanged &&
!nameChanged &&
!tickerChanged
) {
return;
}
if (symbolChanged) {
currentInstrumentRef.current = newVal;
setCurrentInstrument(newVal);
if (unbind) {
dispatch(unbindWidgets({ widgetId }));
}
}
if (nameChanged) {
currentInstrumentNameRef.current =
nextInstrumentName;
setCurrentInstrumentName(nextInstrumentName);
}
if (tickerChanged) {
currentInstrumentTickerRef.current =
nextInstrumentTicker;
setCurrentInstrumentTicker(
nextInstrumentTicker,
);
}
// при смене инструмента очищаем индикативные данные виджета график
// т.к. логика для графика индикатива построена на наличии в widgetContentProps данных indicativeData
saveContentProps({
savedInstrument: newVal,
savedInstrumentName: nextInstrumentName,
savedInstrumentTicker:
nextInstrumentTicker,
withUpdate,
cleanIndicativeData: symbolChanged,
});
if (symbolChanged) {
triggerRelatedWidgetsToUpdateRef.current(
newVal,
);
}
},
[dispatch, saveContentProps, widgetId],
);
const addInstrumentFromModal = useCallback(
(instruments: SelectedInstrument[]): void => {
const instrument = instruments[0];
if (!instrument?.issKey) {
return;
}
onInstrumentChange(
instrument.issKey,
instrument.displayName,
instrument.symbol,
);
},
[onInstrumentChange],
);
const onInstrumentChangeFromBind = useCallback(
(
instrumentId: string,
instrumentName?: string,
instrumentTicker?: string,
): void => {
onInstrumentChange(
instrumentId,
instrumentName,
instrumentTicker,
true,
false,
);
},
[onInstrumentChange],
);
const onDropInstruments = useCallback(
(
instrument: SelectedInstrument,
withUpdate?: boolean,
): void => {
if (!instrument.issKey) {
return;
}
onInstrumentChange(
instrument.issKey,
instrument.displayName,
instrument.symbol,
withUpdate,
);
},
[onInstrumentChange],
);
useChartPublicContext({
setCurrInstrument:
onInstrumentChangeFromBind,
widgetId,
getMasterInstrumentFromPublicContext,
});
useEffect(() => {
triggerRelatedWidgetsToUpdateRef.current(
currentInstrumentRef.current,
);
}, []);
useEffect(() => {
const unsubscribe = communicator.listen(
{
messageType:
CORPACTIONS_OPEN_EXESTED_WIDGET_EVENT,
},
(message) => {
const issKey = (
message as Record<number, string>
)[widgetId];
if (issKey) {
onInstrumentChange(issKey);
}
},
);
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,
currentInstrument,
currentInstrumentName,
currentInstrumentTicker,
isWidgetHeaderContextMenuOpen,
setIsWidgetHeaderContextMenuOpen,
onDropInstruments,
addInstrumentFromModal,
isOver,
};
}