const TradingCup = () => {
  const cupWorkerRef = useRef<Worker>();
  const orderFeedWorkerRef = useRef<Worker>();
  const symbol = useSelector((state: AppState) => state.screenerSlice.symbol);
  const [settings, setSettings] = useState<SettingsCup>({
    timeout: "40",
    aggregation: true,
    maxVolume: window.localStorage.getItem(`cup-volume-${symbol}`) || "1000",
    isFixedVolume: null !== window.localStorage.getItem(`cup-volume-${symbol}`),
    minQuantity: window.localStorage.getItem(`order-feed-min-qty-${symbol}`) || "0",
    dollarsVolume: null !== window.localStorage.getItem(`cup-dollars-volume-${symbol}`),
  });
  const [showSettings, setShowSettings] = useState(false);

  useEffect(() => {
    const channel = new MessageChannel();
    cupWorkerRef.current?.postMessage({type: "set_port", port: channel.port1}, [channel.port1]);
    orderFeedWorkerRef.current?.postMessage({type: "set_port", port: channel.port2}, [channel.port2]);
  }, []);

  useEffect(() => {
    setSettings({
      ...settings,
      maxVolume: window.localStorage.getItem(`cup-volume-${symbol}`) || "1000",
      isFixedVolume: null !== window.localStorage.getItem(`cup-volume-${symbol}`),
      minQuantity: window.localStorage.getItem(`order-feed-min-qty-${symbol}`) || "0",
      dollarsVolume: null !== window.localStorage.getItem(`cup-dollars-volume-${symbol}`),
    });
  }, [symbol]);

  return <Stack direction="row" height="100%">
    <div className={styles.OfferFeed}>
      <OrderFeed
        symbol={symbol}
        workerRef={orderFeedWorkerRef}
        settings={settings}
      />
    </div>
    <div className={styles.Cup}>
      <Cup
        symbol={symbol}
        workerRef={cupWorkerRef}
        settings={settings}
        setShowSettings={setShowSettings}
      />
    </div>
  </Stack>;
};

export default TradingCup;

const Cup = ({workerRef, symbol, settings, setShowSettings}: CupProps) => {
  const cupParams = useSelector((state: AppState) => state.cupSlice);
  const [dpiScale, setDpiScale] = useState(Math.ceil(window.devicePixelRatio));
  const [canvasSize, setCanvasSize] = useState<CanvasSize>({height: 0, width: 0});
  const containerRef = useRef<HTMLDivElement|null>(null);
  const canvasRef = useRef<HTMLCanvasElement|null>(null);
  const [zoom, setZoom] = useState(1);
  const [isLoaded, setIsLoaded] = useState(false);
  const size = useComponentResizeListener(canvasRef);
  const darkMode = useTheme().palette.mode === "dark";
  const dispatch = useDispatch();

  const cupSubscribe = useCallback(async(pair: string, zoom: number) => {
    workerRef.current?.postMessage(JSON.stringify({type: "subscribe", pair, zoom}));
  }, []);

  const cupUnsubscribe = useCallback(async(pair: string) => {
    workerRef.current?.postMessage(JSON.stringify({type: "unsubscribe", pair}));
  }, []);

  const sendSettings = () => {
    workerRef.current?.postMessage(JSON.stringify({
      type: "change_max_volume",
      isFixed: settings.isFixedVolume,
      maxVolume: parseInt(settings.maxVolume || "0") < 0 ? 1 : parseInt(settings.maxVolume),
    }));
    workerRef.current?.postMessage(JSON.stringify({
      type: "change_publisher_timeout",
      value: parseInt(settings.timeout || "0") < 40 ? 40 : parseInt(settings.timeout),
    }));
  };

  const wheelHandler = (e: WheelEvent) => {
    e.preventDefault();
    workerRef.current?.postMessage(JSON.stringify({type: e.deltaY < 0 ? "camera_up" : "camera_down"}));
  };

  useEffect(() => {
    workerRef.current = new Worker(new URL("/workers/cup-builder.ts", import.meta.url));
    canvasRef.current?.addEventListener("wheel", wheelHandler, {passive: false});
    sendSettings();

    return () => {
      workerRef.current?.terminate();
      canvasRef.current?.removeEventListener("wheel", wheelHandler);
    };
  }, []);

  useEffect(() => {
    if (!workerRef.current) return;
    let animationFrameId: number|null = null;

    workerRef.current.onmessage = (event: MessageEvent<{
      type: string,
      camera: number,
      aggregation: number,
      bestBidPrice: number,
      bestAskPrice: number,
      maxVolume: number,
      pricePrecision: number,
      quantityPrecision: number,
      priceStep: number,
      cup: {[key: number]: CupItem},
      rowsCount: number,
    }>) => {
  useEffect(() => {
    cupSubscribe(symbol.toUpperCase(), zoom);

    return () => {
      cupUnsubscribe(symbol.toUpperCase());
    };
  }, [symbol, zoom]);

  useEffect(() => {
    if (settings.isFixedVolume) {
      window.localStorage.setItem(`cup-volume-${symbol}`, settings.maxVolume);
    } else {
      window.localStorage.removeItem(`cup-volume-${symbol}`);
    }

    sendSettings();
  }, [settings]);

  return <div ref={containerRef} className={styles.canvasWrapper}>
    <div className={styles.controls}>
      <IconButton onClick={zoomSub} size="small" disabled={!isLoaded}>
        <RemoveRounded />
      </IconButton>
      <span>x{zoom}</span>
      <IconButton onClick={zoomAdd} size="small" disabled={!isLoaded}>
        <AddRounded />
      </IconButton>
      <IconButton onClick={() => setShowSettings(true)} size="small">
        <SettingsRounded
          // @ts-ignore
          fontSize="xx-small"
        />
      </IconButton>
    </div>
    <canvas
      ref={canvasRef}
      className={[styles.canvas, isLoaded ? "" : styles.loading].join(" ")}
      width={canvasSize?.width}
      height={canvasSize?.height}
    />
  </div>;
};

export default Cup;

  Когда dollarsVolume true - перемножать quantity на price при отрисовке строк стакана. И в воркере максимальный объем определять тоже путем перемножения цены на кол-во