pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/

update linting

+165 -155
+5
.eslintrc.js
··· 23 23 project: "./tsconfig.json", 24 24 tsconfigRootDir: "./", 25 25 }, 26 + settings: { 27 + "import/resolver": { 28 + typescript: {}, 29 + }, 30 + }, 26 31 plugins: ["@typescript-eslint", "import"], 27 32 rules: { 28 33 "react/jsx-uses-react": "off",
+1 -1
package.json
··· 26 26 "build": "vite build", 27 27 "preview": "vite preview", 28 28 "lint": "eslint --ext .tsx,.ts src", 29 - "lint:strict": "eslint --ext .tsx,.ts --max-warnings 0 src", 29 + "lint:fix": "eslint --fix --ext .tsx,.ts src", 30 30 "lint:report": "eslint --ext .tsx,.ts --output-file eslint_report.json --format json src" 31 31 }, 32 32 "browserslist": {
+1 -1
src/components/Dropdown.tsx
··· 57 57 )} 58 58 </Listbox> 59 59 </div> 60 - ) 60 + ); 61 61 }
+1 -1
src/components/SearchBar.tsx
··· 1 1 import { useState } from "react"; 2 - import { MWMediaType, MWQuery } from "@/providers"; 3 2 import { useTranslation } from "react-i18next"; 3 + import { MWMediaType, MWQuery } from "@/providers"; 4 4 import { DropdownButton } from "./buttons/DropdownButton"; 5 5 import { Icon, Icons } from "./Icon"; 6 6 import { TextInputControl } from "./text-inputs/TextInputControl";
+1 -1
src/components/layout/Backdrop.tsx
··· 1 1 import React, { createRef, useEffect, useState } from "react"; 2 - import { useFade } from "@/hooks/useFade"; 3 2 import { createPortal } from "react-dom"; 3 + import { useFade } from "@/hooks/useFade"; 4 4 5 5 interface BackdropProps { 6 6 onClick?: (e: MouseEvent) => void;
+1 -1
src/components/layout/BrandPill.tsx
··· 1 - import { Icon, Icons } from "@/components/Icon"; 2 1 import { useTranslation } from "react-i18next"; 2 + import { Icon, Icons } from "@/components/Icon"; 3 3 4 4 export function BrandPill(props: { clickable?: boolean }) { 5 5 const { t } = useTranslation();
+4 -4
src/components/layout/Loading.tsx
··· 8 8 <div className={props.className}> 9 9 <div className="flex flex-col items-center justify-center"> 10 10 <div className="flex h-12 items-center justify-center"> 11 - <div className="animate-loading-pin bg-denim-300 mx-1 h-2 w-2 rounded-full" /> 12 - <div className="animate-loading-pin bg-denim-300 mx-1 h-2 w-2 rounded-full [animation-delay:150ms]" /> 13 - <div className="animate-loading-pin bg-denim-300 mx-1 h-2 w-2 rounded-full [animation-delay:300ms]" /> 14 - <div className="animate-loading-pin bg-denim-300 mx-1 h-2 w-2 rounded-full [animation-delay:450ms]" /> 11 + <div className="mx-1 h-2 w-2 animate-loading-pin rounded-full bg-denim-300" /> 12 + <div className="mx-1 h-2 w-2 animate-loading-pin rounded-full bg-denim-300 [animation-delay:150ms]" /> 13 + <div className="mx-1 h-2 w-2 animate-loading-pin rounded-full bg-denim-300 [animation-delay:300ms]" /> 14 + <div className="mx-1 h-2 w-2 animate-loading-pin rounded-full bg-denim-300 [animation-delay:450ms]" /> 15 15 </div> 16 16 {props.text && props.text.length ? ( 17 17 <p className="mt-3 max-w-xs text-sm opacity-75">{props.text}</p>
+6 -4
src/components/layout/Paper.tsx
··· 1 1 import { ReactNode } from "react"; 2 2 3 3 export interface PaperProps { 4 - children?: ReactNode, 5 - className?: string, 4 + children?: ReactNode; 5 + className?: string; 6 6 } 7 7 8 8 export function Paper(props: PaperProps) { 9 9 return ( 10 - <div className={`bg-denim-200 lg:rounded-xl px-4 sm:px-8 md:px-12 py-6 sm:py-8 md:py-12 ${props.className}`}> 10 + <div 11 + className={`bg-denim-200 px-4 py-6 sm:px-8 sm:py-8 md:px-12 md:py-12 lg:rounded-xl ${props.className}`} 12 + > 11 13 {props.children} 12 14 </div> 13 - ) 15 + ); 14 16 }
+3 -3
src/components/layout/Seasons.tsx
··· 1 1 import { useEffect, useState } from "react"; 2 2 import { useHistory } from "react-router-dom"; 3 + import { useTranslation } from "react-i18next"; 3 4 import { IconPatch } from "@/components/buttons/IconPatch"; 4 5 import { Dropdown, OptionItem } from "@/components/Dropdown"; 5 6 import { Icons } from "@/components/Icon"; ··· 14 15 MWPortableMedia, 15 16 } from "@/providers"; 16 17 import { getSeasonDataFromMedia } from "@/providers/methods/seasons"; 17 - import { useTranslation } from "react-i18next"; 18 18 19 19 export interface SeasonsProps { 20 20 media: MWMedia; ··· 37 37 ) : ( 38 38 <div className="flex items-center space-x-3"> 39 39 <IconPatch icon={Icons.WARNING} className="text-red-400" /> 40 - <p>{t('seasons.failed')}</p> 40 + <p>{t("seasons.failed")}</p> 41 41 </div> 42 42 )} 43 43 </div> ··· 75 75 76 76 const mapSeason = (season: MWMediaSeason) => ({ 77 77 id: season.id, 78 - name: season.title || `${t('seasons.season', { season: season.sort })}`, 78 + name: season.title || `${t("seasons.season", { season: season.sort })}`, 79 79 }); 80 80 81 81 const options = seasons.seasons.map(mapSeason);
+3 -3
src/components/media/EpisodeButton.tsx
··· 9 9 return ( 10 10 <div 11 11 onClick={props.onClick} 12 - className={`bg-denim-500 hover:bg-denim-400 transition-[background-color, transform, box-shadow] relative mr-3 mb-3 inline-flex h-10 w-10 cursor-pointer select-none items-center justify-center overflow-hidden rounded font-bold text-white active:scale-110 ${ 13 - props.active ? "shadow-bink-500 shadow-[inset_0_0_0_2px]" : "" 12 + className={`transition-[background-color, transform, box-shadow] relative mr-3 mb-3 inline-flex h-10 w-10 cursor-pointer select-none items-center justify-center overflow-hidden rounded bg-denim-500 font-bold text-white hover:bg-denim-400 active:scale-110 ${ 13 + props.active ? "shadow-[inset_0_0_0_2px] shadow-bink-500" : "" 14 14 }`} 15 15 > 16 16 <div 17 - className="bg-bink-500 absolute bottom-0 top-0 left-0 bg-opacity-50" 17 + className="absolute bottom-0 top-0 left-0 bg-bink-500 bg-opacity-50" 18 18 style={{ 19 19 width: `${props.progress || 0}%`, 20 20 }}
+1 -1
src/components/text/DotList.tsx
··· 5 5 6 6 export function DotList(props: DotListProps) { 7 7 return ( 8 - <p className={`text-denim-700 font-semibold ${props.className || ""}`}> 8 + <p className={`font-semibold text-denim-700 ${props.className || ""}`}> 9 9 {props.content.map((item, index) => ( 10 10 <span key={item}> 11 11 {index !== 0 ? (
+11 -6
src/components/text/Link.tsx
··· 16 16 to: string; 17 17 } 18 18 19 - type LinkProps = 20 - | ILinkPropsExternal 21 - | ILinkPropsInternal 22 - | ILinkPropsBase; 19 + type LinkProps = ILinkPropsExternal | ILinkPropsInternal | ILinkPropsBase; 23 20 24 21 export function Link(props: LinkProps) { 25 22 const isExternal = !!(props as ILinkPropsExternal).url; 26 23 const isInternal = !!(props as ILinkPropsInternal).to; 27 24 const content = ( 28 - <span className="text-bink-600 hover:text-bink-700 cursor-pointer font-bold"> 25 + <span className="cursor-pointer font-bold text-bink-600 hover:text-bink-700"> 29 26 {props.children} 30 27 </span> 31 28 ); 32 29 33 30 if (isExternal) 34 - return <a target={(props as ILinkPropsExternal).newTab ? "_blank" : undefined} rel="noreferrer" href={(props as ILinkPropsExternal).url}>{content}</a>; 31 + return ( 32 + <a 33 + target={(props as ILinkPropsExternal).newTab ? "_blank" : undefined} 34 + rel="noreferrer" 35 + href={(props as ILinkPropsExternal).url} 36 + > 37 + {content} 38 + </a> 39 + ); 35 40 if (isInternal) 36 41 return ( 37 42 <LinkRouter to={(props as ILinkPropsInternal).to}>{content}</LinkRouter>
+8 -11
src/hooks/useDebounce.ts
··· 4 4 // State and setters for debounced value 5 5 const [debouncedValue, setDebouncedValue] = useState<T>(value); 6 6 7 - useEffect( 8 - () => { 9 - const handler = setTimeout(() => { 10 - setDebouncedValue(value); 11 - }, delay); 12 - return () => { 13 - clearTimeout(handler); 14 - }; 15 - }, 16 - [value, delay] 17 - ); 7 + useEffect(() => { 8 + const handler = setTimeout(() => { 9 + setDebouncedValue(value); 10 + }, delay); 11 + return () => { 12 + clearTimeout(handler); 13 + }; 14 + }, [value, delay]); 18 15 19 16 return debouncedValue; 20 17 }
+5 -3
src/hooks/useFade.ts
··· 1 1 import React, { useEffect, useState } from "react"; 2 - import './useFade.css' 2 + import "./useFade.css"; 3 3 4 - export const useFade = (initial = false): [boolean, React.Dispatch<React.SetStateAction<boolean>>, any] => { 4 + export const useFade = ( 5 + initial = false 6 + ): [boolean, React.Dispatch<React.SetStateAction<boolean>>, any] => { 5 7 const [show, setShow] = useState<boolean>(initial); 6 8 const [isVisible, setVisible] = useState<boolean>(show); 7 9 ··· 20 22 // These props go on the fading DOM element 21 23 const fadeProps = { 22 24 style, 23 - onAnimationEnd 25 + onAnimationEnd, 24 26 }; 25 27 26 28 return [isVisible, setShow, fadeProps];
+97 -97
src/providers/types.ts
··· 1 - export enum MWMediaType { 2 - MOVIE = "movie", 3 - SERIES = "series", 4 - ANIME = "anime", 5 - } 6 - 7 - export interface MWPortableMedia { 8 - mediaId: string; 9 - mediaType: MWMediaType; 10 - providerId: string; 11 - seasonId?: string; 12 - episodeId?: string; 13 - } 14 - 15 - export type MWMediaStreamType = "m3u8" | "mp4"; 16 - export interface MWMediaCaption { 17 - id: string; 18 - url: string; 19 - label: string; 20 - } 21 - export interface MWMediaStream { 22 - url: string; 23 - type: MWMediaStreamType; 24 - captions: MWMediaCaption[]; 25 - } 26 - 27 - export interface MWMediaMeta extends MWPortableMedia { 28 - title: string; 29 - year: string; 30 - seasonCount?: number; 31 - } 32 - 33 - export interface MWMediaEpisode { 34 - sort: number; 35 - id: string; 36 - title: string; 37 - } 38 - export interface MWMediaSeason { 39 - sort: number; 40 - id: string; 41 - title?: string; 42 - type: "season" | "special"; 43 - episodes: MWMediaEpisode[]; 44 - } 45 - export interface MWMediaSeasons { 46 - seasons: MWMediaSeason[]; 47 - } 48 - 49 - export interface MWMedia extends MWMediaMeta { 50 - seriesData?: MWMediaSeasons; 51 - } 52 - 53 - export type MWProviderMediaResult = Omit<MWMedia, "mediaType" | "providerId">; 54 - 55 - export interface MWQuery { 56 - searchQuery: string; 57 - type: MWMediaType; 58 - } 59 - 60 - export interface MWMediaProviderBase { 61 - id: string; // id of provider, must be unique 62 - enabled: boolean; 63 - type: MWMediaType[]; 64 - displayName: string; 65 - 66 - getMediaFromPortable(media: MWPortableMedia): Promise<MWProviderMediaResult>; 67 - searchForMedia(query: MWQuery): Promise<MWProviderMediaResult[]>; 68 - getStream(media: MWPortableMedia): Promise<MWMediaStream>; 69 - getSeasonDataFromMedia?: (media: MWPortableMedia) => Promise<MWMediaSeasons>; 70 - } 71 - 72 - export type MWMediaProviderSeries = MWMediaProviderBase & { 73 - getSeasonDataFromMedia: (media: MWPortableMedia) => Promise<MWMediaSeasons>; 74 - }; 75 - 76 - export type MWMediaProvider = MWMediaProviderBase; 77 - 78 - export interface MWMediaProviderMetadata { 79 - exists: boolean; 80 - id?: string; 81 - enabled: boolean; 82 - type: MWMediaType[]; 83 - provider?: MWMediaProvider; 84 - } 85 - 86 - export interface MWMassProviderOutput { 87 - providers: { 88 - id: string; 89 - success: boolean; 90 - }[]; 91 - results: MWMedia[]; 92 - stats: { 93 - total: number; 94 - failed: number; 95 - succeeded: number; 96 - }; 97 - } 1 + export enum MWMediaType { 2 + MOVIE = "movie", 3 + SERIES = "series", 4 + ANIME = "anime", 5 + } 6 + 7 + export interface MWPortableMedia { 8 + mediaId: string; 9 + mediaType: MWMediaType; 10 + providerId: string; 11 + seasonId?: string; 12 + episodeId?: string; 13 + } 14 + 15 + export type MWMediaStreamType = "m3u8" | "mp4"; 16 + export interface MWMediaCaption { 17 + id: string; 18 + url: string; 19 + label: string; 20 + } 21 + export interface MWMediaStream { 22 + url: string; 23 + type: MWMediaStreamType; 24 + captions: MWMediaCaption[]; 25 + } 26 + 27 + export interface MWMediaMeta extends MWPortableMedia { 28 + title: string; 29 + year: string; 30 + seasonCount?: number; 31 + } 32 + 33 + export interface MWMediaEpisode { 34 + sort: number; 35 + id: string; 36 + title: string; 37 + } 38 + export interface MWMediaSeason { 39 + sort: number; 40 + id: string; 41 + title?: string; 42 + type: "season" | "special"; 43 + episodes: MWMediaEpisode[]; 44 + } 45 + export interface MWMediaSeasons { 46 + seasons: MWMediaSeason[]; 47 + } 48 + 49 + export interface MWMedia extends MWMediaMeta { 50 + seriesData?: MWMediaSeasons; 51 + } 52 + 53 + export type MWProviderMediaResult = Omit<MWMedia, "mediaType" | "providerId">; 54 + 55 + export interface MWQuery { 56 + searchQuery: string; 57 + type: MWMediaType; 58 + } 59 + 60 + export interface MWMediaProviderBase { 61 + id: string; // id of provider, must be unique 62 + enabled: boolean; 63 + type: MWMediaType[]; 64 + displayName: string; 65 + 66 + getMediaFromPortable(media: MWPortableMedia): Promise<MWProviderMediaResult>; 67 + searchForMedia(query: MWQuery): Promise<MWProviderMediaResult[]>; 68 + getStream(media: MWPortableMedia): Promise<MWMediaStream>; 69 + getSeasonDataFromMedia?: (media: MWPortableMedia) => Promise<MWMediaSeasons>; 70 + } 71 + 72 + export type MWMediaProviderSeries = MWMediaProviderBase & { 73 + getSeasonDataFromMedia: (media: MWPortableMedia) => Promise<MWMediaSeasons>; 74 + }; 75 + 76 + export type MWMediaProvider = MWMediaProviderBase; 77 + 78 + export interface MWMediaProviderMetadata { 79 + exists: boolean; 80 + id?: string; 81 + enabled: boolean; 82 + type: MWMediaType[]; 83 + provider?: MWMediaProvider; 84 + } 85 + 86 + export interface MWMassProviderOutput { 87 + providers: { 88 + id: string; 89 + success: boolean; 90 + }[]; 91 + results: MWMedia[]; 92 + stats: { 93 + total: number; 94 + failed: number; 95 + succeeded: number; 96 + }; 97 + }
+7 -8
src/setup/i18n.ts
··· 1 - import i18n from 'i18next'; 2 - import { initReactI18next } from 'react-i18next'; 1 + import i18n from "i18next"; 2 + import { initReactI18next } from "react-i18next"; 3 3 4 - import Backend from 'i18next-http-backend'; 5 - import LanguageDetector from 'i18next-browser-languagedetector'; 4 + import Backend from "i18next-http-backend"; 5 + import LanguageDetector from "i18next-browser-languagedetector"; 6 6 7 7 i18n 8 8 // load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales) ··· 17 17 // init i18next 18 18 // for all options read: https://www.i18next.com/overview/configuration-options 19 19 .init({ 20 - fallbackLng: 'en-GB', 20 + fallbackLng: "en-GB", 21 21 22 22 interpolation: { 23 23 escapeValue: false, // not needed for react as it escapes by default 24 - } 24 + }, 25 25 }); 26 26 27 - 28 - export default i18n; 27 + export default i18n;
+1 -1
src/state/bookmark/index.ts
··· 1 - export * from "./context"; 1 + export * from "./context";
+1 -1
src/views/MediaView.tsx
··· 1 1 import { ReactElement, useEffect, useState } from "react"; 2 2 import { useHistory } from "react-router-dom"; 3 + import { useTranslation } from "react-i18next"; 3 4 import { IconPatch } from "@/components/buttons/IconPatch"; 4 5 import { Icons } from "@/components/Icon"; 5 6 import { Navigation } from "@/components/layout/Navigation"; ··· 29 30 useBookmarkContext, 30 31 } from "@/state/bookmark"; 31 32 import { getWatchedFromPortable, useWatchedContext } from "@/state/watched"; 32 - import { useTranslation } from "react-i18next"; 33 33 import { NotFoundChecks } from "./notfound/NotFoundChecks"; 34 34 35 35 interface StyledMediaViewProps {
+1 -1
src/views/notfound/NotFoundView.tsx
··· 1 1 import { ReactNode } from "react"; 2 + import { useTranslation } from "react-i18next"; 2 3 import { IconPatch } from "@/components/buttons/IconPatch"; 3 4 import { Icons } from "@/components/Icon"; 4 5 import { Navigation } from "@/components/layout/Navigation"; 5 6 import { ArrowLink } from "@/components/text/ArrowLink"; 6 7 import { Title } from "@/components/text/Title"; 7 - import { useTranslation } from "react-i18next"; 8 8 9 9 function NotFoundWrapper(props: { children?: ReactNode }) { 10 10 return (
+1 -1
src/views/search/HomeView.tsx
··· 1 + import { useTranslation } from "react-i18next"; 1 2 import { Icons } from "@/components/Icon"; 2 3 import { SectionHeading } from "@/components/layout/SectionHeading"; 3 4 import { MediaGrid } from "@/components/media/MediaGrid"; ··· 7 8 useBookmarkContext, 8 9 } from "@/state/bookmark"; 9 10 import { useWatchedContext } from "@/state/watched"; 10 - import { useTranslation } from "react-i18next"; 11 11 12 12 function Bookmarks() { 13 13 const { t } = useTranslation();
+1 -1
src/views/search/SearchLoadingView.tsx
··· 1 - import { Loading } from "@/components/layout/Loading"; 2 1 import { useTranslation } from "react-i18next"; 2 + import { Loading } from "@/components/layout/Loading"; 3 3 4 4 export function SearchLoadingView() { 5 5 const { t } = useTranslation();
+1 -1
src/views/search/SearchResultsPartial.tsx
··· 1 + import { useEffect, useMemo, useState } from "react"; 1 2 import { useDebounce } from "@/hooks/useDebounce"; 2 3 import { MWQuery } from "@/providers"; 3 - import { useEffect, useMemo, useState } from "react"; 4 4 import { HomeView } from "./HomeView"; 5 5 import { SearchLoadingView } from "./SearchLoadingView"; 6 6 import { SearchResultsView } from "./SearchResultsView";
+2 -2
src/views/search/SearchResultsView.tsx
··· 1 + import { useEffect, useState } from "react"; 2 + import { useTranslation } from "react-i18next"; 1 3 import { IconPatch } from "@/components/buttons/IconPatch"; 2 4 import { Icons } from "@/components/Icon"; 3 5 import { SectionHeading } from "@/components/layout/SectionHeading"; ··· 5 7 import { WatchedMediaCard } from "@/components/media/WatchedMediaCard"; 6 8 import { useLoading } from "@/hooks/useLoading"; 7 9 import { MWMassProviderOutput, MWQuery, SearchProviders } from "@/providers"; 8 - import { useEffect, useState } from "react"; 9 - import { useTranslation } from "react-i18next"; 10 10 import { SearchLoadingView } from "./SearchLoadingView"; 11 11 12 12 function SearchSuffix(props: {
+2 -2
src/views/search/SearchView.tsx
··· 1 1 import { useCallback, useState } from "react"; 2 + import Sticky from "react-stickynode"; 3 + import { useTranslation } from "react-i18next"; 2 4 import { Navigation } from "@/components/layout/Navigation"; 3 5 import { ThinContainer } from "@/components/layout/ThinContainer"; 4 6 import { SearchBarInput } from "@/components/SearchBar"; 5 - import Sticky from "react-stickynode"; 6 7 import { Title } from "@/components/text/Title"; 7 8 import { useSearchQuery } from "@/hooks/useSearchQuery"; 8 9 import { WideContainer } from "@/components/layout/WideContainer"; 9 - import { useTranslation } from "react-i18next"; 10 10 import { SearchResultsPartial } from "./SearchResultsPartial"; 11 11 12 12 export function SearchView() {