From 9e2918d13b0d52c615ed8ccfec9150e7c89845ab Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Wed, 2 Aug 2023 23:21:35 -0700 Subject: [PATCH 1/4] Improve mouse tracking --- .../components/ActivityRelationPicker.tsx | 2 +- .../BoardCardEditableFieldEditMode.tsx | 2 +- .../ui/board/components/BoardColumnMenu.tsx | 2 +- .../ui/board/components/BoardHeader.tsx | 28 ++++--- .../hooks/useRegisterCloseFieldHandlers.ts | 2 +- .../components/DropdownMenuContainer.tsx | 2 +- .../components/MultipleEntitySelect.tsx | 2 +- .../components/SingleEntitySelect.tsx | 2 +- .../src/modules/ui/modal/components/Modal.tsx | 2 +- .../right-drawer/components/RightDrawer.tsx | 4 +- .../ui/table/components/EntityTable.tsx | 2 +- .../ui/table/components/EntityTableHeader.tsx | 81 +++++++++++-------- .../hooks/useRegisterCloseCellHandlers.ts | 2 +- .../type/components/DateCellEdit.tsx | 2 +- .../table-header/components/TableHeader.tsx | 28 ++++--- front/src/modules/ui/top-bar/TopBar.tsx | 6 +- .../useListenClickOutsideArrayOfRef.test.tsx | 0 .../hooks/useListenClickOutside.ts | 0 .../pointer-event/hooks/useTrackPointer.ts | 69 ++++++++++++++++ front/src/pages/tasks/Tasks.tsx | 6 +- 20 files changed, 167 insertions(+), 77 deletions(-) rename front/src/modules/ui/utilities/{click-outside => pointer-event}/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx (100%) rename front/src/modules/ui/utilities/{click-outside => pointer-event}/hooks/useListenClickOutside.ts (100%) create mode 100644 front/src/modules/ui/utilities/pointer-event/hooks/useTrackPointer.ts diff --git a/front/src/modules/activities/components/ActivityRelationPicker.tsx b/front/src/modules/activities/components/ActivityRelationPicker.tsx index 49a5518e5006..9b82358a4ddc 100644 --- a/front/src/modules/activities/components/ActivityRelationPicker.tsx +++ b/front/src/modules/activities/components/ActivityRelationPicker.tsx @@ -14,7 +14,7 @@ import { PersonChip } from '@/people/components/PersonChip'; import { useFilteredSearchPeopleQuery } from '@/people/queries'; import { MultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; -import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside'; +import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; diff --git a/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldEditMode.tsx b/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldEditMode.tsx index 1123af4de168..52353e862431 100644 --- a/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldEditMode.tsx +++ b/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldEditMode.tsx @@ -2,7 +2,7 @@ import { ReactElement, useRef } from 'react'; import styled from '@emotion/styled'; import { overlayBackground } from '@/ui/theme/constants/effects'; -import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside'; +import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { BoardCardFieldHotkeyScope } from '../types/BoardCardFieldHotkeyScope'; diff --git a/front/src/modules/ui/board/components/BoardColumnMenu.tsx b/front/src/modules/ui/board/components/BoardColumnMenu.tsx index 345cc5271746..00fbc66607d5 100644 --- a/front/src/modules/ui/board/components/BoardColumnMenu.tsx +++ b/front/src/modules/ui/board/components/BoardColumnMenu.tsx @@ -7,7 +7,7 @@ import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMen import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton'; import { icon } from '@/ui/theme/constants/icon'; -import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside'; +import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { BoardColumnEditTitleMenu } from './BoardColumnEditTitleMenu'; diff --git a/front/src/modules/ui/board/components/BoardHeader.tsx b/front/src/modules/ui/board/components/BoardHeader.tsx index 553f663fbdda..e30f48b804e1 100644 --- a/front/src/modules/ui/board/components/BoardHeader.tsx +++ b/front/src/modules/ui/board/components/BoardHeader.tsx @@ -63,18 +63,22 @@ export function BoardHeader({ {viewName} } - rightComponents={[ - , - - isSortSelected={sorts.length > 0} - availableSorts={availableSorts || []} - onSortSelect={sortSelect} - HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} - />, - ]} + rightComponent={ + <> + + , + + isSortSelected={sorts.length > 0} + availableSorts={availableSorts || []} + onSortSelect={sortSelect} + HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} + /> + , + + } bottomComponent={ (null); @@ -67,38 +68,53 @@ export function EntityTableHeader({ onColumnResize, viewFields }: OwnProps) { const [resizedFieldId, setResizedFieldId] = useState(null); const [offset, setOffset] = useState(0); - const handleResizeHandlerDragStart = useCallback( - (event: PointerEvent, fieldId: string) => { - setIsResizing(true); - setResizedFieldId(fieldId); - setInitialPointerPositionX(event.clientX); + const handleResizeHandlerStart = useCallback( + (positionX: number, _: number) => { + setInitialPointerPositionX(positionX); }, - [setIsResizing, setResizedFieldId, setInitialPointerPositionX], + [], ); - const handleResizeHandlerDrag = useCallback( - (event: PointerEvent) => { - if (!isResizing || initialPointerPositionX === null) return; - - setOffset(event.clientX - initialPointerPositionX); + const handleResizeHandlerMove = useCallback( + (positionX: number, _positionY: number) => { + if (!initialPointerPositionX) return; + setOffset(positionX - (initialPointerPositionX ?? positionX)); }, - [isResizing, initialPointerPositionX], + [setOffset, initialPointerPositionX], ); - const handleResizeHandlerDragEnd = useCallback(() => { - setIsResizing(false); - if (!resizedFieldId) return; - - const nextWidth = Math.round( - Math.max(columnWidths[resizedFieldId] + offset, COLUMN_MIN_WIDTH), - ); - - if (nextWidth !== columnWidths[resizedFieldId]) { - onColumnResize(resizedFieldId, nextWidth); - } + const handleResizeHandlerEnd = useCallback( + (_positionX: number, _positionY: number) => { + if (!resizedFieldId) return; + const nextWidth = Math.round( + Math.max(columnWidths[resizedFieldId] + offset, COLUMN_MIN_WIDTH), + ); + + if (nextWidth !== columnWidths[resizedFieldId]) { + columnWidths[resizedFieldId] = nextWidth; + + onColumnResize(resizedFieldId, nextWidth); + } + setOffset(0); + setInitialPointerPositionX(null); + setResizedFieldId(null); + }, + [ + resizedFieldId, + columnWidths, + offset, + setOffset, + setResizedFieldId, + onColumnResize, + ], + ); - setOffset(0); - }, [resizedFieldId, columnWidths, offset, onColumnResize]); + useTrackPointer({ + shouldTrackPointer: resizedFieldId !== null, + onMouseDown: handleResizeHandlerStart, + onMouseMove: handleResizeHandlerMove, + onMouseUp: handleResizeHandlerEnd, + }); return ( @@ -116,7 +132,7 @@ export function EntityTableHeader({ onColumnResize, viewFields }: OwnProps) { {viewFields.map((viewField) => ( - handleResizeHandlerDragStart(event, viewField.id) - } - onPointerMove={handleResizeHandlerDrag} - onPointerOut={handleResizeHandlerDragEnd} - onPointerUp={handleResizeHandlerDragEnd} + onPointerDown={() => { + setResizedFieldId(viewField.id); + }} /> ))} diff --git a/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts b/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts index cfbc7d710dff..f27160263279 100644 --- a/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts +++ b/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts @@ -1,5 +1,5 @@ -import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; diff --git a/front/src/modules/ui/table/editable-cell/type/components/DateCellEdit.tsx b/front/src/modules/ui/table/editable-cell/type/components/DateCellEdit.tsx index 01491b01926d..7510192b323b 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/DateCellEdit.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/DateCellEdit.tsx @@ -4,8 +4,8 @@ import { Key } from 'ts-key-enum'; import { DateInputEdit } from '@/ui/input/date/components/DateInputEdit'; import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope'; -import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useEditableCell } from '../../hooks/useEditableCell'; diff --git a/front/src/modules/ui/table/table-header/components/TableHeader.tsx b/front/src/modules/ui/table/table-header/components/TableHeader.tsx index f60d56d7524c..6f676fe0220b 100644 --- a/front/src/modules/ui/table/table-header/components/TableHeader.tsx +++ b/front/src/modules/ui/table/table-header/components/TableHeader.tsx @@ -64,18 +64,22 @@ export function TableHeader({ } displayBottomBorder={false} - rightComponents={[ - , - - isSortSelected={sorts.length > 0} - availableSorts={availableSorts || []} - onSortSelect={sortSelect} - HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} - />, - ]} + rightComponent={ + <> + + , + + isSortSelected={sorts.length > 0} + availableSorts={availableSorts || []} + onSortSelect={sortSelect} + HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} + /> + , + + } bottomComponent={ {leftComponent} - {rightComponents} + {rightComponent} {bottomComponent} diff --git a/front/src/modules/ui/utilities/click-outside/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx b/front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx similarity index 100% rename from front/src/modules/ui/utilities/click-outside/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx rename to front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx diff --git a/front/src/modules/ui/utilities/click-outside/hooks/useListenClickOutside.ts b/front/src/modules/ui/utilities/pointer-event/hooks/useListenClickOutside.ts similarity index 100% rename from front/src/modules/ui/utilities/click-outside/hooks/useListenClickOutside.ts rename to front/src/modules/ui/utilities/pointer-event/hooks/useListenClickOutside.ts diff --git a/front/src/modules/ui/utilities/pointer-event/hooks/useTrackPointer.ts b/front/src/modules/ui/utilities/pointer-event/hooks/useTrackPointer.ts new file mode 100644 index 000000000000..9d3d48cc643f --- /dev/null +++ b/front/src/modules/ui/utilities/pointer-event/hooks/useTrackPointer.ts @@ -0,0 +1,69 @@ +import { useCallback, useEffect } from 'react'; + +type MouseMoveListener = (positionX: number, positionY: number) => void; +type MouseDownListener = (positionX: number, positionY: number) => void; +type MouseUpListener = (positionX: number, positionY: number) => void; + +export function useTrackPointer({ + shouldTrackPointer = true, + onMouseMove, + onMouseDown, + onMouseUp, +}: { + shouldTrackPointer?: boolean; + onMouseMove?: MouseMoveListener; + onMouseDown?: MouseDownListener; + onMouseUp?: MouseUpListener; +}) { + const extractPosition = useCallback((event: MouseEvent | TouchEvent) => { + const clientX = + 'clientX' in event ? event.clientX : event.changedTouches[0].clientX; + const clientY = + 'clientY' in event ? event.clientY : event.changedTouches[0].clientY; + + return { clientX, clientY }; + }, []); + + const onInternalMouseMove = useCallback( + (event: MouseEvent | TouchEvent) => { + const { clientX, clientY } = extractPosition(event); + onMouseMove?.(clientX, clientY); + }, + [onMouseMove, extractPosition], + ); + + const onInternalMouseDown = useCallback( + (event: MouseEvent | TouchEvent) => { + const { clientX, clientY } = extractPosition(event); + onMouseDown?.(clientX, clientY); + }, + [onMouseDown, extractPosition], + ); + + const onInternalMouseUp = useCallback( + (event: MouseEvent | TouchEvent) => { + const { clientX, clientY } = extractPosition(event); + onMouseUp?.(clientX, clientY); + }, + [onMouseUp, extractPosition], + ); + + useEffect(() => { + if (shouldTrackPointer) { + document.addEventListener('mousemove', onInternalMouseMove); + document.addEventListener('mousedown', onInternalMouseDown); + document.addEventListener('mouseup', onInternalMouseUp); + + return () => { + document.removeEventListener('mousemove', onInternalMouseMove); + document.removeEventListener('mousedown', onInternalMouseDown); + document.removeEventListener('mouseup', onInternalMouseUp); + }; + } + }, [ + shouldTrackPointer, + onInternalMouseMove, + onInternalMouseDown, + onInternalMouseUp, + ]); +} diff --git a/front/src/pages/tasks/Tasks.tsx b/front/src/pages/tasks/Tasks.tsx index 24b164c139b0..e9b678bcf64c 100644 --- a/front/src/pages/tasks/Tasks.tsx +++ b/front/src/pages/tasks/Tasks.tsx @@ -54,13 +54,13 @@ export function Tasks() { } - rightComponents={[ + rightComponent={ , - ]} + /> + } /> From f5baeb1e4dcb40e3f874bc220211099079c7672e Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Wed, 2 Aug 2023 23:31:13 -0700 Subject: [PATCH 2/4] Fix lint --- .../modules/activities/components/ActivityRelationPicker.tsx | 2 +- .../card-field/components/BoardCardEditableFieldEditMode.tsx | 2 +- .../modules/ui/utilities/pointer-event/hooks/useTrackPointer.ts | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/front/src/modules/activities/components/ActivityRelationPicker.tsx b/front/src/modules/activities/components/ActivityRelationPicker.tsx index 9b82358a4ddc..2a9a6d4071bf 100644 --- a/front/src/modules/activities/components/ActivityRelationPicker.tsx +++ b/front/src/modules/activities/components/ActivityRelationPicker.tsx @@ -14,9 +14,9 @@ import { PersonChip } from '@/people/components/PersonChip'; import { useFilteredSearchPeopleQuery } from '@/people/queries'; import { MultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; -import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { Activity, ActivityTarget, CommentableType } from '~/generated/graphql'; import { assertNotNull } from '~/utils/assert'; diff --git a/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldEditMode.tsx b/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldEditMode.tsx index 52353e862431..180091a13778 100644 --- a/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldEditMode.tsx +++ b/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldEditMode.tsx @@ -2,8 +2,8 @@ import { ReactElement, useRef } from 'react'; import styled from '@emotion/styled'; import { overlayBackground } from '@/ui/theme/constants/effects'; -import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { BoardCardFieldHotkeyScope } from '../types/BoardCardFieldHotkeyScope'; diff --git a/front/src/modules/ui/utilities/pointer-event/hooks/useTrackPointer.ts b/front/src/modules/ui/utilities/pointer-event/hooks/useTrackPointer.ts index 9d3d48cc643f..782e49793a73 100644 --- a/front/src/modules/ui/utilities/pointer-event/hooks/useTrackPointer.ts +++ b/front/src/modules/ui/utilities/pointer-event/hooks/useTrackPointer.ts @@ -50,11 +50,13 @@ export function useTrackPointer({ useEffect(() => { if (shouldTrackPointer) { + console.log('coucou'); document.addEventListener('mousemove', onInternalMouseMove); document.addEventListener('mousedown', onInternalMouseDown); document.addEventListener('mouseup', onInternalMouseUp); return () => { + console.log('coucou2'); document.removeEventListener('mousemove', onInternalMouseMove); document.removeEventListener('mousedown', onInternalMouseDown); document.removeEventListener('mouseup', onInternalMouseUp); From dfa1bcb86c90d8c113bc440ed9603066d80e5768 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Wed, 2 Aug 2023 23:44:30 -0700 Subject: [PATCH 3/4] Fix regression on Filters --- front/src/modules/ui/board/components/BoardHeader.tsx | 2 -- .../modules/ui/table/table-header/components/TableHeader.tsx | 2 -- 2 files changed, 4 deletions(-) diff --git a/front/src/modules/ui/board/components/BoardHeader.tsx b/front/src/modules/ui/board/components/BoardHeader.tsx index e30f48b804e1..9aae9d00afac 100644 --- a/front/src/modules/ui/board/components/BoardHeader.tsx +++ b/front/src/modules/ui/board/components/BoardHeader.tsx @@ -69,14 +69,12 @@ export function BoardHeader({ context={context} HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} /> - , isSortSelected={sorts.length > 0} availableSorts={availableSorts || []} onSortSelect={sortSelect} HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} /> - , } bottomComponent={ diff --git a/front/src/modules/ui/table/table-header/components/TableHeader.tsx b/front/src/modules/ui/table/table-header/components/TableHeader.tsx index 6f676fe0220b..dc17b0773725 100644 --- a/front/src/modules/ui/table/table-header/components/TableHeader.tsx +++ b/front/src/modules/ui/table/table-header/components/TableHeader.tsx @@ -70,14 +70,12 @@ export function TableHeader({ context={TableContext} HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} /> - , isSortSelected={sorts.length > 0} availableSorts={availableSorts || []} onSortSelect={sortSelect} HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} /> - , } bottomComponent={ From 295423b6fa7ccb11c1f74d2e6831402b81b9c583 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Thu, 3 Aug 2023 10:18:55 -0700 Subject: [PATCH 4/4] Fix according to review --- .../table/components/CompanyTableMockData.tsx | 4 +- .../ui/table/components/EntityTable.tsx | 42 ++------- .../ui/table/components/EntityTableCell.tsx | 9 +- .../ui/table/components/EntityTableHeader.tsx | 89 +++++++++++-------- .../ui/table/components/EntityTableRow.tsx | 4 +- .../components/GenericEntityTableData.tsx | 1 - .../src/modules/ui/table/hooks/useLoadView.ts | 4 +- .../ui/table/states/resizeFieldOffsetState.ts | 6 ++ .../ui/table/states/viewFieldsState.ts | 6 +- .../pointer-event/hooks/useTrackPointer.ts | 12 +-- 10 files changed, 78 insertions(+), 99 deletions(-) create mode 100644 front/src/modules/ui/table/states/resizeFieldOffsetState.ts diff --git a/front/src/modules/companies/table/components/CompanyTableMockData.tsx b/front/src/modules/companies/table/components/CompanyTableMockData.tsx index 420501eacd96..5cd168665e6a 100644 --- a/front/src/modules/companies/table/components/CompanyTableMockData.tsx +++ b/front/src/modules/companies/table/components/CompanyTableMockData.tsx @@ -3,7 +3,7 @@ import { useSetRecoilState } from 'recoil'; import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData'; import { entityTableDimensionsState } from '@/ui/table/states/entityTableDimensionsState'; -import { viewFieldsFamilyState } from '@/ui/table/states/viewFieldsState'; +import { viewFieldsState } from '@/ui/table/states/viewFieldsState'; import { companyViewFields } from '../../constants/companyViewFields'; @@ -13,7 +13,7 @@ export function CompanyTableMockData() { const setEntityTableDimensions = useSetRecoilState( entityTableDimensionsState, ); - const setViewFields = useSetRecoilState(viewFieldsFamilyState); + const setViewFields = useSetRecoilState(viewFieldsState); const setEntityTableData = useSetEntityTableData(); setEntityTableData(mockedCompaniesData, []); diff --git a/front/src/modules/ui/table/components/EntityTable.tsx b/front/src/modules/ui/table/components/EntityTable.tsx index 32109f337d4e..c0fb0051c6b4 100644 --- a/front/src/modules/ui/table/components/EntityTable.tsx +++ b/front/src/modules/ui/table/components/EntityTable.tsx @@ -1,15 +1,12 @@ -import { useCallback, useRef } from 'react'; +import { useRef } from 'react'; import styled from '@emotion/styled'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; -import { useUpdateViewFieldMutation } from '~/generated/graphql'; import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus'; import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus'; import { EntityUpdateMutationHookContext } from '../states/EntityUpdateMutationHookContext'; -import { viewFieldsFamilyState } from '../states/viewFieldsState'; import { TableHeader } from '../table-header/components/TableHeader'; import { EntityTableBody } from './EntityTableBody'; @@ -102,11 +99,6 @@ export function EntityTable({ onSortsUpdate, useUpdateEntityMutation, }: OwnProps) { - const viewFields = useRecoilValue(viewFieldsFamilyState); - const setViewFields = useSetRecoilState(viewFieldsFamilyState); - - const [updateViewFieldMutation] = useUpdateViewFieldMutation(); - const tableBodyRef = useRef(null); useMapKeyboardToSoftFocus(); @@ -120,25 +112,6 @@ export function EntityTable({ }, }); - const handleColumnResize = useCallback( - (resizedFieldId: string, width: number) => { - setViewFields((previousViewFields) => - previousViewFields.map((viewField) => - viewField.id === resizedFieldId - ? { ...viewField, columnSize: width } - : viewField, - ), - ); - updateViewFieldMutation({ - variables: { - data: { sizeInPx: width }, - where: { id: resizedFieldId }, - }, - }); - }, - [setViewFields, updateViewFieldMutation], - ); - return ( @@ -150,15 +123,10 @@ export function EntityTable({ onSortsUpdate={onSortsUpdate} /> - {viewFields.length > 0 && ( - - - - - )} + + + + diff --git a/front/src/modules/ui/table/components/EntityTableCell.tsx b/front/src/modules/ui/table/components/EntityTableCell.tsx index ba5882be5aba..a68a06e056b1 100644 --- a/front/src/modules/ui/table/components/EntityTableCell.tsx +++ b/front/src/modules/ui/table/components/EntityTableCell.tsx @@ -34,14 +34,7 @@ export function EntityTableCell({ cellIndex }: { cellIndex: number }) { return ( - handleContextMenu(event)} - style={{ - width: viewField.columnSize, - minWidth: viewField.columnSize, - maxWidth: viewField.columnSize, - }} - > + handleContextMenu(event)}> diff --git a/front/src/modules/ui/table/components/EntityTableHeader.tsx b/front/src/modules/ui/table/components/EntityTableHeader.tsx index 568b84ff6b41..58d30d8f74a3 100644 --- a/front/src/modules/ui/table/components/EntityTableHeader.tsx +++ b/front/src/modules/ui/table/components/EntityTableHeader.tsx @@ -1,12 +1,17 @@ import { useCallback, useMemo, useState } from 'react'; import styled from '@emotion/styled'; +import { + useRecoilCallback, + useRecoilState, + useRecoilValue, + useSetRecoilState, +} from 'recoil'; import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer'; +import { useUpdateViewFieldMutation } from '~/generated/graphql'; -import type { - ViewFieldDefinition, - ViewFieldMetadata, -} from '../types/ViewField'; +import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState'; +import { viewFieldsState } from '../states/viewFieldsState'; import { ColumnHead } from './ColumnHead'; import { SelectAllCheckbox } from './SelectAllCheckbox'; @@ -44,12 +49,11 @@ const StyledResizeHandler = styled.div` z-index: 1; `; -type OwnProps = { - onColumnResize: (resizedFieldId: string, width: number) => void; - viewFields: ViewFieldDefinition[]; -}; +export function EntityTableHeader() { + const viewFields = useRecoilValue(viewFieldsState); + const setViewFields = useSetRecoilState(viewFieldsState); -export function EntityTableHeader({ onColumnResize, viewFields }: OwnProps) { + const [updateViewFieldMutation] = useUpdateViewFieldMutation(); const columnWidths = useMemo( () => viewFields.reduce>( @@ -66,7 +70,26 @@ export function EntityTableHeader({ onColumnResize, viewFields }: OwnProps) { >(null); const [resizedFieldId, setResizedFieldId] = useState(null); - const [offset, setOffset] = useState(0); + const [offset, setOffset] = useRecoilState(resizeFieldOffsetState); + + const handleColumnResize = useCallback( + (resizedFieldId: string, width: number) => { + setViewFields((previousViewFields) => + previousViewFields.map((viewField) => + viewField.id === resizedFieldId + ? { ...viewField, columnSize: width } + : viewField, + ), + ); + updateViewFieldMutation({ + variables: { + data: { sizeInPx: width }, + where: { id: resizedFieldId }, + }, + }); + }, + [setViewFields, updateViewFieldMutation], + ); const handleResizeHandlerStart = useCallback( (positionX: number, _: number) => { @@ -78,35 +101,31 @@ export function EntityTableHeader({ onColumnResize, viewFields }: OwnProps) { const handleResizeHandlerMove = useCallback( (positionX: number, _positionY: number) => { if (!initialPointerPositionX) return; - setOffset(positionX - (initialPointerPositionX ?? positionX)); + setOffset(positionX - initialPointerPositionX); }, [setOffset, initialPointerPositionX], ); - const handleResizeHandlerEnd = useCallback( - (_positionX: number, _positionY: number) => { - if (!resizedFieldId) return; - const nextWidth = Math.round( - Math.max(columnWidths[resizedFieldId] + offset, COLUMN_MIN_WIDTH), - ); - - if (nextWidth !== columnWidths[resizedFieldId]) { - columnWidths[resizedFieldId] = nextWidth; - - onColumnResize(resizedFieldId, nextWidth); - } - setOffset(0); - setInitialPointerPositionX(null); - setResizedFieldId(null); - }, - [ - resizedFieldId, - columnWidths, - offset, - setOffset, - setResizedFieldId, - onColumnResize, - ], + const handleResizeHandlerEnd = useRecoilCallback( + ({ snapshot, set }) => + (_positionX: number, _positionY: number) => { + if (!resizedFieldId) return; + const nextWidth = Math.round( + Math.max( + columnWidths[resizedFieldId] + + snapshot.getLoadable(resizeFieldOffsetState).valueOrThrow(), + COLUMN_MIN_WIDTH, + ), + ); + + if (nextWidth !== columnWidths[resizedFieldId]) { + handleColumnResize(resizedFieldId, nextWidth); + } + set(resizeFieldOffsetState, 0); + setInitialPointerPositionX(null); + setResizedFieldId(null); + }, + [resizedFieldId, columnWidths, setResizedFieldId, handleColumnResize], ); useTrackPointer({ diff --git a/front/src/modules/ui/table/components/EntityTableRow.tsx b/front/src/modules/ui/table/components/EntityTableRow.tsx index d01b14de22a7..8a566221cb17 100644 --- a/front/src/modules/ui/table/components/EntityTableRow.tsx +++ b/front/src/modules/ui/table/components/EntityTableRow.tsx @@ -2,7 +2,7 @@ import styled from '@emotion/styled'; import { useRecoilValue } from 'recoil'; import { ViewFieldContext } from '../states/ViewFieldContext'; -import { viewFieldsFamilyState } from '../states/viewFieldsState'; +import { viewFieldsState } from '../states/viewFieldsState'; import { CheckboxCell } from './CheckboxCell'; import { EntityTableCell } from './EntityTableCell'; @@ -13,7 +13,7 @@ const StyledRow = styled.tr<{ selected: boolean }>` `; export function EntityTableRow({ rowId }: { rowId: string }) { - const viewFields = useRecoilValue(viewFieldsFamilyState); + const viewFields = useRecoilValue(viewFieldsState); return ( diff --git a/front/src/modules/ui/table/components/GenericEntityTableData.tsx b/front/src/modules/ui/table/components/GenericEntityTableData.tsx index 2c4be00ab56e..c6fbebb2d1d2 100644 --- a/front/src/modules/ui/table/components/GenericEntityTableData.tsx +++ b/front/src/modules/ui/table/components/GenericEntityTableData.tsx @@ -33,7 +33,6 @@ export function GenericEntityTableData({ variables: { orderBy, where: whereFilters }, onCompleted: (data: any) => { const entities = data[getRequestResultKey] ?? []; - setEntityTableData(entities, filterDefinitionArray); }, }); diff --git a/front/src/modules/ui/table/hooks/useLoadView.ts b/front/src/modules/ui/table/hooks/useLoadView.ts index 6d1b38e60505..df8381ef0c1b 100644 --- a/front/src/modules/ui/table/hooks/useLoadView.ts +++ b/front/src/modules/ui/table/hooks/useLoadView.ts @@ -9,7 +9,7 @@ import { } from '~/generated/graphql'; import { entityTableDimensionsState } from '../states/entityTableDimensionsState'; -import { viewFieldsFamilyState } from '../states/viewFieldsState'; +import { viewFieldsState } from '../states/viewFieldsState'; import { ViewFieldDefinition, ViewFieldMetadata, @@ -32,7 +32,7 @@ export const useLoadView = ({ const setEntityTableDimensions = useSetRecoilState( entityTableDimensionsState, ); - const setViewFields = useSetRecoilState(viewFieldsFamilyState); + const setViewFields = useSetRecoilState(viewFieldsState); const [createViewFieldsMutation] = useCreateViewFieldsMutation(); diff --git a/front/src/modules/ui/table/states/resizeFieldOffsetState.ts b/front/src/modules/ui/table/states/resizeFieldOffsetState.ts new file mode 100644 index 000000000000..847ebc1c774c --- /dev/null +++ b/front/src/modules/ui/table/states/resizeFieldOffsetState.ts @@ -0,0 +1,6 @@ +import { atom } from 'recoil'; + +export const resizeFieldOffsetState = atom({ + key: 'resizeFieldOffsetState', + default: 0, +}); diff --git a/front/src/modules/ui/table/states/viewFieldsState.ts b/front/src/modules/ui/table/states/viewFieldsState.ts index b1f7a73d0ce0..d78ddc5eee8d 100644 --- a/front/src/modules/ui/table/states/viewFieldsState.ts +++ b/front/src/modules/ui/table/states/viewFieldsState.ts @@ -2,9 +2,7 @@ import { atom } from 'recoil'; import { ViewFieldDefinition, ViewFieldMetadata } from '../types/ViewField'; -export const viewFieldsFamilyState = atom< - ViewFieldDefinition[] ->({ - key: 'viewFieldsFamilyState', +export const viewFieldsState = atom[]>({ + key: 'viewFieldsState', default: [], }); diff --git a/front/src/modules/ui/utilities/pointer-event/hooks/useTrackPointer.ts b/front/src/modules/ui/utilities/pointer-event/hooks/useTrackPointer.ts index 782e49793a73..c3e3f790b43e 100644 --- a/front/src/modules/ui/utilities/pointer-event/hooks/useTrackPointer.ts +++ b/front/src/modules/ui/utilities/pointer-event/hooks/useTrackPointer.ts @@ -1,8 +1,6 @@ import { useCallback, useEffect } from 'react'; -type MouseMoveListener = (positionX: number, positionY: number) => void; -type MouseDownListener = (positionX: number, positionY: number) => void; -type MouseUpListener = (positionX: number, positionY: number) => void; +type MouseListener = (positionX: number, positionY: number) => void; export function useTrackPointer({ shouldTrackPointer = true, @@ -11,9 +9,9 @@ export function useTrackPointer({ onMouseUp, }: { shouldTrackPointer?: boolean; - onMouseMove?: MouseMoveListener; - onMouseDown?: MouseDownListener; - onMouseUp?: MouseUpListener; + onMouseMove?: MouseListener; + onMouseDown?: MouseListener; + onMouseUp?: MouseListener; }) { const extractPosition = useCallback((event: MouseEvent | TouchEvent) => { const clientX = @@ -50,13 +48,11 @@ export function useTrackPointer({ useEffect(() => { if (shouldTrackPointer) { - console.log('coucou'); document.addEventListener('mousemove', onInternalMouseMove); document.addEventListener('mousedown', onInternalMouseDown); document.addEventListener('mouseup', onInternalMouseUp); return () => { - console.log('coucou2'); document.removeEventListener('mousemove', onInternalMouseMove); document.removeEventListener('mousedown', onInternalMouseDown); document.removeEventListener('mouseup', onInternalMouseUp);