ef499b6d47
## Summary - Re-enable one lint rule that was temporarily disabled during the ESLint-to-Oxlint migration: - **`twenty/sort-css-properties-alphabetically`** in twenty-front — 578 violations auto-fixed across 390 files - Document why **`typescript/consistent-type-imports`** cannot be auto-fixed in twenty-server: NestJS relies on `emitDecoratorMetadata` for DI, so converting constructor parameter imports to `import type` erases them at compile time and breaks dependency injection at runtime - Right-size CI runners, reducing 8-core usage from 18 jobs to 3: | Change | Jobs | Rationale | |--------|------|-----------| | **Keep 8-core** | `ci-merge-queue/e2e-test`, `ci-front/front-sb-build`, `ci-front/front-build` | Heavy builds needing max CPU + memory (10GB NODE_OPTIONS, full Storybook webpack bundling) | | **8-core → 4-core** | `ci-server` (build, lint-typecheck, validation, test, integration-test), `ci-front/front-sb-test`, `ci-zapier/server-setup`, `ci-sdk/sdk-e2e-test` | Already sharded into 10-12 parallel instances, I/O-bound (DB/Redis), or moderate single builds | | **8-core → 2-core** | `ci-emails/emails-test` | Trivially lightweight (build + curl health check) | | **Removed** | `ci-front/front-chromatic-deployment` | Dead code — permanently disabled with `if: false` | - Fix merge queue CI issues: - **Concurrency**: Use `merge_group.base_ref` instead of unique merge group ref so new queue entries cancel previous runs - **Required status checks**: Add `merge_group` trigger to all 6 required CI workflows (front, server, shared, website, docker-compose, sdk) with `changed-files-check` auto-skipped for merge_group events — status check jobs auto-pass without re-running full CI - **Build caching**: Add Nx build cache restore/save to E2E test job with fallback to `main` branch cache for faster frontend and server builds ## Test plan - [ ] CI passes on this PR (verifies lint rule auto-fix works) - [ ] Verify 4-core runner jobs complete within their 30-minute timeouts - [ ] Verify merge queue status checks auto-pass (ci-front-status-check, ci-server-status-check, etc.) - [ ] Verify merge queue E2E concurrency cancels previous runs when a new PR enters the queue
111 lines
4.1 KiB
TypeScript
111 lines
4.1 KiB
TypeScript
import { type PageLayoutBreakpoint } from '@/page-layout/constants/PageLayoutBreakpoints';
|
|
import { PAGE_LAYOUT_GRID_OVERLAY_Z_INDEX } from '@/page-layout/constants/PageLayoutGridOverlayZIndex';
|
|
import { useCreateWidgetFromClick } from '@/page-layout/hooks/useCreateWidgetFromClick';
|
|
import { pageLayoutCurrentBreakpointComponentState } from '@/page-layout/states/pageLayoutCurrentBreakpointComponentState';
|
|
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
|
|
import { pageLayoutSelectedCellsComponentState } from '@/page-layout/states/pageLayoutSelectedCellsComponentState';
|
|
import { calculateGridCellPosition } from '@/page-layout/utils/calculateGridCellPosition';
|
|
import { calculateTotalGridRows } from '@/page-layout/utils/calculateTotalGridRows';
|
|
import { generateCellId } from '@/page-layout/utils/generateCellId';
|
|
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
|
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
|
import { styled } from '@linaria/react';
|
|
import { useMemo } from 'react';
|
|
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
|
|
|
const StyledGridOverlay = styled.div<{
|
|
isDragSelecting?: boolean;
|
|
breakpoint: PageLayoutBreakpoint;
|
|
}>`
|
|
bottom: ${themeCssVariables.spacing[2]};
|
|
display: grid;
|
|
gap: ${themeCssVariables.spacing[2]};
|
|
grid-auto-rows: 55px;
|
|
grid-template-columns: ${({ breakpoint }) =>
|
|
breakpoint === 'mobile' ? '1fr' : 'repeat(12, 1fr)'};
|
|
left: ${themeCssVariables.spacing[2]};
|
|
pointer-events: ${({ isDragSelecting }) =>
|
|
isDragSelecting ? 'auto' : 'none'};
|
|
position: absolute;
|
|
right: ${themeCssVariables.spacing[2]};
|
|
top: ${themeCssVariables.spacing[2]};
|
|
z-index: ${PAGE_LAYOUT_GRID_OVERLAY_Z_INDEX};
|
|
`;
|
|
|
|
const StyledGridCell = styled.div<{ isSelected?: boolean }>`
|
|
background: ${({ isSelected }) =>
|
|
isSelected ? themeCssVariables.color.blue3 : 'transparent'};
|
|
border: 1px solid
|
|
${({ isSelected }) =>
|
|
isSelected
|
|
? themeCssVariables.color.blue7
|
|
: themeCssVariables.border.color.light};
|
|
border-radius: ${themeCssVariables.border.radius.md};
|
|
cursor: pointer;
|
|
transition: background-color 0.3s ease;
|
|
|
|
&:hover {
|
|
background: ${themeCssVariables.background.transparent.lighter};
|
|
border-color: ${themeCssVariables.border.color.medium};
|
|
}
|
|
`;
|
|
|
|
export const PageLayoutGridOverlay = () => {
|
|
const pageLayoutCurrentBreakpoint = useAtomComponentStateValue(
|
|
pageLayoutCurrentBreakpointComponentState,
|
|
);
|
|
|
|
const pageLayoutSelectedCells = useAtomComponentStateValue(
|
|
pageLayoutSelectedCellsComponentState,
|
|
);
|
|
|
|
const pageLayoutCurrentLayouts = useAtomComponentStateValue(
|
|
pageLayoutCurrentLayoutsComponentState,
|
|
);
|
|
|
|
const activeTabId = useAtomComponentStateValue(activeTabIdComponentState);
|
|
|
|
const { createWidgetFromClick } = useCreateWidgetFromClick();
|
|
|
|
const numberOfRows = useMemo(() => {
|
|
const currentTabLayouts = pageLayoutCurrentLayouts[activeTabId ?? ''] ?? {
|
|
desktop: [],
|
|
mobile: [],
|
|
};
|
|
return calculateTotalGridRows(currentTabLayouts);
|
|
}, [pageLayoutCurrentLayouts, activeTabId]);
|
|
|
|
const isPageLayoutCurrentBreakpointMobile =
|
|
pageLayoutCurrentBreakpoint === 'mobile';
|
|
|
|
const numberOfColumns = pageLayoutCurrentBreakpoint === 'mobile' ? 1 : 12;
|
|
|
|
return (
|
|
<StyledGridOverlay
|
|
isDragSelecting={!isPageLayoutCurrentBreakpointMobile}
|
|
breakpoint={pageLayoutCurrentBreakpoint}
|
|
>
|
|
{Array.from(
|
|
{
|
|
length: numberOfColumns * numberOfRows,
|
|
},
|
|
(_, i) => {
|
|
const { column, row } = calculateGridCellPosition({
|
|
index: i,
|
|
numberOfColumns,
|
|
});
|
|
const cellId = generateCellId(column, row);
|
|
return (
|
|
<StyledGridCell
|
|
key={i}
|
|
data-selectable-id={cellId}
|
|
isSelected={pageLayoutSelectedCells.has(cellId)}
|
|
onClick={() => createWidgetFromClick(cellId)}
|
|
/>
|
|
);
|
|
},
|
|
)}
|
|
</StyledGridOverlay>
|
|
);
|
|
};
|