Skip to content

Commit

Permalink
fix: expand/collapse sections logic
Browse files Browse the repository at this point in the history
  • Loading branch information
devcatalin committed Oct 27, 2021
1 parent d164da4 commit a786c05
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, {useCallback, useMemo} from 'react';
import React, {useCallback, useEffect, useMemo} from 'react';
import {ItemGroupInstance, SectionBlueprint, SectionInstance} from '@models/navigator';
import {useAppSelector} from '@redux/hooks';
import {useAppDispatch, useAppSelector} from '@redux/hooks';
import navSectionMap from '@src/navsections/sectionBlueprintMap';
import {collapseSectionIds, expandSectionIds} from '@redux/reducers/navigator';
import ItemRenderer from './ItemRenderer';
import NavSectionHeader from './SectionHeader';
import * as S from './styled';
Expand All @@ -21,6 +22,68 @@ function SectionRenderer<ItemType, ScopeType>(props: SectionRendererProps<ItemTy
state => state.navigator.sectionInstanceMap[sectionId]
);

const dispatch = useAppDispatch();

const collapsedSectionIds = useAppSelector(state => state.navigator.collapsedSectionIds);

const isCollapsedMode = useMemo(() => {
if (!sectionInstance?.id) {
return 'expanded';
}
const visibleDescendantSectionIds = sectionInstance?.visibleDescendantSectionIds;
if (visibleDescendantSectionIds) {
if (
collapsedSectionIds.includes(sectionInstance.id) &&
visibleDescendantSectionIds.every(s => collapsedSectionIds.includes(s))
) {
return 'collapsed';
}
if (
!collapsedSectionIds.includes(sectionInstance.id) &&
visibleDescendantSectionIds.every(s => !collapsedSectionIds.includes(s))
) {
return 'expanded';
}
return 'mixed';
}
if (collapsedSectionIds.includes(sectionInstance.id)) {
return 'collapsed';
}
return 'expanded';
}, [collapsedSectionIds, sectionInstance?.id, sectionInstance?.visibleDescendantSectionIds]);

const isCollapsed = useMemo(() => {
return isCollapsedMode === 'collapsed';
}, [isCollapsedMode]);

const expandSection = useCallback(() => {
if (!sectionInstance?.id) {
return;
}
if (!sectionInstance?.visibleDescendantSectionIds || sectionInstance.visibleDescendantSectionIds.length === 0) {
dispatch(expandSectionIds([sectionInstance.id]));
} else {
dispatch(expandSectionIds([sectionInstance.id, ...sectionInstance.visibleDescendantSectionIds]));
}
}, [sectionInstance?.id, sectionInstance?.visibleDescendantSectionIds, dispatch]);

const collapseSection = useCallback(() => {
if (!sectionInstance?.id) {
return;
}
if (!sectionInstance?.visibleDescendantSectionIds || sectionInstance.visibleDescendantSectionIds.length === 0) {
dispatch(collapseSectionIds([sectionInstance?.id]));
} else {
dispatch(collapseSectionIds([sectionInstance?.id, ...sectionInstance.visibleDescendantSectionIds]));
}
}, [sectionInstance?.id, sectionInstance?.visibleDescendantSectionIds, dispatch]);

useEffect(() => {
if (sectionInstance?.shouldExpand) {
expandSection();
}
}, [sectionInstance?.shouldExpand, expandSection]);

const groupInstanceById: Record<string, ItemGroupInstance> = useMemo(() => {
return sectionInstance?.groups
.map<[string, ItemGroupInstance]>(g => [g.id, g])
Expand Down Expand Up @@ -75,20 +138,21 @@ function SectionRenderer<ItemType, ScopeType>(props: SectionRendererProps<ItemTy
<NavSectionHeader
name={sectionName}
isSectionSelected={Boolean(sectionInstance?.isSelected)}
isCollapsed={false}
isCollapsed={isCollapsed}
isCollapsedMode={isCollapsedMode}
isSectionHighlighted={Boolean(sectionInstance?.isHighlighted)}
isLastSection={isLastSection}
hasChildSections={Boolean(sectionBlueprint.childSectionIds && sectionBlueprint.childSectionIds.length > 0)}
isSectionInitialized={Boolean(sectionInstance?.isInitialized)}
isSectionVisible={Boolean(sectionInstance?.isVisible)}
isCollapsedMode="expanded"
level={level}
itemsLength={sectionInstance?.visibleItemIds.length || 0}
expandSection={() => {}}
collapseSection={() => {}}
expandSection={expandSection}
collapseSection={collapseSection}
/>
{sectionInstance &&
sectionInstance.isVisible && // !isCollapsed &&
sectionInstance.isVisible &&
!isCollapsed &&
itemBlueprint &&
sectionInstance.groups.length === 0 &&
sectionInstance.visibleItemIds.map(itemId => (
Expand All @@ -101,6 +165,7 @@ function SectionRenderer<ItemType, ScopeType>(props: SectionRendererProps<ItemTy
/>
))}
{sectionInstance?.isVisible &&
!isCollapsed &&
itemBlueprint &&
groupInstanceById &&
sectionInstance.visibleGroupIds.map(groupId => {
Expand Down
6 changes: 5 additions & 1 deletion src/models/navigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,11 @@ export interface SectionInstance {
shouldExpand: boolean;
}

export interface NavigatorState {
export interface NavigatorInstanceState {
sectionInstanceMap: Record<string, SectionInstance>;
itemInstanceMap: Record<string, ItemInstance>;
}

export interface NavigatorState extends NavigatorInstanceState {
collapsedSectionIds: string[];
}
25 changes: 16 additions & 9 deletions src/navsections/sectionBlueprintMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import {shallowEqual} from 'react-redux';
import {Middleware} from 'redux';
import {updateNavigatorState} from '@redux/reducers/navigator';
import {ItemInstance, NavigatorState, SectionInstance} from '@models/navigator';
import {collapseSectionIds, expandSectionIds, updateNavigatorInstanceState} from '@redux/reducers/navigator';
import {ItemInstance, NavigatorInstanceState, SectionInstance} from '@models/navigator';
import {AppDispatch, RootState} from '@redux/store';
import asyncLib from 'async';
import sectionBlueprintMap from './sectionBlueprintMap';

const fullScopeCache: Record<string, any> = {};

const pickPartialRecord = (record: Record<string, any>, keys: string[]) => {
// return Object.fromEntries(Object.entries(record).filter(([key]) => keys.includes(key)));
return Object.entries(record)
.filter(([key]) => keys.includes(key))
.reduce<Record<string, any>>((acc, [k, v]) => {
Expand All @@ -18,8 +17,11 @@ const pickPartialRecord = (record: Record<string, any>, keys: string[]) => {
}, {});
};

const hasNavigatorStateChanged = (navigatorState: NavigatorState, newNavigatorState: NavigatorState) => {
const {itemInstanceMap, sectionInstanceMap} = newNavigatorState;
const hasNavigatorInstanceStateChanged = (
navigatorState: NavigatorInstanceState,
newNavigatorInstanceState: NavigatorInstanceState
) => {
const {itemInstanceMap, sectionInstanceMap} = newNavigatorInstanceState;
return (
!shallowEqual(pickPartialRecord(navigatorState.itemInstanceMap, Object.keys(itemInstanceMap)), itemInstanceMap) ||
!shallowEqual(
Expand Down Expand Up @@ -187,19 +189,24 @@ const processSectionBlueprints = (state: RootState, dispatch: AppDispatch) => {
return;
}

const newNavigatorState: NavigatorState = {
const newNavigatorInstanceState: NavigatorInstanceState = {
sectionInstanceMap,
itemInstanceMap,
};

if (hasNavigatorStateChanged(state.navigator, newNavigatorState)) {
dispatch(updateNavigatorState(newNavigatorState));
if (hasNavigatorInstanceStateChanged(state.navigator, newNavigatorInstanceState)) {
dispatch(updateNavigatorInstanceState(newNavigatorInstanceState));
}
};

export const sectionBlueprintMiddleware: Middleware = store => next => action => {
next(action);
if (action?.type === updateNavigatorState.type) {
// ignore actions that will not affect any section scope
if (
action?.type === updateNavigatorInstanceState.type ||
action?.type === expandSectionIds.type ||
action?.type === collapseSectionIds.type
) {
console.log('Not processing.');
return;
}
Expand Down
1 change: 1 addition & 0 deletions src/redux/initialState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ const initialUiState: UiState = {
const initialNavigatorState: NavigatorState = {
sectionInstanceMap: {},
itemInstanceMap: {},
collapsedSectionIds: [],
};

export default {
Expand Down
16 changes: 13 additions & 3 deletions src/redux/reducers/navigator.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {createSlice, Draft, PayloadAction} from '@reduxjs/toolkit';
import initialState from '@redux/initialState';
import {NavigatorState} from '@models/navigator';
import {NavigatorInstanceState, NavigatorState} from '@models/navigator';

export const navigatorSlice = createSlice({
name: 'navigator',
initialState: initialState.navigator,
reducers: {
updateNavigatorState: (state: Draft<NavigatorState>, action: PayloadAction<NavigatorState>) => {
updateNavigatorInstanceState: (state: Draft<NavigatorState>, action: PayloadAction<NavigatorInstanceState>) => {
const {sectionInstanceMap, itemInstanceMap} = action.payload;
Object.entries(sectionInstanceMap).forEach(([sectionId, sectionInstance]) => {
state.sectionInstanceMap[sectionId] = sectionInstance;
Expand All @@ -15,8 +15,18 @@ export const navigatorSlice = createSlice({
state.itemInstanceMap[itemId] = itemInstance;
});
},
collapseSectionIds: (state: Draft<NavigatorState>, action: PayloadAction<string[]>) => {
action.payload.forEach(sectionId => {
if (!state.collapsedSectionIds.includes(sectionId)) {
state.collapsedSectionIds.push(sectionId);
}
});
},
expandSectionIds: (state: Draft<NavigatorState>, action: PayloadAction<string[]>) => {
state.collapsedSectionIds = state.collapsedSectionIds.filter(sectionId => !action.payload.includes(sectionId));
},
},
});

export const {updateNavigatorState} = navigatorSlice.actions;
export const {updateNavigatorInstanceState, collapseSectionIds, expandSectionIds} = navigatorSlice.actions;
export default navigatorSlice.reducer;

0 comments on commit a786c05

Please sign in to comment.