From b2caf26da3cab406d338ee0ceef2d1a314c3a5f0 Mon Sep 17 00:00:00 2001 From: Marcin Majewski Date: Sun, 18 Aug 2024 17:31:41 +0200 Subject: [PATCH] Move to Zustand and add Filters to web_test (#1077) * Moved to zustand and started refactored filters - Move from redux to zustand - Refactor Filters typings and add some helper types - Add Filters button and Picker/Switch filter options - Fixed Syosetu typings * Add rule consistent-type-definitions - Add consistent-type-definitions eslint rule to force consistent use of types instead of interfaces - Run eslint to auto-change every interface to types https://dev.to/atheodosiou/types-vs-interfaces-in-typescript-making-the-right-choice-48a4 https://www.youtube.com/watch?v=zM9UPcIyyhQ * Add other filters and refactor - Move Dialog to FiltersDialog - CheckboxFilter, TextFilter, XCheckboxFilter added - Added TripleCheckbox and TripleSwitch as tri-state checkbox/switch items using Mui stylings * TriStateCheckbox/Switch refactor - Change name of TripleSwitch and TripleCheckbox to TriState - Change slightly TriStateSwitch and StyledSwitch styles - Add option to change into TriStateSwitches via `localStorage` value `useSwitches` * Refactor code and fix issues * fix: prettier --------- Co-authored-by: nyagami --- .gitignore | 1 + eslint.config.js | 4 +- package-lock.json | 67 ++++- package.json | 4 +- proxy_server.js | 3 +- .../multisrc/hotnovelpub/filter_refresh.ts | 8 +- scripts/multisrc/hotnovelpub/template.ts | 76 +++--- scripts/multisrc/ifreedom/template.ts | 4 +- scripts/multisrc/lightnovelworld/template.ts | 8 +- scripts/multisrc/lightnovelwp/template.ts | 8 +- scripts/multisrc/madara/template.ts | 8 +- scripts/multisrc/mtlnovel/template.ts | 12 +- scripts/multisrc/readnovelfull/template.ts | 8 +- scripts/multisrc/readwn/template.ts | 8 +- scripts/multisrc/rulate/template.ts | 8 +- src/App.tsx | 42 ++-- src/components/Appbar/index.tsx | 52 ++-- src/components/PathItem.tsx | 4 +- src/components/StyledSwitch.tsx | 14 ++ .../accordions/ParseChapter/index.tsx | 4 +- .../accordions/ParseNovel/index.tsx | 4 +- .../accordions/PopularNovels/index.css | 11 + .../accordions/PopularNovels/index.tsx | 67 +++-- .../accordions/SearchNovels/index.tsx | 25 +- .../components/filters/CheckboxFilter.tsx | 64 +++++ .../components/filters/FiltersDialog.tsx | 211 ++++++++++++++++ .../components/filters/PickerFilter.tsx | 61 +++++ .../components/filters/SwitchFilter.tsx | 33 +++ .../components/filters/TextFilter.tsx | 38 +++ .../components/filters/TriStateCheckbox.tsx | 140 +++++++++++ .../components/filters/TriStateSwitch.tsx | 191 ++++++++++++++ .../components/filters/XCheckboxFilter.tsx | 88 +++++++ src/hooks/usePlugin.ts | 18 -- src/libs/fetch.ts | 8 +- src/libs/filterInputs.ts | 108 +++++--- src/plugins/english/lnmtl.ts | 8 +- src/plugins/english/ranobes.ts | 4 +- src/plugins/english/relibrary.ts | 4 +- src/plugins/english/royalroad.ts | 8 +- src/plugins/english/wtrlab.ts | 44 ++-- src/plugins/english/wuxiaworld.ts | 56 ++--- src/plugins/index.d.ts | 5 +- src/plugins/japanese/Syosetu.ts | 11 +- src/plugins/korean/Agitoon.ts | 16 +- src/plugins/russian/authortoday.ts | 44 ++-- src/plugins/russian/bookriver.ts | 24 +- src/plugins/russian/ficbook.ts | 8 +- src/plugins/russian/neobook.ts | 76 +++--- src/plugins/russian/novelOvh.ts | 76 +++--- src/plugins/russian/novelTL.ts | 64 ++--- src/plugins/russian/ranobehub.ts | 82 +++--- src/plugins/russian/ranobelib.ts | 88 +++---- src/plugins/russian/ranoberf.ts | 108 ++++---- src/plugins/russian/renovels.ts | 40 +-- src/plugins/russian/ruvers.ts | 8 +- src/plugins/spanish/skynovels.ts | 56 ++--- src/plugins/turkish/lshnovel.broken.ts | 2 +- src/plugins/ukrainian/smakolykytl.ts | 52 ++-- src/plugins/vietnamese/Truyenconect.ts | 12 +- src/plugins/vietnamese/lightnovelvn.ts | 8 +- src/provider/DialogProvider/index.tsx | 7 +- src/provider/index.tsx | 5 +- src/redux/dialogSlice.ts | 31 --- src/redux/overlaySlice.ts | 22 -- src/redux/pluginSlice.ts | 22 -- src/redux/store.ts | 16 -- src/store/dialogStore.ts | 39 +++ src/store/index.ts | 28 +++ src/store/overlayStore.ts | 30 +++ src/store/pluginStore.ts | 27 ++ src/types/plugin.ts | 53 ++-- tsconfig.json | 235 +++++++++--------- vite.config.js | 1 + 73 files changed, 1914 insertions(+), 916 deletions(-) create mode 100644 src/components/StyledSwitch.tsx create mode 100644 src/components/accordions/PopularNovels/index.css create mode 100644 src/components/accordions/components/filters/CheckboxFilter.tsx create mode 100644 src/components/accordions/components/filters/FiltersDialog.tsx create mode 100644 src/components/accordions/components/filters/PickerFilter.tsx create mode 100644 src/components/accordions/components/filters/SwitchFilter.tsx create mode 100644 src/components/accordions/components/filters/TextFilter.tsx create mode 100644 src/components/accordions/components/filters/TriStateCheckbox.tsx create mode 100644 src/components/accordions/components/filters/TriStateSwitch.tsx create mode 100644 src/components/accordions/components/filters/XCheckboxFilter.tsx delete mode 100644 src/hooks/usePlugin.ts delete mode 100644 src/redux/dialogSlice.ts delete mode 100644 src/redux/overlaySlice.ts delete mode 100644 src/redux/pluginSlice.ts delete mode 100644 src/redux/store.ts create mode 100644 src/store/dialogStore.ts create mode 100644 src/store/index.ts create mode 100644 src/store/overlayStore.ts create mode 100644 src/store/pluginStore.ts diff --git a/.gitignore b/.gitignore index 3c07993..fd49400 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ src/plugins/*/*\[*\]*.ts dist total.svg src/plugins/index.ts +.history \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js index a88944d..1393852 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -138,13 +138,14 @@ export default tseslint.config( '@typescript-eslint/no-unused-vars': 'warn', '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/consistent-type-definitions': ['error', 'type'], 'no-case-declarations': 'warn', 'no-undef': 'error', }, languageOptions: { ecmaVersion: 5, sourceType: 'module', - globals: Object.assign(...globalsHermes), + globals: Object.assign({}, ...globalsHermes), }, }, { @@ -154,6 +155,7 @@ export default tseslint.config( 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'warn', '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/consistent-type-definitions': ['error', 'type'], '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/ban-ts-comment': 'off', 'no-undef': 'error', diff --git a/package-lock.json b/package-lock.json index 92618ed..8ca8ed9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,11 +26,13 @@ "react-virtuoso": "^4.7.11", "react-window": "^1.8.10", "sanitize-html": "^2.12.1", - "urlencode": "^2.0.0" + "urlencode": "^2.0.0", + "zustand": "^4.5.4" }, "devDependencies": { "@eslint/js": "^9.5.0", "@types/eslint__js": "^8.42.3", + "@types/eslint-config-prettier": "^6.11.3", "@types/qs": "^6.9.15", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", @@ -1868,6 +1870,12 @@ "@types/eslint": "*" } }, + "node_modules/@types/eslint-config-prettier": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@types/eslint-config-prettier/-/eslint-config-prettier-6.11.3.tgz", + "integrity": "sha512-3wXCiM8croUnhg9LdtZUJQwNcQYGWxxdOWDjPe1ykCqJFPVpzAKfs/2dgSoCtAvdPeaponcWPI7mPcGGp9dkKQ==", + "dev": true + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -5661,6 +5669,41 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.4.tgz", + "integrity": "sha512-/BPMyLKJPtFEvVL0E9E9BTUM63MNyhPGlvxk1XjrfWTUlV+BR8jufjsovHzrtR6YNcBEcL7cMHovL1n9xHawEg==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/zustand/node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } } }, "dependencies": { @@ -6763,6 +6806,12 @@ "@types/eslint": "*" } }, + "@types/eslint-config-prettier": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@types/eslint-config-prettier/-/eslint-config-prettier-6.11.3.tgz", + "integrity": "sha512-3wXCiM8croUnhg9LdtZUJQwNcQYGWxxdOWDjPe1ykCqJFPVpzAKfs/2dgSoCtAvdPeaponcWPI7mPcGGp9dkKQ==", + "dev": true + }, "@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -9390,6 +9439,22 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "zustand": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.4.tgz", + "integrity": "sha512-/BPMyLKJPtFEvVL0E9E9BTUM63MNyhPGlvxk1XjrfWTUlV+BR8jufjsovHzrtR6YNcBEcL7cMHovL1n9xHawEg==", + "requires": { + "use-sync-external-store": "1.2.0" + }, + "dependencies": { + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + } + } } } } diff --git a/package.json b/package.json index 8713ecc..b61cc31 100644 --- a/package.json +++ b/package.json @@ -39,11 +39,13 @@ "react-virtuoso": "^4.7.11", "react-window": "^1.8.10", "sanitize-html": "^2.12.1", - "urlencode": "^2.0.0" + "urlencode": "^2.0.0", + "zustand": "^4.5.4" }, "devDependencies": { "@eslint/js": "^9.5.0", "@types/eslint__js": "^8.42.3", + "@types/eslint-config-prettier": "^6.11.3", "@types/qs": "^6.9.15", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", diff --git a/proxy_server.js b/proxy_server.js index a0846d9..f23cad6 100644 --- a/proxy_server.js +++ b/proxy_server.js @@ -26,6 +26,7 @@ const proxyRequest = (req, res) => { proxy.on('proxyRes', function (proxyRes, req, res) { var statusCode = proxyRes.statusCode; + // Redirect if ( statusCode === 301 || statusCode === 302 || @@ -82,7 +83,7 @@ const cookiesHandler = (req, res) => { http .createServer(function (req, res) { - const path = req.url.replace(/^\//, ''); + const path = req.url.charAt(0) === '/' ? req.url.slice(1) : req.url; if (req.headers['access-control-request-method']) { res.setHeader( 'access-control-allow-methods', diff --git a/scripts/multisrc/hotnovelpub/filter_refresh.ts b/scripts/multisrc/hotnovelpub/filter_refresh.ts index 2ede94b..3d6f01d 100644 --- a/scripts/multisrc/hotnovelpub/filter_refresh.ts +++ b/scripts/multisrc/hotnovelpub/filter_refresh.ts @@ -55,16 +55,16 @@ async function getFilters(sources: HotNovelPubMetadata) { return filters; } -interface response { +type response = { status: number; message: string; data?: DataEntity[]; -} -interface DataEntity { +}; +type DataEntity = { id: number; name: string; slug: string; -} +}; async function start() { const result = []; diff --git a/scripts/multisrc/hotnovelpub/template.ts b/scripts/multisrc/hotnovelpub/template.ts index a0ee06b..8eee869 100644 --- a/scripts/multisrc/hotnovelpub/template.ts +++ b/scripts/multisrc/hotnovelpub/template.ts @@ -3,17 +3,17 @@ import { Filters, FilterTypes } from '@libs/filterInputs'; import { Plugin } from '@typings/plugin'; import { NovelStatus } from '@libs/novelStatus'; -export interface HotNovelPubMetadata { +export type HotNovelPubMetadata = { id: string; sourceSite: string; sourceName: string; filters?: Filters; options?: HotNovelPubOptions; -} +}; -interface HotNovelPubOptions { +type HotNovelPubOptions = { lang?: string; -} +}; class HotNovelPubPlugin implements Plugin.PluginBase { id: string; @@ -161,16 +161,16 @@ class HotNovelPubPlugin implements Plugin.PluginBase { resolveUrl = (path: string) => this.site + '/' + path; } -interface responseNovels { +type responseNovels = { status: number; message: string; data: Data; -} -interface Data { +}; +type Data = { category?: Category; books: Books; -} -interface Category { +}; +type Category = { id: number; name: string; description: string; @@ -178,13 +178,13 @@ interface Category { createdAt: string; updatedAt: string; countryId: number; -} -interface Books { +}; +type Books = { total: number; pages_count: number; data?: DataEntity[]; -} -interface DataEntity { +}; +type DataEntity = { id: number; name: string; view: number; @@ -193,24 +193,24 @@ interface DataEntity { slug: string; categories?: CategoriesEntity[] | null; source?: null; -} -interface CategoriesEntity { +}; +type CategoriesEntity = { id: number; name: string; slug?: string; -} +}; -interface responseNovel { +type responseNovel = { status: number; message: string; data: Data1; -} -interface Data1 { +}; +type Data1 = { book: Book1; tags: Tags; chapters?: ChaptersEntity[]; -} -interface Book1 { +}; +type Book1 = { id: number; name: string; description: string; @@ -229,47 +229,47 @@ interface Book1 { source?: null; idSource?: null; prize?: null; -} -interface Authorize { +}; +type Authorize = { id: number; name: string; description: string; avatar: string; slug: string; -} -interface Tags { +}; +type Tags = { tags_name?: string[] | null; tags?: CategoriesEntity[] | null; -} -interface ChaptersEntity { +}; +type ChaptersEntity = { id: number; title: string; slug: string; index: number; -} +}; -interface ChapterType { +type ChapterType = { type: string; data?: string[]; -} +}; -interface responseSearch { +type responseSearch = { status: number; message: string; data: Data2; -} -interface Data2 { +}; +type Data2 = { books?: BooksEntity[] | null; authorizes?: AuthorizesEntityOrCategoriesEntity[] | null; categories?: AuthorizesEntityOrCategoriesEntity[] | null; -} -interface BooksEntity { +}; +type BooksEntity = { id: number; name: string; slug: string; -} -interface AuthorizesEntityOrCategoriesEntity { +}; +type AuthorizesEntityOrCategoriesEntity = { id: number; name: string; slug: string; -} +}; diff --git a/scripts/multisrc/ifreedom/template.ts b/scripts/multisrc/ifreedom/template.ts index afda4c8..88f3d40 100644 --- a/scripts/multisrc/ifreedom/template.ts +++ b/scripts/multisrc/ifreedom/template.ts @@ -5,12 +5,12 @@ import { NovelStatus } from '@libs/novelStatus'; import { load as parseHTML } from 'cheerio'; import dayjs from 'dayjs'; -export interface IfreedomMetadata { +export type IfreedomMetadata = { id: string; sourceSite: string; sourceName: string; filters?: Filters; -} +}; class IfreedomPlugin implements Plugin.PluginBase { id: string; diff --git a/scripts/multisrc/lightnovelworld/template.ts b/scripts/multisrc/lightnovelworld/template.ts index d8669be..df857d5 100644 --- a/scripts/multisrc/lightnovelworld/template.ts +++ b/scripts/multisrc/lightnovelworld/template.ts @@ -4,18 +4,18 @@ import { Plugin } from '@typings/plugin'; import { Filters, FilterTypes } from '@libs/filterInputs'; import dayjs from 'dayjs'; -interface LightNovelWorldOptions { +type LightNovelWorldOptions = { lang?: string; versionIncrements?: number; -} +}; -export interface LightNovelWorldMetadata { +export type LightNovelWorldMetadata = { id: string; sourceSite: string; sourceName: string; options?: LightNovelWorldOptions; filters?: any; -} +}; class LightNovelWorld implements Plugin.PagePlugin { id: string; diff --git a/scripts/multisrc/lightnovelwp/template.ts b/scripts/multisrc/lightnovelwp/template.ts index f9301b0..cdbc737 100644 --- a/scripts/multisrc/lightnovelwp/template.ts +++ b/scripts/multisrc/lightnovelwp/template.ts @@ -6,21 +6,21 @@ import { NovelStatus } from '@libs/novelStatus'; import { defaultCover } from '@libs/defaultCover'; import { Filters } from '@libs/filterInputs'; -interface LightNovelWPOptions { +type LightNovelWPOptions = { reverseChapters?: boolean; lang?: string; versionIncrements?: number; seriesPath?: string; customJs?: string; -} +}; -export interface LightNovelWPMetadata { +export type LightNovelWPMetadata = { id: string; sourceSite: string; sourceName: string; options?: LightNovelWPOptions; filters?: any; -} +}; class LightNovelWPPlugin implements Plugin.PluginBase { id: string; diff --git a/scripts/multisrc/madara/template.ts b/scripts/multisrc/madara/template.ts index 6d8096e..8371319 100644 --- a/scripts/multisrc/madara/template.ts +++ b/scripts/multisrc/madara/template.ts @@ -9,20 +9,20 @@ import dayjs from 'dayjs'; const includesAny = (str: string, keywords: string[]) => new RegExp(keywords.join('|')).test(str); -interface MadaraOptions { +type MadaraOptions = { useNewChapterEndpoint?: boolean; lang?: string; orderBy?: string; versionIncrements?: number; -} +}; -export interface MadaraMetadata { +export type MadaraMetadata = { id: string; sourceSite: string; sourceName: string; options?: MadaraOptions; filters?: any; -} +}; class MadaraPlugin implements Plugin.PluginBase { id: string; diff --git a/scripts/multisrc/mtlnovel/template.ts b/scripts/multisrc/mtlnovel/template.ts index f2bc2a4..7ff3149 100644 --- a/scripts/multisrc/mtlnovel/template.ts +++ b/scripts/multisrc/mtlnovel/template.ts @@ -5,17 +5,17 @@ import { NovelStatus } from '@libs/novelStatus'; import { defaultCover } from '@libs/defaultCover'; import { Filters } from '@libs/filterInputs'; -interface MTLNovelOptions { +type MTLNovelOptions = { lang?: string; -} +}; -export interface MTLNovelMetadata { +export type MTLNovelMetadata = { id: string; sourceSite: string; sourceName: string; options?: MTLNovelOptions; filters?: any; -} +}; class MTLNovelPlugin implements Plugin.PluginBase { id: string; @@ -197,11 +197,11 @@ class MTLNovelPlugin implements Plugin.PluginBase { const result = await res.json(); const novels: Plugin.NovelItem[] = []; - interface SearchEntry { + type SearchEntry = { title: string; thumbnail: string; permalink: string; - } + }; result.items[0].results.map((item: SearchEntry) => { const novelName = item.title.replace(/<\/?strong>/g, ''); const novelCover = item.thumbnail; diff --git a/scripts/multisrc/readnovelfull/template.ts b/scripts/multisrc/readnovelfull/template.ts index f867895..b0eadb0 100644 --- a/scripts/multisrc/readnovelfull/template.ts +++ b/scripts/multisrc/readnovelfull/template.ts @@ -3,18 +3,18 @@ import { fetchApi } from '@libs/fetch'; import { Plugin } from '@typings/plugin'; import { NovelStatus } from '@libs/novelStatus'; -interface ReadNovelFullOptions { +type ReadNovelFullOptions = { lang?: string; versionIncrements?: number; -} +}; -export interface ReadNovelFullMetadata { +export type ReadNovelFullMetadata = { id: string; sourceSite: string; sourceName: string; options?: ReadNovelFullOptions; filters?: any; -} +}; class ReadNovelFullPlugin implements Plugin.PluginBase { id: string; diff --git a/scripts/multisrc/readwn/template.ts b/scripts/multisrc/readwn/template.ts index 348a692..336ffd1 100644 --- a/scripts/multisrc/readwn/template.ts +++ b/scripts/multisrc/readwn/template.ts @@ -6,17 +6,17 @@ import { load as parseHTML } from 'cheerio'; import dayjs from 'dayjs'; import qs from 'qs'; -interface ReadwnOptions { +type ReadwnOptions = { versionIncrements?: number; -} +}; -export interface ReadwnMetadata { +export type ReadwnMetadata = { id: string; sourceSite: string; sourceName: string; filters?: any; options?: ReadwnOptions; -} +}; class ReadwnPlugin implements Plugin.PluginBase { id: string; diff --git a/scripts/multisrc/rulate/template.ts b/scripts/multisrc/rulate/template.ts index 93d0b88..58047f1 100644 --- a/scripts/multisrc/rulate/template.ts +++ b/scripts/multisrc/rulate/template.ts @@ -5,12 +5,12 @@ import { NovelStatus } from '@libs/novelStatus'; import { load as parseHTML } from 'cheerio'; import dayjs from 'dayjs'; -export interface RulateMetadata { +export type RulateMetadata = { id: string; sourceSite: string; sourceName: string; filters?: Filters; -} +}; class RulatePlugin implements Plugin.PluginBase { id: string; @@ -241,10 +241,10 @@ class RulatePlugin implements Plugin.PluginBase { }; } -interface response { +type response = { id: number; title_one: string; title_two: string; url: string; img: string; -} +}; diff --git a/src/App.tsx b/src/App.tsx index ac32fee..21271d9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,8 +8,6 @@ import { ThemeProvider, } from '@mui/material'; import Appbar from '@components/Appbar'; -import { Provider } from 'react-redux'; -import store from '@redux/store'; import AppProvider from './provider'; import PopularNovels from '@components/accordions/PopularNovels'; import SearchNovels from '@components/accordions/SearchNovels'; @@ -30,27 +28,25 @@ function App() { }, }); return ( - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + ); } diff --git a/src/components/Appbar/index.tsx b/src/components/Appbar/index.tsx index 1ff5808..481e12d 100644 --- a/src/components/Appbar/index.tsx +++ b/src/components/Appbar/index.tsx @@ -18,42 +18,29 @@ import { Search, Translate } from '@mui/icons-material'; import { searchPlugins } from '@provider/plugins'; import { Plugin } from '@typings/plugin'; import { FixedSizeList, ListChildComponentProps } from 'react-window'; -import { useDispatch } from 'react-redux'; -import { selectPlugin } from '@redux/pluginSlice'; -import usePlugin from '@hooks/usePlugin'; +import { useAppStore } from '@store'; const resovleIcon = (iconPath: string) => { return '/static/' + iconPath; }; -export default function () { - const [searchedPlugins, setSearchPlugins] = useState([]); - const [keyword, setKeyword] = useState(''); - const [listVisible, setListVisible] = useState(false); - const plugin = usePlugin(); - const dispatch = useDispatch(); - useEffect(() => { - const plugins = searchPlugins(keyword); - setSearchPlugins(plugins); - }, [keyword]); - - function renderPlugin(props: ListChildComponentProps) { +function renderPlugin(setListVisible: (v: boolean) => void) { + return function (props: ListChildComponentProps) { const { index, style, data } = props; const plugin = data[index]; + const selectPlugin = useAppStore(state => state.selectPlugin); return ( { - dispatch( - selectPlugin({ - id: plugin.id, - name: plugin.name, - version: plugin.version, - icon: plugin.icon, - site: plugin.site, - }), - ); + selectPlugin({ + id: plugin.id, + name: plugin.name, + version: plugin.version, + icon: plugin.icon, + site: plugin.site, + }); setListVisible(false); }} > @@ -64,7 +51,20 @@ export default function () { ); - } + }; +} + +export default function Appbar() { + const [searchedPlugins, setSearchPlugins] = useState([]); + const [keyword, setKeyword] = useState(''); + const [listVisible, setListVisible] = useState(false); + + const plugin = useAppStore(state => state.plugin); + + useEffect(() => { + const plugins = searchPlugins(keyword); + setSearchPlugins(plugins); + }, [keyword]); return ( @@ -111,7 +111,7 @@ export default function () { backgroundColor: 'white', }} > - {renderPlugin} + {renderPlugin(setListVisible)} ) : null} diff --git a/src/components/PathItem.tsx b/src/components/PathItem.tsx index 662243f..da5aa88 100644 --- a/src/components/PathItem.tsx +++ b/src/components/PathItem.tsx @@ -3,10 +3,10 @@ import { createTheme, ThemeProvider } from '@mui/material/styles'; import React from 'react'; import CopiableItem from './CopiableItem'; -interface Validation { +type Validation = { type?: 'error' | 'warning'; message?: string; -} +}; const validatePath = (path: string): Validation => { if (path.includes('http')) diff --git a/src/components/StyledSwitch.tsx b/src/components/StyledSwitch.tsx new file mode 100644 index 0000000..2611e0f --- /dev/null +++ b/src/components/StyledSwitch.tsx @@ -0,0 +1,14 @@ +import { styled, Switch } from '@mui/material'; + +export const StyledSwitch = styled(Switch)(() => ({ + padding: 8, + '& .MuiSwitch-track': { + borderRadius: 22 / 2, + }, + '& .MuiSwitch-thumb': { + boxShadow: 'none', + width: 16, + height: 16, + margin: 2, + }, +})); diff --git a/src/components/accordions/ParseChapter/index.tsx b/src/components/accordions/ParseChapter/index.tsx index ca9e0b5..7ce0bc9 100644 --- a/src/components/accordions/ParseChapter/index.tsx +++ b/src/components/accordions/ParseChapter/index.tsx @@ -1,10 +1,10 @@ import { Box, Button, Stack, TextField } from '@mui/material'; import React, { useState } from 'react'; import AccordionContainer from '../components/AccordionContainer'; -import usePlugin from '@hooks/usePlugin'; import Textarea from '@components/Textarea'; +import { useAppStore } from '@store'; export default function ParseChapter() { - const plugin = usePlugin(); + const plugin = useAppStore(state => state.plugin); const [loading, setLoading] = useState(false); const [chapterPath, setchapterPath] = useState(''); const [chapterText, setChapterText] = useState(''); diff --git a/src/components/accordions/ParseNovel/index.tsx b/src/components/accordions/ParseNovel/index.tsx index 8b004c6..6ba0f32 100644 --- a/src/components/accordions/ParseNovel/index.tsx +++ b/src/components/accordions/ParseNovel/index.tsx @@ -2,12 +2,12 @@ import { Box, Button, Pagination, Stack, TextField } from '@mui/material'; import React, { useState } from 'react'; import AccordionContainer from '../components/AccordionContainer'; import { Plugin } from '@typings/plugin'; -import usePlugin from '@hooks/usePlugin'; import SourceNovelCard from './SourceNovelCard'; import ChapterList from './ChaptertList'; +import { useAppStore } from '@store'; export default function ParseNovel() { - const plugin = usePlugin(); + const plugin = useAppStore(state => state.plugin); const [sourceNovel, setSouceNovel] = useState< Plugin.SourceNovel | undefined >(); diff --git a/src/components/accordions/PopularNovels/index.css b/src/components/accordions/PopularNovels/index.css new file mode 100644 index 0000000..9aab880 --- /dev/null +++ b/src/components/accordions/PopularNovels/index.css @@ -0,0 +1,11 @@ +#filtersContent { + display: flex; + flex-direction: column; + align-content: center; +} + +.switch_filter { + display: flex; + flex-direction: column; + align-items: center; +} diff --git a/src/components/accordions/PopularNovels/index.tsx b/src/components/accordions/PopularNovels/index.tsx index ecf5698..52e1db7 100644 --- a/src/components/accordions/PopularNovels/index.tsx +++ b/src/components/accordions/PopularNovels/index.tsx @@ -8,40 +8,42 @@ import { FormControlLabel, FormGroup, } from '@mui/material'; -import React, { useEffect, useState, ChangeEvent } from 'react'; +import React, { useEffect, useState, ChangeEvent, useRef } from 'react'; import AccordionContainer from '../components/AccordionContainer'; import { Plugin } from '@typings/plugin'; import NovelItemCard from '../components/NovelItemCard'; -import { Filters } from '@libs/filterInputs'; -import usePlugin from '@hooks/usePlugin'; +import { Filters, FilterToValues } from '@libs/filterInputs'; +import { useAppStore } from '@store'; +import './index.css'; +import { DialogRef, FiltersDialog } from '../components/filters/FiltersDialog'; export default function PopularNovels() { - const plugin = usePlugin(); + const plugin = useAppStore(state => state.plugin); const [novels, setNovels] = useState([]); - const [filterValues, setFilterValues] = useState(); + const [filterValues, setFilterValues] = useState< + FilterToValues | undefined + >(); const [loading, setLoading] = useState(false); const [currentIndex, setCurrentIndex] = useState(0); const [maxIndex, setMaxIndex] = useState(0); const [isLatest, setIsLatest] = useState(true); + const dialogRef = useRef(null); - const fetchNovelsByIndex = (index: number) => { + const fetchNovelsByIndex = async (index: number) => { if (plugin && index) { setLoading(true); - plugin - .popularNovels(index, { - filters: filterValues, - showLatestNovels: isLatest, - }) - .then(res => { - if (res.length !== 0) { - setCurrentIndex(index); - if (index > maxIndex) { - setMaxIndex(index); - } - setNovels(res); - } - }) - .finally(() => setLoading(false)); + const novels = await plugin.popularNovels(index, { + filters: filterValues || {}, + showLatestNovels: isLatest, + }); + if (novels.length !== 0) { + setCurrentIndex(index); + if (index > maxIndex) { + setMaxIndex(index); + } + setNovels(novels); + } + setLoading(false); } }; @@ -59,14 +61,15 @@ export default function PopularNovels() { useEffect(() => { // Reset when changing plugins. + // TODO: Fix crashing on plugin change! setCurrentIndex(0); setMaxIndex(0); setNovels([]); if (plugin?.filters) { - const filters = {}; + const filters: FilterToValues = {}; for (const fKey in plugin.filters) { - filters[fKey] = { + filters[fKey as keyof typeof filters] = { type: plugin.filters[fKey].type, value: plugin.filters[fKey].value, }; @@ -74,6 +77,7 @@ export default function PopularNovels() { setFilterValues(filters); } }, [plugin]); + return ( @@ -103,10 +107,25 @@ export default function PopularNovels() { > Next Page + + fetchNovelsByIndex(1)} + /> fetchNovelsByIndex(value)} + onChange={(_event, value) => fetchNovelsByIndex(value)} > {Array.from({ length: maxIndex }, (_, index) => index + 1).map( number => ( diff --git a/src/components/accordions/SearchNovels/index.tsx b/src/components/accordions/SearchNovels/index.tsx index 8805fdf..4760c68 100644 --- a/src/components/accordions/SearchNovels/index.tsx +++ b/src/components/accordions/SearchNovels/index.tsx @@ -3,35 +3,34 @@ import React, { useEffect, useState } from 'react'; import AccordionContainer from '../components/AccordionContainer'; import { Plugin } from '@typings/plugin'; import NovelItemCard from '../components/NovelItemCard'; -import { useSelector } from 'react-redux'; -import { AppState } from '@redux/store'; -import { getPlugin } from '@provider/plugins'; +import { useAppStore } from '@store'; export default function SearchNovels() { - const selectedPluginItem = useSelector( - (state: AppState) => state.plugin.selected, - ); + const plugin = useAppStore(state => state.plugin); + const [searchTerm, setSearchTerm] = useState(''); const [page] = useState(1); - const [plugin, setPlugin] = useState(); const [novels, setNovels] = useState([]); const [loading, setLoading] = useState(false); - useEffect(() => { - if (selectedPluginItem) { - setPlugin(getPlugin(selectedPluginItem.id)); - } - }, [selectedPluginItem]); + + const [fetchError, setFetchError] = useState(''); + const fetchNovels = () => { if (plugin) { setLoading(true); plugin .searchNovels(searchTerm, page) .then(res => setNovels(res)) - .finally(() => setLoading(false)); + .finally(() => setLoading(false)) + .catch(e => { + setFetchError(e.message); + }); } }; + return ( + {fetchError && {fetchError}} }; + value: ValueOfFilter; + set: (v: typeof value) => void; +}) => { + const label = filter.label.replace(/s+/gi, '-'); + return ( + + {filter.label} + + {filter.options.map(option => { + return ( + + + { + set( + value.includes(option.value) + ? value.filter(q => q !== option.value) + : [...value, option.value], + ); + }} + checked={value.includes(option.value)} + /> + } + label={option.label} + /> + + + ); + })} + + + ); +}; diff --git a/src/components/accordions/components/filters/FiltersDialog.tsx b/src/components/accordions/components/filters/FiltersDialog.tsx new file mode 100644 index 0000000..a82f1a8 --- /dev/null +++ b/src/components/accordions/components/filters/FiltersDialog.tsx @@ -0,0 +1,211 @@ +import React, { + forwardRef, + ReactNode, + useEffect, + useImperativeHandle, +} from 'react'; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, +} from '@mui/material'; +import { useState } from 'react'; +import { + AnyFilterValue, + Filters, + FilterToValues, + FilterTypes, +} from '@libs/filterInputs'; +import { PickerFilter } from './PickerFilter'; +import { SwitchFilter } from './SwitchFilter'; +import { TextFilter } from './TextFilter'; +import { CheckboxFilter } from './CheckboxFilter'; +import { XCheckboxFilter } from './XCheckboxFilter'; + +const renderFilters = ( + filters: Filters | undefined, + values: FilterToValues | undefined, + set: (key: string, v: AnyFilterValue) => void, +): React.ReactNode => { + const isValueCorrectType = ( + o: AnyFilterValue, + f: T, + ): o is T => { + const checkIfIsCorrectObjectType = (o: AnyFilterValue, f: T): o is T => { + const areArrays = Array.isArray(o) && Array.isArray(f); + const areObjects = typeof o === 'object' && typeof f === 'object'; + return areArrays || areObjects; + }; + return typeof o === typeof f || checkIfIsCorrectObjectType(o, f); + }; + + if (!filters || !values) return false; + return ( + <> + {Object.entries(filters).map(([key, filter]) => { + if (!(key in values)) { + console.error(`No filter value for ${key} in filter values!`); + return null; + } + switch (filter.type) { + case FilterTypes.Picker: { + // Check if filterValues have correct type + // this needs to be inside of every case in this switch to get correct value type + const value = values[key].value; // here value has every possible value type + // We could just do `as typeof filter.value` but just to be sure I made a typeguard + if (!isValueCorrectType(value, filter.value)) { + console.error( + `FilterValue for filter [${key}] has a wrong type!`, + ); + return null; + } + // value; // here value has only Picker's value + return ( + set(key, newValue)} + /> + ); + } + case FilterTypes.Switch: { + // Check if filterValues have correct type + // this needs to be inside of every case in this switch to get correct value type + const value = values[key].value; // here value has every possible value type + // We could just do `as typeof filter.value` but just to be sure I made a typeguard + if (!isValueCorrectType(value, filter.value)) { + console.error( + `FilterValue for filter [${key}] has a wrong type!`, + ); + return null; + } + // value; // here value has only Picker's value + return ( + set(key, newValue)} + /> + ); + } + case FilterTypes.TextInput: { + const value = values[key].value; + if (!isValueCorrectType(value, filter.value)) { + console.error( + `FilterValue for filter [${key}] has a wrong type!`, + ); + return null; + } + return ( + set(key, newValue)} + value={value} + key={`text_filter_${key}`} + /> + ); + } + case FilterTypes.CheckboxGroup: { + const value = values[key].value; + if (!isValueCorrectType(value, filter.value)) { + console.error( + `FilterValue for filter [${key}] has a wrong type!`, + ); + return null; + } + return ( + set(key, newValue)} + value={value} + key={`checkbox_filter_${key}`} + /> + ); + } + case FilterTypes.ExcludableCheckboxGroup: { + const value = values[key].value; + if (!isValueCorrectType(value, filter.value)) { + console.error( + `FilterValue for filter [${key}] has a wrong type!`, + ); + return null; + } + return ( + set(key, newValue)} + value={value} + key={`checkbox_filter_${key}`} + /> + ); + } + } + })} + + ); +}; + +export type DialogRef = { show(): void; hide(): void }; + +export const FiltersDialog = forwardRef< + DialogRef, + { + values: FilterToValues | undefined; + filters: Filters | undefined; + setValues: React.Dispatch< + React.SetStateAction | undefined> + >; + refetch(): void; + } +>(function FiltersDialog({ values, setValues, filters, refetch }, ref) { + const [dialogOpen, setDialogOpen] = useState(false); + const [filterElements, setFilterElements] = useState(false); + const setFilterWithKey = (key: string, newValue: AnyFilterValue) => + setValues(fValues => + !fValues + ? fValues + : { + ...fValues, + [key]: { + ...fValues[key], + value: newValue, + }, + }, + ); + + useImperativeHandle(ref, () => ({ + show() { + setDialogOpen(true); + }, + hide() { + setDialogOpen(false); + }, + })); + + useEffect(() => { + setFilterElements(renderFilters(filters, values, setFilterWithKey)); + }, [values]); + + return ( + setDialogOpen(false)} + > + Filters + + {filterElements} + + + + + + + ); +}); diff --git a/src/components/accordions/components/filters/PickerFilter.tsx b/src/components/accordions/components/filters/PickerFilter.tsx new file mode 100644 index 0000000..23e18db --- /dev/null +++ b/src/components/accordions/components/filters/PickerFilter.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { Filter, FilterTypes, ValueOfFilter } from '@libs/filterInputs'; +import { FormControl, InputLabel, MenuItem, Select } from '@mui/material'; + +type FilterType = FilterTypes.Picker; + +export const PickerFilter = ({ + filter: { key, filter }, + value, + set, +}: { + filter: { key: string; filter: Filter }; + value: ValueOfFilter; + set: (v: ValueOfFilter) => void; +}) => { + const label = filter.label.replace(/s+/gi, '-'); + return ( + + + {filter.label} + + + + ); +}; diff --git a/src/components/accordions/components/filters/SwitchFilter.tsx b/src/components/accordions/components/filters/SwitchFilter.tsx new file mode 100644 index 0000000..2f0c859 --- /dev/null +++ b/src/components/accordions/components/filters/SwitchFilter.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Filter, FilterTypes, ValueOfFilter } from '@libs/filterInputs'; +import { FormControl, FormControlLabel } from '@mui/material'; +import { StyledSwitch } from '../../../StyledSwitch'; + +type FilterType = FilterTypes.Switch; + +export const SwitchFilter = ({ + filter: { key, filter }, + value, + set, +}: { + filter: { key: string; filter: Filter }; + value: ValueOfFilter; + set: (v: typeof value) => void; +}) => { + const label = filter.label.replace(/s+/gi, '-'); + return ( + + set(!value)} />} + /> + + ); +}; diff --git a/src/components/accordions/components/filters/TextFilter.tsx b/src/components/accordions/components/filters/TextFilter.tsx new file mode 100644 index 0000000..c7107a5 --- /dev/null +++ b/src/components/accordions/components/filters/TextFilter.tsx @@ -0,0 +1,38 @@ +import React, { useRef } from 'react'; +import { Filter, FilterTypes, ValueOfFilter } from '@libs/filterInputs'; +import { FormControl, TextField } from '@mui/material'; + +type FilterType = FilterTypes.TextInput; + +export const TextFilter = ({ + filter: { key, filter }, + value, + set, +}: { + filter: { key: string; filter: Filter }; + value: ValueOfFilter; + set: (v: typeof value) => void; +}) => { + const label = filter.label.replace(/s+/gi, '-'); + const textRef = useRef(null); + return ( + + { + if (textRef.current) { + set(textRef.current.value ?? ''); + } + }} + /> + + ); +}; diff --git a/src/components/accordions/components/filters/TriStateCheckbox.tsx b/src/components/accordions/components/filters/TriStateCheckbox.tsx new file mode 100644 index 0000000..fdd639d --- /dev/null +++ b/src/components/accordions/components/filters/TriStateCheckbox.tsx @@ -0,0 +1,140 @@ +import { styled } from '@mui/system'; +import React, { useRef, useState } from 'react'; + +class TriStateCheckboxClasses { + root = 'TriStateCheckboxRoot'; + base = 'TriStateCheckboxBase'; + input = 'TriStateCheckboxInput'; + svg = 'TriStateCheckboxSVG'; + checked: string; + unchecked: string; + neutral: string; + offsets = { + neutral: 16, + checked: 16 * 2, + unchecked: 0, + }; + constructor() { + this.checked = `${this.base}-checked`; + this.neutral = `${this.base}-neutral`; + this.unchecked = `${this.base}-unchecked`; + } +} + +const classes = new TriStateCheckboxClasses(); + +const TriStateCheckboxRoot = styled(`span`, { + name: classes.root, + slot: 'root', +})(({ theme }) => ({ + [`&.${classes.checked}`]: { + color: theme.palette.primary.main, + }, + [`&.${classes.unchecked}`]: { + color: 'red', + }, + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + position: 'relative', + boxSizing: 'border-box', + outline: 0, + border: 0, + margin: 0, + cursor: 'pointer', + userSelect: 'none', + textDecoration: 'none', + borderRadius: '50%', + padding: '9px', +})); + +const TriStateCheckboxInput = styled(`input`, { + name: classes.input, + slot: 'input', +})(() => ({ + cursor: 'inherit', + position: 'absolute', + opacity: 0, + height: '100%', + width: '100%', + top: 0, + left: 0, + margin: 0, + padding: 0, + zIndex: 1, +})); + +const TriStateCheckboxSVG = styled(`svg`, { name: classes.svg, slot: 'image' })( + () => ({ + userSelect: 'none', + width: '1em', + height: '1em', + display: 'inline-block', + fill: 'currentcolor', + flexShrink: 0, + fontSize: '1.5rem', + transition: 'fill 200ms cubic-bezier(0.4,0,0.2,1) 0ms', + }), +); + +const emptyPath = ( + +); + +const checkmarkPath = ( + +); + +const uncheckedPath = ( + +); + +export type TriStateCheckboxState = 'checked' | 'unchecked' | 'neutral'; + +export const TriStateCheckbox = function ({ + value, + onChange, +}: { + value?: TriStateCheckboxState; + onChange?: (newValue: TriStateCheckboxState) => void; +}) { + const [state, sSt] = useState(value ?? 'neutral'); + const setState = (v: TriStateCheckboxState) => { + onChange?.(v); + sSt(value ?? v); + }; + const root = useRef(null); + + const curVal = value ?? state; + + return ( + <> + { + switch (curVal) { + case 'checked': + return setState('unchecked'); + case 'neutral': + return setState('checked'); + case 'unchecked': + return setState('neutral'); + } + }} + > + + + {curVal === 'checked' + ? checkmarkPath + : curVal === 'unchecked' + ? uncheckedPath + : emptyPath} + + + + ); +}; diff --git a/src/components/accordions/components/filters/TriStateSwitch.tsx b/src/components/accordions/components/filters/TriStateSwitch.tsx new file mode 100644 index 0000000..f8a348c --- /dev/null +++ b/src/components/accordions/components/filters/TriStateSwitch.tsx @@ -0,0 +1,191 @@ +import { styled } from '@mui/system'; +import React, { useRef, useState } from 'react'; + +class TriStateSwitchClasses { + root = 'TriStateSwitchRoot'; + base = 'TriStateSwitchBase'; + track = 'TriStateSwitchTrack'; + thumb = 'TriStateSwitchThumb'; + input = 'TriStateSwitchInput'; + checked: string; + unchecked: string; + neutral: string; + offsets = { + neutral: 18, + checked: 18 * 2, + unchecked: 0, + }; + width = 16 * 4 + 10; + constructor() { + this.base; + this.checked = `${this.base}-checked`; + this.neutral = `${this.base}-neutral`; + this.unchecked = `${this.base}-unchecked`; + } +} + +const classes = new TriStateSwitchClasses(); + +const TriStateSwitchRoot = styled(`span`, { + name: classes.root, + slot: 'root', +})(() => ({ + display: 'inline-flex', + width: `${classes.width}px`, + height: '38px', + overflow: 'hidden', + boxSizing: 'border-box', + position: 'relative', + flexShrink: 0, + zIndex: 0, + verticalAlign: 'middle', + padding: '8px', +})); + +const TriStateSwitchTrack = styled(`span`, { + name: classes.track, + slot: 'track', +})(() => ({ + // Android12 visuals: + borderRadius: 22 / 2, + // base Track + width: '100%', + height: '100%', + zIndex: -1, + content: `''`, + transition: + 'opacity 150ms cubic-bezier(0.4,0,0.2,1) 0ms, background-color 150ms cubic-bezier(0.4,0,0.2,1) 0ms', + backgroundColor: '#000', + opacity: 0.38, +})); + +const TriStateSwitchBase = styled(`span`, { + name: classes.base, + slot: 'base', +})(({ theme }) => ({ + [`&.${classes.unchecked}`]: { + transform: `translateX(${classes.offsets.unchecked}px)`, + color: '#f33', + }, + [`&.${classes.checked}`]: { + transform: `translateX(${classes.offsets.checked}px)`, + color: theme.palette.primary.main, + }, + [`&.${classes.neutral}`]: { + transform: `translateX(${classes.offsets.neutral}px)`, + color: 'gray', + }, + + [`&.${classes.checked} + span`]: { + opacity: 0.5, + backgroundColor: theme.palette.primary.main, + }, + [`&.${classes.unchecked} + span`]: { + opacity: 0.5, + backgroundColor: '#f33', + }, + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'transparent', + outline: 0, + border: 0, + margin: 0, + cursor: 'pointer', + userSelect: 'none', + textDecoration: 'none', + padding: '9px', + borderRadius: '50%', + position: 'absolute', + top: 0, + left: 0, + zIndex: 1, + color: '#fff', + transition: + 'left 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,transform 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;', +})); + +const TriStateSwitchInput = styled(`input`, { + name: classes.input, + slot: 'input', +})(() => ({ + left: '-100%', + width: '300%', + cursor: 'inherit', + position: 'absolute', + opacity: 0, + height: '100%', + top: 0, + margin: 0, + padding: 0, + zIndex: 1, +})); + +const TriStateSwitchThumb = styled(`span`, { + name: classes.thumb, + slot: 'thumb', +})(() => ({ + boxShadow: 'none', + width: '16px', + height: '16px', + margin: '2px', + backgroundColor: 'currentColor', + borderRadius: '50%', +})); +type TriStateSwitchState = 'checked' | 'unchecked' | 'neutral'; + +export const TriStateSwitch = function ({ + value, + onChange, +}: { + value?: TriStateSwitchState; + onChange?: (newValue: TriStateSwitchState) => void; +}) { + const [state, sSt] = useState(value ?? 'neutral'); + const setState = (v: TriStateSwitchState) => { + onChange?.(v); + sSt(value ?? v); + }; + + const root = useRef(null); + return ( + <> + { + const offsetX = + e.clientX - (root.current?.getBoundingClientRect().x ?? 0); + + const inputWidth = parseFloat( + getComputedStyle(e.currentTarget).width, + ); + const clickPercentage = offsetX / inputWidth; + const sidePressed = + clickPercentage < 0.33 + ? 'left' + : clickPercentage > 0.66 + ? 'right' + : 'center'; + + console.log(inputWidth, offsetX); + + switch (sidePressed) { + case 'center': + return setState('neutral'); + case 'left': + return setState('unchecked'); + case 'right': + return setState('checked'); + } + }} + > + + + + + + + + ); +}; diff --git a/src/components/accordions/components/filters/XCheckboxFilter.tsx b/src/components/accordions/components/filters/XCheckboxFilter.tsx new file mode 100644 index 0000000..81f569a --- /dev/null +++ b/src/components/accordions/components/filters/XCheckboxFilter.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { Filter, FilterTypes, ValueOfFilter } from '@libs/filterInputs'; +import { FormControl, FormControlLabel, FormLabel, Grid } from '@mui/material'; +import { useAppStore } from '@store'; +import { TriStateCheckbox, TriStateCheckboxState } from './TriStateCheckbox'; +import { TriStateSwitch } from './TriStateSwitch'; + +type FilterType = FilterTypes.ExcludableCheckboxGroup; + +export const XCheckboxFilter = ({ + filter: { key, filter }, + value, + set, +}: { + filter: { key: string; filter: Filter }; + value: ValueOfFilter; + set: (v: typeof value) => void; +}) => { + const useSwitches = useAppStore(state => state.useSwitches); + + const getCheckboxState = (v: string): TriStateCheckboxState => { + if (value.include?.includes(v)) return 'checked'; + if (value.exclude?.includes(v)) return 'unchecked'; + return 'neutral'; + }; + + const handleChange = (key: string, v: TriStateCheckboxState) => { + console.log(`setting ${key} to`, v); + if (v === 'unchecked') + return set({ + exclude: value.exclude + ? Array.from(new Set([...value.exclude, key])) + : [key], + include: value.include?.filter(f => f !== key), + }); + if (v === 'checked') + return set({ + include: value.include + ? Array.from(new Set([...value.include, key])) + : [key], + exclude: value.exclude?.filter(f => f !== key), + }); + return set({ + include: value.include?.filter(f => f !== key), + exclude: value.exclude?.filter(f => f !== key), + }); + }; + + return ( + + {filter.label} + + {filter.options.map(option => { + return ( + + + handleChange(option.value, c)} + /> + ) : ( + handleChange(option.value, c)} + /> + ) + } + label={option.label} + /> + + + ); + })} + + + ); +}; diff --git a/src/hooks/usePlugin.ts b/src/hooks/usePlugin.ts deleted file mode 100644 index 2caad34..0000000 --- a/src/hooks/usePlugin.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { AppState } from '@redux/store'; -import { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; -import { Plugin } from '@typings/plugin'; -import { getPlugin } from '@provider/plugins'; - -export default function usePlugin() { - const selectedPluginItem = useSelector( - (state: AppState) => state.plugin.selected, - ); - const [plugin, setPlugin] = useState(); - useEffect(() => { - if (selectedPluginItem) { - setPlugin(getPlugin(selectedPluginItem.id)); - } - }, [selectedPluginItem]); - return plugin; -} diff --git a/src/libs/fetch.ts b/src/libs/fetch.ts index cd59d5f..401d2b6 100644 --- a/src/libs/fetch.ts +++ b/src/libs/fetch.ts @@ -2,7 +2,7 @@ import { parse as parseProto } from 'protobufjs'; -interface FetchInit { +type FetchInit = { headers?: Record | Headers; method?: string; body?: FormData | string; @@ -12,7 +12,7 @@ interface FetchInit { | undefined | FormData | Headers; -} +}; const makeInit = async (init?: FetchInit) => { const defaultHeaders: Record = { @@ -100,13 +100,13 @@ export const fetchText = async function ( } }; -interface ProtoRequestInit { +type ProtoRequestInit = { // merged .proto file proto: string; requestType: string; requestData?: any; responseType: string; -} +}; const BYTE_MARK = BigInt((1 << 8) - 1); diff --git a/src/libs/filterInputs.ts b/src/libs/filterInputs.ts index 1b15a9e..8cc7eeb 100644 --- a/src/libs/filterInputs.ts +++ b/src/libs/filterInputs.ts @@ -1,8 +1,14 @@ -export interface FilterOption { +/** + * Filter's Options user can choose from + */ +export type FilterOption = { readonly label: string; readonly value: string; -} +}; +/** + * Every currently implemented FilterType + */ export enum FilterTypes { TextInput = 'Text', Picker = 'Picker', @@ -11,59 +17,83 @@ export enum FilterTypes { ExcludableCheckboxGroup = 'XCheckbox', } -interface SwitchFilter { +type SwitchFilter = { type: FilterTypes.Switch; /** Default value */ value: boolean; -} +}; -interface TextFilter { +type TextFilter = { type: FilterTypes.TextInput; /** Default value */ value: string; -} +}; -interface CheckboxFilter { +type CheckboxFilter = { type: FilterTypes.CheckboxGroup; options: readonly FilterOption[]; /** Default value */ value: string[]; -} -interface PickerFilter { +}; +type PickerFilter = { type: FilterTypes.Picker; options: readonly FilterOption[]; /** Default value */ value: string; -} +}; -interface ExcludableCheckboxFilter { +type ExcludableCheckboxFilter = { type: FilterTypes.ExcludableCheckboxGroup; options: readonly FilterOption[]; - value: { include?: string[]; exclude?: string[] }; -} + /** Default value */ + value: { + /** Checkboxes marked as included */ + include?: string[]; + /** Checkboxes marked as excluded */ + exclude?: string[]; + }; +}; -export type Filters = Record< - string, - { label: string } & ( - | PickerFilter - | CheckboxFilter - | TextFilter - | SwitchFilter - | ExcludableCheckboxFilter - ) ->; +/** + * key - filter pairs + */ +export type Filters = Record>; +/** Mapping of each FilterType to a Filter */ +type FilterFromType = { + [FilterTypes.CheckboxGroup]: CheckboxFilter; + [FilterTypes.ExcludableCheckboxGroup]: ExcludableCheckboxFilter; + [FilterTypes.Picker]: PickerFilter; + [FilterTypes.Switch]: SwitchFilter; + [FilterTypes.TextInput]: TextFilter; +}; + +/** + * Get type of a single filter type from the {@link FilterType} + */ +export type Filter = { + label: string; +} & FilterFromType[Type]; + +/** + * Strip {@link FilterObject} object from 'label' and 'options' to get key - filter_value pairs + * @see {@link ValueOfFilter} + */ export type FilterToValues< - T extends Record | undefined, -> = T extends undefined + FilterObject extends Record | undefined, +> = FilterObject extends undefined ? undefined : { - [K in keyof T]: Omit< - { type: NonNullable[K]['type'] } & Filters[string], - 'label' | 'options' + // copy the Filters object, but just get {value,type} pairs instead of the whole Filter object + [SingleFilter in keyof FilterObject]: FilterValueWithType< + FilterType[SingleFilter]> >; }; +/** + * Get value type for a {@link Filter} given it's FilterType + * @see {@link FilterTypes} + */ export type ValueOfFilter = T extends FilterTypes.CheckboxGroup ? CheckboxFilter['value'] @@ -76,3 +106,25 @@ export type ValueOfFilter = : T extends FilterTypes.ExcludableCheckboxGroup ? ExcludableCheckboxFilter['value'] : never; + +/** Get {@link Filter}'s type */ +export type FilterType = T extends { + type: infer K; +} + ? K extends FilterTypes + ? K + : never + : never; + +/** Get {type, value} types for given FilterType + * @see {@link ValueOfFilter} + */ +export type FilterValueWithType = { + type: T; + value: ValueOfFilter; +}; + +/** + * Any possible filter value + */ +export type AnyFilterValue = ValueOfFilter; diff --git a/src/plugins/english/lnmtl.ts b/src/plugins/english/lnmtl.ts index 76e847e..854caff 100644 --- a/src/plugins/english/lnmtl.ts +++ b/src/plugins/english/lnmtl.ts @@ -367,14 +367,14 @@ class LnMTLPlugin implements Plugin.PagePlugin { export default new LnMTLPlugin(); -interface ChapterEntry { +type ChapterEntry = { number: number; title: string; slug: string; created_at: string; -} +}; -interface VolumeEntry { +type VolumeEntry = { id: string; title: string; -} +}; diff --git a/src/plugins/english/ranobes.ts b/src/plugins/english/ranobes.ts index 89dbb57..4d21fe9 100644 --- a/src/plugins/english/ranobes.ts +++ b/src/plugins/english/ranobes.ts @@ -369,9 +369,9 @@ class RanobesPlugin implements Plugin.PagePlugin { } export default new RanobesPlugin(); -interface ChapterEntry { +type ChapterEntry = { id: number; title: string; date: string; link: string; -} +}; diff --git a/src/plugins/english/relibrary.ts b/src/plugins/english/relibrary.ts index cc30b86..7e8376f 100644 --- a/src/plugins/english/relibrary.ts +++ b/src/plugins/english/relibrary.ts @@ -6,10 +6,10 @@ import { defaultCover } from '@libs/defaultCover'; import { NovelItem } from '../../test_web/static/js'; // import { isUrlAbsolute } from "@libs/isAbsoluteUrl"; -interface FuzzySearchOptions { +type FuzzySearchOptions = { caseSensitive: boolean; sort: boolean; -} +}; // Shamelessly stolen from 'https://raw.githubusercontent.com/wouterrutgers/fuzzy-search/master/src/FuzzySearch.mjs' // I did modify the code a fair bit, but the algorithm is still the same under the hood. diff --git a/src/plugins/english/royalroad.ts b/src/plugins/english/royalroad.ts index 26ac8b9..b0f0c1d 100644 --- a/src/plugins/english/royalroad.ts +++ b/src/plugins/english/royalroad.ts @@ -722,18 +722,18 @@ class RoyalRoad implements Plugin.PluginBase { export default new RoyalRoad(); -interface ChapterEntry { +type ChapterEntry = { id: number; volumeId: number; title: string; date: string; order: number; url: string; -} +}; -interface VolumeEntry { +type VolumeEntry = { id: number; title: string; cover: string; order: number; -} +}; diff --git a/src/plugins/english/wtrlab.ts b/src/plugins/english/wtrlab.ts index 98e9f6d..eb6801a 100644 --- a/src/plugins/english/wtrlab.ts +++ b/src/plugins/english/wtrlab.ts @@ -205,22 +205,22 @@ class WTRLAB implements Plugin.PluginBase { } satisfies Filters; } -interface NovelJson { +type NovelJson = { props: Props; page: string; -} +}; -interface Props { +type Props = { pageProps: PageProps; __N_SSP: boolean; -} +}; -interface PageProps { +type PageProps = { serie: Serie; server_time: Date; -} +}; -interface Serie { +type Serie = { serie_data: SerieData; chapters: Chapter[]; recommendation: SerieData[]; @@ -231,9 +231,9 @@ interface Serie { data: Data; is_default: boolean; raw_type: string; -} +}; -interface Chapter { +type Chapter = { serie_id: number; id: number; order: number; @@ -242,16 +242,16 @@ interface Chapter { name: string; created_at: string; updated_at: string; -} -interface ChapterData { +}; +type ChapterData = { data: ChapterContent; -} -interface ChapterContent { +}; +type ChapterContent = { title: string; body: string; -} +}; -interface SerieData { +type SerieData = { serie_id?: number; recommendation_id?: number; score?: string; @@ -273,26 +273,26 @@ interface SerieData { from: null; raw_id: number; genres?: number[]; -} +}; -interface Data { +type Data = { title: string; author: string; description: string; image: string; -} +}; -interface JsonNovel { +type JsonNovel = { success: boolean; data: Datum[]; -} -interface Datum { +}; +type Datum = { serie: Serie; chapters: Chapter[]; updated_at: Date; raw_id: number; slug: string; data: Data; -} +}; export default new WTRLAB(); diff --git a/src/plugins/english/wuxiaworld.ts b/src/plugins/english/wuxiaworld.ts index 5ab3b2c..e3b3356 100644 --- a/src/plugins/english/wuxiaworld.ts +++ b/src/plugins/english/wuxiaworld.ts @@ -4,35 +4,35 @@ import { Plugin } from '@typings/plugin'; import { Filters } from '@libs/filterInputs'; import { NovelStatus } from '@libs/novelStatus'; -interface StringValue { +type StringValue = { value: string; -} +}; -interface BoolValue { +type BoolValue = { value: boolean; -} +}; -interface DecimalValue { +type DecimalValue = { units: bigint; nanos: number; -} +}; -interface Timestamp { +type Timestamp = { seconds: number; nanos: number; -} +}; -interface NovelEntry { +type NovelEntry = { name: string; coverUrl: string; slug: string; -} +}; -interface RelatedChapterUserInfo { +type RelatedChapterUserInfo = { isChapterUnlocked?: BoolValue; -} +}; -interface ChapterItem { +type ChapterItem = { entityId: number; name: string; slug: string; @@ -41,19 +41,19 @@ interface ChapterItem { relatedUserInfo?: RelatedChapterUserInfo; offset: number; publishedAt?: Timestamp; -} +}; -interface GetChapterResponse { +type GetChapterResponse = { item?: ChapterItem; -} +}; -interface ChapterGroupCounts { +type ChapterGroupCounts = { total: number; advance: number; normal: number; -} +}; -interface ChapterGroupItem { +type ChapterGroupItem = { id: number; title: string; order: number; @@ -61,11 +61,11 @@ interface ChapterGroupItem { toChapterNumber?: DecimalValue; chapterList: ChapterItem[]; counts?: ChapterGroupCounts; -} +}; -interface GetChapterListResponse { +type GetChapterListResponse = { items: ChapterGroupItem[]; -} +}; enum NovelItem_Status { Finished = 0, @@ -74,14 +74,14 @@ enum NovelItem_Status { All = -1, } -interface NovelKarmaInfo { +type NovelKarmaInfo = { isActive: boolean; isFree: boolean; maxFreeChapter?: DecimalValue; canUnlockWithVip: boolean; -} +}; -interface NovelItem { +type NovelItem = { id: number; name: string; slug: string; @@ -94,11 +94,11 @@ interface NovelItem { authorName?: StringValue; karmaInfo?: NovelKarmaInfo; genres: string[]; -} +}; -interface GetNovelResponse { +type GetNovelResponse = { item?: NovelItem; -} +}; class WuxiaWorld implements Plugin.PluginBase { id = 'wuxiaworld'; diff --git a/src/plugins/index.d.ts b/src/plugins/index.d.ts index a50fccb..3421420 100644 --- a/src/plugins/index.d.ts +++ b/src/plugins/index.d.ts @@ -1,3 +1,4 @@ -import { Plugin } from '@typings/plugin'; +import { Plugin } from '../types/plugin'; -export default [] as Plugin.PluginBase[]; +declare const PLUGINS: Plugin.PluginItem[]; +export default PLUGINS; diff --git a/src/plugins/japanese/Syosetu.ts b/src/plugins/japanese/Syosetu.ts index 069c800..5827c35 100644 --- a/src/plugins/japanese/Syosetu.ts +++ b/src/plugins/japanese/Syosetu.ts @@ -3,7 +3,6 @@ import { fetchApi } from '@libs/fetch'; import { Plugin } from '@typings/plugin'; import { defaultCover } from '@libs/defaultCover'; import { FilterTypes, Filters } from '@libs/filterInputs'; -import { NovelItem } from '../../test_web/static/js'; // const novelStatus = require('@libs/novelStatus'); // const isUrlAbsolute = require('@libs/isAbsoluteUrl'); // const parseDate = require('@libs/parseDate'); @@ -52,13 +51,13 @@ class Syosetu implements Plugin.PluginBase { if (parseInt(loadedCheerio('.is-current').html() || '1') !== pagenumber) return []; - const novels: NovelItem[] = []; + const novels: Plugin.NovelItem[] = []; loadedCheerio('.c-card').each((_, e) => { const anchor = loadedCheerio(e).find('.p-ranklist-item__title a'); const url = anchor.attr('href'); if (!url) return; const name = anchor.text(); - const novel: NovelItem = { + const novel: Plugin.NovelItem = { path: url.replace(this.novelPrefix, ''), name, cover: defaultCover, @@ -93,12 +92,12 @@ class Syosetu implements Plugin.PluginBase { novel.summary = loadedCheerio('#novel_ex') .text() .replace(/<\s*br.*?>/g, '\n'); - cqGetChapters.each(() => { - const chapterA = loadedCheerio(this).find('a'); + cqGetChapters.each((_, e) => { + const chapterA = loadedCheerio(e).find('a'); const [chapterName, releaseDate, chapterUrl] = [ // set the variables chapterA.text(), - loadedCheerio(this) + loadedCheerio(e) .find('dt') // get title .text() // get text .replace(/(.)/g, '') // remove "(edited)" mark diff --git a/src/plugins/korean/Agitoon.ts b/src/plugins/korean/Agitoon.ts index cb08b3e..5a4650a 100644 --- a/src/plugins/korean/Agitoon.ts +++ b/src/plugins/korean/Agitoon.ts @@ -181,12 +181,12 @@ class Agitoon implements Plugin.PluginBase { export default new Agitoon(); -interface response { +type response = { list_limit: number; list?: ListEntity[] | null; list_count: number; -} -interface ListEntity { +}; +type ListEntity = { wr_id: string; wr_subject: string; np_dir: string; @@ -200,13 +200,13 @@ interface ListEntity { np_country: string; np_age: string; is_scrap: number; -} +}; -interface responseBook { +type responseBook = { list?: ListEntity2[] | null; download_time: string; -} -interface ListEntity2 { +}; +type ListEntity2 = { wr_id: string; wr_subject: string; wr_datetime: string; @@ -214,4 +214,4 @@ interface ListEntity2 { novel_c_style1: string; novel_c_str1: string; data_novel_c_view: string; -} +}; diff --git a/src/plugins/russian/authortoday.ts b/src/plugins/russian/authortoday.ts index 9f6bf5e..35d83e6 100644 --- a/src/plugins/russian/authortoday.ts +++ b/src/plugins/russian/authortoday.ts @@ -368,14 +368,14 @@ function decrypt( return text; } -interface authorization { +type authorization = { userId: number; token: string; issued: string; expires: string; -} +}; -interface currentUser { +type currentUser = { id: number; userName: string; fio: string; @@ -391,9 +391,9 @@ interface currentUser { emailConfirmed: boolean; isDeleted: boolean; isDisabled: boolean; -} +}; -interface response { +type response = { searchResults?: SearchResultsEntity[] | null; realTotalCount: number; code?: string; @@ -402,8 +402,8 @@ interface response { format: Format; duration: Duration; isWorkMarkEnabled: boolean; -} -interface SearchResultsEntity { +}; +type SearchResultsEntity = { id: number; title: string; annotation: string; @@ -446,20 +446,20 @@ interface SearchResultsEntity { isLiked: boolean; isWorkMarkEnabled: boolean; workInLibraryState: string; -} -interface Format { +}; +type Format = { enumValue: string; value: string; title: string; mobileTitle: string; -} -interface Duration { +}; +type Duration = { value: string; title: string; mobileTitle: string; -} +}; -interface responseBook { +type responseBook = { message?: string; chapters?: ChaptersEntity[] | null; allowDownloads: boolean; @@ -543,8 +543,8 @@ interface responseBook { purchaseTime?: null; likeTime?: null; markTime?: null; -} -interface ChaptersEntity { +}; +type ChaptersEntity = { id: number; workId: number; title: string; @@ -554,16 +554,16 @@ interface ChaptersEntity { lastModificationTime: string; textLength: number; isAvailable: boolean; -} +}; -interface GalleryImagesEntity { +type GalleryImagesEntity = { id: string; caption?: null; url: string; height: number; width: number; -} -interface RecommendationsEntity { +}; +type RecommendationsEntity = { title: string; coverUrl: string; authorId: number; @@ -590,9 +590,9 @@ interface RecommendationsEntity { removalReason?: null; removalReasonComment?: null; id: number; -} +}; -interface encryptedСhapter { +type encryptedСhapter = { id: number; title: string; isDraft: boolean; @@ -602,4 +602,4 @@ interface encryptedСhapter { key: string; code?: string; message?: string; -} +}; diff --git a/src/plugins/russian/bookriver.ts b/src/plugins/russian/bookriver.ts index e5afcdf..9f53c31 100644 --- a/src/plugins/russian/bookriver.ts +++ b/src/plugins/russian/bookriver.ts @@ -226,7 +226,7 @@ class Bookriver implements Plugin.PluginBase { } export default new Bookriver(); -interface response { +type response = { props: { pageProps: { state: { @@ -241,9 +241,9 @@ interface response { }; }; }; -} +}; -interface BooksEntity { +type BooksEntity = { name: string; coverImages: { url: string }[]; slug: string; @@ -256,17 +256,17 @@ interface BooksEntity { chapters: Chapter[]; }; statusComplete: string; -} +}; -interface Chapter { +type Chapter = { name: string; available: boolean; firstPublishedAt?: Date | null; createdAt?: Date | null; chapterId: number | string; -} +}; -interface responseChapter { +type responseChapter = { data: { id: number; bookId: number; @@ -288,12 +288,12 @@ interface responseChapter { }; publicationScheduledFor?: null; }; -} +}; -interface responseSearch { +type responseSearch = { data: Data; total: number; -} -interface Data { +}; +type Data = { books?: BooksEntity[] | null; -} +}; diff --git a/src/plugins/russian/ficbook.ts b/src/plugins/russian/ficbook.ts index e433807..d04bdf9 100644 --- a/src/plugins/russian/ficbook.ts +++ b/src/plugins/russian/ficbook.ts @@ -2650,11 +2650,11 @@ class ficbook implements Plugin.PluginBase { export default new ficbook(); -interface Data { +type Data = { data?: DataEntity[] | null; more: boolean; -} -interface DataEntity { +}; +type DataEntity = { id: string; slug: string; author_id: number; @@ -2673,4 +2673,4 @@ interface DataEntity { readedDate?: string | null; reward_cnt: number; cover?: string | null; -} +}; diff --git a/src/plugins/russian/neobook.ts b/src/plugins/russian/neobook.ts index ad71a27..8d9068c 100644 --- a/src/plugins/russian/neobook.ts +++ b/src/plugins/russian/neobook.ts @@ -213,13 +213,13 @@ class Neobook implements Plugin.PluginBase { export default new Neobook(); -interface BundleBooks { +type BundleBooks = { categories: any[]; topSection: any[]; feed: Novels[]; -} +}; -interface BottomSection { +type BottomSection = { id: string; boost: string; preferenceid: string; @@ -230,9 +230,9 @@ interface BottomSection { more: string; part: string; items: Novels[]; -} +}; -interface Novels { +type Novels = { status?: string; error?: string; login?: Login; @@ -290,9 +290,9 @@ interface Novels { carouselItems?: Novels[]; book?: Novels; activeChapterIndex?: number; -} +}; -interface Attachment { +type Attachment = { has: string; id: string; typeid: string; @@ -304,16 +304,16 @@ interface Attachment { video: string; origin: string; ext: string; -} +}; -interface Image { +type Image = { s: string; m: string; l: string; has?: string; -} +}; -interface Chapter { +type Chapter = { id: string; token: string; status: string; @@ -324,9 +324,9 @@ interface Chapter { access: string; loaded: string; data?: Data; -} +}; -interface Data { +type Data = { available: string; access: string; id: string; @@ -339,27 +339,27 @@ interface Data { datePublish: string; attachment: Attachment; lastComments: any[]; -} +}; -interface NovelsCounters { +type NovelsCounters = { impressions: Bookmarks; views: Bookmarks; likes: Bookmarks; comments: Bookmarks; bookmarks: Bookmarks; -} +}; -interface Bookmarks { +type Bookmarks = { value: string; formatted: string; -} +}; -interface Feed { +type Feed = { name: string; canRemoved: string; -} +}; -interface Login { +type Login = { uid: string; utoken: string; has: string; @@ -376,14 +376,14 @@ interface Login { deposit: any[]; affiliate: any[]; preferences: any[]; -} +}; -interface LoginCounters { +type LoginCounters = { notifications: string; messages: string; -} +}; -interface LoginUser { +type LoginUser = { id: string; username: string; firstname: string; @@ -392,39 +392,39 @@ interface LoginUser { status: string; balance: Balance; connectedService: string; -} +}; -interface Balance { +type Balance = { neo: Price; -} +}; -interface Price { +type Price = { has: string; value: string; currency: Currency; formatted: string; deadline: string; -} +}; type Currency = 'NEO'; -interface Rating { +type Rating = { value: string; count: string; my: string; -} +}; -interface ReadProgress { +type ReadProgress = { chapter: string; progress: string; -} +}; -interface ReadTime { +type ReadTime = { hours: string; minutes: string; -} +}; -interface NovelsUser { +type NovelsUser = { available: string; boosted: string; id: string; @@ -452,4 +452,4 @@ interface NovelsUser { isSubscription: string; isBlock: string; gr: string; -} +}; diff --git a/src/plugins/russian/novelOvh.ts b/src/plugins/russian/novelOvh.ts index 93c4faf..50f08d1 100644 --- a/src/plugins/russian/novelOvh.ts +++ b/src/plugins/russian/novelOvh.ts @@ -183,7 +183,7 @@ class novelOvh implements Plugin.PluginBase { export default new novelOvh(); -interface BooksEntity { +type BooksEntity = { id: string; slug: string; type: string; @@ -211,18 +211,18 @@ interface BooksEntity { chaptersCount: number; createdAt: string; updatedAt: string; -} -interface Name { +}; +type Name = { ru: string; original: string; en: string; -} -interface AltNamesEntity { +}; +type AltNamesEntity = { language: string; name: string; -} +}; -interface responseNovel { +type responseNovel = { book: Book; branches: BranchesEntity[]; chapters: ChaptersEntity[]; @@ -233,8 +233,8 @@ interface responseNovel { promotionVideos?: null[] | null; folderItems?: null[] | null; contentNotices?: null[] | null; -} -interface Book { +}; +type Book = { id: string; slug: string; type: string; @@ -273,17 +273,17 @@ interface Book { createdAt: string; updatedAt: string; rating?: null; -} -interface LabelsEntity { +}; +type LabelsEntity = { id: string; slug: string; name: string; -} -interface RelationsEntity { +}; +type RelationsEntity = { type: string; publisher: Publisher; -} -interface Publisher { +}; +type Publisher = { id: string; kind: string; slug: string; @@ -298,8 +298,8 @@ interface Publisher { tonAddress?: null; createdAt: string; updatedAt: string; -} -interface BranchesEntity { +}; +type BranchesEntity = { id: string; licensed: boolean; editorsChoice: boolean; @@ -309,8 +309,8 @@ interface BranchesEntity { publishers?: PublishersEntity[] | null; subscription?: null; bookmark?: null; -} -interface PublishersEntity { +}; +type PublishersEntity = { id: string; kind: string; slug: string; @@ -324,8 +324,8 @@ interface PublishersEntity { tonAddress?: null; createdAt: string; updatedAt: string; -} -interface ChaptersEntity { +}; +type ChaptersEntity = { id: string; name?: string | null; title?: string | null; @@ -341,8 +341,8 @@ interface ChaptersEntity { createdAt: string; expiredAt: string; updatedAt: string; -} -interface RelatedEntityOrBook { +}; +type RelatedEntityOrBook = { id: string; type: string; slug: string; @@ -361,23 +361,23 @@ interface RelatedEntityOrBook { averageRating: number; createdAt: string; updatedAt: string; -} -interface AssociatedEntity { +}; +type AssociatedEntity = { id: string; kind: string; createdAt: string; updatedAt: string; book: RelatedEntityOrBook; -} -interface CoversEntity { +}; +type CoversEntity = { id: string; volume: number; image: string; createdAt: string; updatedAt: string; -} +}; -interface responseChapter { +type responseChapter = { id: string; name?: string; title: string; @@ -397,22 +397,22 @@ interface responseChapter { createdAt: string; expiredAt: string; updatedAt: string; -} -interface Content { +}; +type Content = { type: string; content?: ContentEntity[] | null; -} -interface ContentEntity { +}; +type ContentEntity = { text?: string | null; type: string; attrs?: Attrs | null; content?: Content[] | null; -} -interface Attrs { +}; +type Attrs = { textAlign?: string; pages?: [string]; -} -interface PagesEntity { +}; +type PagesEntity = { id: string; image: string; index: number; @@ -420,4 +420,4 @@ interface PagesEntity { height: number; width: number; createdAt: string; -} +}; diff --git a/src/plugins/russian/novelTL.ts b/src/plugins/russian/novelTL.ts index b9786a5..0371ad3 100644 --- a/src/plugins/russian/novelTL.ts +++ b/src/plugins/russian/novelTL.ts @@ -851,25 +851,25 @@ class TL implements Plugin.PluginBase { export default new TL(); -interface response { +type response = { data: Data; -} +}; -interface Data { +type Data = { projects?: Projects; project?: Project; chapter?: DataChapter; -} +}; -interface DataChapter { +type DataChapter = { text: Text; -} +}; -interface Text { +type Text = { text: string; -} +}; -interface Project { +type Project = { title: string; translationStatus: string; fullUrl: string; @@ -879,58 +879,58 @@ interface Project { tags: Genre[]; annotation: Text; subprojects: Subprojects; -} +}; -interface Cover { +type Cover = { url: string; -} +}; -interface Genre { +type Genre = { nameRu: string; nameEng: string; -} +}; -interface Person { +type Person = { role: string; name: Name; -} +}; -interface Name { +type Name = { firstName: string; lastName: string; -} +}; -interface Subprojects { +type Subprojects = { content: SubprojectsContent[]; -} +}; -interface SubprojectsContent { +type SubprojectsContent = { title: null | string; volumes: Volumes; -} +}; -interface Volumes { +type Volumes = { content: VolumesContent[]; -} +}; -interface VolumesContent { +type VolumesContent = { shortName: null | string; chapters: ChapterElement[]; -} +}; -interface ChapterElement { +type ChapterElement = { title: null | string; publishDate: Date; fullUrl: string; published: boolean; -} +}; -interface Projects { +type Projects = { content: ProjectsContent[]; -} +}; -interface ProjectsContent { +type ProjectsContent = { title: string; fullUrl: string; covers: Cover[]; -} +}; diff --git a/src/plugins/russian/ranobehub.ts b/src/plugins/russian/ranobehub.ts index 4f2a46b..531021c 100644 --- a/src/plugins/russian/ranobehub.ts +++ b/src/plugins/russian/ranobehub.ts @@ -1147,7 +1147,7 @@ class RNBH implements Plugin.PluginBase { export default new RNBH(); -interface responseNovels { +type responseNovels = { id: number; names: Names; rating: number; @@ -1158,27 +1158,27 @@ interface responseNovels { status: string; user?: User; counts: Counts; -} -interface Names { +}; +type Names = { eng?: string; rus?: string; original: string; -} -interface Poster { +}; +type Poster = { medium: string; small: string; color: string; -} -interface User { +}; +type User = { status?: null; liked: boolean; -} -interface Counts { +}; +type Counts = { volumes: string; chapters: string; -} +}; -interface responseNovel { +type responseNovel = { id: number; names: Names; rating: number; @@ -1195,55 +1195,53 @@ interface responseNovel { start_reading_url: string; html: string; tags: Tags; -} -interface Posters { +}; +type Posters = { big: string; medium: string; small: string; tiny: string; color: string; -} -interface AuthorsEntity { +}; +type AuthorsEntity = { name_eng: string; pivot: Pivot; -} -interface Pivot { +}; +type Pivot = { ranobe_id: number; author_id?: number; translator_id?: number; -} -interface TranslatorsEntity { +}; +type TranslatorsEntity = { name: string; pivot: Pivot; -} -interface Status { +}; +type Status = { id: number; title: string; -} -interface Tags { + name: string; +}; +type Tags = { events?: GenresOrEntity[] | null; genres?: GenresOrEntity[] | null; -} -interface GenresOrEntity { +}; +type GenresOrEntity = { id: number; names: Names; url: string; title: string; description?: string | null; -} +}; -interface VolumesEntity { +type VolumesEntity = { id: number; num: number; name: string; status: Status; chapters?: ChaptersEntity[] | null; -} -interface Status { - id: number; - name: string; -} -interface ChaptersEntity { +}; + +type ChaptersEntity = { id: number; name: string; num: number; @@ -1252,18 +1250,18 @@ interface ChaptersEntity { has_images: boolean; changed_at: string; comments_count: string; -} +}; -interface responseSearch { +type responseSearch = { meta: Meta; collections?: DataEntity[] | null; data?: DataEntity[] | null; -} -interface Meta { +}; +type Meta = { key: string; title: string; -} -interface DataEntity { +}; +type DataEntity = { id: number; names?: Names | null; description?: string | null; @@ -1275,10 +1273,10 @@ interface DataEntity { roles?: null[] | null; avatar?: Avatar | null; has_plus?: boolean | null; -} -interface Avatar { +}; +type Avatar = { big: string; color: string; thumb: string; is_default: boolean; -} +}; diff --git a/src/plugins/russian/ranobelib.ts b/src/plugins/russian/ranobelib.ts index df92577..196d7d0 100644 --- a/src/plugins/russian/ranobelib.ts +++ b/src/plugins/russian/ranobelib.ts @@ -526,82 +526,82 @@ function jsonToHtml(json: HTML[], images, html = '') { return html; } -interface HTML { +type HTML = { type: string; content?: HTML[]; attrs?: Attrs; text?: string; -} +}; -interface Attrs { +type Attrs = { src: string; alt: string | null; title: string | null; -} +}; -interface authorization { +type authorization = { token: Token; auth: Auth; timestamp: number; -} -interface Token { +}; +type Token = { token_type: string; expires_in: number; access_token: string; refresh_token: string; timestamp: number; -} -interface Auth { +}; +type Auth = { id: number; username: string; avatar: Cover; last_online_at: string; metadata: Metadata; -} -interface Metadata { +}; +type Metadata = { auth_domains: string; -} +}; -interface TopLevel { +type TopLevel = { data: DataClass | DataClass[]; links?: Links; meta?: Meta; -} +}; -interface AgeRestriction { +type AgeRestriction = { id: number; label: string; -} +}; -interface Branch { +type Branch = { id: number; branch_id: null; created_at: string; teams: BranchTeam[]; user: User; -} +}; -interface BranchTeam { +type BranchTeam = { id: number; slug: string; slug_url: string; model: string; name: string; cover: Cover; -} +}; -interface Cover { +type Cover = { filename: null | string; thumbnail: string; default: string; -} +}; -interface User { +type User = { username: string; id: number; -} +}; -interface DataClass { +type DataClass = { id: number; name: string; rus_name?: string; @@ -633,9 +633,9 @@ interface DataClass { likes_count?: number; content?: any; attachments?: Attachment[]; -} +}; -interface Artist { +type Artist = { id: number; slug: string; slug_url: string; @@ -648,16 +648,16 @@ interface Artist { confirmed: null; user_id: number; titles_count_details: null; -} +}; -interface Subscription { +type Subscription = { is_subscribed: boolean; source_type: string; source_id: number; relation: null; -} +}; -interface Attachment { +type Attachment = { id?: string; filename: string; name: string; @@ -665,14 +665,14 @@ interface Attachment { url: string; width: number; height: number; -} +}; -interface Genre { +type Genre = { id: number; name: string; -} +}; -interface DataTeam { +type DataTeam = { id: number; slug: string; slug_url: string; @@ -682,22 +682,22 @@ interface DataTeam { details?: Details; vk?: string; discord?: null; -} +}; -interface Details { +type Details = { branch_id: null; is_active: boolean; subscriptions_count: null; -} +}; -interface Links { +type Links = { first: string; last: null; prev: null; next: string; -} +}; -interface Meta { +type Meta = { current_page?: number; from?: number; path?: string; @@ -707,9 +707,9 @@ interface Meta { has_next_page?: boolean; seed?: string; country?: string; -} +}; -interface DataChapter { +type DataChapter = { id: number; index: number; item_number: number; @@ -719,4 +719,4 @@ interface DataChapter { name: string; branches_count: number; branches: Branch[]; -} +}; diff --git a/src/plugins/russian/ranoberf.ts b/src/plugins/russian/ranoberf.ts index f8e7b69..bbac441 100644 --- a/src/plugins/russian/ranoberf.ts +++ b/src/plugins/russian/ranoberf.ts @@ -143,7 +143,7 @@ class RNRF implements Plugin.PluginBase { export default new RNRF(); -interface response { +type response = { props: Props; page: string; query: Query; @@ -152,25 +152,25 @@ interface response { gssp: boolean; appGip: boolean; dynamicIds?: string[]; -} +}; -interface Props { +type Props = { pageProps: PageProps; isMobile: boolean; theme: string; __N_SSP: boolean; -} +}; -interface PageProps { +type PageProps = { initialReduxState: InitialReduxState; book?: PagePropsBook; totalData?: TotalData; layoutViewCookies?: string; displayBanner?: boolean; chapter?: PagePropsChapter; -} +}; -interface PagePropsBook { +type PagePropsBook = { id: number; countryId: number; creatorId: number; @@ -215,9 +215,9 @@ interface PagePropsBook { verticalImage: Image; horizontalImage: Image; squareImage: Image; -} +}; -interface ChapterElement { +type ChapterElement = { id: number; bookId: number; title: string; @@ -233,35 +233,35 @@ interface ChapterElement { price: number; views: number; isUserPaid?: boolean; -} +}; -interface Country { +type Country = { id: number; title: string; code: string; -} +}; -interface Genre { +type Genre = { id: number; title: string; slug: string; -} +}; -interface Image { +type Image = { id: number; alt: string; path: string; name: string; url: string; -} +}; -interface Timer { +type Timer = { value: null; for: null; message: string; -} +}; -interface PagePropsChapter { +type PagePropsChapter = { id: number; bookId: number; editorId: null; @@ -289,94 +289,94 @@ interface PagePropsChapter { nextChapter: null; previousChapter: PreviousChapter; corrections: null; -} +}; -interface ChapterBook { +type ChapterBook = { id: number; title: string; titleEn: string; url: string; -} +}; -interface Content { +type Content = { id: number; text: string; symbolsCount: number; -} +}; -interface PreviousChapter { +type PreviousChapter = { id: number; title: string; views: number; publishedAt: Date; url: string; numberChapter: string; -} +}; -interface InitialReduxState { +type InitialReduxState = { data?: Data; auth: Auth; banners?: Banners; corrections?: Corrections; readerSettings?: ReaderSettings; -} +}; -interface Auth { +type Auth = { isAuth: boolean; user: null; -} +}; -interface Banners { +type Banners = { displaySale: boolean; -} +}; -interface Corrections { +type Corrections = { items: null; data: null; -} +}; -interface Data { +type Data = { bookPage: BookPage | null; chapterPage: ChapterPage | null; -} +}; -interface BookPage { +type BookPage = { bookId: number; bookmark: null; -} +}; -interface ChapterPage { +type ChapterPage = { chapterId: number; bookId: number; bookmark: null; -} +}; -interface ReaderSettings { +type ReaderSettings = { fontSize: number; lineHeight: number; textIndent: number; sidebarHide: number; textAlign: string; -} +}; -interface TotalData { +type TotalData = { items: Item[]; _links: Links; pagesData: PagesData; -} +}; -interface Links { +type Links = { self: First; first: First; last: First; next: First; -} +}; -interface First { +type First = { href: string; -} +}; -interface Item { +type Item = { id: number; title: string; description: string; @@ -390,16 +390,16 @@ interface Item { verticalImage: Image; horizontalImage: Image; squareImage: Image; -} +}; -interface PagesData { +type PagesData = { totalCount: number; pageCount: number; currentPage: number; perPage: number; -} +}; -interface Query { +type Query = { book?: string; chapter?: string; -} +}; diff --git a/src/plugins/russian/renovels.ts b/src/plugins/russian/renovels.ts index 3a7aa71..1eb7d7b 100644 --- a/src/plugins/russian/renovels.ts +++ b/src/plugins/russian/renovels.ts @@ -946,7 +946,7 @@ class ReN implements Plugin.PluginBase { export default new ReN(); -interface responseNovels { +type responseNovels = { id: number; secondary_name: string; main_name: string; @@ -969,15 +969,15 @@ interface responseNovels { categories?: ItemEntity[] | null; rus_name?: string; en_name?: string; -} +}; -interface Img { +type Img = { high?: string; mid?: string; low: string; -} +}; -interface responseNovel { +type responseNovel = { id: number; img: Img; secondary_name: string; @@ -1010,42 +1010,42 @@ interface responseNovel { uploaded: number; can_post_comments: boolean; adaptation: Adaptation | null; -} +}; -interface Adaptation { +type Adaptation = { main_name: string; secondary_name: string; dir: string; img: string; -} +}; -interface ItemEntity { +type ItemEntity = { id: number; name: string; -} -interface PublishersEntity { +}; +type PublishersEntity = { id: number; name: string; img: string; dir: string; tagline: string; type: string; -} -interface BranchesEntity { +}; +type BranchesEntity = { id: number; img: string; publishers?: PublishersEntity[] | null; subscribed: boolean; total_votes: number; count_chapters: number; -} -interface FirstChapter { +}; +type FirstChapter = { id: number; tome: number; chapter: string; -} +}; -interface responseСhapters { +type responseСhapters = { id: number; rated?: null; viewed?: null; @@ -1060,9 +1060,9 @@ interface responseСhapters { pub_date?: null; upload_date: string; is_paid: boolean; -} +}; -interface responseСhapter { +type responseСhapter = { id: number; chapter: string; name: string; @@ -1078,4 +1078,4 @@ interface responseСhapter { index: number; publishers?: PublishersEntity[] | null; tome: number; -} +}; diff --git a/src/plugins/russian/ruvers.ts b/src/plugins/russian/ruvers.ts index 6ac1721..68e584c 100644 --- a/src/plugins/russian/ruvers.ts +++ b/src/plugins/russian/ruvers.ts @@ -125,7 +125,7 @@ class RV implements Plugin.PluginBase { export default new RV(); -interface book { +type book = { id: number; name: string; original_name: string; @@ -151,9 +151,9 @@ interface book { updated_at: string; slug: string; chapters_count: number; -} +}; -interface chapters { +type chapters = { id: number; name: string; number: string; @@ -173,7 +173,7 @@ interface chapters { is_new: boolean; purchased_by_user: boolean; book_slug: string; -} +}; function unicodeToUtf8(unicode: string) { return unicode.replace(/\\u([\d\w]{4})/gi, (match, hex) => diff --git a/src/plugins/spanish/skynovels.ts b/src/plugins/spanish/skynovels.ts index 743bbcd..19a2951 100644 --- a/src/plugins/spanish/skynovels.ts +++ b/src/plugins/spanish/skynovels.ts @@ -111,10 +111,10 @@ class SkyNovels implements Plugin.PluginBase { export default new SkyNovels(); -interface response { +type response = { novels?: NovelsEntity[] | null; -} -interface NovelsEntity { +}; +type NovelsEntity = { id: number; nvl_author?: number | null; nvl_content: string; @@ -135,16 +135,16 @@ interface NovelsEntity { nvl_rating?: number | null; nvl_ratings_count: number; genres?: GenresEntity[] | null; -} -interface GenresEntity { +}; +type GenresEntity = { id: number; genre_name: string; -} +}; -interface responseBook { +type responseBook = { novel?: NovelEntity[] | null; -} -interface NovelEntity { +}; +type NovelEntity = { id: number; nvl_author: number; nvl_content: string; @@ -168,29 +168,29 @@ interface NovelEntity { novel_ratings?: NovelRatingsEntity[] | null; collaborators?: CollaboratorsEntity[] | null; genres?: GenresEntity[] | null; -} -interface BookmarksEntity { +}; +type BookmarksEntity = { id: number; user_id: number; chp_id: number; chp_name: string; -} -interface VolumesEntity { +}; +type VolumesEntity = { vlm_title: string; id: number; nvl_id: number; user_id?: number | null; chapters?: ChaptersEntity[] | null; -} -interface ChaptersEntity { +}; +type ChaptersEntity = { id: number; chp_index_title: string; chp_name: string; chp_number: number; chp_status: string; createdAt: string; -} -interface NovelRatingsEntity { +}; +type NovelRatingsEntity = { user_id: number; rate_value: number; rate_comment: string; @@ -201,21 +201,21 @@ interface NovelRatingsEntity { user_login: string; image?: string | null; likes?: (LikesEntity | null)[] | null; -} -interface LikesEntity { +}; +type LikesEntity = { id: number; user_id: number; user_login: string; -} -interface CollaboratorsEntity { +}; +type CollaboratorsEntity = { user_id: number; user_login: string; -} +}; -interface responseChapter { +type responseChapter = { chapter?: ChapterEntity[] | null; -} -interface ChapterEntity { +}; +type ChapterEntity = { id: number; chp_author: number; chp_translator?: null; @@ -237,9 +237,9 @@ interface ChapterEntity { comments?: null[] | null; reactions?: null[] | null; total_reactions?: TotalReactionsEntity[] | null; -} -interface TotalReactionsEntity { +}; +type TotalReactionsEntity = { reaction_id: number; reaction_name: string; reaction_count: number; -} +}; diff --git a/src/plugins/turkish/lshnovel.broken.ts b/src/plugins/turkish/lshnovel.broken.ts index e9590b4..5e6c7d8 100644 --- a/src/plugins/turkish/lshnovel.broken.ts +++ b/src/plugins/turkish/lshnovel.broken.ts @@ -140,7 +140,7 @@ class LSHNovel implements Plugin.PluginBase { async searchNovels( searchTerm: string, - pageNo?: number | undefined, + //pageNo?: number | undefined, ): Promise { const url = `${this.site}?s=${searchTerm}`; const headers = new Headers(); diff --git a/src/plugins/ukrainian/smakolykytl.ts b/src/plugins/ukrainian/smakolykytl.ts index 1f024d9..b8b5696 100644 --- a/src/plugins/ukrainian/smakolykytl.ts +++ b/src/plugins/ukrainian/smakolykytl.ts @@ -142,55 +142,55 @@ function jsonToHtml(json: HTML[], html = '') { return html; } -interface response { +type response = { projects?: TopLevelProject[]; updates?: Update[]; project?: TopLevelProject; books?: BookElement[]; chapter?: TopLevelChapter; -} +}; -interface BookElement { +type BookElement = { id: number; rank: number; title: string; chapters: PurpleChapter[]; -} +}; -interface PurpleChapter { +type PurpleChapter = { id: number; title: string; rank: string; modifiedAt: Date; -} +}; -interface TopLevelChapter { +type TopLevelChapter = { id: number; title: string; rank: string; content: string; modifiedAt: Date; book: ChapterBook; -} +}; -interface ChapterBook { +type ChapterBook = { id: number; rank: number; title: string; chapters: FluffyChapter[]; project: BookProject; -} +}; -interface FluffyChapter { +type FluffyChapter = { id: number; rank: string; -} +}; -interface BookProject { +type BookProject = { id: number; -} +}; -interface TopLevelProject { +type TopLevelProject = { id: number; title: string; description: string; @@ -205,37 +205,37 @@ interface TopLevelProject { image: Image; tags?: Genre[]; genres?: Genre[]; -} +}; -interface Genre { +type Genre = { id: number; title: string; -} +}; -interface Image { +type Image = { id: number; url: string; name: string; -} +}; -interface Update { +type Update = { id: number; title: string; author: string; translator: string; modifiedAt: Date; image: Image; -} +}; -interface HTML { +type HTML = { type: string; content?: HTML[]; attrs?: Attrs; text?: string; -} +}; -interface Attrs { +type Attrs = { src: string; alt: string | null; title: string | null; -} +}; diff --git a/src/plugins/vietnamese/Truyenconect.ts b/src/plugins/vietnamese/Truyenconect.ts index 5759cf9..540a901 100644 --- a/src/plugins/vietnamese/Truyenconect.ts +++ b/src/plugins/vietnamese/Truyenconect.ts @@ -4,21 +4,21 @@ import { Plugin } from '@typings/plugin'; import { FilterTypes, Filters } from '@libs/filterInputs'; import { NovelStatus } from '@libs/novelStatus'; -interface VolumeData { +type VolumeData = { story: string; navigation: string; value: string; -} +}; -interface ChapterSelect { +type ChapterSelect = { chap_selector: string; eps_selector: boolean | string; -} +}; -interface VolumeList { +type VolumeList = { chapters: Plugin.ChapterItem[]; volumes?: VolumeData[]; -} +}; class TruyenConect implements Plugin.PagePlugin { id = 'truyenconect'; diff --git a/src/plugins/vietnamese/lightnovelvn.ts b/src/plugins/vietnamese/lightnovelvn.ts index 5ffaaee..3326dff 100644 --- a/src/plugins/vietnamese/lightnovelvn.ts +++ b/src/plugins/vietnamese/lightnovelvn.ts @@ -4,14 +4,14 @@ import { Plugin } from '@typings/plugin'; import { Filters } from '@libs/filterInputs'; import { NovelStatus } from '@libs/novelStatus'; -interface SearchedNovel { +type SearchedNovel = { name: string; slug: string; coverUrl: string; -} -interface SearchedResult { +}; +type SearchedResult = { data?: SearchedNovel[]; -} +}; class LightNovelVN implements Plugin.PagePlugin { id = 'lightnovel.vn'; diff --git a/src/provider/DialogProvider/index.tsx b/src/provider/DialogProvider/index.tsx index f8b20f9..1a14d02 100644 --- a/src/provider/DialogProvider/index.tsx +++ b/src/provider/DialogProvider/index.tsx @@ -1,11 +1,8 @@ -import { AppState } from '@redux/store'; import React from 'react'; -import { useSelector } from 'react-redux'; +// import { useAppStore } from '@store'; export default function DialogProvider() { - const activeDialogs = useSelector( - (state: AppState) => state.dialog.activeDialogs, - ); + // const _activeDialogs = useAppStore(state => state.activeDialogs); return <>; } diff --git a/src/provider/index.tsx b/src/provider/index.tsx index 4e1f499..8dbb044 100644 --- a/src/provider/index.tsx +++ b/src/provider/index.tsx @@ -1,11 +1,10 @@ import React from 'react'; import DialogProvider from './DialogProvider'; import { Backdrop, Box, CircularProgress, Container } from '@mui/material'; -import { useSelector } from 'react-redux'; -import { AppState } from '@redux/store'; +import { useAppStore } from '@store'; export default function AppProvider() { - const loading = useSelector((state: AppState) => state.overlay.loading); + const loading = useAppStore(state => state.loading); return ( ) { - state.activeDialogs = [...state.activeDialogs, action.payload]; - }, - hideDialog(state, action: PayloadAction) { - state.activeDialogs = state.activeDialogs.filter( - dialog => dialog !== action.payload, - ); - }, - hideAllDialogs(state) { - state.activeDialogs = []; - }, - }, -}); - -export const { showDialog, hideDialog } = dialogSlice.actions; - -export default dialogSlice.reducer; diff --git a/src/redux/overlaySlice.ts b/src/redux/overlaySlice.ts deleted file mode 100644 index 6b7f2e0..0000000 --- a/src/redux/overlaySlice.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; - -const initialState = { - loading: false, -}; - -export const overlaySlice = createSlice({ - name: 'overlay', - initialState: initialState, - reducers: { - showLoading(state) { - state.loading = true; - }, - hideLoading(state) { - state.loading = false; - }, - }, -}); - -export const { showLoading, hideLoading } = overlaySlice.actions; - -export default overlaySlice.reducer; diff --git a/src/redux/pluginSlice.ts b/src/redux/pluginSlice.ts deleted file mode 100644 index 9150f10..0000000 --- a/src/redux/pluginSlice.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { Plugin } from '@typings/plugin'; - -const initialState: { - selected?: Plugin.PluginItem; -} = { - selected: undefined, -}; - -export const pluginSlice = createSlice({ - name: 'plugin', - initialState: initialState, - reducers: { - selectPlugin(state, acion: PayloadAction) { - state.selected = acion.payload; - }, - }, -}); - -export const { selectPlugin } = pluginSlice.actions; - -export default pluginSlice.reducer; diff --git a/src/redux/store.ts b/src/redux/store.ts deleted file mode 100644 index 537bd15..0000000 --- a/src/redux/store.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { configureStore } from '@reduxjs/toolkit'; -import dialogReducer from './dialogSlice'; -import overlaySlice from './overlaySlice'; -import pluginReducer from './pluginSlice'; - -const store = configureStore({ - reducer: { - dialog: dialogReducer, - overlay: overlaySlice, - plugin: pluginReducer, - }, -}); - -export default store; -export type AppState = ReturnType; -export type AppDispatch = typeof store.dispatch; diff --git a/src/store/dialogStore.ts b/src/store/dialogStore.ts new file mode 100644 index 0000000..e964ecb --- /dev/null +++ b/src/store/dialogStore.ts @@ -0,0 +1,39 @@ +import { StoreCreator } from '.'; + +export enum EDialog {} + +export type DialogStore = { + activeDialogs: EDialog[]; + showDialog(dialog: EDialog): void; + hideDialog(dialog: EDialog): void; + hideAllDialogs(): void; +}; + +/** + * @param set Use this inside actions to change the state + * @param get Use this inside actions to get the current state + */ +export const DialogStore: StoreCreator = set => ({ + // this is initial state + activeDialogs: [], + + // those are actions + showDialog(dialog: EDialog) { + set(state => ({ + ...state, + activeDialogs: [...state.activeDialogs, dialog], + })); + }, + hideDialog(dialog) { + set(state => ({ + ...state, + activeDialogs: state.activeDialogs.filter(d => d !== dialog), + })); + }, + hideAllDialogs() { + set(state => ({ + ...state, + activeDialogs: [], + })); + }, +}); diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..35babcf --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,28 @@ +import { create, StateCreator } from 'zustand'; +import { PluginStore } from './pluginStore'; +import { DialogStore } from './dialogStore'; +import { OverlayStore } from './overlayStore'; + +export type AppStore = DialogStore & + OverlayStore & + PluginStore & { useSwitches: boolean; setUseSwitches(value: boolean): void }; + +// Helper types to use "slicing" like in Redux... We could just not use slicing, but eh +export type SetStore = Parameters>[0]; +export type GetStore = Parameters>[1]; +export type StoreCreator = (s: SetStore, g: GetStore) => T; + +export const useAppStore = create((set: SetStore, get: GetStore) => ({ + ...DialogStore(set, get), + ...OverlayStore(set, get), + ...PluginStore(set, get), + useSwitches: !!localStorage.getItem('useSwitches'), + setUseSwitches(uS: boolean) { + set(state => ({ + ...state, + useSwitches: uS, + })); + if (uS) localStorage.setItem('useSwitches', 'true'); + else localStorage.removeItem('useSwitches'); + }, +})); diff --git a/src/store/overlayStore.ts b/src/store/overlayStore.ts new file mode 100644 index 0000000..71be2b0 --- /dev/null +++ b/src/store/overlayStore.ts @@ -0,0 +1,30 @@ +import { StoreCreator } from '.'; + +export type OverlayStore = { + loading: boolean; + showLoading(): void; + hideLoading(): void; +}; + +/** + * @param set Use this inside actions to change the state + * @param get Use this inside actions to get the current state + */ +export const OverlayStore: StoreCreator = set => ({ + // this is initial state + loading: false, + + // those are actions + showLoading() { + set(state => ({ + ...state, + loading: true, + })); + }, + hideLoading() { + set(state => ({ + ...state, + loading: false, + })); + }, +}); diff --git a/src/store/pluginStore.ts b/src/store/pluginStore.ts new file mode 100644 index 0000000..c154e1d --- /dev/null +++ b/src/store/pluginStore.ts @@ -0,0 +1,27 @@ +import { Plugin } from '@typings/plugin'; +import { StoreCreator } from '.'; +import { getPlugin } from '@provider/plugins'; + +export type PluginStore = { + pluginItem?: Plugin.PluginItem; + plugin?: Plugin.PluginBase; + selectPlugin(plugin: Plugin.PluginItem): void; +}; + +/** + * @param set State setter for use inside actions + * @param get State getter for use inside actions, outside of State setter + */ +export const PluginStore: StoreCreator = set => ({ + // this is initial state + // selectedPlugin: undefined, + + // those are actions + selectPlugin(pluginItem) { + set(state => ({ + ...state, + pluginItem, + plugin: getPlugin(pluginItem.id), + })); + }, +}); diff --git a/src/types/plugin.ts b/src/types/plugin.ts index 8f0a547..83d456e 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -1,6 +1,6 @@ import { FilterToValues, Filters } from '@libs/filterInputs'; export namespace Plugin { - export interface ChapterItem { + export type ChapterItem = { name: string; path: string; /** @@ -17,13 +17,13 @@ export namespace Plugin { * For novel without pages only */ page?: string; - } - export interface NovelItem { + }; + export type NovelItem = { name: string; path: string; cover?: string; - } - export interface SourceNovel extends NovelItem { + }; + export type SourceNovel = { /** Comma separated genre list -> "action,fantasy,romance" */ genres?: string; summary?: string; @@ -31,32 +31,32 @@ export namespace Plugin { artist?: string; status?: string; chapters?: ChapterItem[]; - } + } & NovelItem; - export interface SourcePage { + export type SourcePage = { chapters: ChapterItem[]; - } + }; - export interface PopularNovelsOptions< + export type PopularNovelsOptions< Q extends Filters | undefined = Filters | undefined, - > { + > = { showLatestNovels?: boolean; - filters?: Q extends undefined ? undefined : FilterToValues; - } - export interface PluginItem { + filters: Q extends undefined ? undefined : FilterToValues; + }; + export type PluginItem = { id: string; name: string; version: string; icon: string; site: string; - } - export interface ImageRequestInit { + }; + export type ImageRequestInit = { method?: string; headers?: Record; body?: string; - } + }; - export interface PluginBase { + export type PluginBase = { id: string; name: string; /** @@ -87,32 +87,31 @@ export namespace Plugin { parseChapter(chapterPath: string): Promise; searchNovels(searchTerm: string, pageNo: number): Promise; resolveUrl?(path: string, isNovel?: boolean): string; - } + }; - export interface PagePlugin extends PluginBase { + export type PagePlugin = { parseNovel( novelPath: string, ): Promise; parsePage(novelPath: string, page: string): Promise; - } + } & PluginBase; } export namespace HTMLParser2Util { - interface HandlerBase { + type HandlerBase = { onopentag?(name: string, attribs: Record): void; ontext?(data: string): void; onclosetag?(name: string, isImplied: boolean): void; - } + }; - export interface Handler extends HandlerBase { + export type Handler = { isStarted?: boolean; isDone?: boolean; - } + } & HandlerBase; // route htmlparser2 event to handlers - export interface HandlerRouter - extends HandlerBase { + export type HandlerRouter = { handlers: Record; action: ActionType; - } + } & HandlerBase; } diff --git a/tsconfig.json b/tsconfig.json index 6e225b9..d27f2c2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,128 +1,129 @@ { - "compilerOptions": { - "jsx": "react", - /* Visit https://aka.ms/tsconfig to read more about this file */ + "compilerOptions": { + "jsx": "react", + /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "ES5" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - // "lib": [ - // "es2016", - // "dom", - // "es5" - // ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + /* Language and Environment */ + "target": "ES5" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [ + // "es2016", + // "dom", + // "es5" + // ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "ES2020" /* Specify what module code is generated. */, - // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "Bundler" /* Specify how TypeScript looks up a file from a given module specifier. */, - "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - "paths": { - "@libs/*": ["./src/libs/*"], - "@icons/*": ["./src/icons/*"], - "@components/*": ["./src/components/*"], - "@redux/*": ["./src/redux/*"], - "@provider/*": ["./src/provider/*"], - "@plugins/*": ["./src/plugins/*"], - "@hooks/*": ["./src/hooks/*"], - "@scripts/*": ["./scripts/*"], - "@typings/*": ["./src/types/*"] - } /* Specify a set of entries that re-map imports to additional lookup locations. */, - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - "resolveJsonModule": true /* Enable importing .json files. */, - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + /* Modules */ + "module": "ES2020" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "Bundler" /* Specify how TypeScript looks up a file from a given module specifier. */, + "baseUrl": "./" /* Specify the base directory to resolve non-relative module names. */, + "paths": { + "@libs/*": ["./src/libs/*"], + "@icons/*": ["./src/icons/*"], + "@components/*": ["./src/components/*"], + "@redux/*": ["./src/redux/*"], + "@provider/*": ["./src/provider/*"], + "@plugins/*": ["./src/plugins/*"], + "@hooks/*": ["./src/hooks/*"], + "@scripts/*": ["./scripts/*"], + "@typings/*": ["./src/types/*"], + "@store": ["./src/store"] + } /* Specify a set of entries that re-map imports to additional lookup locations. */, + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + "resolveJsonModule": true /* Enable importing .json files. */, + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - // "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, - // "checkJs": true /* Enable error reporting in type-checked JavaScript files. */, - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + /* JavaScript Support */ + // "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, + // "checkJs": true /* Enable error reporting in type-checked JavaScript files. */, + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./.js/" /* Specify an output folder for all emitted files. */, - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - "allowJs": true, - "checkJs": false, + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./.js/" /* Specify an output folder for all emitted files. */, + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + "allowJs": true, + "checkJs": false, - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - /* Type Checking */ - "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - }, - "exclude": ["./src/plugins/**/*.broken.ts", "./.js/**/*", "scripts", "docs"] + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "exclude": ["./src/plugins/**/*.broken.ts", "./.js/**/*", "scripts", "docs"] } diff --git a/vite.config.js b/vite.config.js index 7e67cb5..91adf3c 100644 --- a/vite.config.js +++ b/vite.config.js @@ -19,6 +19,7 @@ export default defineConfig({ '@hooks': path.resolve(dirname, './src/hooks'), '@typings': path.resolve(dirname, './src/types'), '@scripts': path.resolve(dirname, './scripts'), + '@store': path.resolve(dirname, './src/store'), }, }, server: {