Implement Unread Filter for Library (#54)
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
"@mui/system": "^5.0.6",
|
||||
"axios": "^0.21.1",
|
||||
"file-selector": "^0.2.4",
|
||||
"query-string": "^7.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-beautiful-dnd": "^13.0.0",
|
||||
"react-dom": "^17.0.2",
|
||||
@@ -20,6 +21,7 @@
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.3",
|
||||
"react-virtuoso": "^1.8.6",
|
||||
"use-query-params": "^1.2.3",
|
||||
"web-vitals": "^2.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
186
src/App.tsx
186
src/App.tsx
@@ -11,6 +11,7 @@ import {
|
||||
Route,
|
||||
Redirect,
|
||||
} from 'react-router-dom';
|
||||
import { QueryParamProvider } from 'use-query-params';
|
||||
import { Container, useMediaQuery } from '@mui/material';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import {
|
||||
@@ -36,20 +37,32 @@ import Browse from 'screens/manga/Browse';
|
||||
|
||||
declare module '@mui/styles/defaultTheme' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface DefaultTheme extends Theme {}
|
||||
interface DefaultTheme extends Theme {
|
||||
}
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const [title, setTitle] = useState<string>('Tachidesk');
|
||||
const [action, setAction] = useState<any>(<div />);
|
||||
const [override, setOverride] = useState<INavbarOverride>({ status: false, value: <div /> });
|
||||
const [override, setOverride] = useState<INavbarOverride>({
|
||||
status: false,
|
||||
value: <div />,
|
||||
});
|
||||
|
||||
const [darkTheme, setDarkTheme] = useLocalStorage<boolean>('darkTheme', true);
|
||||
|
||||
const navBarContext = {
|
||||
title, setTitle, action, setAction, override, setOverride,
|
||||
title,
|
||||
setTitle,
|
||||
action,
|
||||
setAction,
|
||||
override,
|
||||
setOverride,
|
||||
};
|
||||
const darkThemeContext = {
|
||||
darkTheme,
|
||||
setDarkTheme,
|
||||
};
|
||||
const darkThemeContext = { darkTheme, setDarkTheme };
|
||||
|
||||
const theme = React.useMemo(
|
||||
() => createTheme({
|
||||
@@ -82,90 +95,95 @@ export default function App() {
|
||||
<Router>
|
||||
<StyledEngineProvider injectFirst>
|
||||
<ThemeProvider theme={theme}>
|
||||
<NavbarContext.Provider value={navBarContext}>
|
||||
<CssBaseline />
|
||||
<NavBar />
|
||||
<Container
|
||||
id="appMainContainer"
|
||||
maxWidth={false}
|
||||
disableGutters
|
||||
style={{
|
||||
marginTop: theme.spacing(8),
|
||||
marginLeft: isMobileWidth ? '' : theme.spacing(8),
|
||||
marginBottom: isMobileWidth ? theme.spacing(8) : '',
|
||||
width: 'auto',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<QueryParamProvider ReactRouterRoute={Route}>
|
||||
<NavbarContext.Provider value={navBarContext}>
|
||||
<CssBaseline />
|
||||
<NavBar />
|
||||
<Container
|
||||
id="appMainContainer"
|
||||
maxWidth={false}
|
||||
disableGutters
|
||||
style={{
|
||||
marginTop: theme.spacing(8),
|
||||
marginLeft: isMobileWidth ? '' : theme.spacing(8),
|
||||
marginBottom: isMobileWidth ? theme.spacing(8) : '',
|
||||
width: 'auto',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<Switch>
|
||||
{/* General Routes */}
|
||||
<Route
|
||||
exact
|
||||
path="/"
|
||||
render={() => (
|
||||
<Redirect to="/library" />
|
||||
)}
|
||||
/>
|
||||
<Route path="/settings/about">
|
||||
<About />
|
||||
</Route>
|
||||
<Route path="/settings/categories">
|
||||
<Categories />
|
||||
</Route>
|
||||
<Route path="/settings/backup">
|
||||
<Backup />
|
||||
</Route>
|
||||
<Route path="/settings">
|
||||
<DarkTheme.Provider value={darkThemeContext}>
|
||||
<Settings />
|
||||
</DarkTheme.Provider>
|
||||
</Route>
|
||||
|
||||
{/* Manga Routes */}
|
||||
|
||||
<Route path="/sources/:sourceId/search/">
|
||||
<SearchSingle />
|
||||
</Route>
|
||||
<Route path="/sources/:sourceId/popular/">
|
||||
<SourceMangas popular />
|
||||
</Route>
|
||||
<Route path="/sources/:sourceId/latest/">
|
||||
<SourceMangas popular={false} />
|
||||
</Route>
|
||||
<Route path="/sources/:sourceId/configure/">
|
||||
<SourceConfigure />
|
||||
</Route>
|
||||
<Route path="/downloads">
|
||||
<DownloadQueue />
|
||||
</Route>
|
||||
<Route path="/manga/:mangaId/chapter/:chapterNum">
|
||||
<></>
|
||||
</Route>
|
||||
<Route path="/manga/:id">
|
||||
<Manga />
|
||||
</Route>
|
||||
<Route path="/library">
|
||||
<Library />
|
||||
</Route>
|
||||
<Route path="/updates">
|
||||
<Updates />
|
||||
</Route>
|
||||
<Route path="/browse">
|
||||
<Browse />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Container>
|
||||
<Switch>
|
||||
{/* General Routes */}
|
||||
<Route
|
||||
exact
|
||||
path="/"
|
||||
render={() => (
|
||||
<Redirect to="/library" />
|
||||
)}
|
||||
path="/manga/:mangaId/chapter/:chapterIndex"
|
||||
// passing a key re-mounts the reader when changing chapters
|
||||
render={
|
||||
(props: any) => (
|
||||
<Reader
|
||||
key={props.match.params.chapterIndex}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="/settings/about">
|
||||
<About />
|
||||
</Route>
|
||||
<Route path="/settings/categories">
|
||||
<Categories />
|
||||
</Route>
|
||||
<Route path="/settings/backup">
|
||||
<Backup />
|
||||
</Route>
|
||||
<Route path="/settings">
|
||||
<DarkTheme.Provider value={darkThemeContext}>
|
||||
<Settings />
|
||||
</DarkTheme.Provider>
|
||||
</Route>
|
||||
|
||||
{/* Manga Routes */}
|
||||
|
||||
<Route path="/sources/:sourceId/search/">
|
||||
<SearchSingle />
|
||||
</Route>
|
||||
<Route path="/sources/:sourceId/popular/">
|
||||
<SourceMangas popular />
|
||||
</Route>
|
||||
<Route path="/sources/:sourceId/latest/">
|
||||
<SourceMangas popular={false} />
|
||||
</Route>
|
||||
<Route path="/sources/:sourceId/configure/">
|
||||
<SourceConfigure />
|
||||
</Route>
|
||||
<Route path="/downloads">
|
||||
<DownloadQueue />
|
||||
</Route>
|
||||
<Route path="/manga/:mangaId/chapter/:chapterNum">
|
||||
<></>
|
||||
</Route>
|
||||
<Route path="/manga/:id">
|
||||
<Manga />
|
||||
</Route>
|
||||
<Route path="/library">
|
||||
<Library />
|
||||
</Route>
|
||||
<Route path="/updates">
|
||||
<Updates />
|
||||
</Route>
|
||||
<Route path="/browse">
|
||||
<Browse />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Container>
|
||||
<Switch>
|
||||
<Route
|
||||
path="/manga/:mangaId/chapter/:chapterIndex"
|
||||
// passing a key re-mounts the reader when changing chapters
|
||||
render={
|
||||
(props:any) => <Reader key={props.match.params.chapterIndex} />
|
||||
}
|
||||
/>
|
||||
</Switch>
|
||||
</NavbarContext.Provider>
|
||||
</NavbarContext.Provider>
|
||||
</QueryParamProvider>
|
||||
</ThemeProvider>
|
||||
</StyledEngineProvider>
|
||||
</Router>
|
||||
|
||||
90
src/components/ThreeStateCheckbox.tsx
Normal file
90
src/components/ThreeStateCheckbox.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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 { Checkbox, createSvgIcon } from '@mui/material';
|
||||
import React, {
|
||||
useEffect, useState,
|
||||
} from 'react';
|
||||
|
||||
export interface IThreeStateCheckboxProps {
|
||||
name: string
|
||||
checked: boolean | undefined | null
|
||||
onChange: (change: boolean | undefined | null) => void
|
||||
}
|
||||
|
||||
enum CheckState {
|
||||
SELECTED, INTERMEDIATE, UNSELECTED,
|
||||
}
|
||||
|
||||
function checkedToState(checked: boolean | undefined | null): CheckState {
|
||||
switch (checked) {
|
||||
case true:
|
||||
return CheckState.SELECTED;
|
||||
case false:
|
||||
return CheckState.INTERMEDIATE;
|
||||
default:
|
||||
return CheckState.UNSELECTED;
|
||||
}
|
||||
}
|
||||
function stateToChecked(state: CheckState): boolean | undefined {
|
||||
switch (state) {
|
||||
case CheckState.SELECTED:
|
||||
return true;
|
||||
case CheckState.INTERMEDIATE:
|
||||
return false;
|
||||
default:
|
||||
case CheckState.UNSELECTED:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function stateTransition(state: CheckState): CheckState {
|
||||
switch (state) {
|
||||
case CheckState.SELECTED:
|
||||
return CheckState.INTERMEDIATE;
|
||||
case CheckState.INTERMEDIATE:
|
||||
return CheckState.UNSELECTED;
|
||||
case CheckState.UNSELECTED:
|
||||
default:
|
||||
return CheckState.SELECTED;
|
||||
}
|
||||
}
|
||||
|
||||
const ThreeStateCheckbox = (props: IThreeStateCheckboxProps) => {
|
||||
const {
|
||||
name, checked, onChange,
|
||||
} = props;
|
||||
const [localChecked, setLocalChecked] = useState(checkedToState(checked));
|
||||
useEffect(() => setLocalChecked(checkedToState(checked)), [checked]);
|
||||
const handleChange = () => {
|
||||
setLocalChecked(stateTransition(localChecked));
|
||||
if (onChange) {
|
||||
onChange(stateToChecked(stateTransition(localChecked)));
|
||||
}
|
||||
};
|
||||
const CancelBox = createSvgIcon(
|
||||
<>
|
||||
<path
|
||||
d="M 19 6.41 L 13.41 12 L 19 17.59 L 17.59 19 L 12 13.41 L 6.41 19 V 19 H 6.41 L 5 17.59 L 11 12 L 5 6.41 L 6.41 5 L 12 10.59 L 17.59 5 L 19 6.41 M 5 5 m 0 -2 H 5 c -1.1 0 -2 0.9 -2 2 v 14 c 0 1.1 0.9 2 2 2 h 14 c 1.1 0 2 -0.9 2 -2 V 5 c 0 -1.1 -0.9 -2 -2 -2 z "
|
||||
/>
|
||||
</>,
|
||||
'CancelBox',
|
||||
);
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
name={name}
|
||||
checked={localChecked === CheckState.SELECTED}
|
||||
indeterminate={localChecked === CheckState.INTERMEDIATE}
|
||||
indeterminateIcon={<CancelBox />}
|
||||
onChange={handleChange}
|
||||
className={`${localChecked}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default ThreeStateCheckbox;
|
||||
51
src/components/library/LibraryMangaGrid.tsx
Normal file
51
src/components/library/LibraryMangaGrid.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 from 'react';
|
||||
import MangaGrid, { IMangaGridProps } from '../manga/MangaGrid';
|
||||
import useLibraryOptions, { NullAndUndefined } from '../../util/useLibraryOptions';
|
||||
|
||||
const FILTERED_OUT_MESSAGE = 'There are no Manga matching this filter';
|
||||
|
||||
function unreadFilter(unread: NullAndUndefined<boolean>, { unreadCount }: IMangaCard): boolean {
|
||||
switch (unread) {
|
||||
case true:
|
||||
return !!unreadCount && unreadCount >= 1;
|
||||
case false:
|
||||
return unreadCount === 0;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function filterManga(mangas: IMangaCard[]): IMangaCard[] {
|
||||
const { unread } = useLibraryOptions();
|
||||
return mangas
|
||||
.filter((manga) => unreadFilter(unread, manga));
|
||||
}
|
||||
|
||||
export default function LibraryMangaGrid(props: IMangaGridProps) {
|
||||
const {
|
||||
mangas, isLoading, hasNextPage, lastPageNum, setLastPageNum, message,
|
||||
} = props;
|
||||
|
||||
const { active } = useLibraryOptions();
|
||||
const filteredManga = filterManga(mangas);
|
||||
const showFilteredOutMessage = active && filteredManga.length === 0 && mangas.length > 0;
|
||||
|
||||
return (
|
||||
<MangaGrid
|
||||
mangas={filteredManga}
|
||||
isLoading={isLoading}
|
||||
hasNextPage={hasNextPage}
|
||||
lastPageNum={lastPageNum}
|
||||
setLastPageNum={setLastPageNum}
|
||||
message={showFilteredOutMessage ? FILTERED_OUT_MESSAGE : message}
|
||||
/>
|
||||
);
|
||||
}
|
||||
51
src/components/library/LibraryOptions.tsx
Normal file
51
src/components/library/LibraryOptions.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 from 'react';
|
||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||
import { Drawer, FormControlLabel, IconButton } from '@mui/material';
|
||||
import useLibraryOptions from '../../util/useLibraryOptions';
|
||||
import ThreeStateCheckbox from '../ThreeStateCheckbox';
|
||||
|
||||
function Options() {
|
||||
const { unread, setUnread } = useLibraryOptions();
|
||||
return (
|
||||
<div>
|
||||
<FormControlLabel control={<ThreeStateCheckbox name="Unread" checked={unread} onChange={setUnread} />} label="Unread" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function LibraryOptions() {
|
||||
const [filtersOpen, setFiltersOpen] = React.useState(false);
|
||||
const { active } = useLibraryOptions();
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
onClick={() => setFiltersOpen(!filtersOpen)}
|
||||
color={active ? 'warning' : 'default'}
|
||||
>
|
||||
<FilterListIcon />
|
||||
</IconButton>
|
||||
|
||||
<Drawer
|
||||
anchor="bottom"
|
||||
open={filtersOpen}
|
||||
onClose={() => setFiltersOpen(false)}
|
||||
PaperProps={{
|
||||
style: {
|
||||
maxWidth: 600, padding: '1em', marginLeft: 'auto', marginRight: 'auto',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Options />
|
||||
</Drawer>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import EmptyView from 'components/EmptyView';
|
||||
import LoadingPlaceholder from 'components/LoadingPlaceholder';
|
||||
import MangaCard from './MangaCard';
|
||||
|
||||
interface IProps{
|
||||
export interface IMangaGridProps{
|
||||
mangas: IMangaCard[]
|
||||
isLoading: boolean
|
||||
message?: string
|
||||
@@ -21,7 +21,7 @@ interface IProps{
|
||||
setLastPageNum: (lastPageNum: number) => void
|
||||
}
|
||||
|
||||
export default function MangaGrid(props: IProps) {
|
||||
export default function MangaGrid(props: IMangaGridProps) {
|
||||
const {
|
||||
mangas, isLoading, message, messageExtra, hasNextPage, lastPageNum, setLastPageNum,
|
||||
} = props;
|
||||
|
||||
@@ -7,13 +7,14 @@
|
||||
|
||||
import { Tab, Tabs } from '@mui/material';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import MangaGrid from 'components/manga/MangaGrid';
|
||||
import NavbarContext from 'context/NavbarContext';
|
||||
import client from 'util/client';
|
||||
import cloneObject from 'util/cloneObject';
|
||||
import EmptyView from 'components/EmptyView';
|
||||
import LoadingPlaceholder from 'components/LoadingPlaceholder';
|
||||
import TabPanel from 'components/util/TabPanel';
|
||||
import LibraryOptions from '../../components/library/LibraryOptions';
|
||||
import LibraryMangaGrid from '../../components/library/LibraryMangaGrid';
|
||||
|
||||
interface IMangaCategory {
|
||||
category: ICategory
|
||||
@@ -23,7 +24,13 @@ interface IMangaCategory {
|
||||
|
||||
export default function Library() {
|
||||
const { setTitle, setAction } = useContext(NavbarContext);
|
||||
useEffect(() => { setTitle('Library'); setAction(<></>); }, []);
|
||||
useEffect(() => {
|
||||
setTitle('Library'); setAction(
|
||||
<>
|
||||
<LibraryOptions />
|
||||
</>,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const [tabs, setTabs] = useState<IMangaCategory[]>();
|
||||
const [tabNum, setTabNum] = useState<number>(0);
|
||||
@@ -88,7 +95,7 @@ export default function Library() {
|
||||
|
||||
const tabBodies = tabs.map((tab) => (
|
||||
<TabPanel index={tab.category.order} currentIndex={tabNum}>
|
||||
<MangaGrid
|
||||
<LibraryMangaGrid
|
||||
mangas={tab.mangas}
|
||||
hasNextPage={false}
|
||||
lastPageNum={lastPageNum}
|
||||
@@ -122,7 +129,7 @@ export default function Library() {
|
||||
} else {
|
||||
const mangas = tabs.length === 1 ? tabs[0].mangas : [];
|
||||
toRender = (
|
||||
<MangaGrid
|
||||
<LibraryMangaGrid
|
||||
mangas={mangas}
|
||||
hasNextPage={false}
|
||||
lastPageNum={lastPageNum}
|
||||
|
||||
32
src/util/useLibraryOptions.ts
Normal file
32
src/util/useLibraryOptions.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 { BooleanParam, useQueryParams } from 'use-query-params';
|
||||
|
||||
export type NullAndUndefined<T> = T | null | undefined;
|
||||
|
||||
interface IUseLibraryOptions {
|
||||
unread: NullAndUndefined<boolean>
|
||||
setUnread: (unread: NullAndUndefined<boolean>) => void
|
||||
active: boolean
|
||||
}
|
||||
|
||||
export default function useLibraryOptions(): IUseLibraryOptions {
|
||||
const [query, setQuery] = useQueryParams({
|
||||
unread: BooleanParam,
|
||||
});
|
||||
const { unread } = query;
|
||||
const setUnread = (newUnread: NullAndUndefined<boolean>) => {
|
||||
setQuery(Object.assign(query, { unread: newUnread }), 'replace');
|
||||
};
|
||||
// eslint-disable-next-line eqeqeq
|
||||
const active = !(unread == undefined);
|
||||
return {
|
||||
unread, setUnread, active,
|
||||
};
|
||||
}
|
||||
37
yarn.lock
37
yarn.lock
@@ -5435,6 +5435,11 @@ fill-range@^7.0.1:
|
||||
dependencies:
|
||||
to-regex-range "^5.0.1"
|
||||
|
||||
filter-obj@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b"
|
||||
integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs=
|
||||
|
||||
finalhandler@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
|
||||
@@ -9478,6 +9483,16 @@ query-string@^4.1.0:
|
||||
object-assign "^4.1.0"
|
||||
strict-uri-encode "^1.0.0"
|
||||
|
||||
query-string@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.0.1.tgz#45bd149cf586aaa582dffc7ec7a8ad97dd02f75d"
|
||||
integrity sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==
|
||||
dependencies:
|
||||
decode-uri-component "^0.2.0"
|
||||
filter-obj "^1.1.0"
|
||||
split-on-first "^1.0.0"
|
||||
strict-uri-encode "^2.0.0"
|
||||
|
||||
querystring-es3@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
||||
@@ -10361,6 +10376,11 @@ serialize-javascript@^5.0.1:
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
serialize-query-params@^1.3.5:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/serialize-query-params/-/serialize-query-params-1.3.5.tgz#ca6a6419219ead24cf3b55d7b4d6a4b716b70359"
|
||||
integrity sha512-BrLH1RqgzVxm6dco+KP9S6BodeFiUVvKwtY3GSWQlupIdblT19KCGTRkHZ2yIU6Bjy0Prjts0tYe11VpTMbAeQ==
|
||||
|
||||
serve-index@^1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
|
||||
@@ -10657,6 +10677,11 @@ spdy@^4.0.2:
|
||||
select-hose "^2.0.0"
|
||||
spdy-transport "^3.0.0"
|
||||
|
||||
split-on-first@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"
|
||||
integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==
|
||||
|
||||
split-string@^3.0.1, split-string@^3.0.2:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
|
||||
@@ -10750,6 +10775,11 @@ strict-uri-encode@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
|
||||
integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
|
||||
|
||||
strict-uri-encode@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
|
||||
integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
|
||||
|
||||
string-length@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
|
||||
@@ -11459,6 +11489,13 @@ use-memo-one@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.2.tgz#0c8203a329f76e040047a35a1197defe342fab20"
|
||||
integrity sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==
|
||||
|
||||
use-query-params@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/use-query-params/-/use-query-params-1.2.3.tgz#306c31a0cbc714e8a3b4bd7e91a6a9aaccaa5e22"
|
||||
integrity sha512-cdG0tgbzK+FzsV6DAt2CN8Saa3WpRnze7uC4Rdh7l15epSFq7egmcB/zuREvPNwO5Yk80nUpDZpiyHsoq50d8w==
|
||||
dependencies:
|
||||
serialize-query-params "^1.3.5"
|
||||
|
||||
use@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
|
||||
|
||||
Reference in New Issue
Block a user