Merge pull request #7 from usebruno/feature/search-collections

feat: search collections, resolves #1
This commit is contained in:
Anoop M D 2022-10-01 18:24:32 +05:30 committed by GitHub
commit 2313afdb82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 75 additions and 11 deletions

View File

@ -1,4 +1,4 @@
import React, { useState, useRef, forwardRef } from 'react'; import React, { useState, useRef, forwardRef, useEffect } from 'react';
import range from 'lodash/range'; import range from 'lodash/range';
import classnames from 'classnames'; import classnames from 'classnames';
import { IconChevronRight, IconDots } from '@tabler/icons'; import { IconChevronRight, IconDots } from '@tabler/icons';
@ -13,10 +13,11 @@ import RenameCollectionItem from './RenameCollectionItem';
import CloneCollectionItem from './CloneCollectionItem'; import CloneCollectionItem from './CloneCollectionItem';
import DeleteCollectionItem from './DeleteCollectionItem'; import DeleteCollectionItem from './DeleteCollectionItem';
import { isItemARequest, isItemAFolder, itemIsOpenedInTabs } from 'utils/tabs'; import { isItemARequest, isItemAFolder, itemIsOpenedInTabs } from 'utils/tabs';
import { doesRequestMatchSearchText, doesFolderHaveItemsMatchSearchText } from 'utils/collections/search';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const CollectionItem = ({item, collection}) => { const CollectionItem = ({item, collection, searchText}) => {
const tabs = useSelector((state) => state.tabs.tabs); const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid); const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const isDragging = useSelector((state) => state.app.isDragging); const isDragging = useSelector((state) => state.app.isDragging);
@ -27,6 +28,15 @@ const CollectionItem = ({item, collection}) => {
const [deleteItemModalOpen, setDeleteItemModalOpen] = useState(false); const [deleteItemModalOpen, setDeleteItemModalOpen] = useState(false);
const [newRequestModalOpen, setNewRequestModalOpen] = useState(false); const [newRequestModalOpen, setNewRequestModalOpen] = useState(false);
const [newFolderModalOpen, setNewFolderModalOpen] = useState(false); const [newFolderModalOpen, setNewFolderModalOpen] = useState(false);
const [itemIsCollapsed, setItemisCollapsed] = useState(item.collapsed);
useEffect(() => {
if (searchText && searchText.length) {
setItemisCollapsed(false);
} else {
setItemisCollapsed(item.collapsed);
}
}, [searchText, item]);
const dropdownTippyRef = useRef(); const dropdownTippyRef = useRef();
const MenuIcon = forwardRef((props, ref) => { const MenuIcon = forwardRef((props, ref) => {
@ -38,7 +48,7 @@ const CollectionItem = ({item, collection}) => {
}); });
const iconClassName = classnames({ const iconClassName = classnames({
'rotate-90': item.collapsed 'rotate-90': !itemIsCollapsed
}); });
const itemRowClassName = classnames('flex collection-item-name items-center', { const itemRowClassName = classnames('flex collection-item-name items-center', {
@ -73,6 +83,18 @@ const CollectionItem = ({item, collection}) => {
'is-dragging': isDragging 'is-dragging': isDragging
}); });
if(searchText && searchText.length) {
if(isItemARequest(item)) {
if(!doesRequestMatchSearchText(item, searchText)) {
return null;
}
} else {
if (!doesFolderHaveItemsMatchSearchText(item, searchText)) {
return null;
};
}
}
return ( return (
<StyledWrapper className={className}> <StyledWrapper className={className}>
{renameItemModalOpen && <RenameCollectionItem item={item} collection={collection} onClose={() => setRenameItemModalOpen(false)}/>} {renameItemModalOpen && <RenameCollectionItem item={item} collection={collection} onClose={() => setRenameItemModalOpen(false)}/>}
@ -159,13 +181,14 @@ const CollectionItem = ({item, collection}) => {
</div> </div>
</div> </div>
{item.collapsed ? ( {!itemIsCollapsed ? (
<div> <div>
{item.items && item.items.length ? item.items.map((i) => { {item.items && item.items.length ? item.items.map((i) => {
return <CollectionItem return <CollectionItem
key={i.uid} key={i.uid}
item={i} item={i}
collection={collection} collection={collection}
searchText={searchText}
/> />
}) : null} }) : null}
</div> </div>

View File

@ -1,4 +1,4 @@
import React, { useState, forwardRef, useRef } from 'react'; import React, { useState, forwardRef, useRef, useEffect } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { IconChevronRight, IconDots } from '@tabler/icons'; import { IconChevronRight, IconDots } from '@tabler/icons';
import Dropdown from 'components/Dropdown'; import Dropdown from 'components/Dropdown';
@ -7,14 +7,24 @@ import { useDispatch } from 'react-redux';
import NewRequest from 'components/Sidebar/NewRequest'; import NewRequest from 'components/Sidebar/NewRequest';
import NewFolder from 'components/Sidebar/NewFolder'; import NewFolder from 'components/Sidebar/NewFolder';
import CollectionItem from './CollectionItem'; import CollectionItem from './CollectionItem';
import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const Collection = ({collection}) => { const Collection = ({collection, searchText}) => {
const [showNewFolderModal, setShowNewFolderModal] = useState(false); const [showNewFolderModal, setShowNewFolderModal] = useState(false);
const [showNewRequestModal, setShowNewRequestModal] = useState(false); const [showNewRequestModal, setShowNewRequestModal] = useState(false);
const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed);
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => {
if (searchText && searchText.length) {
setCollectionIsCollapsed(false);
} else {
setCollectionIsCollapsed(collection.collapsed);
}
}, [searchText, collection]);
const menuDropdownTippyRef = useRef(); const menuDropdownTippyRef = useRef();
const onMenuDropdownCreate = (ref) => menuDropdownTippyRef.current = ref; const onMenuDropdownCreate = (ref) => menuDropdownTippyRef.current = ref;
const MenuIcon = forwardRef((props, ref) => { const MenuIcon = forwardRef((props, ref) => {
@ -26,13 +36,19 @@ const Collection = ({collection}) => {
}); });
const iconClassName = classnames({ const iconClassName = classnames({
'rotate-90': !collection.collapsed 'rotate-90': !collectionIsCollapsed
}); });
const handleClick = (event) => { const handleClick = (event) => {
dispatch(collectionClicked(collection.uid)); dispatch(collectionClicked(collection.uid));
}; };
if(searchText && searchText.length) {
if(!doesCollectionHaveItemsMatchingSearchText(collection, searchText)) {
return null;
}
}
return ( return (
<StyledWrapper className="flex flex-col"> <StyledWrapper className="flex flex-col">
{showNewRequestModal && <NewRequest collection={collection} onClose={() => setShowNewRequestModal(false)}/>} {showNewRequestModal && <NewRequest collection={collection} onClose={() => setShowNewRequestModal(false)}/>}
@ -71,13 +87,14 @@ const Collection = ({collection}) => {
</div> </div>
<div> <div>
{!collection.collapsed ? ( {!collectionIsCollapsed ? (
<div> <div>
{collection.items && collection.items.length ? collection.items.map((i) => { {collection.items && collection.items.length ? collection.items.map((i) => {
return <CollectionItem return <CollectionItem
key={i.uid} key={i.uid}
item={i} item={i}
collection={collection} collection={collection}
searchText={searchText}
/> />
}) : null} }) : null}
</div> </div>

View File

@ -1,8 +1,8 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import Collection from './Collection'; import Collection from './Collection';
const Collections = () => { const Collections = ({searchText}) => {
const collections = useSelector((state) => state.collections.collections); const collections = useSelector((state) => state.collections.collections);
console.log(collections); console.log(collections);
@ -10,6 +10,7 @@ const Collections = () => {
<div className="mt-4 flex flex-col"> <div className="mt-4 flex flex-col">
{collections && collections.length ? collections.map((c) => { {collections && collections.length ? collections.map((c) => {
return <Collection return <Collection
searchText={searchText}
collection={c} collection={c}
key={c.uid} key={c.uid}
/> />

View File

@ -18,6 +18,7 @@ const Sidebar = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [dragging, setDragging] = useState(false); const [dragging, setDragging] = useState(false);
const [searchText, setSearchText] = useState('');
const handleMouseMove = (e) => { const handleMouseMove = (e) => {
if(dragging) { if(dragging) {
@ -86,10 +87,11 @@ const Sidebar = () => {
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
className="block w-full pl-7 py-1 sm:text-sm" className="block w-full pl-7 py-1 sm:text-sm"
placeholder="search" placeholder="search"
onChange={(e) => setSearchText(e.target.value.toLowerCase())}
/> />
</div> </div>
<Collections /> <Collections searchText={searchText}/>
</div> </div>
<div className="flex px-1 py-2 items-center cursor-pointer text-gray-500 select-none"> <div className="flex px-1 py-2 items-center cursor-pointer text-gray-500 select-none">
<div className="flex items-center ml-1 text-xs "> <div className="flex items-center ml-1 text-xs ">

View File

@ -0,0 +1,21 @@
import { flattenItems, isItemARequest } from "./index";
import filter from 'lodash/filter';
import find from 'lodash/find';
export const doesRequestMatchSearchText = (request, searchText) => {
return request.name.toLowerCase().includes(searchText.toLowerCase());
};
export const doesFolderHaveItemsMatchSearchText = (item, searchText) => {
let flattenedItems = flattenItems(item.items);
let requestItems = filter(flattenedItems, (item) => isItemARequest(item));
return find(requestItems, (request) => doesRequestMatchSearchText(request, searchText));
};
export const doesCollectionHaveItemsMatchingSearchText = (collection, searchText) => {
let flattenedItems = flattenItems(collection.items);
let requestItems = filter(flattenedItems, (item) => isItemARequest(item));
return find(requestItems, (request) => doesRequestMatchSearchText(request, searchText));
}