Загрузка данных
import { BehaviorSubject } from 'rxjs';
enum DOMObjectType {
Drawing = 'Drawing',
Indicator = 'Indicator',
}
export interface IDOMObject {
id: string;
hidden: BehaviorSubject<boolean>;
zIndex: number;
type: DOMObjectType;
name: string;
delete(): void;
hide(): void;
show(): void;
lastUpdated(): void;
moveUp(): void;
moveDown(): void;
setZIndex(next: number): void;
}
export interface DOMObjectParams {
id: string;
zIndex: number;
onDelete: (id: string) => void;
moveUp: (id: string) => void;
moveDown: (id: string) => void;
}
export class DOMObject implements IDOMObject {
public readonly id: string;
public zIndex: number;
public hidden = new BehaviorSubject(false);
public moveUp: () => void;
public moveDown: () => void;
protected onDelete: (id: string) => void;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
name: string;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
type: DOMObjectType;
constructor({ id, zIndex, onDelete, moveUp, moveDown }: DOMObjectParams) {
this.id = id;
this.zIndex = zIndex;
this.onDelete = onDelete;
this.moveUp = () => moveUp(this.id);
this.moveDown = () => moveDown(this.id);
}
delete(): void {
this.onDelete(this.id);
}
hide(): void {
this.hidden.next(true);
}
show(): void {
this.hidden.next(false);
}
lastUpdated(): void {}
setZIndex(next: number): void {
this.zIndex = next;
}
}
import { BehaviorSubject, Observable } from 'rxjs';
import { DOM } from '@components/DOM';
import { IDOMObject } from '@core/DOMObject';
import { ModalRenderer } from '@core/ModalRenderer';
interface DOMModelParams {
modalRenderer: ModalRenderer;
}
/**
* Абстракция над библиотекой для построения графиков
*/
export class DOMModel {
// ∈ symbol&pane
private modalRenderer: ModalRenderer;
private lastZIndex = 0;
private entities: BehaviorSubject<IDOMObject[]> = new BehaviorSubject<IDOMObject[]>([]); // drawings/indicators/series
// private panes: Panes[]; // todo: пока что на каждый пейн будет один objectTree
constructor({ modalRenderer }: DOMModelParams) {
this.modalRenderer = modalRenderer;
}
public removeEntity = <T extends IDOMObject>(entity: T): void => {
this.entities.next(this.entities.value.filter((d) => d.id !== entity.id));
};
public setEntity = <T extends IDOMObject>(
cb: (zIndex: number, moveUp: (id: string) => void, moveDown: (id: string) => void) => T,
): T => {
const entity = cb(this.lastZIndex++, this.moveUp, this.moveDown);
this.entities.next([...this.entities.value, entity].sort((a, b) => a.zIndex - b.zIndex));
return entity;
};
private moveUp = (id: string): void => {
const entities = this.entities.value;
const curr = entities.find((e) => e.id === id);
if (!curr) {
return;
}
const next = entities.find((e) => e.zIndex === curr.zIndex + 1);
const ent = entities.filter((e) => e.id !== id && e.zIndex !== curr.zIndex + 1);
if (!next) {
return;
}
const [left, right] = [curr.zIndex, next.zIndex];
curr.setZIndex(right);
next.setZIndex(left);
this.entities.next([...ent, curr, next].sort((a, b) => a.zIndex - b.zIndex));
};
private moveDown = (id: string): void => {
const entities = this.entities.value;
const curr = entities.find((e) => e.id === id);
if (!curr) {
return;
}
const prev = entities.find((e) => e.zIndex === curr.zIndex - 1);
const ent = entities.filter((e) => e.id !== id && e.zIndex !== curr.zIndex - 1);
if (!prev) {
return;
}
const [left, right] = [curr.zIndex, prev.zIndex];
curr.setZIndex(right);
prev.setZIndex(left);
this.entities.next([...ent, curr, prev].sort((a, b) => a.zIndex - b.zIndex));
};
public getEntities = (): Observable<IDOMObject[]> => {
return this.entities.asObservable();
};
public toggleDOM = () => {
this.modalRenderer.renderComponent(<DOM elementsObs={this.getEntities()} />, {
title: 'Дерево объектов',
onSave: () => console.warn('dom state saved'),
acceptLabel: '',
rejectLabel: '',
});
};
public destroy(): void {
// todo implement
}
}
import { Button } from 'exchange-elements/v2';
import { Observable } from 'rxjs';
import { TrashIcon } from '@components/Icon';
import { IDOMObject } from '@core/DOMObject';
import { useObservable } from '@src/utils';
import EyeBrushIcon from '../Icon/EyeBrush';
import styles from './index.module.scss';
interface ObjectTreeProps {
elementsObs: Observable<IDOMObject[]>;
}
interface DOMRowProps {
elem: IDOMObject;
}
function DOMRow({ elem }: DOMRowProps) {
const hidden = useObservable(elem.hidden, elem.hidden.value);
const handleToggleHidden = () => {
if (hidden) {
elem.show();
return;
}
elem.hide();
};
return (
<div className={styles.domRow}>
<div className={styles.entityName}>
{elem.id} - {elem.zIndex}
</div>
<div className={styles.controlGroup}>
<Button
size="sm"
className={styles.button}
onClick={() => elem.delete()}
label={<TrashIcon />}
/>
<Button
size="sm"
className={styles.button}
onClick={handleToggleHidden}
label={<EyeBrushIcon className={hidden ? styles.eyeBlurred : ''} />}
/>
<Button
size="sm"
className={styles.button}
onClick={() => elem.moveDown()}
label="↑"
/>
<Button
size="sm"
className={styles.button}
onClick={() => elem.moveUp()}
label="↓"
/>
</div>
</div>
);
}
export function DOM({ elementsObs }: ObjectTreeProps) {
const elements = useObservable(elementsObs, []);
return (
<div>
{elements?.map((elem) => (
<DOMRow
key={elem.id}
elem={elem}
/>
))}
</div>
);
}