grungnet/src/contexts/GroupContext.js

232 lines
5.8 KiB
JavaScript
Raw Normal View History

import React, { useState, useContext, useEffect } from "react";
import cloneDeep from "lodash.clonedeep";
2021-06-05 02:38:01 -04:00
import Fuse from "fuse.js";
import { useKeyboard, useBlur } from "./KeyboardContext";
2021-05-24 01:17:23 -04:00
import { getGroupItems, groupsFromIds } from "../helpers/group";
import shortcuts from "../shortcuts";
const GroupContext = React.createContext();
export function GroupProvider({
groups,
2021-06-05 02:38:01 -04:00
itemNames,
onGroupsChange,
onGroupsSelect,
disabled,
children,
}) {
const [selectedGroupIds, setSelectedGroupIds] = useState([]);
// Either single, multiple or range
const [selectMode, setSelectMode] = useState("single");
2021-06-05 03:16:39 -04:00
/**
* Group Open
*/
2021-06-05 02:38:01 -04:00
const [openGroupId, setOpenGroupId] = useState();
const [openGroupItems, setOpenGroupItems] = useState([]);
useEffect(() => {
if (openGroupId) {
setOpenGroupItems(getGroupItems(groupsFromIds([openGroupId], groups)[0]));
} else {
setOpenGroupItems([]);
}
}, [openGroupId, groups]);
function handleGroupOpen(groupId) {
setSelectedGroupIds([]);
setOpenGroupId(groupId);
}
function handleGroupClose() {
setSelectedGroupIds([]);
setOpenGroupId();
}
2021-06-05 03:16:39 -04:00
/**
* Search
*/
const [filter, setFilter] = useState();
const [filteredGroupItems, setFilteredGroupItems] = useState([]);
const [fuse, setFuse] = useState();
// Update search index when items change
useEffect(() => {
let items = [];
for (let group of groups) {
const itemsToAdd = getGroupItems(group);
const namedItems = itemsToAdd.map((item) => ({
...item,
name: itemNames[item.id],
}));
items.push(...namedItems);
}
setFuse(new Fuse(items, { keys: ["name"] }));
}, [groups, itemNames]);
// Perform search when search changes
useEffect(() => {
if (filter) {
const query = fuse.search(filter);
setFilteredGroupItems(query.map((result) => result.item));
setOpenGroupId();
} else {
setFilteredGroupItems([]);
}
}, [filter, fuse]);
/**
* Handlers
*/
const activeGroups = openGroupId
? openGroupItems
: filter
? filteredGroupItems
: groups;
/**
* @param {string|undefined} groupId The group to apply changes to, leave undefined to replace the full group object
*/
function handleGroupsChange(newGroups, groupId) {
if (groupId) {
// If a group is specidifed then update that group with the new items
const groupIndex = groups.findIndex((group) => group.id === groupId);
let updatedGroups = cloneDeep(groups);
const group = updatedGroups[groupIndex];
updatedGroups[groupIndex] = { ...group, items: newGroups };
onGroupsChange(updatedGroups);
} else {
onGroupsChange(newGroups);
}
}
function handleGroupSelect(groupId) {
let groupIds = [];
if (groupId) {
switch (selectMode) {
case "single":
groupIds = [groupId];
break;
case "multiple":
if (selectedGroupIds.includes(groupId)) {
groupIds = selectedGroupIds.filter((id) => id !== groupId);
} else {
groupIds = [...selectedGroupIds, groupId];
}
break;
case "range":
2021-06-05 03:16:39 -04:00
if (selectedGroupIds.length > 0) {
const currentIndex = activeGroups.findIndex(
(g) => g.id === groupId
);
const lastIndex = activeGroups.findIndex(
(g) => g.id === selectedGroupIds[selectedGroupIds.length - 1]
);
let idsToAdd = [];
let idsToRemove = [];
const direction = currentIndex > lastIndex ? 1 : -1;
for (
let i = lastIndex + direction;
direction < 0 ? i >= currentIndex : i <= currentIndex;
i += direction
) {
const id = activeGroups[i].id;
if (selectedGroupIds.includes(id)) {
idsToRemove.push(id);
} else {
idsToAdd.push(id);
}
}
groupIds = [...selectedGroupIds, ...idsToAdd].filter(
(id) => !idsToRemove.includes(id)
);
} else {
groupIds = [groupId];
}
break;
default:
groupIds = [];
}
}
setSelectedGroupIds(groupIds);
onGroupsSelect(groupIds);
}
/**
* Shortcuts
*/
function handleKeyDown(event) {
if (disabled) {
return;
}
if (shortcuts.selectRange(event)) {
setSelectMode("range");
}
if (shortcuts.selectMultiple(event)) {
setSelectMode("multiple");
}
}
function handleKeyUp(event) {
if (disabled) {
return;
}
if (shortcuts.selectRange(event) && selectMode === "range") {
setSelectMode("single");
}
if (shortcuts.selectMultiple(event) && selectMode === "multiple") {
setSelectMode("single");
}
}
useKeyboard(handleKeyDown, handleKeyUp);
// Set select mode to single when cmd+tabing
function handleBlur() {
setSelectMode("single");
}
useBlur(handleBlur);
const value = {
groups,
2021-06-05 02:38:01 -04:00
activeGroups,
openGroupId,
openGroupItems,
2021-06-05 02:38:01 -04:00
filter,
filteredGroupItems,
selectedGroupIds,
selectMode,
2021-06-05 02:38:01 -04:00
onSelectModeChange: setSelectMode,
onGroupOpen: handleGroupOpen,
onGroupClose: handleGroupClose,
onGroupsChange: handleGroupsChange,
onGroupSelect: handleGroupSelect,
2021-06-05 02:38:01 -04:00
onFilterChange: setFilter,
};
return (
<GroupContext.Provider value={value}>{children}</GroupContext.Provider>
);
}
2021-06-04 23:04:26 -04:00
GroupProvider.defaultProps = {
groups: [],
2021-06-05 02:38:01 -04:00
itemNames: {},
2021-06-04 23:04:26 -04:00
onGroupsChange: () => {},
onGroupsSelect: () => {},
disabled: false,
};
export function useGroup() {
const context = useContext(GroupContext);
if (context === undefined) {
throw new Error("useGroup must be used within a GroupProvider");
}
return context;
}
export default GroupContext;