Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/twenty-front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
"react-datepicker": "^6.7.1",
"react-dropzone": "^14.2.3",
"react-error-boundary": "^4.0.11",
"react-grid-layout": "^1.5.2",
"react-grid-layout": "^2.2.2",
"react-helmet-async": "^1.3.0",
"react-hook-form": "^7.45.1",
"react-hotkeys-hook": "^4.4.4",
Expand Down Expand Up @@ -127,7 +127,6 @@
"@types/file-saver": "^2.0.7",
"@types/js-cookie": "^3.0.3",
"@types/json-logic-js": "^2",
"@types/react-grid-layout": "^1",
"@typescript-eslint/eslint-plugin": "^8.39.0",
"@typescript-eslint/utils": "^8.39.0",
"eslint": "^9.32.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,15 @@ import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLa
import { pageLayoutDraggingWidgetIdComponentState } from '@/page-layout/states/pageLayoutDraggingWidgetIdComponentState';
import { pageLayoutResizingWidgetIdComponentState } from '@/page-layout/states/pageLayoutResizingWidgetIdComponentState';
import { addPendingPlaceholderToLayouts } from '@/page-layout/utils/addPendingPlaceholderToLayouts';
import { filterPendingPlaceholderFromLayouts } from '@/page-layout/utils/filterPendingPlaceholderFromLayouts';
import { prepareGridLayoutItemsWithPlaceholders } from '@/page-layout/utils/prepareGridLayoutItemsWithPlaceholders';
import { WidgetPlaceholder } from '@/page-layout/widgets/components/WidgetPlaceholder';
import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import styled from '@emotion/styled';
import { useMemo, useRef } from 'react';
import {
Responsive,
WidthProvider,
type Layout,
type Layouts,
type ResponsiveProps,
} from 'react-grid-layout';
import { useMemo } from 'react';
import { Responsive, useContainerWidth } from 'react-grid-layout';
import { verticalCompactor } from 'react-grid-layout/core';
import { isDefined } from 'twenty-shared/utils';

const StyledGridContainer = styled.div`
Expand Down Expand Up @@ -64,20 +58,13 @@ const StyledGridContainer = styled.div`
}
`;

type ExtendedResponsiveProps = ResponsiveProps & {
maxCols?: number;
preventCollision?: boolean;
};

const ResponsiveGridLayout = WidthProvider(
Responsive,
) as React.ComponentType<ExtendedResponsiveProps>;

type PageLayoutGridLayoutProps = {
tabId: string;
};

export const PageLayoutGridLayout = ({ tabId }: PageLayoutGridLayoutProps) => {
const { width, containerRef, mounted } = useContainerWidth();

const setPageLayoutCurrentBreakpoint = useSetRecoilComponentState(
pageLayoutCurrentBreakpointComponentState,
);
Expand All @@ -92,18 +79,6 @@ export const PageLayoutGridLayout = ({ tabId }: PageLayoutGridLayoutProps) => {

const { handleLayoutChange } = usePageLayoutHandleLayoutChange();

const handleLayoutChangeWithoutPendingPlaceholder = (
currentLayout: Layout[],
allLayouts: Layouts,
) => {
handleLayoutChange(
currentLayout,
filterPendingPlaceholderFromLayouts(allLayouts),
);
};

const gridContainerRef = useRef<HTMLDivElement>(null);

const isPageLayoutInEditMode = useRecoilComponentValue(
isPageLayoutInEditModeComponentState,
);
Expand Down Expand Up @@ -143,61 +118,72 @@ export const PageLayoutGridLayout = ({ tabId }: PageLayoutGridLayoutProps) => {
);

return (
<StyledGridContainer ref={gridContainerRef}>
<StyledGridContainer ref={containerRef as React.RefObject<HTMLDivElement>}>
{isPageLayoutInEditMode && (
<>
<PageLayoutGridOverlay />
<PageLayoutGridLayoutDragSelector
gridContainerRef={gridContainerRef}
gridContainerRef={containerRef as React.RefObject<HTMLDivElement>}
/>
</>
)}

<ResponsiveGridLayout
className="layout"
layouts={layouts}
breakpoints={PAGE_LAYOUT_CONFIG.breakpoints}
cols={PAGE_LAYOUT_CONFIG.columns}
rowHeight={PAGE_LAYOUT_GRID_ROW_HEIGHT}
maxCols={12}
containerPadding={[0, 0]}
margin={[PAGE_LAYOUT_GRID_MARGIN, PAGE_LAYOUT_GRID_MARGIN]}
isDraggable={isPageLayoutInEditMode}
isResizable={isPageLayoutInEditMode}
draggableHandle=".drag-handle"
compactType="vertical"
preventCollision={false}
resizeHandle={
isPageLayoutInEditMode ? <PageLayoutGridResizeHandle /> : undefined
}
resizeHandles={['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw']}
onDragStart={(_layout, _oldItem, newItem) => {
setDraggingWidgetId(newItem.i);
}}
onDragStop={() => {
setDraggingWidgetId(null);
}}
onResizeStart={(_layout, _oldItem, newItem) => {
setResizingWidgetId(newItem.i);
}}
onResizeStop={() => {
setResizingWidgetId(null);
}}
onLayoutChange={handleLayoutChangeWithoutPendingPlaceholder}
onBreakpointChange={(newBreakpoint) =>
setPageLayoutCurrentBreakpoint(newBreakpoint as PageLayoutBreakpoint)
}
>
{gridLayoutItems.map((item) => (
<ReactGridLayoutCardWrapper key={item.id}>
{item.type === 'placeholder' ? (
<WidgetPlaceholder />
) : (
<WidgetRenderer widget={item.widget} />
)}
</ReactGridLayoutCardWrapper>
))}
</ResponsiveGridLayout>
{mounted && (
<Responsive
className="layout"
width={width}
layouts={layouts}
breakpoints={PAGE_LAYOUT_CONFIG.breakpoints}
cols={PAGE_LAYOUT_CONFIG.columns}
rowHeight={PAGE_LAYOUT_GRID_ROW_HEIGHT}
margin={[PAGE_LAYOUT_GRID_MARGIN, PAGE_LAYOUT_GRID_MARGIN]}
containerPadding={[0, 0]}
compactor={verticalCompactor}
dragConfig={{
enabled: isPageLayoutInEditMode,
handle: '.drag-handle',
}}
resizeConfig={{
enabled: isPageLayoutInEditMode,
handles: ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'],
handleComponent: isPageLayoutInEditMode ? (
<PageLayoutGridResizeHandle />
) : undefined,
}}
onDragStart={(_layout, _oldItem, newItem) => {
if (isDefined(newItem)) {
setDraggingWidgetId(newItem.i);
}
}}
onDragStop={() => {
setDraggingWidgetId(null);
}}
onResizeStart={(_layout, _oldItem, newItem) => {
if (isDefined(newItem)) {
setResizingWidgetId(newItem.i);
}
}}
onResizeStop={() => {
setResizingWidgetId(null);
}}
onLayoutChange={handleLayoutChange}
onBreakpointChange={(newBreakpoint) =>
setPageLayoutCurrentBreakpoint(
newBreakpoint as PageLayoutBreakpoint,
)
}
>
{gridLayoutItems.map((item) => (
<ReactGridLayoutCardWrapper key={item.id}>
{item.type === 'placeholder' ? (
<WidgetPlaceholder />
) : (
<WidgetRenderer widget={item.widget} />
)}
</ReactGridLayoutCardWrapper>
))}
</Responsive>
)}
</StyledGridContainer>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type Layouts } from 'react-grid-layout';
import { type ResponsiveLayouts } from 'react-grid-layout';

export const EMPTY_LAYOUT: Layouts = {
export const EMPTY_LAYOUT: ResponsiveLayouts = {
desktop: [{ i: 'empty-placeholder', x: 0, y: 0, w: 4, h: 4, static: true }],
mobile: [{ i: 'empty-placeholder', x: 0, y: 0, w: 1, h: 4, static: true }],
};
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,16 @@ export const useDuplicatePageLayoutTab = (pageLayoutIdFromProps?: string) => {
...generateDuplicatedTimestamps(),
};

const sourceLayouts = allTabLayouts[tabId] ?? {
desktop: [],
mobile: [],
};
const sourceLayouts = allTabLayouts[tabId];
const sourceDesktop = sourceLayouts?.desktop ?? [];
const sourceMobile = sourceLayouts?.mobile ?? [];

const newLayouts = {
desktop: sourceLayouts.desktop.map((layout) => ({
desktop: sourceDesktop.map((layout) => ({
...layout,
i: widgetOldIdNewIdMap.get(layout.i) || layout.i,
})),
mobile: sourceLayouts.mobile.map((layout) => ({
mobile: sourceMobile.map((layout) => ({
...layout,
i: widgetOldIdNewIdMap.get(layout.i) || layout.i,
})),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,18 @@ export const useDuplicatePageLayoutWidget = (
...generateDuplicatedTimestamps(),
};

const currentTabLayouts = allTabLayouts[sourceTab.id] || {
desktop: [],
mobile: [],
};
const currentTabLayouts = allTabLayouts[sourceTab.id];
const currentDesktop = currentTabLayouts?.desktop ?? [];

const sourceLayout = currentTabLayouts.desktop.find(
const sourceLayout = currentDesktop.find(
(layout) => layout.i === widgetId,
);

if (!isDefined(sourceLayout)) {
throw new Error(`Layout for widget ${widgetId} not found`);
}

const maxY = currentTabLayouts.desktop.reduce(
const maxY = currentDesktop.reduce(
(max, layout) => Math.max(max, layout.y + layout.h),
0,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { convertLayoutsToWidgets } from '@/page-layout/utils/convertLayoutsToWidgets';
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { type Layout, type Layouts } from 'react-grid-layout';
import { type Layout, type ResponsiveLayouts } from 'react-grid-layout';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { convertLayoutsToWidgets } from '@/page-layout/utils/convertLayoutsToWidgets';

export const usePageLayoutHandleLayoutChange = (
pageLayoutIdFromProps?: string,
Expand Down Expand Up @@ -37,7 +37,7 @@ export const usePageLayoutHandleLayoutChange = (

const handleLayoutChange = useRecoilCallback(
({ snapshot, set }) =>
(_: Layout[], allLayouts: Layouts) => {
(_: Layout, allLayouts: ResponsiveLayouts) => {
const activeTabId = snapshot.getLoadable(activeTabIdState).getValue();

if (!isDefined(activeTabId)) return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { type Layouts } from 'react-grid-layout';
import { type ResponsiveLayouts } from 'react-grid-layout';

export type TabLayouts = Record<string, Layouts>;
export type TabLayouts = Record<string, ResponsiveLayouts>;
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ describe('convertPageLayoutToTabLayouts', () => {
const richTextMinSize =
WIDGET_SIZES[WidgetType.STANDALONE_RICH_TEXT]!.minimum;

expect(result['tab-1'].desktop[0]).toMatchObject({
expect(result['tab-1']!.desktop![0]).toMatchObject({
i: 'rich-text-widget',
minW: richTextMinSize.w,
minH: richTextMinSize.h,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { PENDING_WIDGET_PLACEHOLDER_LAYOUT_KEY } from '@/page-layout/constants/PendingWidgetPlaceholderLayoutKey';
import { type Layouts } from 'react-grid-layout';
import { filterPendingPlaceholderFromLayouts } from '@/page-layout/utils/filterPendingPlaceholderFromLayouts';
import { type ResponsiveLayouts } from 'react-grid-layout';

describe('filterPendingPlaceholderFromLayouts', () => {
it('should remove pending placeholder from both desktop and mobile layouts', () => {
const layouts: Layouts = {
const layouts: ResponsiveLayouts = {
desktop: [
{ i: 'widget-1', x: 0, y: 0, w: 4, h: 4 },
{ i: PENDING_WIDGET_PLACEHOLDER_LAYOUT_KEY, x: 4, y: 0, w: 4, h: 4 },
Expand Down Expand Up @@ -33,7 +33,7 @@ describe('filterPendingPlaceholderFromLayouts', () => {
});

it('should handle layouts with no pending placeholder', () => {
const layouts: Layouts = {
const layouts: ResponsiveLayouts = {
desktop: [
{ i: 'widget-1', x: 0, y: 0, w: 4, h: 4 },
{ i: 'widget-2', x: 4, y: 0, w: 4, h: 4 },
Expand All @@ -59,7 +59,7 @@ describe('filterPendingPlaceholderFromLayouts', () => {
{ i: 'widget-1', x: 0, y: 0, w: 1, h: 4 },
{ i: PENDING_WIDGET_PLACEHOLDER_LAYOUT_KEY, x: 0, y: 4, w: 1, h: 4 },
],
} as Layouts;
} as ResponsiveLayouts;

const result1 = filterPendingPlaceholderFromLayouts(layoutsWithoutDesktop);

Expand All @@ -73,7 +73,7 @@ describe('filterPendingPlaceholderFromLayouts', () => {
{ i: 'widget-1', x: 0, y: 0, w: 4, h: 4 },
{ i: PENDING_WIDGET_PLACEHOLDER_LAYOUT_KEY, x: 4, y: 0, w: 4, h: 4 },
],
} as Layouts;
} as ResponsiveLayouts;

const result2 = filterPendingPlaceholderFromLayouts(layoutsWithoutMobile);

Expand All @@ -85,7 +85,7 @@ describe('filterPendingPlaceholderFromLayouts', () => {
});

it('should handle empty layouts', () => {
const emptyLayouts: Layouts = {
const emptyLayouts: ResponsiveLayouts = {
desktop: [],
mobile: [],
};
Expand All @@ -97,7 +97,7 @@ describe('filterPendingPlaceholderFromLayouts', () => {
});

it('should handle layouts with only pending placeholder', () => {
const layouts: Layouts = {
const layouts: ResponsiveLayouts = {
desktop: [
{ i: PENDING_WIDGET_PLACEHOLDER_LAYOUT_KEY, x: 0, y: 0, w: 4, h: 4 },
],
Expand All @@ -113,7 +113,7 @@ describe('filterPendingPlaceholderFromLayouts', () => {
});

it('should not mutate the original layouts', () => {
const layouts: Layouts = {
const layouts: ResponsiveLayouts = {
desktop: [
{ i: 'widget-1', x: 0, y: 0, w: 4, h: 4 },
{ i: PENDING_WIDGET_PLACEHOLDER_LAYOUT_KEY, x: 4, y: 0, w: 4, h: 4 },
Expand Down
Loading
Loading