Загрузка данных
import api from '@api/index';
import { setWidgetParentWorkspaceId } from '@store/slices/widgets';
import { addWorkspace, setCurrentWorkspace } from '@store/slices/workspaces';
import { getStore } from '@store/store';
import type { RequiredWithPartial } from 'types/utilityTypes';
import type { Workspace } from 'types/Workspace';
type CreateWorkspaceArgs = RequiredWithPartial<Omit<Workspace, 'id' | 'isCurrent' | 'position'>, 'isAddWidgets'>;
type CreateWorkspaceResult = Pick<Workspace, 'id'>;
export default async function createWorkspace(params: CreateWorkspaceArgs): Promise<CreateWorkspaceResult> {
const position = getStore().getState().workspaces.workspaces.length;
const newWorkspace: Workspace = {
...params,
id: 0,
isCurrent: true,
saved: true,
pinned: false,
position: getStore().getState().workspaces.workspaces.length,
isNew: params.isNew ?? true,
isAddWidgets: params.isAddWidgets ?? true,
};
const { workspaceController } = api;
await workspaceController
.create({
name: params.name,
position,
isCurrent: true,
isNew: params.isNew ?? true,
isAddWidgets: params.isAddWidgets ?? true,
favorite: false,
})
.then((res) => {
const { id } = res.data;
newWorkspace.id = id;
getStore().dispatch(addWorkspace(newWorkspace));
getStore().dispatch(setCurrentWorkspace(id));
const widgets = getStore()
.getState()
.widgets.widgets.filter(({ workspaceId }) => newWorkspace.id === workspaceId)
.map((widget) => ({
id: widget.id,
workspaceId: id,
}));
if (widgets.length > 0) {
getStore().dispatch(setWidgetParentWorkspaceId(widgets));
}
});
return {
id: newWorkspace.id,
};
}
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;
}
import { workspaceController } from '@api/controllers/workspace';
export const exportJSONFile = (item: number, name: string) => {
workspaceController.export(item).then((response) => {
const json = JSON.stringify(response.data);
const blob = new Blob([json], { type: ' text/plain;charset=utf-8 ' });
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = `${name}.json`;
link.click();
});
};
import { useCallback } from 'react';
import { alertManager } from '@terminal/components/Alerts/alertManager';
import { useAppSelect } from '@hooks/useAppSelector';
import { userInfoSelector } from '@store/selectors/user';
import { workspacesNamesSelector } from '@store/selectors/workspaces';
import { createNameForNewWorkspace } from '@terminal/desktop/components/Header/utils';
import createWidget from '@utils/createWidget';
import createWorkspace from '@utils/createWorkspace';
import { deepClone } from '@utils/deepClone';
import { isRemovedWidget } from '@utils/isRemovedWidget';
import { unpackWidgetProps } from '@utils/widgetPropsProceeders';
import { convertPercentToPx } from '@utils/widgetSizesNPositionProceeders';
import { User } from 'types/User';
import { PackedPropsProperties, WidgetContentType } from 'types/Widgets';
type WidgetProperty = {
key: string;
value: string;
};
type Widget = {
id: number | string;
properties?: WidgetProperty[];
};
const getFilteredWidgets = (widgets: Widget[]) => {
const hiddenWidgetTypes: Set<string> = new Set([
]);
const isBannedWidget = (item: Widget) =>
item.properties?.some((p) => p.key === 'type' && hiddenWidgetTypes.has(p.value));
return widgets.filter((item) => !isBannedWidget(item) && !isRemovedWidget(item.properties));
};
export const useWorkspaceCreator = (externalUser: User | null) => {
const workspacesNames = useAppSelect(workspacesNamesSelector);
const userInfo = useAppSelect(userInfoSelector());
const loadWorkspaceWidgets = useCallback(
(workspace: JSON, widgetsArray: JSON, workspaceName?: string): void => {
const workspaceImport = JSON.parse(JSON.stringify(workspace));
const newWorkspaceName = createNameForNewWorkspace(workspaceName ?? workspaceImport.name, workspacesNames);
const widgets: Widget[] = JSON.parse(JSON.stringify(widgetsArray));
// TODO 11175 убрать после доработки торгового функционала
const widgetsFiltered = getFilteredWidgets(widgets);
const widgetsProtectedFlag = widgetsFiltered.some((widget: Widget) =>
widget?.properties?.some(
(prop: { key: string; value: string }) =>
prop.key === 'type' && !(externalUser || userInfo)?.widgetTypes.includes(prop.value as WidgetContentType),
),
);
if (widgetsProtectedFlag) {
alertManager.notify({
message: `Импорт рабочего стола невозможен, так как в нем содержатся данные или функционал,
к которым у вас нет доступа.`,
type: 'error',
timeout: 5000,
});
return;
}
createWorkspace({
name: newWorkspaceName,
isNew: false,
}).then(({ id }) => {
const newWidgets = widgetsFiltered.map((item: Record<string, unknown>) => {
const newWidget = deepClone(item);
delete (newWidget as unknown as Record<string, unknown>).id;
const unpackedWidget = unpackWidgetProps(
convertPercentToPx({
widgetId: newWidget.widgetId as number,
properties: newWidget.properties as PackedPropsProperties[],
}),
);
unpackedWidget.workspaceId = id;
if (!unpackedWidget.isExpand && unpackedWidget.beforeExpandParams) {
unpackedWidget.beforeExpandParams = null;
}
if (!unpackedWidget.widgetContentProps) {
unpackedWidget.widgetContentProps = {};
}
// Удаляет id списка инструментов при импорте рабочего стола
delete unpackedWidget?.widgetContentProps?.widgetQuotesState?.instrumentListId;
return unpackedWidget;
});
createWidget(newWidgets);
});
},
[externalUser, userInfo, workspacesNames],
);
return loadWorkspaceWidgets;
};