Fix/scope hotkeys (#581)

* WIP

* asd

* Fix

* Fix lint

* Removed console log

* asd

* Removed isDefined

* Fix/debounce company card onchange (#580)

* Add internal state and debounce for editable text card

* Use debounce for date fields too

* Update refetch

* Nit

* Removed comments

* Ménage

---------

Co-authored-by: Emilien Chauvet <emilien.chauvet.enpc@gmail.com>
This commit is contained in:
Lucas Bordeau
2023-07-11 03:53:46 +02:00
committed by GitHub
parent 1c8aaff39d
commit 5f98b70c6a
71 changed files with 581 additions and 509 deletions

View File

@ -1,4 +1,3 @@
import { useBoardCardField } from '@/ui/board-card-field/hooks/useBoardCardField';
import { InplaceInputDateEditMode } from '@/ui/inplace-inputs/components/InplaceInputDateEditMode';
type OwnProps = {
@ -10,11 +9,8 @@ export function BoardCardEditableFieldDateEditMode({
value,
onChange,
}: OwnProps) {
const { closeBoardCardField } = useBoardCardField();
function handleDateChange(newDate: Date) {
onChange(newDate);
closeBoardCardField();
}
return <InplaceInputDateEditMode value={value} onChange={handleDateChange} />;

View File

@ -19,9 +19,11 @@ export function BoardCardEditableFieldText({
editModeHorizontalAlign,
}: OwnProps) {
const [internalValue, setInternalValue] = useState(value);
const debouncedOnChange = useMemo(() => {
return debounce(onChange, 200);
}, [onChange]);
return (
<BoardCardEditableField
editModeHorizontalAlign={editModeHorizontalAlign}

View File

@ -1,6 +1,6 @@
import { ReactElement } from 'react';
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
import { HotkeysScope } from '@/hotkeys/types/internal/HotkeysScope';
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
import { BoardCardFieldContext } from '../states/BoardCardFieldContext';
@ -12,7 +12,7 @@ type OwnProps = {
nonEditModeContent: ReactElement;
editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below';
editHotkeysScope?: HotkeysScopeStackItem;
editHotkeysScope?: HotkeysScope;
};
export function BoardCardEditableField(props: OwnProps) {

View File

@ -6,9 +6,9 @@ import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysSc
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
import { overlayBackground } from '@/ui/themes/effects';
import { useBoardCardField } from '../hooks/useBoardCardField';
export const BoardCardFieldEditModeContainer = styled.div<OwnProps>`
export const BoardCardFieldEditModeContainer = styled.div<
Omit<OwnProps, 'onExit'>
>`
align-items: center;
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.sm};
@ -31,38 +31,37 @@ type OwnProps = {
children: ReactElement;
editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below';
onOutsideClick?: () => void;
onExit: () => void;
};
export function BoardCardEditableFieldEditMode({
editModeHorizontalAlign,
editModeVerticalPosition,
children,
onExit,
}: OwnProps) {
const wrapperRef = useRef(null);
const { closeBoardCardField } = useBoardCardField();
useListenClickOutsideArrayOfRef([wrapperRef], () => {
closeBoardCardField();
onExit();
});
useScopedHotkeys(
'enter',
() => {
closeBoardCardField();
onExit();
},
InternalHotkeysScope.BoardCardFieldEditMode,
[closeBoardCardField],
[onExit],
);
useScopedHotkeys(
'esc',
() => {
closeBoardCardField();
onExit();
},
InternalHotkeysScope.BoardCardFieldEditMode,
[closeBoardCardField],
[onExit],
);
return (

View File

@ -1,8 +1,8 @@
import { ReactElement } from 'react';
import styled from '@emotion/styled';
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
import { usePreviousHotkeysScope } from '@/hotkeys/hooks/internal/usePreviousHotkeysScope';
import { HotkeysScope } from '@/hotkeys/types/internal/HotkeysScope';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useBoardCardField } from '../hooks/useBoardCardField';
@ -26,7 +26,7 @@ type OwnProps = {
nonEditModeContent: ReactElement;
editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below';
editHotkeysScope?: HotkeysScopeStackItem;
editHotkeysScope?: HotkeysScope;
};
export function BoardCardEditableFieldInternal({
@ -39,25 +39,35 @@ export function BoardCardEditableFieldInternal({
const { openBoardCardField, isBoardCardFieldInEditMode } =
useBoardCardField();
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
const { closeBoardCardField } = useBoardCardField();
const {
goBackToPreviousHotkeysScope,
setHotkeysScopeAndMemorizePreviousScope,
} = usePreviousHotkeysScope();
function handleOnClick() {
if (!isBoardCardFieldInEditMode) {
openBoardCardField();
addToHotkeysScopeStack(
editHotkeysScope ?? {
scope: InternalHotkeysScope.BoardCardFieldEditMode,
},
setHotkeysScopeAndMemorizePreviousScope(
editHotkeysScope?.scope ?? InternalHotkeysScope.BoardCardFieldEditMode,
editHotkeysScope?.customScopes ?? {},
);
}
}
function handleEditModeExit() {
goBackToPreviousHotkeysScope();
closeBoardCardField();
}
return (
<BoardCardFieldContainer onClick={handleOnClick}>
{isBoardCardFieldInEditMode ? (
<BoardCardEditableFieldEditMode
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
onExit={handleEditModeExit}
>
{editModeContent}
</BoardCardEditableFieldEditMode>

View File

@ -1,11 +1,11 @@
import { ReactElement } from 'react';
import styled from '@emotion/styled';
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
import { HotkeysScope } from '@/hotkeys/types/internal/HotkeysScope';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useEditableCell } from './hooks/useCloseEditableCell';
import { useCurrentCellEditMode } from './hooks/useCurrentCellEditMode';
import { useEditableCell } from './hooks/useEditableCell';
import { useIsSoftFocusOnCurrentCell } from './hooks/useIsSoftFocusOnCurrentCell';
import { useSetSoftFocusOnCurrentCell } from './hooks/useSetSoftFocusOnCurrentCell';
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
@ -28,7 +28,7 @@ type OwnProps = {
nonEditModeContent: ReactElement;
editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below';
editHotkeysScope?: HotkeysScopeStackItem;
editHotkeysScope?: HotkeysScope;
};
export function EditableCell({
@ -60,9 +60,9 @@ export function EditableCell({
scope: InternalHotkeysScope.CellEditMode,
},
);
} else {
setSoftFocusOnCurrentCell();
}
setSoftFocusOnCurrentCell();
}
return (

View File

@ -7,7 +7,7 @@ import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsid
import { useMoveSoftFocus } from '@/ui/tables/hooks/useMoveSoftFocus';
import { overlayBackground } from '@/ui/themes/effects';
import { useEditableCell } from './hooks/useCloseEditableCell';
import { useEditableCell } from './hooks/useEditableCell';
export const EditableCellEditModeContainer = styled.div<OwnProps>`
align-items: center;

View File

@ -1,17 +1,17 @@
import React from 'react';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
import { HotkeysScope } from '@/hotkeys/types/internal/HotkeysScope';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { isNonTextWritingKey } from '@/utils/hotkeys/isNonTextWritingKey';
import { useEditableCell } from './hooks/useCloseEditableCell';
import { useEditableCell } from './hooks/useEditableCell';
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
export function EditableCellSoftFocusMode({
children,
editHotkeysScope,
}: React.PropsWithChildren<{ editHotkeysScope?: HotkeysScopeStackItem }>) {
}: React.PropsWithChildren<{ editHotkeysScope?: HotkeysScope }>) {
const { closeEditableCell, openEditableCell } = useEditableCell();
useScopedHotkeys(

View File

@ -1,8 +1,8 @@
import { useRecoilCallback } from 'recoil';
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
import { useRemoveHighestHotkeysScopeStackItem } from '@/hotkeys/hooks/useRemoveHighestHotkeysScopeStackItem';
import { HotkeysScopeStackItem } from '@/hotkeys/types/internal/HotkeysScopeStackItems';
import { useSetHotkeysScope } from '@/hotkeys/hooks/useSetHotkeysScope';
import { HotkeysScope } from '@/hotkeys/types/internal/HotkeysScope';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useCloseCurrentCellInEditMode } from '@/ui/tables/hooks/useClearCellInEditMode';
import { isSoftFocusActiveState } from '@/ui/tables/states/isSoftFocusActiveState';
import { isSomeInputInEditModeState } from '@/ui/tables/states/isSomeInputInEditModeState';
@ -12,21 +12,18 @@ import { useCurrentCellEditMode } from './useCurrentCellEditMode';
export function useEditableCell() {
const { setCurrentCellInEditMode } = useCurrentCellEditMode();
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
const setHotkeysScope = useSetHotkeysScope();
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
const removeHighestHotkeysScopedStackItem =
useRemoveHighestHotkeysScopeStackItem();
function closeEditableCell() {
closeCurrentCellInEditMode();
removeHighestHotkeysScopedStackItem();
setHotkeysScope(InternalHotkeysScope.TableSoftFocus);
}
const openEditableCell = useRecoilCallback(
({ snapshot, set }) =>
(hotkeysScopeStackItem: HotkeysScopeStackItem) => {
(hotkeysScope: HotkeysScope) => {
const isSomeInputInEditMode = snapshot
.getLoadable(isSomeInputInEditModeState)
.valueOrThrow();
@ -37,10 +34,10 @@ export function useEditableCell() {
setCurrentCellInEditMode();
addToHotkeysScopeStack(hotkeysScopeStackItem);
setHotkeysScope(hotkeysScope.scope);
}
},
[setCurrentCellInEditMode, addToHotkeysScopeStack],
[setCurrentCellInEditMode, setHotkeysScope],
);
return {

View File

@ -1,7 +1,7 @@
import { useCallback, useMemo } from 'react';
import { useRecoilState } from 'recoil';
import { useAddToHotkeysScopeStack } from '@/hotkeys/hooks/useAddToHotkeysScopeStack';
import { useSetHotkeysScope } from '@/hotkeys/hooks/useSetHotkeysScope';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { useSetSoftFocusPosition } from '@/ui/tables/hooks/useSetSoftFocusPosition';
@ -34,16 +34,16 @@ export function useSetSoftFocusOnCurrentCell() {
const [, setIsSoftFocusActive] = useRecoilState(isSoftFocusActiveState);
const addToHotkeysScopeStack = useAddToHotkeysScopeStack();
const setHotkeysScope = useSetHotkeysScope();
return useCallback(() => {
setSoftFocusPosition(currentTablePosition);
setIsSoftFocusActive(true);
addToHotkeysScopeStack({ scope: InternalHotkeysScope.TableSoftFocus });
setHotkeysScope(InternalHotkeysScope.TableSoftFocus);
}, [
setSoftFocusPosition,
currentTablePosition,
setIsSoftFocusActive,
addToHotkeysScopeStack,
setHotkeysScope,
]);
}

View File

@ -1,6 +1,6 @@
import { InplaceInputDateEditMode } from '@/ui/inplace-inputs/components/InplaceInputDateEditMode';
import { useEditableCell } from '../hooks/useCloseEditableCell';
import { useEditableCell } from '../hooks/useEditableCell';
export type EditableDateProps = {
value: Date;

View File

@ -7,7 +7,7 @@ import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysSc
import { InplaceInputTextEditMode } from '@/ui/inplace-inputs/components/InplaceInputTextEditMode';
import { useMoveSoftFocus } from '@/ui/tables/hooks/useMoveSoftFocus';
import { useEditableCell } from '../hooks/useCloseEditableCell';
import { useEditableCell } from '../hooks/useEditableCell';
type OwnProps = {
firstValue: string;

View File

@ -1,5 +1,10 @@
import { ChangeEvent } from 'react';
import { ChangeEvent, useRef } from 'react';
import styled from '@emotion/styled';
import { Key } from 'ts-key-enum';
import { usePreviousHotkeysScope } from '@/hotkeys/hooks/internal/usePreviousHotkeysScope';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
type OwnProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
@ -52,10 +57,37 @@ export function TextInput({
fullWidth,
...props
}: OwnProps): JSX.Element {
const inputRef = useRef<HTMLInputElement>(null);
const {
goBackToPreviousHotkeysScope,
setHotkeysScopeAndMemorizePreviousScope,
} = usePreviousHotkeysScope();
function handleFocus() {
setHotkeysScopeAndMemorizePreviousScope(InternalHotkeysScope.TextInput);
}
function handleBlur() {
goBackToPreviousHotkeysScope();
}
useScopedHotkeys(
[Key.Enter, Key.Escape],
() => {
inputRef.current?.blur();
},
InternalHotkeysScope.TextInput,
);
return (
<StyledContainer>
{label && <StyledLabel>{label}</StyledLabel>}
<StyledInput
ref={inputRef}
tabIndex={props.tabIndex ?? 0}
onFocus={handleFocus}
onBlur={handleBlur}
fullWidth={fullWidth ?? false}
value={value}
onChange={(event: ChangeEvent<HTMLInputElement>) => {

View File

@ -1,11 +1,13 @@
import { useCallback, useState } from 'react';
import { Key } from 'ts-key-enum';
import { activeTableFiltersScopedState } from '@/filters-and-sorts/states/activeTableFiltersScopedState';
import { filterDropdownSearchInputScopedState } from '@/filters-and-sorts/states/filterDropdownSearchInputScopedState';
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '@/filters-and-sorts/states/isFilterDropdownOperandSelectUnfoldedScopedState';
import { selectedOperandInDropdownScopedState } from '@/filters-and-sorts/states/selectedOperandInDropdownScopedState';
import { tableFilterDefinitionUsedInDropdownScopedState } from '@/filters-and-sorts/states/tableFilterDefinitionUsedInDropdownScopedState';
import { useHotkeysScopeOnBooleanState } from '@/hotkeys/hooks/useHotkeysScopeOnBooleanState';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { useSetHotkeysScope } from '@/hotkeys/hooks/useSetHotkeysScope';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { TableContext } from '@/ui/tables/states/TableContext';
@ -23,11 +25,6 @@ import { FilterDropdownTextSearchInput } from './FilterDropdownTextSearchInput';
export function FilterDropdownButton() {
const [isUnfolded, setIsUnfolded] = useState(false);
useHotkeysScopeOnBooleanState(
{ scope: InternalHotkeysScope.TableHeaderDropdownButton },
isUnfolded,
);
const [
isFilterDropdownOperandSelectUnfolded,
setIsFilterDropdownOperandSelectUnfolded,
@ -71,18 +68,27 @@ export function FilterDropdownButton() {
const isFilterSelected = (activeTableFilters?.length ?? 0) > 0;
const setHotkeysScope = useSetHotkeysScope();
function handleIsUnfoldedChange(newIsUnfolded: boolean) {
if (newIsUnfolded) {
setIsUnfolded(true);
} else {
if (tableFilterDefinitionUsedInDropdown?.type === 'entity') {
setHotkeysScope(InternalHotkeysScope.Table);
}
setIsUnfolded(false);
resetState();
}
}
useHotkeysScopeOnBooleanState(
{ scope: InternalHotkeysScope.RelationPicker },
tableFilterDefinitionUsedInDropdown?.type === 'entity',
useScopedHotkeys(
[Key.Escape],
() => {
handleIsUnfoldedChange(false);
},
InternalHotkeysScope.RelationPicker,
[handleIsUnfoldedChange],
);
return (

View File

@ -3,6 +3,8 @@ import { filterDropdownSearchInputScopedState } from '@/filters-and-sorts/states
import { selectedOperandInDropdownScopedState } from '@/filters-and-sorts/states/selectedOperandInDropdownScopedState';
import { tableFilterDefinitionUsedInDropdownScopedState } from '@/filters-and-sorts/states/tableFilterDefinitionUsedInDropdownScopedState';
import { getOperandsForFilterType } from '@/filters-and-sorts/utils/getOperandsForFilterType';
import { useSetHotkeysScope } from '@/hotkeys/hooks/useSetHotkeysScope';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/recoil-scope/hooks/useRecoilScopedValue';
import { TableContext } from '@/ui/tables/states/TableContext';
@ -33,6 +35,8 @@ export function FilterDropdownFilterSelect() {
TableContext,
);
const setHotkeysScope = useSetHotkeysScope();
return (
<DropdownMenuItemContainer style={{ maxHeight: '300px' }}>
{availableTableFilters.map((availableTableFilter, index) => (
@ -40,6 +44,11 @@ export function FilterDropdownFilterSelect() {
key={`select-filter-${index}`}
onClick={() => {
setTableFilterDefinitionUsedInDropdown(availableTableFilter);
if (availableTableFilter.type === 'entity') {
setHotkeysScope(InternalHotkeysScope.RelationPicker);
}
setSelectedOperandInDropdown(
getOperandsForFilterType(availableTableFilter.type)?.[0],
);

View File

@ -1,7 +1,7 @@
import { useRecoilValue } from 'recoil';
import { useCurrentHotkeysScope } from '@/hotkeys/hooks/useCurrentHotkeysScope';
import { useResetHotkeysScopeStack } from '@/hotkeys/hooks/useResetHotkeysScopeStack';
import { useSetHotkeysScope } from '@/hotkeys/hooks/useSetHotkeysScope';
import { currentHotkeysScopeState } from '@/hotkeys/states/internal/currentHotkeysScopeState';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { isSoftFocusActiveState } from '../states/isSoftFocusActiveState';
@ -11,12 +11,13 @@ import { useCloseCurrentCellInEditMode } from './useClearCellInEditMode';
import { useDisableSoftFocus } from './useDisableSoftFocus';
export function useLeaveTableFocus() {
const resetHotkeysScopeStack = useResetHotkeysScopeStack();
const currentHotkeysScope = useCurrentHotkeysScope();
const currentHotkeysScope = useRecoilValue(currentHotkeysScopeState);
const disableSoftFocus = useDisableSoftFocus();
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
const setHotkeysScope = useSetHotkeysScope();
const isSoftFocusActive = useRecoilValue(isSoftFocusActiveState);
const isSomeInputInEditMode = useRecoilValue(isSomeInputInEditModeState);
@ -32,6 +33,7 @@ export function useLeaveTableFocus() {
closeCurrentCellInEditMode();
disableSoftFocus();
resetHotkeysScopeStack(InternalHotkeysScope.Table);
setHotkeysScope(InternalHotkeysScope.Table, { goto: true });
};
}

View File

@ -1,8 +1,8 @@
import { useRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
import { useRemoveFromHotkeysScopeStack } from '@/hotkeys/hooks/useRemoveFromHotkeysScopeStack';
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
import { useSetHotkeysScope } from '@/hotkeys/hooks/useSetHotkeysScope';
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
import { isSomeInputInEditModeState } from '../states/isSomeInputInEditModeState';
@ -13,8 +13,8 @@ import { useMoveSoftFocus } from './useMoveSoftFocus';
export function useMapKeyboardToSoftFocus() {
const { moveDown, moveLeft, moveRight, moveUp } = useMoveSoftFocus();
const removeFromHotkeysScopedStack = useRemoveFromHotkeysScopeStack();
const disableSoftFocus = useDisableSoftFocus();
const setHotkeysScope = useSetHotkeysScope();
const [isSomeInputInEditMode] = useRecoilState(isSomeInputInEditModeState);
@ -65,10 +65,10 @@ export function useMapKeyboardToSoftFocus() {
useScopedHotkeys(
[Key.Escape],
() => {
removeFromHotkeysScopedStack(InternalHotkeysScope.TableSoftFocus);
setHotkeysScope(InternalHotkeysScope.Table, { goto: true });
disableSoftFocus();
},
InternalHotkeysScope.TableSoftFocus,
[removeFromHotkeysScopedStack, disableSoftFocus],
[disableSoftFocus],
);
}