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


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>
  );
}