add Search to Library (#55)
* Implement bare bones library search * Add nice interactions
This commit is contained in:
@@ -23,10 +23,15 @@ function unreadFilter(unread: NullAndUndefined<boolean>, { unreadCount }: IManga
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function queryFilter(query: NullAndUndefined<string>, { title }: IMangaCard): boolean {
|
||||||
|
if (!query) return true;
|
||||||
|
return title.toLowerCase().includes(query.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
function filterManga(mangas: IMangaCard[]): IMangaCard[] {
|
function filterManga(mangas: IMangaCard[]): IMangaCard[] {
|
||||||
const { unread } = useLibraryOptions();
|
const { unread, query } = useLibraryOptions();
|
||||||
return mangas
|
return mangas
|
||||||
.filter((manga) => unreadFilter(unread, manga));
|
.filter((manga) => unreadFilter(unread, manga) && queryFilter(query, manga));
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LibraryMangaGrid(props: IMangaGridProps) {
|
export default function LibraryMangaGrid(props: IMangaGridProps) {
|
||||||
|
|||||||
58
src/components/library/LibrarySearch.tsx
Normal file
58
src/components/library/LibrarySearch.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
import { IconButton, Input } from '@mui/material';
|
||||||
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import useLibraryOptions from '../../util/useLibraryOptions';
|
||||||
|
|
||||||
|
export default function LibrarySearch() {
|
||||||
|
const {
|
||||||
|
query,
|
||||||
|
setQuery,
|
||||||
|
} = useLibraryOptions();
|
||||||
|
const [searchOpen, setSearchOpen] = useState(!!query);
|
||||||
|
const inputRef = React.useRef<HTMLInputElement>();
|
||||||
|
|
||||||
|
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
setQuery(e.target.value === '' ? undefined : e.target.value);
|
||||||
|
}
|
||||||
|
const cancelSearch = () => {
|
||||||
|
setQuery(null);
|
||||||
|
setSearchOpen(false);
|
||||||
|
};
|
||||||
|
const handleBlur = () => { if (!query) setSearchOpen(false); };
|
||||||
|
const openSearch = () => {
|
||||||
|
setSearchOpen(true);
|
||||||
|
// Put Focus Action at the end of the Callstack so Input actually exists on the dom
|
||||||
|
setTimeout(() => {
|
||||||
|
if (inputRef && inputRef.current) inputRef.current.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{searchOpen
|
||||||
|
? (
|
||||||
|
<Input
|
||||||
|
value={query || ''}
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
inputRef={inputRef}
|
||||||
|
endAdornment={(
|
||||||
|
<IconButton
|
||||||
|
onClick={cancelSearch}
|
||||||
|
>
|
||||||
|
<CancelIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : <SearchIcon onClick={openSearch} /> }
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
|
|||||||
import TabPanel from 'components/util/TabPanel';
|
import TabPanel from 'components/util/TabPanel';
|
||||||
import LibraryOptions from 'components/library/LibraryOptions';
|
import LibraryOptions from 'components/library/LibraryOptions';
|
||||||
import LibraryMangaGrid from 'components/library/LibraryMangaGrid';
|
import LibraryMangaGrid from 'components/library/LibraryMangaGrid';
|
||||||
|
import LibrarySearch from 'components/library/LibrarySearch';
|
||||||
|
|
||||||
interface IMangaCategory {
|
interface IMangaCategory {
|
||||||
category: ICategory
|
category: ICategory
|
||||||
@@ -27,6 +28,7 @@ export default function Library() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle('Library'); setAction(
|
setTitle('Library'); setAction(
|
||||||
<>
|
<>
|
||||||
|
<LibrarySearch />
|
||||||
<LibraryOptions />
|
<LibraryOptions />
|
||||||
</>,
|
</>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,27 +6,33 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BooleanParam, useQueryParams } from 'use-query-params';
|
import { BooleanParam, useQueryParams, StringParam } from 'use-query-params';
|
||||||
|
|
||||||
export type NullAndUndefined<T> = T | null | undefined;
|
export type NullAndUndefined<T> = T | null | undefined;
|
||||||
|
|
||||||
interface IUseLibraryOptions {
|
interface IUseLibraryOptions {
|
||||||
unread: NullAndUndefined<boolean>
|
unread: NullAndUndefined<boolean>
|
||||||
setUnread: (unread: NullAndUndefined<boolean>) => void
|
setUnread: (unread: NullAndUndefined<boolean>) => void
|
||||||
|
query: NullAndUndefined<string>
|
||||||
|
setQuery: (query: NullAndUndefined<string>) => void
|
||||||
active: boolean
|
active: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useLibraryOptions(): IUseLibraryOptions {
|
export default function useLibraryOptions(): IUseLibraryOptions {
|
||||||
const [query, setQuery] = useQueryParams({
|
const [searchQuery, setSearchQuery] = useQueryParams({
|
||||||
unread: BooleanParam,
|
unread: BooleanParam,
|
||||||
|
query: StringParam,
|
||||||
});
|
});
|
||||||
const { unread } = query;
|
const { unread, query } = searchQuery;
|
||||||
const setUnread = (newUnread: NullAndUndefined<boolean>) => {
|
const setUnread = (newUnread: NullAndUndefined<boolean>) => {
|
||||||
setQuery(Object.assign(query, { unread: newUnread }), 'replace');
|
setSearchQuery(Object.assign(searchQuery, { unread: newUnread }), 'replace');
|
||||||
|
};
|
||||||
|
const setQuery = (newQuery: NullAndUndefined<string>) => {
|
||||||
|
setSearchQuery(Object.assign(searchQuery, { query: newQuery }), 'replace');
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line eqeqeq
|
// eslint-disable-next-line eqeqeq
|
||||||
const active = !(unread == undefined);
|
const active = !(unread == undefined);
|
||||||
return {
|
return {
|
||||||
unread, setUnread, active,
|
unread, setUnread, active, query, setQuery,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user