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


import classNames from 'classnames';

import { Divider } from 'exchange-elements/v2';

import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react';

import { Observable } from 'rxjs';

import { Button } from '@src/components/Button';
import { MenuList } from '@src/components/Menu';
import SplitDropdown from '@src/components/SplitDropdown';

import {
  gannAndFibonacciTools,
  geometricShapes,
  measurementTools,
  trendLines,
} from '@src/components/Toolbar/constants';

import { Tooltip } from '@src/components/Tooltip';
import { DrawingsNames } from '@src/constants';
import { t } from '@src/translations';
import { ActiveDrawingTool } from '@src/types';
import { ensureDefined, useObservable } from '@src/utils';

import {
  CrossIcon,
  EyeBrushIcon,
  HeartIcon,
  LayersIcon,
  MagnetIcon,
  PencilLockerIcon,
  RulerIcon,
  SplineCurveIcon,
  TrashIcon,
  TypeIcon,
  UnlockIcon,
  ZoomInIcon,
} from '../Icon';

import styles from './index.module.scss';

interface ToolbarProps {
  toggleDOM: () => void;
  addDrawing: (name: DrawingsNames) => void;
  setEndlessDrawingsMode: (value: boolean) => void;
  isEndlessDrawingsMode$: Observable<boolean>;
  activateCrosshair: () => void;
  activeTool$: Observable<ActiveDrawingTool>;
}

const implemented = {
  crosshair: true,
  lines: true,
  fib: true,
  rectangle: true,
  text: true,
  XABCD: false,
  position: true,
  icons: false,
};

export default function Toolbar({
  toggleDOM,
  addDrawing,
  setEndlessDrawingsMode,
  isEndlessDrawingsMode$,
  activateCrosshair,
  activeTool$,
}: ToolbarProps) {
  const [selectedLineType, setSelectedLineType] = useState<DrawingsNames>(DrawingsNames.trendLine);
  const [selectedMeasurementTool, setSelectedMeasurementTool] = useState(DrawingsNames.fixedRangeProfile);
  const [selectedGeometricShape, setSelectedGeometricShape] = useState(DrawingsNames.rectangle);
  const [selectedGannAndFibonacci, setSelectedGannAndFibonacci] = useState(DrawingsNames.fibonacciRetracement);

  const isEndlessDrawingsMode = useObservable(isEndlessDrawingsMode$);
  const activeTool = useObservable(activeTool$, 'crosshair');

  const toolbarRef = useRef<HTMLDivElement | null>(null);

  const createDrawingHandler = (setter: Dispatch<SetStateAction<DrawingsNames>>) => (value: DrawingsNames) => {
    setter(value);
    addDrawing(value);
  };

  const findOptionByValue = <T extends { value: string }>(options: T[], selectedValue: T['value']): T | undefined =>
    options.find((item) => item.value === selectedValue);

  const selectedTrendLineOption = findOptionByValue(trendLines(), selectedLineType);
  const selectedMeasurementToolOption = findOptionByValue(measurementTools(), selectedMeasurementTool);
  const selectedGeometricShapeOption = findOptionByValue(geometricShapes(), selectedGeometricShape);
  const selectedGannAndFibonacciOption = findOptionByValue(gannAndFibonacciTools(), selectedGannAndFibonacci);

  const isButtonPressed = (tool: ActiveDrawingTool): boolean => activeTool === tool;

  useEffect(() => {
    if (activeTool === 'crosshair') {
      return;
    }

    if (trendLines().some(({ value }) => value === activeTool)) {
      setSelectedLineType(activeTool);
      return;
    }
    if (gannAndFibonacciTools().some(({ value }) => value === activeTool)) {
      setSelectedGannAndFibonacci(activeTool);
      return;
    }
    if (geometricShapes().some(({ value }) => value === activeTool)) {
      setSelectedGeometricShape(activeTool);
      return;
    }
    if (measurementTools().some(({ value }) => value === activeTool)) {
      setSelectedMeasurementTool(activeTool);
    }
  }, [activeTool]);

  return (
    <div
      ref={toolbarRef}
      className={styles.toolbar}
    >
      <div className={classNames(styles.group)}>
        {implemented.crosshair && (
          <Tooltip
            showMessageOnFocus
            label={t('Crosshair')}
            location="right"
          >
            <Button
              onClick={activateCrosshair}
              pressed={isButtonPressed('crosshair')}
              label={<CrossIcon />}
            />
          </Tooltip>
        )}

        {implemented.lines && (
          <SplitDropdown
            anchorRef={toolbarRef}
            mainContent={ensureDefined(selectedTrendLineOption?.icon)}
            mainTooltip={ensureDefined(selectedTrendLineOption).label}
            onMainClick={() => addDrawing(selectedLineType)}
            menuTooltip={t('Trend lines')}
            mainButtonClassName={getButtonClassName(selectedLineType)}
          >
            <MenuList
              mode="single"
              value={selectedLineType}
              options={trendLines()}
              onClick={createDrawingHandler(setSelectedLineType)}
            />
          </SplitDropdown>
        )}

        {implemented.fib && (
          <SplitDropdown
            anchorRef={toolbarRef}
            mainContent={ensureDefined(selectedGannAndFibonacciOption?.icon)}
            mainTooltip={ensureDefined(selectedGannAndFibonacciOption).label}
            onMainClick={() => addDrawing(selectedGannAndFibonacci)}
            menuTooltip={t('Gann and Fibonacci')}
            mainButtonClassName={getButtonClassName(selectedGannAndFibonacci)}
          >
            <MenuList
              mode="single"
              value={selectedGannAndFibonacci}
              options={gannAndFibonacciTools()}
              onClick={createDrawingHandler(setSelectedGannAndFibonacci)}
            />
          </SplitDropdown>
        )}

        {implemented.rectangle && (
          <SplitDropdown
            anchorRef={toolbarRef}
            mainContent={ensureDefined(selectedGeometricShapeOption?.icon)}
            mainTooltip={ensureDefined(selectedGeometricShapeOption).label}
            onMainClick={() => addDrawing(selectedGeometricShape)}
            menuTooltip={t('Geometric shapes')}
            mainButtonClassName={getButtonClassName(selectedGeometricShape)}
          >
            <MenuList
              mode="single"
              value={selectedGeometricShape}
              options={geometricShapes()}
              onClick={createDrawingHandler(setSelectedGeometricShape)}
            />
          </SplitDropdown>
        )}

        {implemented.text && (
          <Tooltip
            showMessageOnFocus
            label={t('Text')}
            location="right"
          >
            <Button
              pressed={isButtonPressed(DrawingsNames.text)}
              onClick={() => addDrawing(DrawingsNames.text)}
              label={<TypeIcon />}
            />
          </Tooltip>
        )}

        {implemented.XABCD && (
          <Tooltip
            showMessageOnFocus
            label={t('XABCD template')}
            location="right"
          >
            <Button
              onClick={() => {}}
              label={<SplineCurveIcon />}
            />
          </Tooltip>
        )}

        {implemented.position && (
          <SplitDropdown
            anchorRef={toolbarRef}
            mainContent={ensureDefined(selectedMeasurementToolOption?.icon)}
            mainTooltip={ensureDefined(selectedMeasurementToolOption).label}
            menuTooltip={t('Measurement tools')}
            onMainClick={() => addDrawing(selectedMeasurementTool)}
            mainButtonClassName={getButtonClassName(selectedMeasurementTool)}
          >
            <MenuList
              mode="single"
              value={selectedMeasurementTool}
              options={measurementTools()}
              onClick={createDrawingHandler(setSelectedMeasurementTool)}
            />
          </SplitDropdown>
        )}

        {implemented.icons && (
          <Tooltip
            label={t('Pin')}
            location="right"
            showMessageOnFocus
          >
            <Button
              onClick={() => {}}
              label={<HeartIcon />}
            />
          </Tooltip>
        )}
      </div>

      <Divider
        direction="horizontal"
        pt={{ divider: { className: styles.divider } }}
      />

      <div className={classNames(styles.group)}>
        <Tooltip
          showMessageOnFocus
          label={t('Ruler')}
          location="right"
        >
          <Button
            pressed={isButtonPressed(DrawingsNames.ruler)}
            onClick={() => addDrawing(DrawingsNames.ruler)}
            label={<RulerIcon />}
          />
        </Tooltip>

        <Tooltip
          showMessageOnFocus
          label={t('Upscale')}
          location="right"
          className={styles.notImplemented}
        >
          <Button
            onClick={() => {}}
            label={<ZoomInIcon />}
          />
        </Tooltip>
      </div>

      <Divider
        direction="horizontal"
        pt={{ divider: { className: classNames(styles.divider) } }}
      />

      <div className={classNames(styles.group)}>
        <Tooltip
          showMessageOnFocus
          label={t('Magnet allows you to attract objects points to the nearest bar prices')}
          location="right"
          className={styles.notImplemented}
        >
          <Button
            onClick={() => {}}
            label={<MagnetIcon />}
          />
        </Tooltip>

        <Tooltip
          showMessageOnFocus
          label={t('Endless drawing mode')}
          location="right"
        >
          <Button
            pressed={isEndlessDrawingsMode}
            onClick={() => setEndlessDrawingsMode(!isEndlessDrawingsMode)}
            label={<PencilLockerIcon />}
          />
        </Tooltip>

        <Tooltip
          showMessageOnFocus
          label={t('Fix all objects')}
          location="right"
          className={styles.notImplemented}
        >
          <Button
            onClick={() => {}}
            label={<UnlockIcon />}
          />
        </Tooltip>

        <Tooltip
          showMessageOnFocus
          label={t('Hide all drawing objects')}
          location="right"
          className={styles.notImplemented}
        >
          <Button
            onClick={() => {}}
            label={<EyeBrushIcon />}
          />
        </Tooltip>
      </div>

      <Divider
        direction="horizontal"
        pt={{ divider: { className: classNames(styles.divider) } }}
      />

      <div className={classNames(styles.group)}>
        <Tooltip
          showMessageOnFocus
          label={t('Clear objects')}
          location="right"
          className={styles.notImplemented}
        >
          <Button
            onClick={() => {}}
            label={<TrashIcon />}
          />
        </Tooltip>

        <Tooltip
          showMessageOnFocus
          label={t('DOM tree')}
          location="right"
        >
          <Button
            onClick={toggleDOM}
            label={<LayersIcon />}
          />
        </Tooltip>
      </div>
    </div>
  );
}





import classNames from 'classnames';
import { PropsWithChildren, RefObject } from 'react';

import { Button } from '@src/components/Button';

import { DropdownPosition } from '@src/components/Dropdown/types';
import SmallChevronLeftIcon from '@src/components/Icon/SmallChevronLeft';

import { Tooltip } from '@src/components/Tooltip';

import { Dropdown } from '../Dropdown';

import styles from './index.module.scss';

interface SplitDropdownProps extends PropsWithChildren {
  position?: DropdownPosition;
  mainButtonClassName?: string;

  mainContent: JSX.Element;
  menuButtonContent?: JSX.Element;

  mainTooltip?: string;
  menuTooltip?: string;

  anchorRef?: RefObject<HTMLDivElement>;
  onMainClick?: () => void;

  tooltipClassName?: string;
}

export default function SplitDropdown({
  position = 'right',
  mainButtonClassName = '',
  mainContent,
  menuButtonContent = <SmallChevronLeftIcon />,
  mainTooltip,
  menuTooltip,
  anchorRef,
  onMainClick,
  tooltipClassName,
  children,
}: SplitDropdownProps) {
  return (
    <Dropdown
      position={position}
      anchorRef={anchorRef}
      buttonClassName={mainButtonClassName}
      renderTrigger={({ isOpen, onToggle }) => (
        <div className={`${styles.dropdown} ${isOpen ? styles.open : ''}`}>
          <Tooltip
            tooltipClassName={classNames(styles.tooltip__main, tooltipClassName)}
            showMessageOnFocus={!isOpen}
            location="right"
            label={mainTooltip}
          >
            <Button
              className={mainButtonClassName}
              pressed={isOpen}
              onClick={onMainClick}
              label={mainContent}
            />
          </Tooltip>
          <Tooltip
            tooltipClassName={tooltipClassName}
            showMessageOnFocus={!isOpen}
            location="right"
            label={menuTooltip}
          >
            <Button
              className={styles.menu_button}
              pressed={isOpen}
              onClick={onToggle}
              label={menuButtonContent}
            />
          </Tooltip>
        </div>
      )}
    >
      {children}
    </Dropdown>
  );
}



.dropdown {
  position: relative;
  display: flex;
  align-items: stretch;
  width: var(--space-2000);
  height: var(--space-2000);

  &:hover .menu_button,
  &:focus-within .menu_button,
  &.open .menu_button {
    opacity: 1;
    pointer-events: auto;
  }

  .menu_button {
    min-width: var(--space-0625);
    padding: 0;
    opacity: 0;
    border-radius: var(--space-0125);
    pointer-events: none;

    div {
      p {
        svg {
          width: var(--space-0625);
          height: var(--space-0625);
          transform: rotate(-180deg);
        }
      }
    }

    &.pressed div p svg {
      transform: rotate(0deg);
    }
  }
}

.tooltip__main {
  margin-left: var(--space-0375);
}