eeca9cd42e
closes https://discord.com/channels/1130383047699738754/1518291134382608394 https://github.com/user-attachments/assets/931e4e88-44e8-4634-a7f9-e0564bd80fff <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21936?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
194 lines
6.4 KiB
TypeScript
194 lines
6.4 KiB
TypeScript
import { useDuplicateFieldsWidgetForPageLayout } from '@/page-layout/hooks/useDuplicateFieldsWidgetForPageLayout';
|
|
import { useDuplicateRecordTableWidgetForPageLayout } from '@/page-layout/hooks/useDuplicateRecordTableWidgetForPageLayout';
|
|
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
|
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
|
|
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
|
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
|
|
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
|
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
|
import { generateDuplicatedTimestamps } from '@/page-layout/utils/generateDuplicatedTimestamps';
|
|
import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition';
|
|
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
|
|
import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel';
|
|
import { calculateNewPosition } from '@/ui/layout/draggable-list/utils/calculateNewPosition';
|
|
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
|
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
|
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
|
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
|
import { useStore } from 'jotai';
|
|
import { useCallback } from 'react';
|
|
import { SidePanelPages } from 'twenty-shared/types';
|
|
import { appendCopySuffix, isDefined } from 'twenty-shared/utils';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
export const useDuplicatePageLayoutTab = ({
|
|
pageLayoutId: pageLayoutIdFromProps,
|
|
tabListInstanceId,
|
|
}: {
|
|
pageLayoutId: string;
|
|
tabListInstanceId: string;
|
|
}) => {
|
|
const pageLayoutId = useAvailableComponentInstanceIdOrThrow(
|
|
PageLayoutComponentInstanceContext,
|
|
pageLayoutIdFromProps,
|
|
);
|
|
|
|
const pageLayoutDraft = useAtomComponentStateCallbackState(
|
|
pageLayoutDraftComponentState,
|
|
pageLayoutId,
|
|
);
|
|
|
|
const pageLayoutCurrentLayouts = useAtomComponentStateCallbackState(
|
|
pageLayoutCurrentLayoutsComponentState,
|
|
pageLayoutId,
|
|
);
|
|
|
|
const store = useStore();
|
|
|
|
const setActiveTabId = useSetAtomComponentState(
|
|
activeTabIdComponentState,
|
|
tabListInstanceId,
|
|
);
|
|
|
|
const setPageLayoutTabSettingsOpenTabId = useSetAtomComponentState(
|
|
pageLayoutTabSettingsOpenTabIdComponentState,
|
|
pageLayoutId,
|
|
);
|
|
|
|
const { navigatePageLayoutSidePanel } = useNavigatePageLayoutSidePanel();
|
|
|
|
const { closeSidePanelMenu } = useSidePanelMenu();
|
|
|
|
const { duplicateFieldsWidget } = useDuplicateFieldsWidgetForPageLayout({
|
|
pageLayoutId,
|
|
});
|
|
|
|
const { duplicateRecordTableWidget } =
|
|
useDuplicateRecordTableWidgetForPageLayout({
|
|
pageLayoutId,
|
|
});
|
|
|
|
const duplicateTab = useCallback(
|
|
(tabId: string): string => {
|
|
const currentPageLayoutDraft = store.get(pageLayoutDraft);
|
|
|
|
const allTabLayouts = store.get(pageLayoutCurrentLayouts);
|
|
|
|
const sourceTab = currentPageLayoutDraft.tabs.find(
|
|
(tab) => tab.id === tabId,
|
|
);
|
|
|
|
if (!isDefined(sourceTab)) {
|
|
throw new Error(`Tab with id ${tabId} not found`);
|
|
}
|
|
|
|
const newTabId = uuidv4();
|
|
const widgetOldIdNewIdMap = new Map<string, string>();
|
|
|
|
const clonedWidgets: PageLayoutWidget[] = sourceTab.widgets.map(
|
|
(widget) => {
|
|
const newWidgetId = uuidv4();
|
|
widgetOldIdNewIdMap.set(widget.id, newWidgetId);
|
|
|
|
const widgetViewCopyResult =
|
|
duplicateFieldsWidget({
|
|
sourceWidget: widget,
|
|
newWidgetId,
|
|
}) ??
|
|
duplicateRecordTableWidget({
|
|
sourceWidget: widget,
|
|
newWidgetId,
|
|
});
|
|
|
|
const clonedConfiguration = isDefined(widgetViewCopyResult)
|
|
? {
|
|
...widget.configuration,
|
|
viewId: widgetViewCopyResult.newViewId,
|
|
}
|
|
: widget.configuration;
|
|
|
|
return {
|
|
...widget,
|
|
id: newWidgetId,
|
|
pageLayoutTabId: newTabId,
|
|
configuration: clonedConfiguration,
|
|
...generateDuplicatedTimestamps(),
|
|
};
|
|
},
|
|
);
|
|
|
|
const sortedTabs = sortTabsByPosition(currentPageLayoutDraft.tabs);
|
|
const sourceIndex = sortedTabs.findIndex((t) => t.id === tabId);
|
|
|
|
const newTabPosition = calculateNewPosition({
|
|
items: sortedTabs,
|
|
destinationIndex: sourceIndex + 1,
|
|
sourceIndex,
|
|
});
|
|
|
|
const newTab: PageLayoutTab = {
|
|
...sourceTab,
|
|
id: newTabId,
|
|
title: appendCopySuffix(sourceTab.title),
|
|
position: newTabPosition,
|
|
widgets: clonedWidgets,
|
|
...generateDuplicatedTimestamps(),
|
|
};
|
|
|
|
const sourceLayouts = allTabLayouts[tabId] ?? {
|
|
desktop: [],
|
|
mobile: [],
|
|
};
|
|
|
|
const newLayouts = {
|
|
desktop: sourceLayouts.desktop.map((layout) => ({
|
|
...layout,
|
|
i: widgetOldIdNewIdMap.get(layout.i) || layout.i,
|
|
})),
|
|
mobile: sourceLayouts.mobile.map((layout) => ({
|
|
...layout,
|
|
i: widgetOldIdNewIdMap.get(layout.i) || layout.i,
|
|
})),
|
|
};
|
|
|
|
store.set(pageLayoutCurrentLayouts, {
|
|
...allTabLayouts,
|
|
[newTabId]: newLayouts,
|
|
});
|
|
|
|
const prev = store.get(pageLayoutDraft);
|
|
store.set(pageLayoutDraft, {
|
|
...prev,
|
|
tabs: [...prev.tabs, newTab],
|
|
});
|
|
|
|
closeSidePanelMenu();
|
|
|
|
setActiveTabId(newTabId);
|
|
|
|
navigatePageLayoutSidePanel({
|
|
sidePanelPage: SidePanelPages.PageLayoutTabSettings,
|
|
pageTitle: newTab.title,
|
|
focusTitleInput: true,
|
|
});
|
|
|
|
setPageLayoutTabSettingsOpenTabId(newTabId);
|
|
|
|
return newTabId;
|
|
},
|
|
[
|
|
closeSidePanelMenu,
|
|
duplicateFieldsWidget,
|
|
duplicateRecordTableWidget,
|
|
navigatePageLayoutSidePanel,
|
|
pageLayoutCurrentLayouts,
|
|
pageLayoutDraft,
|
|
setActiveTabId,
|
|
setPageLayoutTabSettingsOpenTabId,
|
|
store,
|
|
],
|
|
);
|
|
|
|
return { duplicateTab };
|
|
};
|