[WEB-1501] dev: multiple select core components (#4667)
* dev: multiple select core components * chore: added export statement
This commit is contained in:
parent
c8c86a33f8
commit
98ebe88c86
10 changed files with 691 additions and 0 deletions
220
web/store/multiple_select.store.ts
Normal file
220
web/store/multiple_select.store.ts
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
import differenceWith from "lodash/differenceWith";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import remove from "lodash/remove";
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
import { computedFn } from "mobx-utils";
|
||||
// hooks
|
||||
import { TEntityDetails } from "@/hooks/use-multiple-select";
|
||||
// services
|
||||
import { IssueService } from "@/services/issue";
|
||||
|
||||
export type IMultipleSelectStore = {
|
||||
// computed functions
|
||||
isSelectionActive: boolean;
|
||||
selectedEntityIds: string[];
|
||||
// helper actions
|
||||
getIsEntitySelected: (entityID: string) => boolean;
|
||||
getIsEntityActive: (entityID: string) => boolean;
|
||||
getLastSelectedEntityDetails: () => TEntityDetails | null;
|
||||
getPreviousActiveEntity: () => TEntityDetails | null;
|
||||
getNextActiveEntity: () => TEntityDetails | null;
|
||||
getActiveEntityDetails: () => TEntityDetails | null;
|
||||
// entity actions
|
||||
updateSelectedEntityDetails: (entityDetails: TEntityDetails, action: "add" | "remove") => void;
|
||||
bulkUpdateSelectedEntityDetails: (entitiesList: TEntityDetails[], action: "add" | "remove") => void;
|
||||
updateLastSelectedEntityDetails: (entityDetails: TEntityDetails | null) => void;
|
||||
updatePreviousActiveEntity: (entityDetails: TEntityDetails | null) => void;
|
||||
updateNextActiveEntity: (entityDetails: TEntityDetails | null) => void;
|
||||
updateActiveEntityDetails: (entityDetails: TEntityDetails | null) => void;
|
||||
clearSelection: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description the MultipleSelectStore manages multiple selection states by keeping track of the selected entities and providing a bunch of helper functions and actions to maintain the selected states
|
||||
* @description use the useMultipleSelectStore custom hook to access the observables
|
||||
* @description use the useMultipleSelect custom hook for added functionality on top of the store, including-
|
||||
* 1. Keyboard and mouse interaction
|
||||
* 2. Clear state on route change
|
||||
*/
|
||||
export class MultipleSelectStore implements IMultipleSelectStore {
|
||||
// observables
|
||||
selectedEntityDetails: TEntityDetails[] = [];
|
||||
lastSelectedEntityDetails: TEntityDetails | null = null;
|
||||
previousActiveEntity: TEntityDetails | null = null;
|
||||
nextActiveEntity: TEntityDetails | null = null;
|
||||
activeEntityDetails: TEntityDetails | null = null;
|
||||
// service
|
||||
issueService;
|
||||
|
||||
constructor() {
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
selectedEntityDetails: observable,
|
||||
lastSelectedEntityDetails: observable,
|
||||
previousActiveEntity: observable,
|
||||
nextActiveEntity: observable,
|
||||
activeEntityDetails: observable,
|
||||
// computed functions
|
||||
isSelectionActive: computed,
|
||||
selectedEntityIds: computed,
|
||||
// actions
|
||||
updateSelectedEntityDetails: action,
|
||||
bulkUpdateSelectedEntityDetails: action,
|
||||
updateLastSelectedEntityDetails: action,
|
||||
updatePreviousActiveEntity: action,
|
||||
updateNextActiveEntity: action,
|
||||
updateActiveEntityDetails: action,
|
||||
clearSelection: action,
|
||||
});
|
||||
|
||||
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}
|
||||
*/
|
||||
getIsEntitySelected = 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}
|
||||
*/
|
||||
getIsEntityActive = computedFn((entityID: string): boolean => this.activeEntityDetails?.entityID === entityID);
|
||||
|
||||
/**
|
||||
* @description get the last selected entity details
|
||||
* @returns {TEntityDetails}
|
||||
*/
|
||||
getLastSelectedEntityDetails = computedFn(() => this.lastSelectedEntityDetails);
|
||||
|
||||
/**
|
||||
* @description get the details of the entity preceding the active entity
|
||||
* @returns {TEntityDetails}
|
||||
*/
|
||||
getPreviousActiveEntity = computedFn(() => this.previousActiveEntity);
|
||||
|
||||
/**
|
||||
* @description get the details of the entity succeeding the active entity
|
||||
* @returns {TEntityDetails}
|
||||
*/
|
||||
getNextActiveEntity = computedFn(() => this.nextActiveEntity);
|
||||
|
||||
/**
|
||||
* @description get the active entity details
|
||||
* @returns {TEntityDetails}
|
||||
*/
|
||||
getActiveEntityDetails = computedFn(() => this.activeEntityDetails);
|
||||
|
||||
// entity actions
|
||||
/**
|
||||
* @description add or remove entities
|
||||
* @param {TEntityDetails} entityDetails
|
||||
* @param {"add" | "remove"} action
|
||||
*/
|
||||
updateSelectedEntityDetails = (entityDetails: TEntityDetails, action: "add" | "remove") => {
|
||||
if (action === "add") {
|
||||
runInAction(() => {
|
||||
if (this.getIsEntitySelected(entityDetails.entityID)) {
|
||||
remove(this.selectedEntityDetails, (en) => en.entityID === entityDetails.entityID);
|
||||
}
|
||||
this.selectedEntityDetails.push(entityDetails);
|
||||
this.updateLastSelectedEntityDetails(entityDetails);
|
||||
});
|
||||
} else {
|
||||
let currentSelection = [...this.selectedEntityDetails];
|
||||
currentSelection = currentSelection.filter((en) => en.entityID !== entityDetails.entityID);
|
||||
runInAction(() => {
|
||||
remove(this.selectedEntityDetails, (en) => en.entityID === entityDetails.entityID);
|
||||
this.updateLastSelectedEntityDetails(currentSelection[currentSelection.length - 1] ?? null);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description add or remove multiple entities
|
||||
* @param {TEntityDetails[]} entitiesList
|
||||
* @param {"add" | "remove"} action
|
||||
*/
|
||||
bulkUpdateSelectedEntityDetails = (entitiesList: TEntityDetails[], action: "add" | "remove") => {
|
||||
if (action === "add") {
|
||||
runInAction(() => {
|
||||
let newEntities: TEntityDetails[] = [];
|
||||
newEntities = differenceWith(this.selectedEntityDetails, entitiesList, isEqual);
|
||||
newEntities = newEntities.concat(entitiesList);
|
||||
this.selectedEntityDetails = newEntities;
|
||||
if (entitiesList.length > 0) this.updateLastSelectedEntityDetails(entitiesList[entitiesList.length - 1]);
|
||||
});
|
||||
} else {
|
||||
runInAction(() => {
|
||||
this.selectedEntityDetails = differenceWith(this.selectedEntityDetails, entitiesList, isEqual);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description update last selected entity
|
||||
* @param {TEntityDetails} entityDetails
|
||||
*/
|
||||
updateLastSelectedEntityDetails = (entityDetails: TEntityDetails | null) => {
|
||||
runInAction(() => {
|
||||
this.lastSelectedEntityDetails = entityDetails;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description update previous active entity
|
||||
* @param {TEntityDetails} entityDetails
|
||||
*/
|
||||
updatePreviousActiveEntity = (entityDetails: TEntityDetails | null) => {
|
||||
runInAction(() => {
|
||||
this.previousActiveEntity = entityDetails;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description update next active entity
|
||||
* @param {TEntityDetails} entityDetails
|
||||
*/
|
||||
updateNextActiveEntity = (entityDetails: TEntityDetails | null) => {
|
||||
runInAction(() => {
|
||||
this.nextActiveEntity = entityDetails;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description update active entity
|
||||
* @param {TEntityDetails} entityDetails
|
||||
*/
|
||||
updateActiveEntityDetails = (entityDetails: TEntityDetails | null) => {
|
||||
runInAction(() => {
|
||||
this.activeEntityDetails = entityDetails;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description clear selection and reset all the observables
|
||||
*/
|
||||
clearSelection = () => {
|
||||
runInAction(() => {
|
||||
this.selectedEntityDetails = [];
|
||||
this.lastSelectedEntityDetails = null;
|
||||
this.previousActiveEntity = null;
|
||||
this.nextActiveEntity = null;
|
||||
this.activeEntityDetails = null;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import { ILabelStore, LabelStore } from "./label.store";
|
|||
import { IMemberRootStore, MemberRootStore } from "./member";
|
||||
import { IModuleStore, ModulesStore } from "./module.store";
|
||||
import { IModuleFilterStore, ModuleFilterStore } from "./module_filter.store";
|
||||
import { IMultipleSelectStore, MultipleSelectStore } from "./multiple_select.store";
|
||||
import { IProjectPageStore, ProjectPageStore } from "./pages/project-page.store";
|
||||
import { IProjectRootStore, ProjectRootStore } from "./project";
|
||||
import { IProjectViewStore, ProjectViewStore } from "./project-view.store";
|
||||
|
|
@ -48,6 +49,7 @@ export class RootStore {
|
|||
instance: IInstanceStore;
|
||||
user: IUserStore;
|
||||
projectInbox: IProjectInboxStore;
|
||||
multipleSelect: IMultipleSelectStore;
|
||||
|
||||
constructor() {
|
||||
this.router = new RouterStore();
|
||||
|
|
@ -70,6 +72,7 @@ export class RootStore {
|
|||
this.theme = new ThemeStore(this);
|
||||
this.eventTracker = new EventTrackerStore(this);
|
||||
this.instance = new InstanceStore();
|
||||
this.multipleSelect = new MultipleSelectStore();
|
||||
// inbox
|
||||
this.projectInbox = new ProjectInboxStore(this);
|
||||
this.projectPages = new ProjectPageStore(this);
|
||||
|
|
@ -101,5 +104,6 @@ export class RootStore {
|
|||
this.user = new UserStore(this);
|
||||
this.projectInbox = new ProjectInboxStore(this);
|
||||
this.projectPages = new ProjectPageStore(this);
|
||||
this.multipleSelect = new MultipleSelectStore();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue