Fix/table rerenders (#609)

* Fixed top bar rerenders

* Fixed rerender on editable cell

* Fix lint

* asd

* Fix

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-07-12 05:51:24 +02:00
committed by GitHub
parent b5de2abd48
commit 5e0e449e4c
17 changed files with 211 additions and 126 deletions

View File

@ -27,15 +27,15 @@ root.render(
<ApolloProvider>
<AppThemeProvider>
<StrictMode>
<HotkeysProvider initiallyActiveScopes={INITIAL_HOTKEYS_SCOPES}>
<BrowserRouter>
<UserProvider>
<ClientConfigProvider>
<BrowserRouter>
<UserProvider>
<ClientConfigProvider>
<HotkeysProvider initiallyActiveScopes={INITIAL_HOTKEYS_SCOPES}>
<App />
</ClientConfigProvider>
</UserProvider>
</BrowserRouter>
</HotkeysProvider>
</HotkeysProvider>
</ClientConfigProvider>
</UserProvider>
</BrowserRouter>
</StrictMode>
</AppThemeProvider>
</ApolloProvider>

View File

@ -15,5 +15,11 @@ export function useGoToHotkeys(key: Keys, location: string) {
navigate(location);
},
InternalHotkeysScope.Goto,
{
enableOnContentEditable: true,
enableOnFormTags: true,
preventDefault: true,
},
[navigate],
);
}

View File

@ -14,6 +14,7 @@ export function useSequenceHotkeys(
enableOnFormTags: true,
preventDefault: true,
},
deps: any[] = [],
) {
const [pendingHotkey, setPendingHotkey] = useRecoilState(pendingHotkeyState);
@ -23,7 +24,7 @@ export function useSequenceHotkeys(
setPendingHotkey(firstKey);
},
{ ...options, scopes: [scope] },
[pendingHotkey],
[setPendingHotkey],
);
useHotkeys(
@ -36,6 +37,6 @@ export function useSequenceHotkeys(
callback();
},
{ ...options, scopes: [scope] },
[pendingHotkey, setPendingHotkey],
[pendingHotkey, setPendingHotkey, ...deps],
);
}

View File

@ -6,6 +6,16 @@ import { DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES } from '../constants';
import { currentHotkeysScopeState } from '../states/internal/currentHotkeysScopeState';
import { CustomHotkeysScopes } from '../types/internal/CustomHotkeysScope';
function isCustomScopesEqual(
customScopesA: CustomHotkeysScopes | undefined,
customScopesB: CustomHotkeysScopes | undefined,
) {
return (
customScopesA?.commandMenu === customScopesB?.commandMenu &&
customScopesA?.goto === customScopesB?.goto
);
}
export function useSetHotkeysScope() {
return useRecoilCallback(
({ snapshot, set }) =>
@ -14,16 +24,6 @@ export function useSetHotkeysScope() {
currentHotkeysScopeState,
);
function isCustomScopesEqual(
customScopesA: CustomHotkeysScopes | undefined,
customScopesB: CustomHotkeysScopes | undefined,
) {
return (
customScopesA?.commandMenu === customScopesB?.commandMenu &&
customScopesA?.goto === customScopesB?.goto
);
}
if (currentHotkeysScope.scope === hotkeysScopeToSet) {
if (!isDefined(customScopes)) {
if (

View File

@ -2,12 +2,9 @@ import { ReactElement } from 'react';
import styled from '@emotion/styled';
import { HotkeysScope } from '@/hotkeys/types/internal/HotkeysScope';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useCurrentCellEditMode } from './hooks/useCurrentCellEditMode';
import { useEditableCell } from './hooks/useEditableCell';
import { useIsSoftFocusOnCurrentCell } from './hooks/useIsSoftFocusOnCurrentCell';
import { useSetSoftFocusOnCurrentCell } from './hooks/useSetSoftFocusOnCurrentCell';
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
import { EditableCellEditMode } from './EditableCellEditMode';
import { EditableCellSoftFocusMode } from './EditableCellSoftFocusMode';
@ -40,33 +37,10 @@ export function EditableCell({
}: OwnProps) {
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
const { openEditableCell } = useEditableCell();
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
// TODO: we might have silent problematic behavior because of the setTimeout in openEditableCell, investigate
// Maybe we could build a switchEditableCell to handle the case where we go from one cell to another.
// See https://github.com/twentyhq/twenty/issues/446
function handleOnClick() {
if (isCurrentCellInEditMode) {
return;
}
if (hasSoftFocus) {
openEditableCell(
editHotkeysScope ?? {
scope: InternalHotkeysScope.CellEditMode,
},
);
} else {
setSoftFocusOnCurrentCell();
}
}
return (
<CellBaseContainer onClick={handleOnClick}>
<CellBaseContainer>
{isCurrentCellInEditMode ? (
<EditableCellEditMode
editModeHorizontalAlign={editModeHorizontalAlign}

View File

@ -1,9 +1,9 @@
import styled from '@emotion/styled';
import { useIsSoftFocusOnCurrentCell } from './hooks/useIsSoftFocusOnCurrentCell';
import { useSetSoftFocusOnCurrentCell } from './hooks/useSetSoftFocusOnCurrentCell';
type Props = {
softFocus: boolean;
softFocus?: boolean;
};
export const EditableCellNormalModeOuterContainer = styled.div<Props>`
@ -35,10 +35,14 @@ export const EditableCellNormalModeInnerContainer = styled.div`
export function EditableCellDisplayMode({
children,
}: React.PropsWithChildren<unknown>) {
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
function handleOnClick() {
setSoftFocusOnCurrentCell();
}
return (
<EditableCellNormalModeOuterContainer softFocus={hasSoftFocus}>
<EditableCellNormalModeOuterContainer onClick={handleOnClick}>
<EditableCellNormalModeInnerContainer>
{children}
</EditableCellNormalModeInnerContainer>

View File

@ -6,25 +6,32 @@ import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysSc
import { isNonTextWritingKey } from '@/utils/hotkeys/isNonTextWritingKey';
import { useEditableCell } from './hooks/useEditableCell';
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
import {
EditableCellNormalModeInnerContainer,
EditableCellNormalModeOuterContainer,
} from './EditableCellDisplayMode';
export function EditableCellSoftFocusMode({
children,
editHotkeysScope,
}: React.PropsWithChildren<{ editHotkeysScope?: HotkeysScope }>) {
const { closeEditableCell, openEditableCell } = useEditableCell();
const { openEditableCell } = useEditableCell();
function openEditMode() {
openEditableCell(
editHotkeysScope ?? {
scope: InternalHotkeysScope.CellEditMode,
},
);
}
useScopedHotkeys(
'enter',
() => {
openEditableCell(
editHotkeysScope ?? {
scope: InternalHotkeysScope.CellEditMode,
},
);
openEditMode();
},
InternalHotkeysScope.TableSoftFocus,
[closeEditableCell, editHotkeysScope],
[openEditMode],
);
useScopedHotkeys(
@ -39,18 +46,27 @@ export function EditableCellSoftFocusMode({
return;
}
openEditableCell(
editHotkeysScope ?? {
scope: InternalHotkeysScope.CellEditMode,
},
);
openEditMode();
},
InternalHotkeysScope.TableSoftFocus,
[openEditableCell, editHotkeysScope],
[openEditMode],
{
preventDefault: false,
},
);
return <EditableCellDisplayMode>{children}</EditableCellDisplayMode>;
function handleClick() {
openEditMode();
}
return (
<EditableCellNormalModeOuterContainer
onClick={handleClick}
softFocus={true}
>
<EditableCellNormalModeInnerContainer>
{children}
</EditableCellNormalModeInnerContainer>
</EditableCellNormalModeOuterContainer>
);
}

View File

@ -1,5 +1,5 @@
import { useCallback, useMemo } from 'react';
import { useRecoilState } from 'recoil';
import { useMemo } from 'react';
import { useRecoilCallback } from 'recoil';
import { useSetHotkeysScope } from '@/hotkeys/hooks/useSetHotkeysScope';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
@ -14,6 +14,7 @@ import { CellPosition } from '@/ui/tables/types/CellPosition';
export function useSetSoftFocusOnCurrentCell() {
const setSoftFocusPosition = useSetSoftFocusPosition();
const [currentRowNumber] = useRecoilScopedState(
currentRowNumberScopedState,
RowContext,
@ -32,18 +33,17 @@ export function useSetSoftFocusOnCurrentCell() {
[currentColumnNumber, currentRowNumber],
);
const [, setIsSoftFocusActive] = useRecoilState(isSoftFocusActiveState);
const setHotkeysScope = useSetHotkeysScope();
return useCallback(() => {
setSoftFocusPosition(currentTablePosition);
setIsSoftFocusActive(true);
setHotkeysScope(InternalHotkeysScope.TableSoftFocus);
}, [
setSoftFocusPosition,
currentTablePosition,
setIsSoftFocusActive,
setHotkeysScope,
]);
return useRecoilCallback(
({ set }) =>
() => {
setSoftFocusPosition(currentTablePosition);
set(isSoftFocusActiveState, true);
setHotkeysScope(InternalHotkeysScope.TableSoftFocus);
},
[setHotkeysScope, currentTablePosition, setSoftFocusPosition],
);
}

View File

@ -0,0 +1,7 @@
import styled from '@emotion/styled';
export const FlexExpandingContainer = styled.div`
display: flex;
height: 100%;
width: 100%;
`;

View File

@ -1,9 +1,9 @@
import styled from '@emotion/styled';
import { ContentContainer } from './ContentContainer';
import { RightDrawerContainer } from './RightDrawerContainer';
type OwnProps = {
children: JSX.Element;
children: JSX.Element | JSX.Element[];
};
const StyledContainer = styled.div`
@ -15,7 +15,7 @@ const StyledContainer = styled.div`
export function NoTopBarContainer({ children }: OwnProps) {
return (
<StyledContainer>
<ContentContainer topMargin={16}>{children}</ContentContainer>
<RightDrawerContainer topMargin={16}>{children}</RightDrawerContainer>
</StyledContainer>
);
}

View File

@ -0,0 +1,43 @@
import styled from '@emotion/styled';
import { Panel } from '../Panel';
import { RightDrawer } from '../right-drawer/components/RightDrawer';
type OwnProps = {
children: JSX.Element | JSX.Element[];
topMargin?: number;
};
const StyledMainContainer = styled.div<{ topMargin: number }>`
background: ${({ theme }) => theme.background.noisy};
display: flex;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(2)};
height: calc(100% - ${(props) => props.topMargin}px);
padding-bottom: ${({ theme }) => theme.spacing(3)};
padding-right: ${({ theme }) => theme.spacing(3)};
width: calc(100% - ${({ theme }) => theme.spacing(3)});
`;
type LeftContainerProps = {
isRightDrawerOpen?: boolean;
};
const StyledLeftContainer = styled.div<LeftContainerProps>`
display: flex;
position: relative;
width: 100%;
`;
export function RightDrawerContainer({ children, topMargin }: OwnProps) {
return (
<StyledMainContainer topMargin={topMargin ?? 0}>
<StyledLeftContainer>
<Panel>{children}</Panel>
</StyledLeftContainer>
<RightDrawer />
</StyledMainContainer>
);
}

View File

@ -0,0 +1,15 @@
import styled from '@emotion/styled';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`;
export function VerticalFullWidthContainer({
children,
}: {
children: JSX.Element[];
}) {
return <StyledContainer>{children}</StyledContainer>;
}

View File

@ -1,12 +1,13 @@
import { ReactNode } from 'react';
import styled from '@emotion/styled';
import { TopBarHotkeys } from '../top-bar/TableTopBarHotkeys';
import { TOP_BAR_MIN_HEIGHT, TopBar } from '../top-bar/TopBar';
import { ContentContainer } from './ContentContainer';
import { RightDrawerContainer } from './RightDrawerContainer';
type OwnProps = {
children: JSX.Element;
children: JSX.Element | JSX.Element[];
title: string;
icon: ReactNode;
onAddButtonClick?: () => void;
@ -26,10 +27,11 @@ export function WithTopBarContainer({
}: OwnProps) {
return (
<StyledContainer>
<TopBarHotkeys onAddButtonClick={onAddButtonClick} />
<TopBar title={title} icon={icon} onAddButtonClick={onAddButtonClick} />
<ContentContainer topMargin={TOP_BAR_MIN_HEIGHT + 16 + 16}>
<RightDrawerContainer topMargin={TOP_BAR_MIN_HEIGHT + 16 + 16}>
{children}
</ContentContainer>
</RightDrawerContainer>
</StyledContainer>
);
}

View File

@ -0,0 +1,17 @@
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
type OwnProps = {
onAddButtonClick?: () => void;
};
export function TopBarHotkeys({ onAddButtonClick }: OwnProps) {
useScopedHotkeys(
'c',
() => onAddButtonClick?.(),
InternalHotkeysScope.Table,
[onAddButtonClick],
);
return <></>;
}

View File

@ -1,8 +1,6 @@
import { ReactNode } from 'react';
import styled from '@emotion/styled';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { IconPlus } from '@/ui/icons/index';
import NavCollapseButton from '../navbar/NavCollapseButton';
@ -51,8 +49,6 @@ type OwnProps = {
};
export function TopBar({ title, icon, onAddButtonClick }: OwnProps) {
useScopedHotkeys('c', () => onAddButtonClick?.(), InternalHotkeysScope.Table);
return (
<>
<TopBarContainer>

View File

@ -1,4 +1,4 @@
import { useRecoilValue } from 'recoil';
import { useRecoilCallback } from 'recoil';
import { useSetHotkeysScope } from '@/hotkeys/hooks/useSetHotkeysScope';
import { currentHotkeysScopeState } from '@/hotkeys/states/internal/currentHotkeysScopeState';
@ -11,29 +11,39 @@ import { useCloseCurrentCellInEditMode } from './useClearCellInEditMode';
import { useDisableSoftFocus } from './useDisableSoftFocus';
export function useLeaveTableFocus() {
const currentHotkeysScope = useRecoilValue(currentHotkeysScopeState);
const disableSoftFocus = useDisableSoftFocus();
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
const setHotkeysScope = useSetHotkeysScope();
const isSoftFocusActive = useRecoilValue(isSoftFocusActiveState);
const isSomeInputInEditMode = useRecoilValue(isSomeInputInEditModeState);
return useRecoilCallback(
({ snapshot }) =>
() => {
const isSoftFocusActive = snapshot
.getLoadable(isSoftFocusActiveState)
.valueOrThrow();
return async function leaveTableFocus() {
// TODO: replace with scope ancestor ?
if (!isSoftFocusActive && !isSomeInputInEditMode) {
return;
}
const isSomeInputInEditMode = snapshot
.getLoadable(isSomeInputInEditModeState)
.valueOrThrow();
if (currentHotkeysScope?.scope === InternalHotkeysScope.Table) {
return;
}
const currentHotkeysScope = snapshot
.getLoadable(currentHotkeysScopeState)
.valueOrThrow();
closeCurrentCellInEditMode();
disableSoftFocus();
if (!isSoftFocusActive && !isSomeInputInEditMode) {
return;
}
setHotkeysScope(InternalHotkeysScope.Table, { goto: true });
};
if (currentHotkeysScope?.scope === InternalHotkeysScope.Table) {
return;
}
closeCurrentCellInEditMode();
disableSoftFocus();
setHotkeysScope(InternalHotkeysScope.Table, { goto: true });
},
[setHotkeysScope, closeCurrentCellInEditMode, disableSoftFocus],
);
}

View File

@ -1,12 +1,12 @@
import { getOperationName } from '@apollo/client/utilities';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { v4 as uuidv4 } from 'uuid';
import { GET_PEOPLE } from '@/people/services';
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
import { EntityTableActionBar } from '@/ui/components/table/action-bar/EntityTableActionBar';
import { IconUser } from '@/ui/icons/index';
import { IconBuildingSkyscraper } from '@/ui/icons/index';
import { FlexExpandingContainer } from '@/ui/layout/containers/FlexExpandingContainer';
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
import { TableContext } from '@/ui/tables/states/TableContext';
import { useInsertPersonMutation } from '~/generated/graphql';
@ -15,12 +15,6 @@ import { TableActionBarButtonCreateCommentThreadPeople } from './table/TableActi
import { TableActionBarButtonDeletePeople } from './table/TableActionBarButtonDeletePeople';
import { PeopleTable } from './PeopleTable';
const StyledPeopleContainer = styled.div`
display: flex;
height: 100%;
width: 100%;
`;
export function People() {
const [insertPersonMutation] = useInsertPersonMutation();
@ -42,20 +36,20 @@ export function People() {
const theme = useTheme();
return (
<WithTopBarContainer
title="People"
icon={<IconUser size={theme.icon.size.md} />}
onAddButtonClick={handleAddButtonClick}
>
<RecoilScope SpecificContext={TableContext}>
<StyledPeopleContainer>
<RecoilScope SpecificContext={TableContext}>
<WithTopBarContainer
title="Companies"
icon={<IconBuildingSkyscraper size={theme.icon.size.md} />}
onAddButtonClick={handleAddButtonClick}
>
<FlexExpandingContainer>
<PeopleTable />
</StyledPeopleContainer>
</FlexExpandingContainer>
<EntityTableActionBar>
<TableActionBarButtonCreateCommentThreadPeople />
<TableActionBarButtonDeletePeople />
</EntityTableActionBar>
</RecoilScope>
</WithTopBarContainer>
</WithTopBarContainer>
</RecoilScope>
);
}