mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix performance issue is selection of bulk ops
This commit is contained in:
parent
f1e8b1769e
commit
cd038c5400
@ -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";
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user