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


import isNil from 'lodash/isNil';

import api from '@api/index';
import {
  BIND_ICON_COLORS,
  MOBILE_SIZE,
  WIDGET_DEFAULT_SIZE_LIMITS,
  WIDGET_DEFAULT_SIZES,
  Z_INDEX,
} from '@configs/appConfig';

import { widgetPropertiesMap } from '@modules/widgetProperties';
import { createChatWidget } from '@store/slices/chatSlice';
import { addValuesToPublicContext } from '@store/slices/publicContext';
import { setCreatedWidgetId } from '@store/slices/searchNews';
import { addWidget } from '@store/slices/widgets';
import { getStore } from '@store/store';

import { Widget, WidgetContentType } from 'types/Widgets';

import { getLimits } from '../widgets/utils/limits';
import { getWidgetSizes } from '../widgets/utils/sizes';

import { createBindMap } from './createBindMap';
import getDefaultPositionForWidget from './getDefaultPositionForWidget';
import { getIssKeyFromWidgets } from './getIssKeyFromWidgets';
import getWorkspaceSizes from './getWorkspaceSizes';
import { packWidgetProps } from './widgetPropsProceeders';
import { convertPxToPercents } from './widgetSizesNPositionProceeders';

import type { PartialWithRequired } from 'types/utilityTypes';

type CreateWidgetArgs = PartialWithRequired<Widget, 'workspaceId' | 'type'>;

type CreatedWidgetData = Pick<Widget, 'id' | 'name' | 'workspaceId'>;

type BuildNewWidgetsProps = {
  data: CreatedWidgetData[];
  items: CreateWidgetArgs[];
  basicWidgetZIndex: number;
  isMobileView: boolean;
  workspaceSizes: { width: number; height: number };
  center: boolean;
};

function getBasicWidgetZIndex(items: CreateWidgetArgs[]): number {
  const currentWidgets = getStore()
    .getState()
    .widgets.widgets.filter((item) => items[0]?.workspaceId === item?.workspaceId);

  return currentWidgets.length > 0
    ? currentWidgets.map((item) => item.zIndex).sort((a, b) => b - a)[0] + 1
    : Z_INDEX.WIDGET_BASE;
}

function isAddingWidgetsAllowed(workspaceId: Widget['workspaceId']): boolean {
  const workspace = getStore()
    .getState()
    .workspaces.workspaces.find(({ id }) => id === workspaceId);

  return Boolean(workspace?.isAddWidgets);
}

function isWidgetsLimitExceeded(workspaceId: Widget['workspaceId']): boolean {
  const widgetsOnWorkspace = getStore()
    .getState()
    .widgets.widgets.filter((widget) => widget.workspaceId === workspaceId);

  return widgetsOnWorkspace.length > 49;
}

function buildNewWidgets({
  data,
  items,
  basicWidgetZIndex,
  isMobileView,
  workspaceSizes,
  center,
}: BuildNewWidgetsProps): Widget[] {
  return data.map(({ id, name, workspaceId }, index) => {
    const argItem = items[index];
    const newWidgetZIndex = basicWidgetZIndex + 1 + index;
    const widgetSizes = getWidgetSizes(argItem.type, isMobileView, workspaceSizes);
    return {
      ...argItem,
      id,
      name,
      workspaceId,
      type: argItem.type,
      sizes: argItem.sizes ?? widgetSizes ?? WIDGET_DEFAULT_SIZES,
      sizeLimits: argItem.sizeLimits || (getLimits(argItem.type) ?? WIDGET_DEFAULT_SIZE_LIMITS),
      zIndex: argItem.zIndex ?? newWidgetZIndex,
      position:
        argItem.position ?? getDefaultPositionForWidget(widgetSizes ?? WIDGET_DEFAULT_SIZES, workspaceId, center),
      widgetContentProps: argItem.widgetContentProps ?? widgetPropertiesMap[argItem.type] ?? {},
      isExpand: argItem.isExpand ?? false,
      beforeExpandParams: argItem.beforeExpandParams ?? null,
    };
  });
}

function createOldToNewWidgetIdMap(items: CreateWidgetArgs[], newWidgets: Widget[]): Record<number, number> {
  const oldToNewWidgetIdMap: Record<number, number> = {};

  // Что-то страшное. Если количество не совпадает то не можем смаппить
  if (newWidgets.length === items.length) {
    newWidgets.forEach((widget, index) => {
      const oldId = items[index].id;
      if (typeof oldId !== 'undefined') {
        oldToNewWidgetIdMap[oldId] = widget.id;
      }
    });
  }

  return oldToNewWidgetIdMap;
}

function updateWidgetMasters(widgets: Widget[], oldToNewWidgetIdMap: Record<number, number>): Widget[] {
  return widgets.map((widget) => {
    const master = isNil(widget.master) ? widget.master : (oldToNewWidgetIdMap[widget.master] ?? widget.master);
    const masters = isNil(widget.masters) ? widget.masters : widget.masters?.map((m) => oldToNewWidgetIdMap[m] ?? m);
    return {
      ...widget,
      master: master || null,
      masters: masters || [],
    };
  });
}

function dispatchCreatedWidgetSideEffects(newWidgets: Widget[]): void {
  const firstWidget = newWidgets[0];
  // Запись этого ID должна быть максильно близко к записи виджетов в стор
  if (firstWidget.type === WidgetContentType.news) {
    getStore().dispatch(setCreatedWidgetId(firstWidget.id));
  }
  if (firstWidget.type === WidgetContentType.noTradeChat) {
    getStore().dispatch(createChatWidget(String(firstWidget.id)));
  }
}

function updateWidgetPropertiesAndPublicContext(newWidgets: Widget[]): void {
  const { widgetPropertiesController } = api;
  const bindMap = createBindMap(newWidgets);
  let masterIndex = 0;
  // Создать новые связки убрав предыдущие
  // diff контроллер к этому моменту ещё не прошёлся по новыми виджетам
  // И добавление пропертей ничего не даст. Плюс он не анализирует добавление виджетов
  newWidgets.forEach((widget) => {
    const { properties } = packWidgetProps({
      master: widget.master,
      masters: widget.masters,
    });
    widgetPropertiesController.update({
      widgetId: widget.id,
      properties,
    });

    const masterPublicContext = getStore()
      .getState()
      .publicContext.publicContext?.find(({ id }) => id === widget.id);
    if (!widget.masters?.length && !masterPublicContext && Object.keys(bindMap).includes(widget.id.toString())) {
      const instrumentId = getIssKeyFromWidgets(widget);
      getStore().dispatch(
        addValuesToPublicContext({
          id: widget.id,
          color: BIND_ICON_COLORS[masterIndex],
          publicProperties: [{ name: 'instrId', value: instrumentId }],
        }),
      );
      masterIndex += 1;
    }
  });
}

// Фактически результат нигде не используется
export default async function createWidget(items: CreateWidgetArgs[], center = false): Promise<Widget[]> {
  const currentWorkspaceId = items.map(({ workspaceId }) => workspaceId)[0];

  if (!isAddingWidgetsAllowed(currentWorkspaceId)) {
    return [];
  }

  if (isWidgetsLimitExceeded(currentWorkspaceId)) {
    // eslint-disable-next-line no-alert -- нужный alert
    alert('Виджетов может быть максимум 50 на одной странице!');
    return [];
  }

  const isMobileView = window.screen.width <= MOBILE_SIZE || window.innerWidth <= MOBILE_SIZE;
  const workspaceSizes = getWorkspaceSizes();
  const { widgetsController, widgetPropertiesController } = api;
  const basicWidgetZIndex = getBasicWidgetZIndex(items);

  const res = await widgetsController.create(
    items.map(({ name, workspaceId }) => ({
      workspaceId,
      name: name ?? '',
    })),
  );

  if (!res.data) {
    return [];
  }

  const newWidgets = buildNewWidgets({
    data: res.data,
    items,
    basicWidgetZIndex,
    isMobileView,
    workspaceSizes,
    center,
  });

  await widgetPropertiesController.create(newWidgets.map((widget) => convertPxToPercents(packWidgetProps(widget))));

  const oldToNewWidgetIdMap = createOldToNewWidgetIdMap(items, newWidgets);
  const newWidgetsWithUpdatedMasters = updateWidgetMasters(newWidgets, oldToNewWidgetIdMap);

  dispatchCreatedWidgetSideEffects(newWidgetsWithUpdatedMasters);
  getStore().dispatch(addWidget(newWidgetsWithUpdatedMasters));
  updateWidgetPropertiesAndPublicContext(newWidgetsWithUpdatedMasters);

  return newWidgetsWithUpdatedMasters;
}