Refactor/context and scopes (#1602)

* Put onImport in a context

* Refactored RecoilScopeContexts

* Refactored naming

* Fix tests

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-09-15 21:51:46 +02:00
committed by GitHub
parent d07474ece7
commit 0a7a0ac6cb
102 changed files with 639 additions and 552 deletions

View File

@ -1,12 +1,13 @@
import { useContext } from 'react';
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { BoardContext } from '@/companies/states/contexts/BoardContext';
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { ViewBar, type ViewBarProps } from '@/ui/view-bar/components/ViewBar';
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
import { ViewBar } from '@/ui/view-bar/components/ViewBar';
import { ViewBarContext } from '@/ui/view-bar/contexts/ViewBarContext';
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
@ -22,30 +23,33 @@ import { BoardOptionsDropdown } from './BoardOptionsDropdown';
export type BoardHeaderProps = {
className?: string;
onStageAdd?: (boardColumn: BoardColumnDefinition) => void;
} & Pick<ViewBarProps, 'scopeContext'>;
};
export function BoardHeader({
className,
onStageAdd,
scopeContext,
}: BoardHeaderProps) {
export function BoardHeader({ className, onStageAdd }: BoardHeaderProps) {
const { onCurrentViewSubmit, ...viewBarContextProps } =
useContext(ViewBarContext);
const tableScopeId = useContextScopeId(scopeContext);
const BoardRecoilScopeContext =
useContext(BoardContext).BoardRecoilScopeContext;
const ViewBarRecoilScopeContext =
useContext(ViewBarContext).ViewBarRecoilScopeContext;
const boardRecoilScopeId = useRecoilScopeId(BoardRecoilScopeContext);
const currentViewId = useRecoilScopedValue(
currentViewIdScopedState,
scopeContext,
ViewBarRecoilScopeContext,
);
const canPersistBoardCardFields = useRecoilValue(
canPersistBoardCardFieldsScopedFamilySelector([
tableScopeId,
currentViewId,
]),
canPersistBoardCardFieldsScopedFamilySelector({
recoilScopeId: boardRecoilScopeId,
viewId: currentViewId,
}),
);
const [boardCardFields, setBoardCardFields] = useRecoilScopedState(
boardCardFieldsScopedState,
scopeContext,
BoardRecoilScopeContext,
);
const [savedBoardCardFields, setSavedBoardCardFields] = useRecoilState(
savedBoardCardFieldsFamilyState(currentViewId),
@ -59,9 +63,12 @@ export function BoardHeader({
const savedBoardCardFields = await snapshot.getPromise(
savedBoardCardFieldsFamilyState(viewId),
);
set(boardCardFieldsScopedState(tableScopeId), savedBoardCardFields);
set(
boardCardFieldsScopedState(boardRecoilScopeId),
savedBoardCardFields,
);
},
[tableScopeId],
[boardRecoilScopeId],
);
const handleCurrentViewSubmit = async () => {
@ -73,7 +80,7 @@ export function BoardHeader({
};
return (
<RecoilScope SpecificContext={DropdownRecoilScopeContext}>
<RecoilScope CustomRecoilScopeContext={DropdownRecoilScopeContext}>
<ViewBarContext.Provider
value={{
...viewBarContextProps,
@ -89,11 +96,9 @@ export function BoardHeader({
<BoardOptionsDropdown
customHotkeyScope={{ scope: BoardOptionsHotkeyScope.Dropdown }}
onStageAdd={onStageAdd}
scopeContext={scopeContext}
/>
}
optionsDropdownKey={BoardOptionsDropdownKey}
scopeContext={scopeContext}
/>
</ViewBarContext.Provider>
</RecoilScope>

View File

@ -10,13 +10,12 @@ import {
type BoardOptionsDropdownProps = Pick<
BoardOptionsDropdownContentProps,
'customHotkeyScope' | 'onStageAdd' | 'scopeContext'
'customHotkeyScope' | 'onStageAdd'
>;
export function BoardOptionsDropdown({
customHotkeyScope,
onStageAdd,
scopeContext,
}: BoardOptionsDropdownProps) {
return (
<DropdownButton
@ -25,7 +24,6 @@ export function BoardOptionsDropdown({
<BoardOptionsDropdownContent
customHotkeyScope={customHotkeyScope}
onStageAdd={onStageAdd}
scopeContext={scopeContext}
/>
}
dropdownHotkeyScope={customHotkeyScope}

View File

@ -1,10 +1,11 @@
import { type Context, useRef, useState } from 'react';
import { useContext, useRef, useState } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
import { v4 } from 'uuid';
import { BoardContext } from '@/companies/states/contexts/BoardContext';
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
@ -23,8 +24,8 @@ import { MenuItemNavigate } from '@/ui/menu-item/components/MenuItemNavigate';
import { ThemeColor } from '@/ui/theme/constants/colors';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
import { ViewFieldsVisibilityDropdownSection } from '@/ui/view-bar/components/ViewFieldsVisibilityDropdownSection';
import { useUpsertView } from '@/ui/view-bar/hooks/useUpsertView';
import { viewsByIdScopedSelector } from '@/ui/view-bar/states/selectors/viewsByIdScopedSelector';
@ -42,7 +43,6 @@ import { BoardOptionsDropdownKey } from '../types/BoardOptionsDropdownKey';
export type BoardOptionsDropdownContentProps = {
customHotkeyScope: HotkeyScope;
onStageAdd?: (boardColumn: BoardColumnDefinition) => void;
scopeContext: Context<string | null>;
};
const StyledIconSettings = styled(IconSettings)`
@ -61,10 +61,13 @@ type ColumnForCreate = {
export function BoardOptionsDropdownContent({
customHotkeyScope,
onStageAdd,
scopeContext,
}: BoardOptionsDropdownContentProps) {
const theme = useTheme();
const scopeId = useContextScopeId(scopeContext);
const BoardRecoilScopeContext =
useContext(BoardContext).BoardRecoilScopeContext;
const boardRecoilScopeId = useRecoilScopeId(BoardRecoilScopeContext);
const stageInputRef = useRef<HTMLInputElement>(null);
const viewEditInputRef = useRef<HTMLInputElement>(null);
@ -77,16 +80,19 @@ export function BoardOptionsDropdownContent({
const hiddenBoardCardFields = useRecoilScopedValue(
hiddenBoardCardFieldsScopedSelector,
scopeContext,
BoardRecoilScopeContext,
);
const hasHiddenFields = hiddenBoardCardFields.length > 0;
const visibleBoardCardFields = useRecoilScopedValue(
visibleBoardCardFieldsScopedSelector,
scopeContext,
BoardRecoilScopeContext,
);
const hasVisibleFields = visibleBoardCardFields.length > 0;
const viewsById = useRecoilScopedValue(viewsByIdScopedSelector, scopeContext);
const viewsById = useRecoilScopedValue(
viewsByIdScopedSelector,
BoardRecoilScopeContext, // TODO: replace with ViewBarRecoilScopeContext
);
const viewEditMode = useRecoilValue(viewEditModeState);
const handleStageSubmit = () => {
@ -107,13 +113,13 @@ export function BoardOptionsDropdownContent({
onStageAdd?.(columnToCreate);
};
const { upsertView } = useUpsertView({ scopeContext });
const { upsertView } = useUpsertView();
const handleViewNameSubmit = useRecoilCallback(
({ set, snapshot }) =>
async () => {
const boardCardFields = await snapshot.getPromise(
boardCardFieldsScopedState(scopeId),
boardCardFieldsScopedState(boardRecoilScopeId),
);
const isCreateMode = viewEditMode.mode === 'create';
const name = viewEditInputRef.current?.value;
@ -123,7 +129,7 @@ export function BoardOptionsDropdownContent({
set(savedBoardCardFieldsFamilyState(view.id), boardCardFields);
}
},
[scopeId, upsertView, viewEditMode.mode],
[boardRecoilScopeId, upsertView, viewEditMode.mode],
);
const resetMenu = () => setCurrentMenu(undefined);
@ -133,7 +139,7 @@ export function BoardOptionsDropdownContent({
setCurrentMenu(menu);
};
const { handleFieldVisibilityChange } = useBoardCardFields({ scopeContext });
const { handleFieldVisibilityChange } = useBoardCardFields();
const { closeDropdownButton } = useDropdownButton({
dropdownId: BoardOptionsDropdownKey,

View File

@ -1,4 +1,4 @@
import { type Context, useCallback, useRef } from 'react';
import { useCallback, useRef } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import styled from '@emotion/styled';
import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350
@ -35,7 +35,6 @@ export type EntityBoardProps = {
onColumnAdd?: (boardColumn: BoardColumnDefinition) => void;
onColumnDelete?: (boardColumnId: string) => void;
onEditColumnTitle: (columnId: string, title: string, color: string) => void;
scopeContext: Context<string | null>;
};
const StyledWrapper = styled.div`
@ -54,7 +53,6 @@ export function EntityBoard({
onColumnAdd,
onColumnDelete,
onEditColumnTitle,
scopeContext,
}: EntityBoardProps) {
const [boardColumns] = useRecoilState(boardColumnsState);
const setCardSelected = useSetCardSelected();
@ -130,14 +128,14 @@ export function EntityBoard({
return (boardColumns?.length ?? 0) > 0 ? (
<StyledWrapper>
<StyledBoardHeader onStageAdd={onColumnAdd} scopeContext={scopeContext} />
<StyledBoardHeader onStageAdd={onColumnAdd} />
<ScrollWrapper>
<StyledBoard ref={boardRef}>
<DragDropContext onDragEnd={onDragEnd}>
{sortedBoardColumns.map((column) => (
<BoardColumnIdContext.Provider value={column.id} key={column.id}>
<RecoilScope
SpecificContext={BoardColumnRecoilScopeContext}
CustomRecoilScopeContext={BoardColumnRecoilScopeContext}
key={column.id}
>
<EntityBoardColumn
@ -145,7 +143,6 @@ export function EntityBoard({
column={column}
onDelete={onColumnDelete}
onTitleEdit={onEditColumnTitle}
scopeContext={scopeContext}
/>
</RecoilScope>
</BoardColumnIdContext.Provider>

View File

@ -1,4 +1,3 @@
import type { Context } from 'react';
import { Draggable } from '@hello-pangea/dnd';
import { useSetRecoilState } from 'recoil';
@ -12,12 +11,10 @@ export function EntityBoardCard({
boardOptions,
cardId,
index,
scopeContext,
}: {
boardOptions: BoardOptions;
cardId: string;
index: number;
scopeContext: Context<string | null>;
}) {
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
@ -46,7 +43,7 @@ export function EntityBoardCard({
data-select-disable
onContextMenu={handleContextMenu}
>
{<boardOptions.CardComponent scopeContext={scopeContext} />}
{<boardOptions.CardComponent />}
</div>
)}
</Draggable>

View File

@ -1,4 +1,4 @@
import { type Context, useContext } from 'react';
import { useContext } from 'react';
import styled from '@emotion/styled';
import { Draggable, Droppable, DroppableProvided } from '@hello-pangea/dnd';
import { useRecoilValue } from 'recoil';
@ -52,13 +52,11 @@ export function EntityBoardColumn({
column,
onDelete,
onTitleEdit,
scopeContext,
}: {
boardOptions: BoardOptions;
column: BoardColumnDefinition;
onDelete?: (columnId: string) => void;
onTitleEdit: (columnId: string, title: string, color: string) => void;
scopeContext: Context<string | null>;
}) {
const boardColumnId = useContext(BoardColumnIdContext) ?? '';
@ -94,7 +92,6 @@ export function EntityBoardColumn({
index={index}
cardId={cardId}
boardOptions={boardOptions}
scopeContext={scopeContext}
/>
</BoardCardIdContext.Provider>
))}

View File

@ -1,5 +1,3 @@
import type { Context } from 'react';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
@ -10,18 +8,18 @@ import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoi
import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState';
import { boardCardFieldsByKeyScopedSelector } from '../states/selectors/boardCardFieldsByKeyScopedSelector';
export const useBoardCardFields = ({
scopeContext,
}: {
scopeContext: Context<string | null>;
}) => {
import { useBoardContext } from './useBoardContext';
export const useBoardCardFields = () => {
const { BoardRecoilScopeContext } = useBoardContext();
const [boardCardFields, setBoardCardFields] = useRecoilScopedState(
boardCardFieldsScopedState,
scopeContext,
BoardRecoilScopeContext,
);
const boardCardFieldsByKey = useRecoilScopedValue(
boardCardFieldsByKeyScopedSelector,
scopeContext,
BoardRecoilScopeContext,
);
const handleFieldVisibilityChange = (

View File

@ -0,0 +1,7 @@
import { useContext } from 'react';
import { BoardContext } from '@/companies/states/contexts/BoardContext';
export const useBoardContext = () => {
return useContext(BoardContext);
};

View File

@ -8,10 +8,16 @@ import { savedBoardCardFieldsFamilyState } from '../savedBoardCardFieldsFamilySt
export const canPersistBoardCardFieldsScopedFamilySelector = selectorFamily({
key: 'canPersistBoardCardFieldsScopedFamilySelector',
get:
([scopeId, viewId]: [string, string | undefined]) =>
({
recoilScopeId,
viewId,
}: {
recoilScopeId: string;
viewId: string | undefined;
}) =>
({ get }) =>
!isDeeplyEqual(
get(savedBoardCardFieldsFamilyState(viewId)),
get(boardCardFieldsScopedState(scopeId)),
get(boardCardFieldsScopedState(recoilScopeId)),
),
});

View File

@ -1,4 +1,4 @@
import type { ComponentType, Context } from 'react';
import type { ComponentType } from 'react';
import { FilterDefinitionByEntity } from '@/ui/view-bar/types/FilterDefinitionByEntity';
import { SortDefinition } from '@/ui/view-bar/types/SortDefinition';
@ -6,7 +6,7 @@ import { PipelineProgress } from '~/generated/graphql';
export type BoardOptions = {
newCardComponent: React.ReactNode;
CardComponent: ComponentType<{ scopeContext: Context<string | null> }>;
CardComponent: ComponentType;
filters: FilterDefinitionByEntity<PipelineProgress>[];
sorts: SortDefinition[];
};