Загрузка данных
import {
BehaviorSubject,
combineLatest,
distinctUntilChanged,
map,
Observable,
Subscription,
} from 'rxjs';
import {
ChartOptionsModel,
ChartSeriesType,
Intervals,
SymbolInfo,
SymbolInfoInput,
TimeFormat,
} from '@src/types';
import { Defaults } from '@src/types/defaults';
import { Timeframes } from '@src/types/timeframes';
import {
areSymbolInfosEqual,
DateFormat,
getTimeframeByInterval,
normalizeSymbolInfo,
shouldShowTime,
} from '@src/utils';
import { ChartSettings, ChartSettingsSource, parseChartSettings } from './ChartSettings';
import { UndoKey, UndoRedo } from './UndoRedo';
interface EventManagerParams {
initialTimeframe: Timeframes;
initialSeries: ChartSeriesType;
initialSymbolInfo: SymbolInfoInput;
initialTimeFormat?: TimeFormat;
initialDateFormat?: DateFormat;
initialInterval?: Intervals | null;
}
interface SetWithHistoryOptions {
history?: boolean;
}
/**
* Менеджер (настроек)*, которые меняются во время использование
* Отвечает за централизованное управление (настроек)
* * имеются в виду настройки, которые пользователь применяет к графику
*/
export class EventManager {
private timeframe$: BehaviorSubject<Timeframes>;
private seriesSelected$: BehaviorSubject<ChartSeriesType>;
private symbolInfo$: BehaviorSubject<SymbolInfo>;
private timeFormat$: BehaviorSubject<TimeFormat>;
private dateFormat$: BehaviorSubject<DateFormat>;
private interval$: BehaviorSubject<Intervals | null>;
private controlBarVisible$ = new BehaviorSubject<boolean>(false); // todo: move to render
private undoRedo: UndoRedo;
constructor({
initialTimeframe,
initialSeries,
initialSymbolInfo,
initialTimeFormat,
initialDateFormat,
initialInterval = null,
}: EventManagerParams) {
const normalizedSymbolInfo = normalizeSymbolInfo(initialSymbolInfo);
if (!normalizedSymbolInfo) {
throw new Error('[EventManager] symbolId is required');
}
this.timeframe$ = new BehaviorSubject<Timeframes>(initialTimeframe);
this.seriesSelected$ = new BehaviorSubject<ChartSeriesType>(initialSeries);
this.symbolInfo$ = new BehaviorSubject<SymbolInfo>(normalizedSymbolInfo);
this.timeFormat$ = new BehaviorSubject<TimeFormat>(
initialTimeFormat ?? Defaults.timeFormat,
);
this.dateFormat$ = new BehaviorSubject<DateFormat>(
initialDateFormat ?? Defaults.dateFormat,
);
this.interval$ = new BehaviorSubject<Intervals | null>(initialInterval);
this.undoRedo = new UndoRedo({
timeframe: (value) => this.timeframe$.next(value),
seriesSelected: (value) => this.seriesSelected$.next(value),
symbolInfo: (value) => this.symbolInfo$.next(value),
timeFormat: (value) => this.timeFormat$.next(value),
dateFormat: (value) => this.dateFormat$.next(value),
interval: (value) => this.interval$.next(value),
});
}
private setWithHistory<K extends UndoKey, V>(
key: K,
subject: BehaviorSubject<V>,
next: V,
options?: SetWithHistoryOptions,
): void {
const prev = subject.getValue();
if (Object.is(prev, next)) {
return;
}
subject.next(next);
const historyEnabled = options?.history ?? true;
if (historyEnabled) {
this.undoRedo.push(key, prev, next);
}
}
public getUndoRedo(): UndoRedo {
return this.undoRedo;
}
public getTimeframe(): Timeframes {
return this.timeframe$.value;
}
public setInterval = (
next: Intervals,
options?: SetWithHistoryOptions,
): void => {
const timeframe = getTimeframeByInterval(next);
this.undoRedo.group(() => {
this.setWithHistory('timeframe', this.timeframe$, timeframe, options);
this.setWithHistory('interval', this.interval$, next, options);
});
};
public resetInterval = (options?: SetWithHistoryOptions): void =>
this.setWithHistory('interval', this.interval$, null, options);
public getInterval(): Observable<Intervals | null> {
return this.interval$.asObservable();
}
public setSymbol = (
symbolInfoInput: SymbolInfoInput,
options?: SetWithHistoryOptions,
): void => {
const nextSymbolInfo = normalizeSymbolInfo(symbolInfoInput);
if (!nextSymbolInfo) {
return;
}
const currentSymbolInfo = this.symbolInfo$.value;
if (areSymbolInfosEqual(currentSymbolInfo, nextSymbolInfo)) {
return;
}
this.setWithHistory(
'symbolInfo',
this.symbolInfo$,
nextSymbolInfo,
options,
);
};
/**
* Существующее имя сохранено.
* Метод возвращает технический идентификатор для загрузки данных.
*/
public getSymbol(): Observable<string> {
return this.symbolId();
}
public getSymbolInfo(): SymbolInfo {
return this.symbolInfo$.value;
}
public getSymbolId(): string {
return this.symbolInfo$.value.symbolId;
}
public symbolInfo(): Observable<SymbolInfo> {
return this.symbolInfo$.pipe(
distinctUntilChanged(areSymbolInfosEqual),
);
}
public symbolId(): Observable<string> {
return this.symbolInfo$.pipe(
map(({ symbolId }) => symbolId),
distinctUntilChanged(),
);
}
public symbol(): Observable<string> {
return this.symbolInfo$.pipe(
map(({ symbol }) => symbol),
distinctUntilChanged(),
);
}
public symbolName(): Observable<string> {
return this.symbolInfo$.pipe(
map(({ symbolName }) => symbolName),
distinctUntilChanged(),
);
}
public setTimeFormat = (
next: TimeFormat,
options?: SetWithHistoryOptions,
): void =>
this.setWithHistory('timeFormat', this.timeFormat$, next, options);
public setDateFormat = (
next: DateFormat,
options?: SetWithHistoryOptions,
): void =>
this.setWithHistory('dateFormat', this.dateFormat$, next, options);
public getChartOptionsModel(): Observable<ChartOptionsModel> {
// todo: подумать - стоит ли унести это в чарт
return combineLatest([
this.timeFormat$,
this.dateFormat$,
this.timeframe$,
]).pipe(
map(([timeFormat, dateFormat, timeframe]) => ({
timeFormat,
dateFormat,
showTime: shouldShowTime(timeframe),
})),
);
}
public setTimeframe = (
next: Timeframes,
options?: SetWithHistoryOptions,
): void =>
this.undoRedo.group(() => {
this.resetInterval(options);
this.setWithHistory('timeframe', this.timeframe$, next, options);
});
public timeframe(): Observable<Timeframes> {
return this.timeframe$.asObservable();
}
public subscribeTimeframe(
callback: (format: Timeframes) => void,
): Subscription {
return this.timeframe$.subscribe(callback);
}
public getTimeframeObs(): Observable<Timeframes> {
return this.timeframe$.asObservable();
}
public setSeriesSelected = (
next: ChartSeriesType,
options?: SetWithHistoryOptions,
): void =>
this.setWithHistory(
'seriesSelected',
this.seriesSelected$,
next,
options,
);
public getSelectedSeries(): Observable<ChartSeriesType> {
return this.seriesSelected$.asObservable();
}
public subscribeSeriesSelected(
callback: (next: ChartSeriesType) => void,
): Subscription {
return this.seriesSelected$.subscribe(callback);
}
public setControlBarVisible(visible: boolean): void {
this.controlBarVisible$.next(visible);
}
public getControlBarVisible(): Observable<boolean> {
return this.controlBarVisible$.asObservable();
}
public exportChartSettings(): ChartSettings {
return {
symbolInfo: this.symbolInfo$.value,
timeframe: this.timeframe$.value,
seriesSelected: this.seriesSelected$.value,
timeFormat: this.timeFormat$.value,
dateFormat: this.dateFormat$.value,
interval: this.interval$.value,
};
}
public importChartSettings(settings: ChartSettingsSource): void {
const {
symbolInfo,
seriesSelected,
timeframe,
timeFormat,
dateFormat,
interval,
} = parseChartSettings(settings);
const setOptions = {
history: false,
};
if (symbolInfo) {
this.setSymbol(symbolInfo, setOptions);
}
if (seriesSelected) {
this.setSeriesSelected(seriesSelected, setOptions);
}
if (timeFormat) {
this.setTimeFormat(timeFormat, setOptions);
}
if (dateFormat) {
this.setDateFormat(dateFormat, setOptions);
}
if (interval != null) {
this.setInterval(interval, setOptions);
return;
}
if (timeframe) {
this.setTimeframe(timeframe, setOptions);
return;
}
if (interval === null) {
this.resetInterval(setOptions);
}
}
public destroy(): void {
this.timeFormat$.complete();
this.dateFormat$.complete();
this.timeframe$.complete();
this.controlBarVisible$.complete();
this.interval$.complete();
this.symbolInfo$.complete();
this.seriesSelected$.complete();
}
}