Feat/improve new views (#2298)
* POC new recoil injected scoped states * Finished useViewScopedState refactor * Finished refactor * Renamed mappers * Fixed update view fields bug * Post merge * Complete refactor * Fix tests --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { IconChevronDown, IconPlus } from '@/ui/display/icon';
|
||||
@ -10,13 +11,14 @@ import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useView } from '@/views/hooks/useView';
|
||||
|
||||
import { useViewGetStates } from '../hooks/useViewGetStates';
|
||||
import { useViewScopedStates } from '../hooks/internal/useViewScopedStates';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: inline-flex;
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export type UpdateViewButtonGroupProps = {
|
||||
hotkeyScope: string;
|
||||
onViewEditModeChange?: () => void;
|
||||
@ -28,7 +30,11 @@ export const UpdateViewButtonGroup = ({
|
||||
}: UpdateViewButtonGroupProps) => {
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const { updateCurrentView, setViewEditMode } = useView();
|
||||
const { canPersistFilters, canPersistSorts } = useViewGetStates();
|
||||
const { canPersistFiltersSelector, canPersistSortsSelector } =
|
||||
useViewScopedStates();
|
||||
|
||||
const canPersistFilters = useRecoilValue(canPersistFiltersSelector);
|
||||
const canPersistSorts = useRecoilValue(canPersistSortsSelector);
|
||||
|
||||
const canPersistView = canPersistFilters || canPersistSorts;
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { TopBar } from '@/ui/layout/top-bar/TopBar';
|
||||
@ -8,8 +9,8 @@ import { FiltersHotkeyScope } from '@/ui/object/object-filter-dropdown/types/Fil
|
||||
import { ObjectSortDropdownButton } from '@/ui/object/object-sort-dropdown/components/ObjectSortDropdownButton';
|
||||
import { ObjectSortDropdownScope } from '@/ui/object/object-sort-dropdown/scopes/ObjectSortDropdownScope';
|
||||
|
||||
import { useViewScopedStates } from '../hooks/internal/useViewScopedStates';
|
||||
import { useView } from '../hooks/useView';
|
||||
import { useViewGetStates } from '../hooks/useViewGetStates';
|
||||
import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope';
|
||||
|
||||
import { UpdateViewButtonGroup } from './UpdateViewButtonGroup';
|
||||
@ -32,8 +33,16 @@ export const ViewBar = ({
|
||||
dropdownScopeId: optionsDropdownScopeId,
|
||||
});
|
||||
const { upsertViewSort, upsertViewFilter } = useView();
|
||||
const { availableFilterDefinitions, availableSortDefinitions } =
|
||||
useViewGetStates();
|
||||
|
||||
const { availableFilterDefinitionsState, availableSortDefinitionsState } =
|
||||
useViewScopedStates();
|
||||
|
||||
const availableFilterDefinitions = useRecoilValue(
|
||||
availableFilterDefinitionsState,
|
||||
);
|
||||
const availableSortDefinitions = useRecoilValue(
|
||||
availableSortDefinitionsState,
|
||||
);
|
||||
|
||||
return (
|
||||
<ObjectFilterDropdownScope
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { ReactNode } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { IconArrowDown, IconArrowUp } from '@/ui/display/icon/index';
|
||||
import { AddObjectFilterFromDetailsButton } from '@/ui/object/object-filter-dropdown/components/AddObjectFilterFromDetailsButton';
|
||||
import { getOperandLabelShort } from '@/ui/object/object-filter-dropdown/utils/getOperandLabel';
|
||||
|
||||
import { useViewScopedStates } from '../hooks/internal/useViewScopedStates';
|
||||
import { useView } from '../hooks/useView';
|
||||
import { useViewGetStates } from '../hooks/useViewGetStates';
|
||||
|
||||
import SortOrFilterChip from './SortOrFilterChip';
|
||||
|
||||
@ -88,12 +89,18 @@ export const ViewBarDetails = ({
|
||||
rightComponent,
|
||||
}: ViewBarDetailsProps) => {
|
||||
const {
|
||||
currentViewSorts,
|
||||
currentViewFilters,
|
||||
canPersistFilters,
|
||||
canPersistSorts,
|
||||
isViewBarExpanded,
|
||||
} = useViewGetStates();
|
||||
currentViewSortsState,
|
||||
currentViewFiltersState,
|
||||
canPersistFiltersSelector,
|
||||
canPersistSortsSelector,
|
||||
isViewBarExpandedState,
|
||||
} = useViewScopedStates();
|
||||
|
||||
const currentViewSorts = useRecoilValue(currentViewSortsState);
|
||||
const currentViewFilters = useRecoilValue(currentViewFiltersState);
|
||||
const canPersistFilters = useRecoilValue(canPersistFiltersSelector);
|
||||
const canPersistSorts = useRecoilValue(canPersistSortsSelector);
|
||||
const isViewBarExpanded = useRecoilValue(isViewBarExpandedState);
|
||||
|
||||
const { resetViewBar, removeViewSort, removeViewFilter } = useView();
|
||||
|
||||
|
||||
@ -1,66 +1,58 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useFindManyObjects } from '@/metadata/hooks/useFindManyObjects';
|
||||
import { PaginatedObjectTypeResults } from '@/metadata/types/PaginatedObjectTypeResults';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { assertNotNull } from '~/utils/assert';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
import { useViewScopedStates } from '../hooks/internal/useViewScopedStates';
|
||||
import { useView } from '../hooks/useView';
|
||||
import { useViewGetStates } from '../hooks/useViewGetStates';
|
||||
import { availableFieldDefinitionsScopedState } from '../states/availableFieldDefinitionsScopedState';
|
||||
import { availableFilterDefinitionsScopedState } from '../states/availableFilterDefinitionsScopedState';
|
||||
import { availableSortDefinitionsScopedState } from '../states/availableSortDefinitionsScopedState';
|
||||
import { onViewFieldsChangeScopedState } from '../states/onViewFieldsChangeScopedState';
|
||||
import { onViewFiltersChangeScopedState } from '../states/onViewFiltersChangeScopedState';
|
||||
import { onViewSortsChangeScopedState } from '../states/onViewSortsChangeScopedState';
|
||||
import { savedViewFieldsScopedFamilyState } from '../states/savedViewFieldsScopedFamilyState';
|
||||
import { savedViewFiltersScopedFamilyState } from '../states/savedViewFiltersScopedFamilyState';
|
||||
import { savedViewSortsScopedFamilyState } from '../states/savedViewSortsScopedFamilyState';
|
||||
import { viewsScopedState } from '../states/viewsScopedState';
|
||||
import { View } from '../types/View';
|
||||
import { ViewField } from '../types/ViewField';
|
||||
import { ViewFilter } from '../types/ViewFilter';
|
||||
import { ViewSort } from '../types/ViewSort';
|
||||
import { getViewScopedStatesFromSnapshot } from '../utils/getViewScopedStatesFromSnapshot';
|
||||
import { getViewScopedStateValuesFromSnapshot } from '../utils/getViewScopedStateValuesFromSnapshot';
|
||||
|
||||
export const ViewBarEffect = () => {
|
||||
const {
|
||||
scopeId: viewScopeId,
|
||||
setCurrentViewFields,
|
||||
setSavedViewFields,
|
||||
setCurrentViewFilters,
|
||||
setSavedViewFilters,
|
||||
setCurrentViewSorts,
|
||||
setSavedViewSorts,
|
||||
currentViewId,
|
||||
setViews,
|
||||
loadView,
|
||||
changeViewInUrl,
|
||||
setCurrentViewId,
|
||||
} = useView();
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
const currentViewIdFromUrl = searchParams.get('view');
|
||||
|
||||
const { viewType, viewObjectId } = useViewGetStates(viewScopeId);
|
||||
const { viewTypeState, viewObjectIdState } = useViewScopedStates();
|
||||
|
||||
const viewType = useRecoilValue(viewTypeState);
|
||||
const viewObjectId = useRecoilValue(viewObjectIdState);
|
||||
|
||||
useFindManyObjects({
|
||||
objectNamePlural: 'viewsV2',
|
||||
filter: { type: { eq: viewType }, objectId: { eq: viewObjectId } },
|
||||
onCompleted: useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
({ snapshot, set }) =>
|
||||
async (data: PaginatedObjectTypeResults<View>) => {
|
||||
const nextViews = data.edges.map((view) => ({
|
||||
id: view.node.id,
|
||||
name: view.node.name,
|
||||
objectId: view.node.objectId,
|
||||
}));
|
||||
const views = snapshot
|
||||
.getLoadable(viewsScopedState({ scopeId: viewScopeId }))
|
||||
.getValue();
|
||||
|
||||
if (!isDeeplyEqual(views, nextViews)) setViews(nextViews);
|
||||
const { viewsState } = getViewScopedStatesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
});
|
||||
|
||||
const views = getSnapshotValue(snapshot, viewsState);
|
||||
|
||||
if (!isDeeplyEqual(views, nextViews)) set(viewsState, nextViews);
|
||||
|
||||
if (!nextViews.length) return;
|
||||
|
||||
@ -74,43 +66,39 @@ export const ViewBarEffect = () => {
|
||||
objectNamePlural: 'viewFieldsV2',
|
||||
filter: { viewId: { eq: currentViewId } },
|
||||
onCompleted: useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
({ snapshot, set }) =>
|
||||
async (data: PaginatedObjectTypeResults<ViewField>) => {
|
||||
const availableFields = snapshot
|
||||
.getLoadable(
|
||||
availableFieldDefinitionsScopedState({ scopeId: viewScopeId }),
|
||||
)
|
||||
.getValue();
|
||||
const {
|
||||
availableFieldDefinitions,
|
||||
onViewFieldsChange,
|
||||
savedViewFields,
|
||||
currentViewId,
|
||||
} = getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
});
|
||||
|
||||
const onViewFieldsChange = snapshot
|
||||
.getLoadable(
|
||||
onViewFieldsChangeScopedState({ scopeId: viewScopeId }),
|
||||
)
|
||||
.getValue();
|
||||
const { savedViewFieldsState, currentViewFieldsState } =
|
||||
getViewScopedStatesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
});
|
||||
|
||||
if (!availableFields || !currentViewId) {
|
||||
if (!availableFieldDefinitions || !currentViewId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const savedViewFields = snapshot
|
||||
.getLoadable(
|
||||
savedViewFieldsScopedFamilyState({
|
||||
scopeId: viewScopeId,
|
||||
familyKey: currentViewId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
const queriedViewFields = data.edges
|
||||
.map((viewField) => viewField.node)
|
||||
.filter(assertNotNull);
|
||||
|
||||
if (!isDeeplyEqual(savedViewFields, queriedViewFields)) {
|
||||
setCurrentViewFields?.(queriedViewFields);
|
||||
setSavedViewFields?.(queriedViewFields);
|
||||
set(currentViewFieldsState, queriedViewFields);
|
||||
set(savedViewFieldsState, queriedViewFields);
|
||||
onViewFieldsChange?.(queriedViewFields);
|
||||
}
|
||||
},
|
||||
[viewScopeId],
|
||||
),
|
||||
});
|
||||
|
||||
@ -119,33 +107,28 @@ export const ViewBarEffect = () => {
|
||||
objectNamePlural: 'viewFiltersV2',
|
||||
filter: { viewId: { eq: currentViewId } },
|
||||
onCompleted: useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
({ snapshot, set }) =>
|
||||
async (data: PaginatedObjectTypeResults<Required<ViewFilter>>) => {
|
||||
const availableFilterDefinitions = snapshot
|
||||
.getLoadable(
|
||||
availableFilterDefinitionsScopedState({ scopeId: viewScopeId }),
|
||||
)
|
||||
.getValue();
|
||||
const {
|
||||
availableFilterDefinitions,
|
||||
savedViewFilters,
|
||||
onViewFiltersChange,
|
||||
currentViewId,
|
||||
} = getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
});
|
||||
|
||||
const { savedViewFiltersState, currentViewFiltersState } =
|
||||
getViewScopedStatesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
});
|
||||
|
||||
if (!availableFilterDefinitions || !currentViewId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const savedViewFilters = snapshot
|
||||
.getLoadable(
|
||||
savedViewFiltersScopedFamilyState({
|
||||
scopeId: viewScopeId,
|
||||
familyKey: currentViewId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
const onViewFiltersChange = snapshot
|
||||
.getLoadable(
|
||||
onViewFiltersChangeScopedState({ scopeId: viewScopeId }),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
const queriedViewFilters = data.edges
|
||||
.map(({ node }) => {
|
||||
const availableFilterDefinition = availableFilterDefinitions.find(
|
||||
@ -163,11 +146,12 @@ export const ViewBarEffect = () => {
|
||||
.filter(assertNotNull);
|
||||
|
||||
if (!isDeeplyEqual(savedViewFilters, queriedViewFilters)) {
|
||||
setSavedViewFilters?.(queriedViewFilters);
|
||||
setCurrentViewFilters?.(queriedViewFilters);
|
||||
set(savedViewFiltersState, queriedViewFilters);
|
||||
set(currentViewFiltersState, queriedViewFilters);
|
||||
onViewFiltersChange?.(queriedViewFilters);
|
||||
}
|
||||
},
|
||||
[viewScopeId],
|
||||
),
|
||||
});
|
||||
|
||||
@ -176,31 +160,28 @@ export const ViewBarEffect = () => {
|
||||
objectNamePlural: 'viewSortsV2',
|
||||
filter: { viewId: { eq: currentViewId } },
|
||||
onCompleted: useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
({ snapshot, set }) =>
|
||||
async (data: PaginatedObjectTypeResults<Required<ViewSort>>) => {
|
||||
const availableSortDefinitions = snapshot
|
||||
.getLoadable(
|
||||
availableSortDefinitionsScopedState({ scopeId: viewScopeId }),
|
||||
)
|
||||
.getValue();
|
||||
const {
|
||||
availableSortDefinitions,
|
||||
savedViewSorts,
|
||||
onViewSortsChange,
|
||||
currentViewId,
|
||||
} = getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
});
|
||||
|
||||
const { savedViewSortsState, currentViewSortsState } =
|
||||
getViewScopedStatesFromSnapshot({
|
||||
snapshot,
|
||||
viewScopeId,
|
||||
});
|
||||
|
||||
if (!availableSortDefinitions || !currentViewId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const savedViewSorts = snapshot
|
||||
.getLoadable(
|
||||
savedViewSortsScopedFamilyState({
|
||||
scopeId: viewScopeId,
|
||||
familyKey: currentViewId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
const onViewSortsChange = snapshot
|
||||
.getLoadable(onViewSortsChangeScopedState({ scopeId: viewScopeId }))
|
||||
.getValue();
|
||||
|
||||
const queriedViewSorts = data.edges
|
||||
.map(({ node }) => {
|
||||
const availableSortDefinition = availableSortDefinitions.find(
|
||||
@ -219,18 +200,20 @@ export const ViewBarEffect = () => {
|
||||
.filter(assertNotNull);
|
||||
|
||||
if (!isDeeplyEqual(savedViewSorts, queriedViewSorts)) {
|
||||
setSavedViewSorts?.(queriedViewSorts);
|
||||
setCurrentViewSorts?.(queriedViewSorts);
|
||||
set(savedViewSortsState, queriedViewSorts);
|
||||
set(currentViewSortsState, queriedViewSorts);
|
||||
onViewSortsChange?.(queriedViewSorts);
|
||||
}
|
||||
},
|
||||
[viewScopeId],
|
||||
),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentViewIdFromUrl) return;
|
||||
|
||||
loadView(currentViewIdFromUrl);
|
||||
}, [currentViewIdFromUrl, loadView, setCurrentViewId]);
|
||||
}, [currentViewIdFromUrl, loadView]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
|
||||
import {
|
||||
IconChevronDown,
|
||||
@ -22,8 +22,8 @@ import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { assertNotNull } from '~/utils/assert';
|
||||
|
||||
import { ViewsDropdownId } from '../constants/ViewsDropdownId';
|
||||
import { useViewScopedStates } from '../hooks/internal/useViewScopedStates';
|
||||
import { useView } from '../hooks/useView';
|
||||
import { useViewGetStates } from '../hooks/useViewGetStates';
|
||||
|
||||
const StyledBoldDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
@ -68,12 +68,17 @@ export const ViewsDropdownButton = ({
|
||||
optionsDropdownScopeId,
|
||||
}: ViewsDropdownButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const { scopeId, removeView, currentViewId, changeViewInUrl } = useView();
|
||||
const { removeView, changeViewInUrl } = useView();
|
||||
|
||||
const { views, currentView, entityCountInCurrentView } = useViewGetStates(
|
||||
scopeId,
|
||||
currentViewId,
|
||||
const { viewsState, currentViewSelector, entityCountInCurrentViewState } =
|
||||
useViewScopedStates();
|
||||
|
||||
const views = useRecoilValue(viewsState);
|
||||
const currentView = useRecoilValue(currentViewSelector);
|
||||
const entityCountInCurrentView = useRecoilValue(
|
||||
entityCountInCurrentViewState,
|
||||
);
|
||||
|
||||
const { setViewEditMode } = useView();
|
||||
|
||||
const {
|
||||
|
||||
Reference in New Issue
Block a user