fix performance issue is selection of bulk ops

This commit is contained in:
rahulramesha 2024-05-24 14:13:33 +05:30
parent f1e8b1769e
commit cd038c5400
5 changed files with 61 additions and 50 deletions

View File

@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import { TSelectionHelper, TSelectionSnapshot, useMultipleSelect } from "@/hooks/use-multiple-select";
type Props = {
children: (helpers: TSelectionHelper, snapshot: TSelectionSnapshot) => React.ReactNode;
children: (helpers: TSelectionHelper) => React.ReactNode;
containerRef: React.MutableRefObject<HTMLElement | null>;
entities: Record<string, string[]>; // { groupID: entityIds[] }
};
@ -11,10 +11,12 @@ type Props = {
export const MultipleSelectGroup: React.FC<Props> = observer((props) => {
const { children, containerRef, entities } = props;
const { helpers, snapshot } = useMultipleSelect({
const helpers = useMultipleSelect({
containerRef,
entities,
});
return <>{children(helpers, snapshot)}</>;
return <>{children(helpers)}</>;
});
MultipleSelectGroup.displayName = "MultipleSelectGroup";

View File

@ -14,15 +14,15 @@ import {
import { cn } from "@/helpers/common.helper";
// hooks
import { TSelectionHelper, TSelectionSnapshot } from "@/hooks/use-multiple-select";
import { useMultipleSelectStore } from "@/hooks/store";
type Props = {
className?: string;
selectionHelpers: TSelectionHelper;
snapshot: TSelectionSnapshot;
};
export const IssueBulkOperationsRoot: React.FC<Props> = observer((props) => {
const { className, selectionHelpers, snapshot } = props;
const { className, selectionHelpers } = props;
// states
const [isBulkArchiveModalOpen, setIsBulkArchiveModalOpen] = useState(false);
const [isBulkDeleteModalOpen, setIsBulkDeleteModalOpen] = useState(false);
@ -30,10 +30,10 @@ export const IssueBulkOperationsRoot: React.FC<Props> = observer((props) => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// serviced values
const { isSelectionActive, selectedEntityIds } = snapshot;
const { isSelectionActive, selectedEntityIds } = useMultipleSelectStore();
const { handleClearSelection } = selectionHelpers;
if (!snapshot.isSelectionActive) return null;
if (!isSelectionActive) return null;
return (
<div className="sticky bottom-0 left-0 z-[2] h-14">
@ -109,7 +109,10 @@ export const IssueBulkOperationsRoot: React.FC<Props> = observer((props) => {
</Tooltip>
</div>
<div className="h-7 pl-3 flex-grow">
<IssueBulkOperationsProperties selectionHelpers={selectionHelpers} snapshot={snapshot} />
<IssueBulkOperationsProperties
selectionHelpers={selectionHelpers}
snapshot={{ isSelectionActive, selectedEntityIds }}
/>
</div>
</div>
</div>

View File

@ -139,7 +139,7 @@ const GroupByList: React.FC<IGroupByList> = observer((props) => {
>
{groups && (
<MultipleSelectGroup containerRef={containerRef} entities={entities}>
{(helpers, snapshot) => (
{(helpers) => (
<>
{groups.map(
(group: IGroupByColumn) =>
@ -169,7 +169,7 @@ const GroupByList: React.FC<IGroupByList> = observer((props) => {
/>
)
)}
<IssueBulkOperationsRoot selectionHelpers={helpers} snapshot={snapshot} />
<IssueBulkOperationsRoot selectionHelpers={helpers} />
</>
)}
</MultipleSelectGroup>
@ -178,6 +178,8 @@ const GroupByList: React.FC<IGroupByList> = observer((props) => {
);
});
GroupByList.displayName = "GroupByList";
export interface IList {
issueIds: TGroupedIssues | TUnGroupedIssues | any;
issuesMap: TIssueMap;

View File

@ -33,15 +33,14 @@ export const useMultipleSelect = (props: Props) => {
const router = useRouter();
// store hooks
const {
selectedEntityDetails,
updateSelectedEntityDetails,
activeEntityDetails,
getActiveEntityDetails,
updateActiveEntityDetails,
previousActiveEntity,
getPreviousActiveEntity,
updatePreviousActiveEntity,
nextActiveEntity,
getNextActiveEntity,
updateNextActiveEntity,
lastSelectedEntityDetails,
getLastSelectedEntityDetails,
clearSelection,
isEntitySelected,
isEntityActive,
@ -176,6 +175,7 @@ export const useMultipleSelect = (props: Props) => {
*/
const handleEntityClick = useCallback(
(e: React.MouseEvent, entityID: string, groupID: string) => {
const lastSelectedEntityDetails = getLastSelectedEntityDetails();
if (e.shiftKey && lastSelectedEntityDetails) {
const currentEntityIndex = entitiesList.findIndex((entity) => entity?.entityID === entityID);
@ -211,7 +211,7 @@ export const useMultipleSelect = (props: Props) => {
handleEntitySelection({ entityID, groupID }, false);
},
[entitiesList, handleEntitySelection, lastSelectedEntityDetails]
[entitiesList, handleEntitySelection, getLastSelectedEntityDetails]
);
/**
@ -262,6 +262,9 @@ export const useMultipleSelect = (props: Props) => {
const handleKeyDown = (e: KeyboardEvent) => {
if (!e.shiftKey) return;
const activeEntityDetails = getActiveEntityDetails();
const nextActiveEntity = getNextActiveEntity();
const previousActiveEntity = getPreviousActiveEntity();
if (e.key === "ArrowDown" && activeEntityDetails) {
if (!nextActiveEntity) return;
// console.log("selected by down", elementDetails.entityID);
@ -279,15 +282,16 @@ export const useMultipleSelect = (props: Props) => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [
activeEntityDetails,
getActiveEntityDetails,
handleEntitySelection,
lastSelectedEntityDetails?.entityID,
nextActiveEntity,
previousActiveEntity,
getLastSelectedEntityDetails,
getNextActiveEntity,
getPreviousActiveEntity,
]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const activeEntityDetails = getActiveEntityDetails();
// set active entity id to the first entity
if (["ArrowUp", "ArrowDown"].includes(e.key) && !activeEntityDetails) {
const firstElementDetails = entitiesList[0];
@ -317,7 +321,7 @@ export const useMultipleSelect = (props: Props) => {
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [activeEntityDetails, entitiesList, groups, getPreviousAndNextEntities, handleActiveEntityChange]);
}, [getActiveEntityDetails, entitiesList, groups, getPreviousAndNextEntities, handleActiveEntityChange]);
// clear selection on route change
useEffect(() => {
@ -330,17 +334,6 @@ export const useMultipleSelect = (props: Props) => {
};
}, [clearSelection, router.events]);
/**
* @description snapshot of the current state of selection
*/
const snapshot: TSelectionSnapshot = useMemo(
() => ({
isSelectionActive: selectedEntityDetails.length > 0,
selectedEntityIds: selectedEntityDetails.map((en) => en.entityID),
}),
[selectedEntityDetails]
);
/**
* @description helper functions for selection
*/
@ -353,16 +346,8 @@ export const useMultipleSelect = (props: Props) => {
handleGroupClick,
isGroupSelected,
}),
[clearSelection, handleEntityClick, handleGroupClick, isEntityActive, isEntitySelected, isGroupSelected]
[handleEntityClick, handleGroupClick, isEntityActive, isEntitySelected, isGroupSelected]
);
const returnValue = useMemo(
() => ({
helpers,
snapshot,
}),
[helpers, snapshot]
);
return returnValue;
return helpers;
};

View File

@ -1,19 +1,21 @@
import { action, makeObservable, observable, runInAction } from "mobx";
import { action, computed, makeObservable, observable, runInAction } from "mobx";
// hooks
import { TEntityDetails } from "@/hooks/use-multiple-select";
// services
import { IssueService } from "@/services/issue";
import { computedFn } from "mobx-utils";
export type IMultipleSelectStore = {
// observables
selectedEntityDetails: TEntityDetails[];
lastSelectedEntityDetails: TEntityDetails | null;
previousActiveEntity: TEntityDetails | null;
nextActiveEntity: TEntityDetails | null;
activeEntityDetails: TEntityDetails | null;
isSelectionActive: boolean;
selectedEntityIds: string[];
// helper actions
isEntitySelected: (entityID: string) => boolean;
isEntityActive: (entityID: string) => boolean;
getLastSelectedEntityDetails: () => TEntityDetails | null;
getPreviousActiveEntity: () => TEntityDetails | null;
getNextActiveEntity: () => TEntityDetails | null;
getActiveEntityDetails: () => TEntityDetails | null;
// entity actions
updateSelectedEntityDetails: (entityDetails: TEntityDetails, action: "add" | "remove") => void;
updateLastSelectedEntityDetails: (entityDetails: TEntityDetails | null) => void;
@ -42,6 +44,8 @@ export class MultipleSelectStore implements IMultipleSelectStore {
nextActiveEntity: observable,
activeEntityDetails: observable,
// entity actions
isSelectionActive: computed,
selectedEntityIds: computed,
updateSelectedEntityDetails: action,
updateLastSelectedEntityDetails: action,
updatePreviousActiveEntity: action,
@ -53,20 +57,35 @@ export class MultipleSelectStore implements IMultipleSelectStore {
this.issueService = new IssueService();
}
get isSelectionActive() {
return this.selectedEntityDetails.length > 0;
}
get selectedEntityIds() {
return this.selectedEntityDetails.map((en) => en.entityID);
}
// helper actions
/**
* @description returns if the entity is selected or not
* @param {string} entityID
* @returns {boolean}
*/
isEntitySelected = (entityID: string): boolean => this.selectedEntityDetails.some((en) => en.entityID === entityID);
isEntitySelected = computedFn((entityID: string): boolean =>
this.selectedEntityDetails.some((en) => en.entityID === entityID)
);
/**
* @description returns if the entity is active or not
* @param {string} entityID
* @returns {boolean}
*/
isEntityActive = (entityID: string): boolean => this.activeEntityDetails?.entityID === entityID;
isEntityActive = computedFn((entityID: string): boolean => this.activeEntityDetails?.entityID === entityID);
getLastSelectedEntityDetails = computedFn(() => this.lastSelectedEntityDetails);
getPreviousActiveEntity = computedFn(() => this.previousActiveEntity);
getNextActiveEntity = computedFn(() => this.nextActiveEntity);
getActiveEntityDetails = computedFn(() => this.activeEntityDetails);
// entity actions
/**