diff --git a/src/components/MangaCard.tsx b/src/components/MangaCard.tsx index c6a15c91..ebcceeda 100644 --- a/src/components/MangaCard.tsx +++ b/src/components/MangaCard.tsx @@ -21,7 +21,7 @@ import { ContinueReadingButton } from '@/components/manga/ContinueReadingButton. import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts'; import { MangaOptionButton } from '@/components/manga/MangaOptionButton.tsx'; import { MangaActionMenuItems, SingleModeProps } from '@/components/manga/MangaActionMenuItems.tsx'; -import { Menu } from '@/components/manga/Menu.tsx'; +import { Menu } from '@/components/menu/Menu.tsx'; const BottomGradient = styled('div')({ position: 'absolute', diff --git a/src/components/manga/ChapterActionMenuItems.tsx b/src/components/chapter/ChapterActionMenuItems.tsx similarity index 89% rename from src/components/manga/ChapterActionMenuItems.tsx rename to src/components/chapter/ChapterActionMenuItems.tsx index 16e27c28..79de034b 100644 --- a/src/components/manga/ChapterActionMenuItems.tsx +++ b/src/components/chapter/ChapterActionMenuItems.tsx @@ -15,7 +15,6 @@ import { useTranslation } from 'react-i18next'; import BookmarkRemove from '@mui/icons-material/BookmarkRemove'; import BookmarkAdd from '@mui/icons-material/BookmarkAdd'; import DoneAll from '@mui/icons-material/DoneAll'; -import { t as translate } from 'i18next'; import { useMemo } from 'react'; import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts'; import { @@ -27,28 +26,10 @@ import { Chapters, } from '@/lib/data/Chapters.ts'; import { TChapter } from '@/typings.ts'; -import { MenuItem } from '@/components/manga/MenuItem.tsx'; -import { IChapterWithMeta } from '@/components/manga/ChapterList.tsx'; +import { MenuItem } from '@/components/menu/MenuItem.tsx'; +import { IChapterWithMeta } from '@/components/chapter/ChapterList.tsx'; import { ChaptersWithMeta } from '@/lib/data/ChaptersWithMeta.ts'; - -const createGetMenuItemTitle = - (isSingleMode: boolean) => - (action: ChapterAction, count: number): string => { - const countSuffix = count > 0 ? ` (${count})` : ''; - return `${translate( - actionToTranslationKey[action].action[isSingleMode ? 'single' : 'selected'], - )}${countSuffix}`; - }; - -const createShouldShowMenuItem = - (isSingleMode: boolean) => - (shouldBeVisible: boolean = false): boolean => - isSingleMode ? shouldBeVisible : true; - -const createIsMenuItemDisabled = - (isSingleMode: boolean) => - (shouldBeDisabled: boolean): boolean => - isSingleMode ? false : shouldBeDisabled; +import { createGetMenuItemTitle, createIsMenuItemDisabled, createShouldShowMenuItem } from '@/components/menu/util.ts'; type BaseProps = { onClose: () => void }; @@ -80,7 +61,7 @@ export const ChapterActionMenuItems = ({ const isSingleMode = !!chapter; const { isDownloaded, isRead, isBookmarked } = chapter ?? {}; - const getMenuItemTitle = createGetMenuItemTitle(isSingleMode); + const getMenuItemTitle = createGetMenuItemTitle(isSingleMode, actionToTranslationKey); const shouldShowMenuItem = createShouldShowMenuItem(isSingleMode); const isMenuItemDisabled = createIsMenuItemDisabled(isSingleMode); diff --git a/src/components/manga/ChapterCard.tsx b/src/components/chapter/ChapterCard.tsx similarity index 97% rename from src/components/manga/ChapterCard.tsx rename to src/components/chapter/ChapterCard.tsx index 9c010ae2..d444477a 100644 --- a/src/components/manga/ChapterCard.tsx +++ b/src/components/chapter/ChapterCard.tsx @@ -18,12 +18,12 @@ import React, { TouchEvent } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import PopupState, { bindMenu, bindTrigger } from 'material-ui-popup-state'; -import { getUploadDateString } from '@/util/date'; -import { DownloadStateIndicator } from '@/components/molecules/DownloadStateIndicator'; +import { getUploadDateString } from '@/util/date.ts'; +import { DownloadStateIndicator } from '@/components/molecules/DownloadStateIndicator.tsx'; import { DownloadType } from '@/lib/graphql/generated/graphql.ts'; import { TChapter } from '@/typings.ts'; -import { ChapterActionMenuItems } from '@/components/manga/ChapterActionMenuItems.tsx'; -import { Menu } from '@/components/manga/Menu.tsx'; +import { ChapterActionMenuItems } from '@/components/chapter/ChapterActionMenuItems.tsx'; +import { Menu } from '@/components/menu/Menu.tsx'; interface IProps { chapter: TChapter; diff --git a/src/components/manga/ChapterList.tsx b/src/components/chapter/ChapterList.tsx similarity index 94% rename from src/components/manga/ChapterList.tsx rename to src/components/chapter/ChapterList.tsx index 4de07a49..68675ed6 100644 --- a/src/components/manga/ChapterList.tsx +++ b/src/components/chapter/ChapterList.tsx @@ -14,21 +14,21 @@ import { useTranslation } from 'react-i18next'; import IconButton from '@mui/material/IconButton'; import DownloadIcon from '@mui/icons-material/Download'; import DoneAllIcon from '@mui/icons-material/DoneAll'; -import { TChapter, TManga } from '@/typings'; +import { TChapter, TManga } from '@/typings.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts'; -import { ChapterCard } from '@/components/manga/ChapterCard'; -import { ResumeFab } from '@/components/manga/ResumeFAB'; -import { filterAndSortChapters, useChapterOptions } from '@/components/manga/util'; -import { EmptyView } from '@/components/util/EmptyView'; -import { ChaptersToolbarMenu } from '@/components/manga/ChaptersToolbarMenu'; -import { SelectionFAB } from '@/components/manga/SelectionFAB'; -import { DEFAULT_FULL_FAB_HEIGHT } from '@/components/util/StyledFab'; +import { ChapterCard } from '@/components/chapter/ChapterCard.tsx'; +import { ResumeFab } from '@/components/manga/ResumeFAB.tsx'; +import { filterAndSortChapters, useChapterOptions } from '@/components/chapter/util.tsx'; +import { EmptyView } from '@/components/util/EmptyView.tsx'; +import { ChaptersToolbarMenu } from '@/components/chapter/ChaptersToolbarMenu.tsx'; +import { SelectionFAB } from '@/components/collection/SelectionFAB.tsx'; +import { DEFAULT_FULL_FAB_HEIGHT } from '@/components/util/StyledFab.tsx'; import { DownloadType } from '@/lib/graphql/generated/graphql.ts'; import { useSelectableCollection } from '@/components/collection/useSelectableCollection.ts'; import { SelectableCollectionSelectAll } from '@/components/collection/SelectableCollectionSelectAll.tsx'; import { Chapters } from '@/lib/data/Chapters.ts'; import { ChaptersWithMeta } from '@/lib/data/ChaptersWithMeta.ts'; -import { ChapterActionMenuItems } from '@/components/manga/ChapterActionMenuItems.tsx'; +import { ChapterActionMenuItems } from '@/components/chapter/ChapterActionMenuItems.tsx'; const ChapterListHeader = styled(Stack)(({ theme }) => ({ margin: 8, diff --git a/src/components/manga/ChapterOptions.tsx b/src/components/chapter/ChapterOptions.tsx similarity index 94% rename from src/components/manga/ChapterOptions.tsx rename to src/components/chapter/ChapterOptions.tsx index 13808452..231c6cd4 100644 --- a/src/components/manga/ChapterOptions.tsx +++ b/src/components/chapter/ChapterOptions.tsx @@ -9,12 +9,12 @@ import { RadioGroup } from '@mui/material'; import React from 'react'; import { useTranslation } from 'react-i18next'; -import { ChapterListOptions, ChapterOptionsReducerAction, TranslationKey } from '@/typings'; -import { RadioInput } from '@/components/atoms/RadioInput'; -import { SortRadioInput } from '@/components/atoms/SortRadioInput'; -import { ThreeStateCheckboxInput } from '@/components/atoms/ThreeStateCheckboxInput'; -import { OptionsTabs } from '@/components/molecules/OptionsTabs'; -import { SORT_OPTIONS } from '@/components/manga/util'; +import { ChapterListOptions, ChapterOptionsReducerAction, TranslationKey } from '@/typings.ts'; +import { RadioInput } from '@/components/atoms/RadioInput.tsx'; +import { SortRadioInput } from '@/components/atoms/SortRadioInput.tsx'; +import { ThreeStateCheckboxInput } from '@/components/atoms/ThreeStateCheckboxInput.tsx'; +import { OptionsTabs } from '@/components/molecules/OptionsTabs.tsx'; +import { SORT_OPTIONS } from '@/components/chapter/util.tsx'; interface IProps { open: boolean; diff --git a/src/components/manga/ChaptersToolbarMenu.tsx b/src/components/chapter/ChaptersToolbarMenu.tsx similarity index 89% rename from src/components/manga/ChaptersToolbarMenu.tsx rename to src/components/chapter/ChaptersToolbarMenu.tsx index f7a37701..11db49cc 100644 --- a/src/components/manga/ChaptersToolbarMenu.tsx +++ b/src/components/chapter/ChaptersToolbarMenu.tsx @@ -10,9 +10,9 @@ import FilterList from '@mui/icons-material/FilterList'; import { IconButton, Tooltip } from '@mui/material'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { ChapterListOptions, ChapterOptionsReducerAction } from '@/typings'; -import { ChapterOptions } from '@/components/manga/ChapterOptions'; -import { isFilterActive } from '@/components/manga/util'; +import { ChapterListOptions, ChapterOptionsReducerAction } from '@/typings.ts'; +import { ChapterOptions } from '@/components/chapter/ChapterOptions.tsx'; +import { isFilterActive } from '@/components/chapter/util.tsx'; interface IProps { options: ChapterListOptions; diff --git a/src/components/manga/util.tsx b/src/components/chapter/util.tsx similarity index 97% rename from src/components/manga/util.tsx rename to src/components/chapter/util.tsx index ec63ee08..70f43400 100644 --- a/src/components/manga/util.tsx +++ b/src/components/chapter/util.tsx @@ -14,8 +14,8 @@ import { NullAndUndefined, TChapter, TranslationKey, -} from '@/typings'; -import { useReducerLocalStorage } from '@/util/useLocalStorage'; +} from '@/typings.ts'; +import { useReducerLocalStorage } from '@/util/useLocalStorage.tsx'; const defaultChapterOptions: ChapterListOptions = { active: false, diff --git a/src/components/manga/SelectionFAB.tsx b/src/components/collection/SelectionFAB.tsx similarity index 95% rename from src/components/manga/SelectionFAB.tsx rename to src/components/collection/SelectionFAB.tsx index fdd8ee54..1e10dc99 100644 --- a/src/components/manga/SelectionFAB.tsx +++ b/src/components/collection/SelectionFAB.tsx @@ -11,9 +11,9 @@ import { Fab, Box, styled } from '@mui/material'; import React from 'react'; import { useTranslation } from 'react-i18next'; import PopupState, { bindMenu, bindTrigger } from 'material-ui-popup-state'; -import { DEFAULT_FAB_STYLE } from '@/components/util/StyledFab'; +import { DEFAULT_FAB_STYLE } from '@/components/util/StyledFab.tsx'; import { TranslationKey } from '@/typings.ts'; -import { Menu } from '@/components/manga/Menu.tsx'; +import { Menu } from '@/components/menu/Menu.tsx'; interface SelectionFABProps { children: (handleClose: () => void, setHideMenu: (hide: boolean) => void) => JSX.Element; diff --git a/src/components/manga/MangaActionMenuItems.tsx b/src/components/manga/MangaActionMenuItems.tsx index 82393b8b..28c6a88b 100644 --- a/src/components/manga/MangaActionMenuItems.tsx +++ b/src/components/manga/MangaActionMenuItems.tsx @@ -15,34 +15,15 @@ import { useTranslation } from 'react-i18next'; import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder'; import Label from '@mui/icons-material/Label'; import { useMemo, useState } from 'react'; -import { t as translate } from 'i18next'; import { TManga } from '@/typings.ts'; import { actionToTranslationKey, MangaAction, MangaDownloadInfo, Mangas, MangaUnreadInfo } from '@/lib/data/Mangas.ts'; import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts'; import { CategorySelect } from '@/components/navbar/action/CategorySelect.tsx'; -import { MenuItem } from '@/components/manga/MenuItem.tsx'; +import { MenuItem } from '@/components/menu/MenuItem.tsx'; +import { createGetMenuItemTitle, createIsMenuItemDisabled, createShouldShowMenuItem } from '@/components/menu/util.ts'; const ACTION_DISABLES_SELECTION_MODE: MangaAction[] = ['remove_from_library'] as const; -const createGetMenuItemTitle = - (isSingleMode: boolean) => - (action: MangaAction, count: number): string => { - const countSuffix = count > 0 ? ` (${count})` : ''; - return `${translate( - actionToTranslationKey[action].action[isSingleMode ? 'single' : 'selected'], - )}${countSuffix}`; - }; - -const createShouldShowMenuItem = - (isSingleMode: boolean) => - (shouldBeVisible: boolean = false): boolean => - isSingleMode ? shouldBeVisible : true; - -const createIsMenuItemDisabled = - (isSingleMode: boolean) => - (shouldBeDisabled: boolean): boolean => - isSingleMode ? false : shouldBeDisabled; - type BaseProps = { onClose: (selectionModeState: boolean) => void; setHideMenu: (hide: boolean) => void }; export type SingleModeProps = { @@ -65,7 +46,7 @@ export const MangaActionMenuItems = ({ manga, handleSelection, selectedMangas = const isSingleMode = !!manga; - const getMenuItemTitle = createGetMenuItemTitle(isSingleMode); + const getMenuItemTitle = createGetMenuItemTitle(isSingleMode, actionToTranslationKey); const shouldShowMenuItem = createShouldShowMenuItem(isSingleMode); const isMenuItemDisabled = createIsMenuItemDisabled(isSingleMode); diff --git a/src/components/manga/hooks.ts b/src/components/manga/useRefreshManga.ts similarity index 66% rename from src/components/manga/hooks.ts rename to src/components/manga/useRefreshManga.ts index b48a52ea..4f622e26 100644 --- a/src/components/manga/hooks.ts +++ b/src/components/manga/useRefreshManga.ts @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useState } from 'react'; import { requestManager } from '@/lib/requests/RequestManager.ts'; export const useRefreshManga = (mangaId: string) => { @@ -22,19 +22,3 @@ export const useRefreshManga = (mangaId: string) => { return [handleRefresh, { loading: fetchingOnline }] as const; }; - -export const useDebounce = (value: Value, delay: number): Value => { - const [debouncedValue, setDebouncedValue] = useState(value); - - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedValue(value); - }, delay); - - return () => { - clearTimeout(handler); - }; - }, [value, delay]); - - return debouncedValue; -}; diff --git a/src/components/manga/Menu.tsx b/src/components/menu/Menu.tsx similarity index 100% rename from src/components/manga/Menu.tsx rename to src/components/menu/Menu.tsx diff --git a/src/components/manga/MenuItem.tsx b/src/components/menu/MenuItem.tsx similarity index 100% rename from src/components/manga/MenuItem.tsx rename to src/components/menu/MenuItem.tsx diff --git a/src/components/menu/util.ts b/src/components/menu/util.ts new file mode 100644 index 00000000..567265a1 --- /dev/null +++ b/src/components/menu/util.ts @@ -0,0 +1,42 @@ +/* + * 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 { t as translate } from 'i18next'; +import { TranslationKey } from '@/typings.ts'; + +export const createGetMenuItemTitle = + ( + isSingleMode: boolean, + actionToTranslationKey: Record< + Action, + { + action: { + single: TranslationKey; + selected: TranslationKey; + }; + success: TranslationKey; + error: TranslationKey; + } + >, + ) => + (action: Action, count: number): string => { + const countSuffix = count > 0 ? ` (${count})` : ''; + return `${translate( + actionToTranslationKey[action].action[isSingleMode ? 'single' : 'selected'], + )}${countSuffix}`; + }; + +export const createShouldShowMenuItem = + (isSingleMode: boolean) => + (shouldBeVisible: boolean = false): boolean => + isSingleMode ? shouldBeVisible : true; + +export const createIsMenuItemDisabled = + (isSingleMode: boolean) => + (shouldBeDisabled: boolean): boolean => + isSingleMode ? false : shouldBeDisabled; diff --git a/src/components/source/filters/TextFilter.tsx b/src/components/source/filters/TextFilter.tsx index a2f16244..9127195b 100644 --- a/src/components/source/filters/TextFilter.tsx +++ b/src/components/source/filters/TextFilter.tsx @@ -9,7 +9,7 @@ import SearchIcon from '@mui/icons-material/Search'; import { FormControl, Input, InputAdornment, InputLabel } from '@mui/material'; import React, { useEffect } from 'react'; -import { useDebounce } from '@/components/manga/hooks'; +import { useDebounce } from '@/util/useDebounce.ts'; interface Props { state: string; diff --git a/src/screens/Library.tsx b/src/screens/Library.tsx index bec29f22..82ddba2f 100644 --- a/src/screens/Library.tsx +++ b/src/screens/Library.tsx @@ -24,7 +24,7 @@ import { useSelectableCollection } from '@/components/collection/useSelectableCo import { TManga } from '@/typings.ts'; import { SelectableCollectionSelectMode } from '@/components/collection/SelectableCollectionSelectMode.tsx'; import { useGetVisibleLibraryMangas } from '@/components/library/useGetVisibleLibraryMangas.ts'; -import { SelectionFAB } from '@/components/manga/SelectionFAB.tsx'; +import { SelectionFAB } from '@/components/collection/SelectionFAB.tsx'; import { PARTIAL_MANGA_FIELDS } from '@/lib/graphql/Fragments.ts'; import { MangaActionMenuItems } from '@/components/manga/MangaActionMenuItems.tsx'; diff --git a/src/screens/Manga.tsx b/src/screens/Manga.tsx index a6f0a58e..d5093646 100644 --- a/src/screens/Manga.tsx +++ b/src/screens/Manga.tsx @@ -14,8 +14,8 @@ import { useParams } from 'react-router-dom'; import { isNetworkRequestInFlight } from '@apollo/client/core/networkStatus'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext'; -import { ChapterList } from '@/components/manga/ChapterList'; -import { useRefreshManga } from '@/components/manga/hooks'; +import { ChapterList } from '@/components/chapter/ChapterList.tsx'; +import { useRefreshManga } from '@/components/manga/useRefreshManga.ts'; import { MangaDetails } from '@/components/manga/MangaDetails'; import { MangaToolbarMenu } from '@/components/manga/MangaToolbarMenu'; import { EmptyView } from '@/components/util/EmptyView'; diff --git a/src/screens/Reader.tsx b/src/screens/Reader.tsx index 77eea13e..5409c849 100644 --- a/src/screens/Reader.tsx +++ b/src/screens/Reader.tsx @@ -27,7 +27,7 @@ import { VerticalPager } from '@/components/reader/pager/VerticalPager'; import { ReaderNavBar } from '@/components/navbar/ReaderNavBar'; import { makeToast } from '@/components/util/Toast'; import { NavBarContext } from '@/components/context/NavbarContext.tsx'; -import { useDebounce } from '@/components/manga/hooks.ts'; +import { useDebounce } from '@/util/useDebounce.ts'; import { UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts'; import { useMetadataServerSettings } from '@/util/metadataServerSettings.ts'; diff --git a/src/screens/SearchAll.tsx b/src/screens/SearchAll.tsx index 330f5cc0..a02bb076 100644 --- a/src/screens/SearchAll.tsx +++ b/src/screens/SearchAll.tsx @@ -19,7 +19,7 @@ import { translateExtensionLanguage } from '@/screens/util/Extensions'; import { AppbarSearch } from '@/components/util/AppbarSearch'; import { LangSelect } from '@/components/navbar/action/LangSelect'; import { MangaGrid } from '@/components/MangaGrid'; -import { useDebounce } from '@/components/manga/hooks'; +import { useDebounce } from '@/util/useDebounce.ts'; import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx'; type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean }; diff --git a/src/screens/SourceMangas.tsx b/src/screens/SourceMangas.tsx index dceeba7e..3739a01f 100644 --- a/src/screens/SourceMangas.tsx +++ b/src/screens/SourceMangas.tsx @@ -23,7 +23,7 @@ import { AbortableApolloUseMutationPaginatedResponse, SPECIAL_ED_SOURCES, } from '@/lib/requests/RequestManager.ts'; -import { useDebounce } from '@/components/manga/hooks'; +import { useDebounce } from '@/util/useDebounce.ts'; import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext'; import { SourceGridLayout } from '@/components/source/GridLayouts'; import { AppbarSearch } from '@/components/util/AppbarSearch'; diff --git a/src/util/useDebounce.ts b/src/util/useDebounce.ts new file mode 100644 index 00000000..db677eb4 --- /dev/null +++ b/src/util/useDebounce.ts @@ -0,0 +1,25 @@ +/* + * 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 { useEffect, useState } from 'react'; + +export const useDebounce = (value: Value, delay: number): Value => { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(handler); + }; + }, [value, delay]); + + return debouncedValue; +};