mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Migrate from perttier/eslint to biome (#484)
This commit is contained in:
commit
30e2a5c867
15
.github/workflows/coding-style.yml
vendored
15
.github/workflows/coding-style.yml
vendored
@ -25,18 +25,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Biome
|
||||||
uses: actions/setup-node@v4
|
uses: biomejs/setup-biome@v2
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
version: latest
|
||||||
cache: yarn
|
|
||||||
cache-dependency-path: front/yarn.lock
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Run Biome
|
||||||
run: yarn install --immutable
|
run: biome ci .
|
||||||
|
|
||||||
- name: Lint
|
|
||||||
run: yarn lint && yarn format
|
|
||||||
|
|
||||||
scanner:
|
scanner:
|
||||||
name: "Lint scanner/autosync"
|
name: "Lint scanner/autosync"
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["next/core-web-vitals", "prettier"],
|
|
||||||
"plugins": ["header"],
|
|
||||||
"settings": {
|
|
||||||
"next": {
|
|
||||||
"rootDir": "apps/web/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"@next/next/no-img-element": "off",
|
|
||||||
"header/header": [
|
|
||||||
"error",
|
|
||||||
"block",
|
|
||||||
[
|
|
||||||
"",
|
|
||||||
" * Kyoo - A portable and vast media library solution.",
|
|
||||||
" * Copyright (c) Kyoo.",
|
|
||||||
" *",
|
|
||||||
" * See AUTHORS.md and LICENSE file in the project root for full license information.",
|
|
||||||
" *",
|
|
||||||
" * Kyoo is free software: you can redistribute it and/or modify",
|
|
||||||
" * it under the terms of the GNU General Public License as published by",
|
|
||||||
" * the Free Software Foundation, either version 3 of the License, or",
|
|
||||||
" * any later version.",
|
|
||||||
" *",
|
|
||||||
" * Kyoo is distributed in the hope that it will be useful,",
|
|
||||||
" * but WITHOUT ANY WARRANTY; without even the implied warranty of",
|
|
||||||
" * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the",
|
|
||||||
" * GNU General Public License for more details.",
|
|
||||||
" *",
|
|
||||||
" * You should have received a copy of the GNU General Public License",
|
|
||||||
" * along with Kyoo. If not, see <https://www.gnu.org/licenses/>.",
|
|
||||||
" "
|
|
||||||
],
|
|
||||||
2
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
.yarn
|
|
@ -42,9 +42,6 @@ const config = {
|
|||||||
icon: "./assets/icon.png",
|
icon: "./assets/icon.png",
|
||||||
userInterfaceStyle: "automatic",
|
userInterfaceStyle: "automatic",
|
||||||
splash,
|
splash,
|
||||||
updates: {
|
|
||||||
fallbackToCacheTimeout: 0,
|
|
||||||
},
|
|
||||||
assetBundlePatterns: ["**/*"],
|
assetBundlePatterns: ["**/*"],
|
||||||
ios: {
|
ios: {
|
||||||
supportsTablet: true,
|
supportsTablet: true,
|
||||||
@ -59,6 +56,7 @@ const config = {
|
|||||||
},
|
},
|
||||||
updates: {
|
updates: {
|
||||||
url: "https://u.expo.dev/55de6b52-c649-4a15-9a45-569ff5ed036c",
|
url: "https://u.expo.dev/55de6b52-c649-4a15-9a45-569ff5ed036c",
|
||||||
|
fallbackToCacheTimeout: 0,
|
||||||
},
|
},
|
||||||
runtimeVersion: {
|
runtimeVersion: {
|
||||||
policy: "sdkVersion",
|
policy: "sdkVersion",
|
||||||
|
@ -19,10 +19,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Icon } from "@kyoo/primitives";
|
import { Icon } from "@kyoo/primitives";
|
||||||
import { Tabs } from "expo-router";
|
|
||||||
import Home from "@material-symbols/svg-400/rounded/home-fill.svg";
|
|
||||||
import Browse from "@material-symbols/svg-400/rounded/browse-fill.svg";
|
import Browse from "@material-symbols/svg-400/rounded/browse-fill.svg";
|
||||||
import Downloading from "@material-symbols/svg-400/rounded/downloading-fill.svg";
|
import Downloading from "@material-symbols/svg-400/rounded/downloading-fill.svg";
|
||||||
|
import Home from "@material-symbols/svg-400/rounded/home-fill.svg";
|
||||||
|
import { Tabs } from "expo-router";
|
||||||
|
|
||||||
export default function TabsLayout() {
|
export default function TabsLayout() {
|
||||||
return (
|
return (
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Account, ConnectionErrorContext, useAccount } from "@kyoo/models";
|
import { type Account, ConnectionErrorContext, useAccount } from "@kyoo/models";
|
||||||
import { NavbarProfile, NavbarTitle } from "@kyoo/ui";
|
import { NavbarProfile, NavbarTitle } from "@kyoo/ui";
|
||||||
import { Redirect, Stack } from "expo-router";
|
import { Redirect, Stack } from "expo-router";
|
||||||
import { useContext, useRef } from "react";
|
import { useContext, useRef } from "react";
|
||||||
@ -30,7 +30,7 @@ export default function PublicLayout() {
|
|||||||
const { error } = useContext(ConnectionErrorContext);
|
const { error } = useContext(ConnectionErrorContext);
|
||||||
const oldAccount = useRef<Account | null>(account);
|
const oldAccount = useRef<Account | null>(account);
|
||||||
|
|
||||||
if (account && !error && account != oldAccount.current) return <Redirect href="/" />;
|
if (account && !error && account !== oldAccount.current) return <Redirect href="/" />;
|
||||||
oldAccount.current = account;
|
oldAccount.current = account;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -20,39 +20,39 @@
|
|||||||
|
|
||||||
import "react-native-reanimated";
|
import "react-native-reanimated";
|
||||||
|
|
||||||
import { PortalProvider } from "@gorhom/portal";
|
|
||||||
import { SnackbarProvider, ThemeSelector } from "@kyoo/primitives";
|
|
||||||
import { DownloadProvider } from "@kyoo/ui";
|
|
||||||
import { AccountProvider, createQueryClient, storage, useUserTheme } from "@kyoo/models";
|
|
||||||
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
|
|
||||||
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
|
|
||||||
import i18next from "i18next";
|
|
||||||
import { Slot } from "expo-router";
|
|
||||||
import { getLocales } from "expo-localization";
|
|
||||||
import * as SplashScreen from "expo-splash-screen";
|
|
||||||
import {
|
import {
|
||||||
useFonts,
|
|
||||||
Poppins_300Light,
|
Poppins_300Light,
|
||||||
Poppins_400Regular,
|
Poppins_400Regular,
|
||||||
Poppins_900Black,
|
Poppins_900Black,
|
||||||
|
useFonts,
|
||||||
} from "@expo-google-fonts/poppins";
|
} from "@expo-google-fonts/poppins";
|
||||||
import { ReactNode, useEffect, useState } from "react";
|
|
||||||
import { useColorScheme } from "react-native";
|
|
||||||
import { initReactI18next } from "react-i18next";
|
|
||||||
import { ThemeProvider as RNThemeProvider } from "@react-navigation/native";
|
|
||||||
import "intl-pluralrules";
|
|
||||||
import "@formatjs/intl-locale/polyfill";
|
|
||||||
import "@formatjs/intl-displaynames/polyfill";
|
|
||||||
import "@formatjs/intl-displaynames/locale-data/en";
|
import "@formatjs/intl-displaynames/locale-data/en";
|
||||||
import "@formatjs/intl-displaynames/locale-data/fr";
|
import "@formatjs/intl-displaynames/locale-data/fr";
|
||||||
|
import "@formatjs/intl-displaynames/polyfill";
|
||||||
|
import "@formatjs/intl-locale/polyfill";
|
||||||
|
import { PortalProvider } from "@gorhom/portal";
|
||||||
|
import { AccountProvider, createQueryClient, storage, useUserTheme } from "@kyoo/models";
|
||||||
|
import { SnackbarProvider, ThemeSelector } from "@kyoo/primitives";
|
||||||
|
import { DownloadProvider } from "@kyoo/ui";
|
||||||
|
import { ThemeProvider as RNThemeProvider } from "@react-navigation/native";
|
||||||
|
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
|
||||||
|
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
|
||||||
|
import { getLocales } from "expo-localization";
|
||||||
|
import { Slot } from "expo-router";
|
||||||
|
import * as SplashScreen from "expo-splash-screen";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import "intl-pluralrules";
|
||||||
|
import { type ReactNode, useEffect, useState } from "react";
|
||||||
|
import { initReactI18next } from "react-i18next";
|
||||||
|
import { useColorScheme } from "react-native";
|
||||||
|
|
||||||
|
import NetInfo from "@react-native-community/netinfo";
|
||||||
|
import { onlineManager } from "@tanstack/react-query";
|
||||||
|
import { useTheme } from "yoshiki/native";
|
||||||
// TODO: use a backend to load jsons.
|
// TODO: use a backend to load jsons.
|
||||||
import en from "../../../translations/en.json";
|
import en from "../../../translations/en.json";
|
||||||
import fr from "../../../translations/fr.json";
|
import fr from "../../../translations/fr.json";
|
||||||
import zh from "../../../translations/zh.json";
|
import zh from "../../../translations/zh.json";
|
||||||
import { useTheme } from "yoshiki/native";
|
|
||||||
import NetInfo from "@react-native-community/netinfo";
|
|
||||||
import { onlineManager } from "@tanstack/react-query";
|
|
||||||
|
|
||||||
onlineManager.setEventListener((setOnline) => {
|
onlineManager.setEventListener((setOnline) => {
|
||||||
return NetInfo.addEventListener((state) => {
|
return NetInfo.addEventListener((state) => {
|
||||||
|
@ -18,14 +18,14 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Stack, useLocalSearchParams } from "expo-router";
|
import { type QueryPage, useHasPermission } from "@kyoo/models";
|
||||||
import { ComponentType, useEffect } from "react";
|
|
||||||
import { StatusBar, StatusBarProps } from "react-native";
|
|
||||||
import * as ScreenOrientation from "expo-screen-orientation";
|
|
||||||
import * as NavigationBar from "expo-navigation-bar";
|
|
||||||
import arrayShuffle from "array-shuffle";
|
|
||||||
import { QueryPage, useHasPermission } from "@kyoo/models";
|
|
||||||
import { Unauthorized } from "@kyoo/ui";
|
import { Unauthorized } from "@kyoo/ui";
|
||||||
|
import arrayShuffle from "array-shuffle";
|
||||||
|
import * as NavigationBar from "expo-navigation-bar";
|
||||||
|
import { Stack, useLocalSearchParams } from "expo-router";
|
||||||
|
import * as ScreenOrientation from "expo-screen-orientation";
|
||||||
|
import { type ComponentType, useEffect } from "react";
|
||||||
|
import { StatusBar, type StatusBarProps } from "react-native";
|
||||||
|
|
||||||
const FullscreenProvider = () => {
|
const FullscreenProvider = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { getDefaultConfig } = require("expo/metro-config");
|
const { getDefaultConfig } = require("expo/metro-config");
|
||||||
const path = require("path");
|
const path = require("node:path");
|
||||||
|
|
||||||
const projectRoot = __dirname;
|
const projectRoot = __dirname;
|
||||||
const defaultConfig = getDefaultConfig(projectRoot);
|
const defaultConfig = getDefaultConfig(projectRoot);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "expo/tsconfig.base",
|
"extends": "expo/tsconfig.base",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true,
|
"strict": true
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const path = require("path");
|
const path = require("node:path");
|
||||||
const CopyPlugin = require("copy-webpack-plugin");
|
const CopyPlugin = require("copy-webpack-plugin");
|
||||||
const DefinePlugin = require("webpack").DefinePlugin;
|
const DefinePlugin = require("webpack").DefinePlugin;
|
||||||
|
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
"name": "web",
|
"name": "web",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"sideEffects": [
|
"sideEffects": ["./src/polyfill.ts"],
|
||||||
"./src/polyfill.ts"
|
|
||||||
],
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
@ -56,8 +54,6 @@
|
|||||||
"@types/react": "18.2.48",
|
"@types/react": "18.2.48",
|
||||||
"@types/react-dom": "18.2.18",
|
"@types/react-dom": "18.2.18",
|
||||||
"copy-webpack-plugin": "^12.0.2",
|
"copy-webpack-plugin": "^12.0.2",
|
||||||
"eslint": "^8.56.0",
|
|
||||||
"eslint-config-next": "14.1.0",
|
|
||||||
"react-native": "0.73.2",
|
"react-native": "0.73.2",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"webpack": "^5.90.0"
|
"webpack": "^5.90.0"
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import "i18next";
|
import "i18next";
|
||||||
import en from "../../../translations/en.json";
|
import type en from "../../../translations/en.json";
|
||||||
|
|
||||||
declare module "i18next" {
|
declare module "i18next" {
|
||||||
interface CustomTypeOptions {
|
interface CustomTypeOptions {
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentType, useMemo } from "react";
|
import i18next, { type InitOptions } from "i18next";
|
||||||
import i18next, { InitOptions } from "i18next";
|
import type { AppContext, AppInitialProps, AppProps } from "next/app";
|
||||||
|
import { type ComponentType, useMemo } from "react";
|
||||||
import { I18nextProvider } from "react-i18next";
|
import { I18nextProvider } from "react-i18next";
|
||||||
import { AppContext, AppInitialProps, type AppProps } from "next/app";
|
|
||||||
|
|
||||||
import en from "../../../translations/en.json";
|
import en from "../../../translations/en.json";
|
||||||
import fr from "../../../translations/fr.json";
|
import fr from "../../../translations/fr.json";
|
||||||
@ -40,18 +40,15 @@ export const withTranslations = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AppWithTranslations = (props: AppProps) => {
|
const AppWithTranslations = (props: AppProps) => {
|
||||||
const li18n = useMemo(
|
const li18n = useMemo(() => {
|
||||||
() =>
|
if (typeof window === "undefined") return i18n;
|
||||||
typeof window === "undefined"
|
i18next.init({
|
||||||
? i18n
|
...commonOptions,
|
||||||
: (i18next.init({
|
lng: props.pageProps.__lang,
|
||||||
...commonOptions,
|
resources: props.pageProps.__resources,
|
||||||
lng: props.pageProps.__lang,
|
});
|
||||||
resources: props.pageProps.__resources,
|
return i18next;
|
||||||
}),
|
}, [props.pageProps.__lang, props.pageProps.__resources]);
|
||||||
i18next),
|
|
||||||
[props.pageProps.__lang, props.pageProps.__resources],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<I18nextProvider i18n={li18n}>
|
<I18nextProvider i18n={li18n}>
|
||||||
|
@ -20,42 +20,42 @@
|
|||||||
|
|
||||||
import "../polyfill";
|
import "../polyfill";
|
||||||
|
|
||||||
import { HydrationBoundary, QueryClientProvider, dehydrate } from "@tanstack/react-query";
|
import { PortalProvider } from "@gorhom/portal";
|
||||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
|
||||||
import {
|
|
||||||
HiddenIfNoJs,
|
|
||||||
TouchOnlyCss,
|
|
||||||
SkeletonCss,
|
|
||||||
ThemeSelector,
|
|
||||||
SnackbarProvider,
|
|
||||||
} from "@kyoo/primitives";
|
|
||||||
import { WebTooltip } from "@kyoo/primitives/src/tooltip.web";
|
|
||||||
import {
|
import {
|
||||||
AccountP,
|
AccountP,
|
||||||
AccountProvider,
|
AccountProvider,
|
||||||
ConnectionErrorContext,
|
ConnectionErrorContext,
|
||||||
|
type QueryIdentifier,
|
||||||
|
type QueryPage,
|
||||||
|
ServerInfoP,
|
||||||
|
UserP,
|
||||||
createQueryClient,
|
createQueryClient,
|
||||||
fetchQuery,
|
fetchQuery,
|
||||||
getTokenWJ,
|
getTokenWJ,
|
||||||
QueryIdentifier,
|
|
||||||
QueryPage,
|
|
||||||
ServerInfoP,
|
|
||||||
setSsrApiUrl,
|
setSsrApiUrl,
|
||||||
UserP,
|
|
||||||
useUserTheme,
|
useUserTheme,
|
||||||
} from "@kyoo/models";
|
} from "@kyoo/models";
|
||||||
import { ComponentType, useContext, useState } from "react";
|
|
||||||
import NextApp, { AppContext, type AppProps } from "next/app";
|
|
||||||
import { Poppins } from "next/font/google";
|
|
||||||
import { useTheme, useMobileHover, useStyleRegistry, StyleRegistryProvider } from "yoshiki/web";
|
|
||||||
import superjson from "superjson";
|
|
||||||
import Head from "next/head";
|
|
||||||
import { withTranslations } from "../i18n";
|
|
||||||
import arrayShuffle from "array-shuffle";
|
|
||||||
import { Tooltip } from "react-tooltip";
|
|
||||||
import { getCurrentAccount, readCookie, updateAccount } from "@kyoo/models/src/account-internal";
|
import { getCurrentAccount, readCookie, updateAccount } from "@kyoo/models/src/account-internal";
|
||||||
import { PortalProvider } from "@gorhom/portal";
|
import {
|
||||||
|
HiddenIfNoJs,
|
||||||
|
SkeletonCss,
|
||||||
|
SnackbarProvider,
|
||||||
|
ThemeSelector,
|
||||||
|
TouchOnlyCss,
|
||||||
|
} from "@kyoo/primitives";
|
||||||
|
import { WebTooltip } from "@kyoo/primitives/src/tooltip.web";
|
||||||
import { ConnectionError } from "@kyoo/ui";
|
import { ConnectionError } from "@kyoo/ui";
|
||||||
|
import { HydrationBoundary, QueryClientProvider, dehydrate } from "@tanstack/react-query";
|
||||||
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||||
|
import arrayShuffle from "array-shuffle";
|
||||||
|
import NextApp, { type AppContext, type AppProps } from "next/app";
|
||||||
|
import { Poppins } from "next/font/google";
|
||||||
|
import Head from "next/head";
|
||||||
|
import { type ComponentType, useContext, useState } from "react";
|
||||||
|
import { Tooltip } from "react-tooltip";
|
||||||
|
import superjson from "superjson";
|
||||||
|
import { StyleRegistryProvider, useMobileHover, useStyleRegistry, useTheme } from "yoshiki/web";
|
||||||
|
import { withTranslations } from "../i18n";
|
||||||
|
|
||||||
const font = Poppins({ weight: ["300", "400", "900"], subsets: ["latin"], display: "swap" });
|
const font = Poppins({ weight: ["300", "400", "900"], subsets: ["latin"], display: "swap" });
|
||||||
|
|
||||||
@ -114,7 +114,6 @@ const GlobalCssTheme = () => {
|
|||||||
|
|
||||||
const YoshikiDebug = ({ children }: { children: JSX.Element }) => {
|
const YoshikiDebug = ({ children }: { children: JSX.Element }) => {
|
||||||
if (typeof window === "undefined") return children;
|
if (typeof window === "undefined") return children;
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
const registry = useStyleRegistry();
|
const registry = useStyleRegistry();
|
||||||
return <StyleRegistryProvider registry={registry}>{children}</StyleRegistryProvider>;
|
return <StyleRegistryProvider registry={registry}>{children}</StyleRegistryProvider>;
|
||||||
};
|
};
|
||||||
|
@ -18,9 +18,9 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { type DocumentContext, Head, Html, Main, NextScript } from "next/document";
|
||||||
import { AppRegistry } from "react-native";
|
import { AppRegistry } from "react-native";
|
||||||
import { Html, Main, Head, NextScript, DocumentContext } from "next/document";
|
import { StyleRegistryProvider, createStyleRegistry } from "yoshiki/web";
|
||||||
import { createStyleRegistry, StyleRegistryProvider } from "yoshiki/web";
|
|
||||||
|
|
||||||
export const style = `
|
export const style = `
|
||||||
/**
|
/**
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { QueryPage, useHasPermission } from "@kyoo/models";
|
import { type QueryPage, useHasPermission } from "@kyoo/models";
|
||||||
import { Unauthorized } from "@kyoo/ui";
|
import { Unauthorized } from "@kyoo/ui";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { ComponentType } from "react";
|
import type { ComponentType } from "react";
|
||||||
|
|
||||||
export const withRoute = <Props,>(
|
export const withRoute = <Props,>(
|
||||||
Component: ComponentType<Props>,
|
Component: ComponentType<Props>,
|
||||||
|
@ -16,9 +16,9 @@
|
|||||||
"incremental": true,
|
"incremental": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["src/*"],
|
"~/*": ["src/*"]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||||
"exclude": ["node_modules"],
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
51
front/biome.json
Normal file
51
front/biome.json
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"formatWithErrors": false,
|
||||||
|
"indentStyle": "tab",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineEnding": "lf",
|
||||||
|
"lineWidth": 100,
|
||||||
|
"attributePosition": "auto",
|
||||||
|
"ignore": ["**/.yarn/**", "**/.next/**", "**/.expo/**", "**/next-env.d.ts"]
|
||||||
|
},
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true,
|
||||||
|
"style": {
|
||||||
|
"noNonNullAssertion": "off",
|
||||||
|
"noParameterAssign": "off",
|
||||||
|
"useEnumInitializers": "off"
|
||||||
|
},
|
||||||
|
"suspicious": {
|
||||||
|
"noExplicitAny": "off",
|
||||||
|
"noArrayIndexKey": "off"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"noDangerouslySetInnerHtml": "off"
|
||||||
|
},
|
||||||
|
"complexity": {
|
||||||
|
"noBannedTypes": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ignore": ["**/.yarn/**", "**/.next/**", "**/.expo/**", "**/next-env.d.ts"]
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"jsxQuoteStyle": "double",
|
||||||
|
"quoteProperties": "asNeeded",
|
||||||
|
"trailingComma": "all",
|
||||||
|
"semicolons": "always",
|
||||||
|
"arrowParentheses": "always",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"quoteStyle": "double",
|
||||||
|
"attributePosition": "auto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,28 +10,13 @@
|
|||||||
"build:mobile:apk": "yarn workspace mobile build:apk",
|
"build:mobile:apk": "yarn workspace mobile build:apk",
|
||||||
"build:mobile:dev": "yarn workspace mobile build:dev",
|
"build:mobile:dev": "yarn workspace mobile build:dev",
|
||||||
"update": "yarn workspace mobile update",
|
"update": "yarn workspace mobile update",
|
||||||
"lint": "eslint .",
|
"lint": "biome lint .",
|
||||||
"format": "prettier -c .",
|
"format": "biome format .",
|
||||||
"format:fix": "prettier -w ."
|
"format:fix": "biome format . --write"
|
||||||
},
|
|
||||||
"eslintIgnore": [
|
|
||||||
"next-env.d.ts"
|
|
||||||
],
|
|
||||||
"workspaces": [
|
|
||||||
"apps/*",
|
|
||||||
"packages/*"
|
|
||||||
],
|
|
||||||
"prettier": {
|
|
||||||
"useTabs": true,
|
|
||||||
"printWidth": 100,
|
|
||||||
"trailingComma": "all"
|
|
||||||
},
|
},
|
||||||
|
"workspaces": ["apps/*", "packages/*"],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "8.56.0",
|
"@biomejs/biome": "1.7.3",
|
||||||
"eslint-config-next": "14.1.0",
|
|
||||||
"eslint-config-prettier": "^9.1.0",
|
|
||||||
"eslint-plugin-header": "^3.1.1",
|
|
||||||
"prettier": "^3.2.4",
|
|
||||||
"typescript": "5.3.3"
|
"typescript": "5.3.3"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@3.2.4"
|
"packageManager": "yarn@3.2.4"
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ZodTypeAny, z } from "zod";
|
|
||||||
import { Account, AccountP } from "./accounts";
|
|
||||||
import { MMKV } from "react-native-mmkv";
|
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
|
import { MMKV } from "react-native-mmkv";
|
||||||
|
import { type ZodTypeAny, z } from "zod";
|
||||||
|
import { type Account, AccountP } from "./accounts";
|
||||||
|
|
||||||
export const storage = new MMKV();
|
export const storage = new MMKV();
|
||||||
|
|
||||||
@ -49,8 +49,8 @@ export const setCookie = (key: string, val?: unknown) => {
|
|||||||
const d = new Date();
|
const d = new Date();
|
||||||
// A year
|
// A year
|
||||||
d.setTime(d.getTime() + 365 * 24 * 60 * 60 * 1000);
|
d.setTime(d.getTime() + 365 * 24 * 60 * 60 * 1000);
|
||||||
const expires = value ? "expires=" + d.toUTCString() : "expires=Thu, 01 Jan 1970 00:00:01 GMT";
|
const expires = value ? `expires=${d.toUTCString()}` : "expires=Thu, 01 Jan 1970 00:00:01 GMT";
|
||||||
document.cookie = key + "=" + value + ";" + expires + ";path=/;samesite=strict";
|
document.cookie = `${key}=${value};${expires};path=/;samesite=strict`;
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -65,10 +65,10 @@ export const readCookie = <T extends ZodTypeAny>(
|
|||||||
const ca = decodedCookie.split(";");
|
const ca = decodedCookie.split(";");
|
||||||
for (let i = 0; i < ca.length; i++) {
|
for (let i = 0; i < ca.length; i++) {
|
||||||
let c = ca[i];
|
let c = ca[i];
|
||||||
while (c.charAt(0) == " ") {
|
while (c.charAt(0) === " ") {
|
||||||
c = c.substring(1);
|
c = c.substring(1);
|
||||||
}
|
}
|
||||||
if (c.indexOf(name) == 0) {
|
if (c.indexOf(name) === 0) {
|
||||||
const str = c.substring(name.length, c.length);
|
const str = c.substring(name.length, c.length);
|
||||||
return parser ? parser.parse(JSON.parse(str)) : str;
|
return parser ? parser.parse(JSON.parse(str)) : str;
|
||||||
}
|
}
|
||||||
@ -85,7 +85,7 @@ export const addAccount = (account: Account) => {
|
|||||||
const accounts = readAccounts();
|
const accounts = readAccounts();
|
||||||
|
|
||||||
// Prevent the user from adding the same account twice.
|
// Prevent the user from adding the same account twice.
|
||||||
if (accounts.find((x) => x.id == account.id)) {
|
if (accounts.find((x) => x.id === account.id)) {
|
||||||
updateAccount(account.id, account);
|
updateAccount(account.id, account);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -106,7 +106,7 @@ export const removeAccounts = (filter: (acc: Account) => boolean) => {
|
|||||||
|
|
||||||
export const updateAccount = (id: string, account: Account) => {
|
export const updateAccount = (id: string, account: Account) => {
|
||||||
const accounts = readAccounts();
|
const accounts = readAccounts();
|
||||||
const idx = accounts.findIndex((x) => x.id == id);
|
const idx = accounts.findIndex((x) => x.id === id);
|
||||||
if (idx === -1) return;
|
if (idx === -1) return;
|
||||||
|
|
||||||
const selected = account.selected;
|
const selected = account.selected;
|
||||||
|
@ -18,17 +18,25 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReactNode, createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
||||||
import { ServerInfoP, User, UserP } from "./resources";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { zdate } from "./utils";
|
|
||||||
import { removeAccounts, setCookie, updateAccount } from "./account-internal";
|
|
||||||
import { useMMKVString } from "react-native-mmkv";
|
|
||||||
import { Platform } from "react-native";
|
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { atom, getDefaultStore, useAtomValue, useSetAtom } from "jotai";
|
import { atom, getDefaultStore, useAtomValue, useSetAtom } from "jotai";
|
||||||
|
import {
|
||||||
|
type ReactNode,
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
import { useMMKVString } from "react-native-mmkv";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { removeAccounts, setCookie, updateAccount } from "./account-internal";
|
||||||
|
import type { KyooErrors } from "./kyoo-errors";
|
||||||
import { useFetch } from "./query";
|
import { useFetch } from "./query";
|
||||||
import { KyooErrors } from "./kyoo-errors";
|
import { ServerInfoP, type User, UserP } from "./resources";
|
||||||
|
import { zdate } from "./utils";
|
||||||
|
|
||||||
export const TokenP = z.object({
|
export const TokenP = z.object({
|
||||||
token_type: z.literal("Bearer"),
|
token_type: z.literal("Bearer"),
|
||||||
@ -72,7 +80,6 @@ export const ConnectionErrorContext = createContext<{
|
|||||||
setError: (error: KyooErrors) => void;
|
setError: (error: KyooErrors) => void;
|
||||||
}>({ error: null, loading: true, setError: () => {} });
|
}>({ error: null, loading: true, setError: () => {} });
|
||||||
|
|
||||||
/* eslint-disable react-hooks/rules-of-hooks */
|
|
||||||
export const AccountProvider = ({
|
export const AccountProvider = ({
|
||||||
children,
|
children,
|
||||||
ssrAccount,
|
ssrAccount,
|
||||||
@ -115,7 +122,7 @@ export const AccountProvider = ({
|
|||||||
acc?.map((account) => ({
|
acc?.map((account) => ({
|
||||||
...account,
|
...account,
|
||||||
select: () => updateAccount(account.id, { ...account, selected: true }),
|
select: () => updateAccount(account.id, { ...account, selected: true }),
|
||||||
remove: () => removeAccounts((x) => x.id == x.id),
|
remove: () => removeAccounts((x) => x.id === account.id),
|
||||||
})) ?? [],
|
})) ?? [],
|
||||||
[acc],
|
[acc],
|
||||||
);
|
);
|
||||||
@ -151,6 +158,7 @@ export const AccountProvider = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// if the user change account (or connect/disconnect), reset query cache.
|
// if the user change account (or connect/disconnect), reset query cache.
|
||||||
if (
|
if (
|
||||||
|
// biome-ignore lint/suspicious/noDoubleEquals: id can be an id, null or undefined
|
||||||
selected?.id != oldSelected.current?.id ||
|
selected?.id != oldSelected.current?.id ||
|
||||||
(userIsError && selected?.token.access_token !== oldSelected.current?.token)
|
(userIsError && selected?.token.access_token !== oldSelected.current?.token)
|
||||||
) {
|
) {
|
||||||
|
@ -18,13 +18,13 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { queryFn } from "./query";
|
|
||||||
import { KyooErrors } from "./kyoo-errors";
|
|
||||||
import { Account, Token, TokenP, getCurrentApiUrl } from "./accounts";
|
|
||||||
import { UserP } from "./resources";
|
|
||||||
import { addAccount, getCurrentAccount, removeAccounts, updateAccount } from "./account-internal";
|
|
||||||
import { Platform } from "react-native";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
import { addAccount, getCurrentAccount, removeAccounts, updateAccount } from "./account-internal";
|
||||||
|
import { type Account, type Token, TokenP, getCurrentApiUrl } from "./accounts";
|
||||||
|
import type { KyooErrors } from "./kyoo-errors";
|
||||||
|
import { queryFn } from "./query";
|
||||||
|
import { UserP } from "./resources";
|
||||||
|
|
||||||
type Result<A, B> =
|
type Result<A, B> =
|
||||||
| { ok: true; value: A; error?: undefined }
|
| { ok: true; value: A; error?: undefined }
|
||||||
@ -94,7 +94,7 @@ let running: ReturnType<typeof getTokenWJ> | null = null;
|
|||||||
|
|
||||||
export const getTokenWJ = async (
|
export const getTokenWJ = async (
|
||||||
acc?: Account | null,
|
acc?: Account | null,
|
||||||
forceRefresh: boolean = false,
|
forceRefresh = false,
|
||||||
): Promise<readonly [string, Token, null] | readonly [null, null, KyooErrors | null]> => {
|
): Promise<readonly [string, Token, null] | readonly [null, null, KyooErrors | null]> => {
|
||||||
if (acc === undefined) acc = getCurrentAccount();
|
if (acc === undefined) acc = getCurrentAccount();
|
||||||
if (!acc) return [null, null, null] as const;
|
if (!acc) return [null, null, null] as const;
|
||||||
@ -150,6 +150,7 @@ export const useToken = () => {
|
|||||||
account ? `${account.token.token_type} ${account.token.access_token}` : null,
|
account ? `${account.token.token_type} ${account.token.access_token}` : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: Refresh token when account change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function run() {
|
async function run() {
|
||||||
const nToken = await getTokenWJ();
|
const nToken = await getTokenWJ();
|
||||||
|
@ -18,18 +18,18 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentType, ReactElement } from "react";
|
|
||||||
import {
|
import {
|
||||||
QueryClient,
|
QueryClient,
|
||||||
QueryFunctionContext,
|
type QueryFunctionContext,
|
||||||
useInfiniteQuery,
|
useInfiniteQuery,
|
||||||
useQuery,
|
useQuery,
|
||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
import { z } from "zod";
|
import type { ComponentType, ReactElement } from "react";
|
||||||
import { KyooErrors } from "./kyoo-errors";
|
import type { z } from "zod";
|
||||||
import { Page, Paged } from "./page";
|
|
||||||
import { getToken, getTokenWJ } from "./login";
|
|
||||||
import { getCurrentApiUrl } from ".";
|
import { getCurrentApiUrl } from ".";
|
||||||
|
import type { KyooErrors } from "./kyoo-errors";
|
||||||
|
import { getToken, getTokenWJ } from "./login";
|
||||||
|
import { type Page, Paged } from "./page";
|
||||||
|
|
||||||
export let lastUsedUrl: string = null!;
|
export let lastUsedUrl: string = null!;
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
|
|||||||
.join("/")
|
.join("/")
|
||||||
.replace("//", "/")
|
.replace("//", "/")
|
||||||
.replace("/?", "?");
|
.replace("/?", "?");
|
||||||
let resp;
|
let resp: Response;
|
||||||
try {
|
try {
|
||||||
resp = await fetch(path, {
|
resp = await fetch(path, {
|
||||||
method: context.method,
|
method: context.method,
|
||||||
@ -97,11 +97,11 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
|
|||||||
if (resp.status === 403 && iToken === undefined && token) {
|
if (resp.status === 403 && iToken === undefined && token) {
|
||||||
const [newToken, _, error] = await getTokenWJ(undefined, true);
|
const [newToken, _, error] = await getTokenWJ(undefined, true);
|
||||||
if (newToken) return await queryFn(context, type, newToken);
|
if (newToken) return await queryFn(context, type, newToken);
|
||||||
else console.error("refresh error while retrying a forbidden", error);
|
console.error("refresh error while retrying a forbidden", error);
|
||||||
}
|
}
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
const error = await resp.text();
|
const error = await resp.text();
|
||||||
let data;
|
let data: Record<string, any>;
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(error);
|
data = JSON.parse(error);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -122,7 +122,7 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
|
|||||||
|
|
||||||
if ("plainText" in context && context.plainText) return (await resp.text()) as unknown;
|
if ("plainText" in context && context.plainText) return (await resp.text()) as unknown;
|
||||||
|
|
||||||
let data;
|
let data: Record<string, any>;
|
||||||
try {
|
try {
|
||||||
data = await resp.json();
|
data = await resp.json();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -204,11 +204,10 @@ export const toQueryKey = (query: {
|
|||||||
query.options?.apiUrl,
|
query.options?.apiUrl,
|
||||||
...query.path,
|
...query.path,
|
||||||
query.params
|
query.params
|
||||||
? "?" +
|
? `?${Object.entries(query.params)
|
||||||
Object.entries(query.params)
|
|
||||||
.filter(([_, v]) => v !== undefined)
|
.filter(([_, v]) => v !== undefined)
|
||||||
.map(([k, v]) => `${k}=${Array.isArray(v) ? v.join(",") : v}`)
|
.map(([k, v]) => `${k}=${Array.isArray(v) ? v.join(",") : v}`)
|
||||||
.join("&")
|
.join("&")}`
|
||||||
: null,
|
: null,
|
||||||
].filter((x) => x);
|
].filter((x) => x);
|
||||||
};
|
};
|
||||||
@ -267,12 +266,11 @@ export const fetchQuery = async (queries: QueryIdentifier[], authToken?: string
|
|||||||
queryFn: (ctx) => queryFn(ctx, Paged(query.parser), authToken),
|
queryFn: (ctx) => queryFn(ctx, Paged(query.parser), authToken),
|
||||||
initialPageParam: undefined,
|
initialPageParam: undefined,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
return client.prefetchQuery({
|
|
||||||
queryKey: toQueryKey(query),
|
|
||||||
queryFn: (ctx) => queryFn(ctx, query.parser, authToken),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return client.prefetchQuery({
|
||||||
|
queryKey: toQueryKey(query),
|
||||||
|
queryFn: (ctx) => queryFn(ctx, query.parser, authToken),
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
return client;
|
return client;
|
||||||
|
@ -19,9 +19,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zdate } from "../utils";
|
|
||||||
import { ImagesP, imageFn } from "../traits";
|
import { ImagesP, imageFn } from "../traits";
|
||||||
import { ResourceP } from "../traits/resource";
|
import { ResourceP } from "../traits/resource";
|
||||||
|
import { zdate } from "../utils";
|
||||||
|
|
||||||
export const BaseEpisodeP = ResourceP("episode")
|
export const BaseEpisodeP = ResourceP("episode")
|
||||||
.merge(ImagesP)
|
.merge(ImagesP)
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ShowP } from "./show";
|
|
||||||
import { BaseEpisodeP } from "./episode.base";
|
import { BaseEpisodeP } from "./episode.base";
|
||||||
|
import { ShowP } from "./show";
|
||||||
import { WatchStatusP } from "./watch-status";
|
import { WatchStatusP } from "./watch-status";
|
||||||
|
|
||||||
export const EpisodeP = BaseEpisodeP.and(
|
export const EpisodeP = BaseEpisodeP.and(
|
||||||
|
@ -19,13 +19,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zdate } from "../utils";
|
|
||||||
import { ImagesP, ResourceP, imageFn } from "../traits";
|
import { ImagesP, ResourceP, imageFn } from "../traits";
|
||||||
import { Genre } from "./genre";
|
import { zdate } from "../utils";
|
||||||
import { StudioP } from "./studio";
|
|
||||||
import { Status } from "./show";
|
|
||||||
import { CollectionP } from "./collection";
|
import { CollectionP } from "./collection";
|
||||||
|
import { Genre } from "./genre";
|
||||||
import { MetadataP } from "./metadata";
|
import { MetadataP } from "./metadata";
|
||||||
|
import { Status } from "./show";
|
||||||
|
import { StudioP } from "./studio";
|
||||||
import { WatchStatusP } from "./watch-status";
|
import { WatchStatusP } from "./watch-status";
|
||||||
|
|
||||||
export const MovieP = ResourceP("movie")
|
export const MovieP = ResourceP("movie")
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { MovieP } from "./movie";
|
|
||||||
import { EpisodeP } from "./episode";
|
import { EpisodeP } from "./episode";
|
||||||
|
import { MovieP } from "./movie";
|
||||||
|
|
||||||
export const NewsP = z.union([
|
export const NewsP = z.union([
|
||||||
/*
|
/*
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zdate } from "../utils";
|
|
||||||
import { ImagesP, ResourceP } from "../traits";
|
import { ImagesP, ResourceP } from "../traits";
|
||||||
|
import { zdate } from "../utils";
|
||||||
|
|
||||||
export const SeasonP = ResourceP("season").merge(ImagesP).extend({
|
export const SeasonP = ResourceP("season").merge(ImagesP).extend({
|
||||||
/**
|
/**
|
||||||
|
@ -18,9 +18,9 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Platform } from "react-native";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { imageFn } from "..";
|
import { imageFn } from "..";
|
||||||
import { Platform } from "react-native";
|
|
||||||
|
|
||||||
export const OidcInfoP = z.object({
|
export const OidcInfoP = z.object({
|
||||||
/*
|
/*
|
||||||
|
@ -19,12 +19,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zdate } from "../utils";
|
|
||||||
import { ImagesP, ResourceP } from "../traits";
|
import { ImagesP, ResourceP } from "../traits";
|
||||||
import { Genre } from "./genre";
|
import { zdate } from "../utils";
|
||||||
import { StudioP } from "./studio";
|
|
||||||
import { BaseEpisodeP } from "./episode.base";
|
import { BaseEpisodeP } from "./episode.base";
|
||||||
|
import { Genre } from "./genre";
|
||||||
import { MetadataP } from "./metadata";
|
import { MetadataP } from "./metadata";
|
||||||
|
import { StudioP } from "./studio";
|
||||||
import { ShowWatchStatusP } from "./watch-status";
|
import { ShowWatchStatusP } from "./watch-status";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ResourceP } from "../traits/resource";
|
|
||||||
import { imageFn } from "../traits/images";
|
import { imageFn } from "../traits/images";
|
||||||
|
import { ResourceP } from "../traits/resource";
|
||||||
|
|
||||||
export const UserP = ResourceP("user")
|
export const UserP = ResourceP("user")
|
||||||
.extend({
|
.extend({
|
||||||
|
@ -18,9 +18,9 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import i18next from "i18next";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { imageFn } from "../traits";
|
import { imageFn } from "../traits";
|
||||||
import i18next from "i18next";
|
|
||||||
import { QualityP } from "./quality";
|
import { QualityP } from "./quality";
|
||||||
|
|
||||||
const getDisplayName = (sub: Track) => {
|
const getDisplayName = (sub: Track) => {
|
||||||
@ -196,8 +196,9 @@ export const WatchInfoP = z
|
|||||||
|
|
||||||
// from https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
|
// from https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
|
||||||
const humanFileSize = (size: number): string => {
|
const humanFileSize = (size: number): string => {
|
||||||
var i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
|
const i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
|
||||||
// @ts-ignore I'm not gonna fix stackoverflow's working code.
|
// @ts-ignore I'm not gonna fix stackoverflow's working code.
|
||||||
|
// biome-ignore lint: same as above
|
||||||
return (size / Math.pow(1024, i)).toFixed(2) * 1 + " " + ["B", "kB", "MB", "GB", "TB"][i];
|
return (size / Math.pow(1024, i)).toFixed(2) * 1 + " " + ["B", "kB", "MB", "GB", "TB"][i];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,13 +18,12 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Platform } from "react-native";
|
||||||
import { useMMKVString } from "react-native-mmkv";
|
import { useMMKVString } from "react-native-mmkv";
|
||||||
import { setCookie, storage } from "./account-internal";
|
import { setCookie, storage } from "./account-internal";
|
||||||
import { Platform } from "react-native";
|
|
||||||
|
|
||||||
export const useUserTheme = (ssrTheme?: "light" | "dark" | "auto") => {
|
export const useUserTheme = (ssrTheme?: "light" | "dark" | "auto") => {
|
||||||
if (Platform.OS === "web" && typeof window === "undefined" && ssrTheme) return ssrTheme;
|
if (Platform.OS === "web" && typeof window === "undefined" && ssrTheme) return ssrTheme;
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
const [value] = useMMKVString("theme", storage);
|
const [value] = useMMKVString("theme", storage);
|
||||||
if (!value) return "auto";
|
if (!value) return "auto";
|
||||||
return value as "light" | "dark" | "auto";
|
return value as "light" | "dark" | "auto";
|
||||||
|
@ -19,10 +19,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { Movie, Show } from "./resources";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { useMMKVString } from "react-native-mmkv";
|
import { useMMKVString } from "react-native-mmkv";
|
||||||
|
import { z } from "zod";
|
||||||
import { storage } from "./account-internal";
|
import { storage } from "./account-internal";
|
||||||
|
import type { Movie, Show } from "./resources";
|
||||||
|
|
||||||
export const zdate = z.coerce.date;
|
export const zdate = z.coerce.date;
|
||||||
|
|
||||||
@ -38,7 +38,8 @@ export const getDisplayDate = (data: Show | Movie) => {
|
|||||||
return startAir.getFullYear().toString();
|
return startAir.getFullYear().toString();
|
||||||
}
|
}
|
||||||
return startAir.getFullYear() + (endAir ? ` - ${endAir.getFullYear()}` : "");
|
return startAir.getFullYear() + (endAir ? ` - ${endAir.getFullYear()}` : "");
|
||||||
} else if (airDate) {
|
}
|
||||||
|
if (airDate) {
|
||||||
return airDate.getFullYear().toString();
|
return airDate.getFullYear().toString();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -18,9 +18,9 @@
|
|||||||
"incremental": true,
|
"incremental": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["src/*"],
|
"~/*": ["src/*"]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", "**/*.tsx"],
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
"exclude": ["node_modules"],
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,8 @@
|
|||||||
|
|
||||||
// Stolen from https://github.com/necolas/react-native-web/issues/1026#issuecomment-1458279681
|
// Stolen from https://github.com/necolas/react-native-web/issues/1026#issuecomment-1458279681
|
||||||
|
|
||||||
import { Alert as RNAlert, type AlertOptions, type AlertButton } from "react-native";
|
import { type AlertButton, type AlertOptions, Alert as RNAlert } from "react-native";
|
||||||
import { type SweetAlertIcon } from "sweetalert2";
|
import type { SweetAlertIcon } from "sweetalert2";
|
||||||
|
|
||||||
export interface ExtendedAlertStatic {
|
export interface ExtendedAlertStatic {
|
||||||
alert: (
|
alert: (
|
||||||
|
@ -20,9 +20,10 @@
|
|||||||
|
|
||||||
// Stolen from https://github.com/necolas/react-native-web/issues/1026#issuecomment-1458279681
|
// Stolen from https://github.com/necolas/react-native-web/issues/1026#issuecomment-1458279681
|
||||||
|
|
||||||
import { type AlertButton, type AlertOptions } from "react-native";
|
import type { AlertButton, AlertOptions } from "react-native";
|
||||||
import Swal, { type SweetAlertIcon } from "sweetalert2";
|
import Swal, { type SweetAlertIcon } from "sweetalert2";
|
||||||
|
|
||||||
|
// biome-ignore lint/complexity/noStaticOnlyClass: Compatibility with rn
|
||||||
export class Alert {
|
export class Alert {
|
||||||
static alert(
|
static alert(
|
||||||
title: string,
|
title: string,
|
||||||
|
@ -18,23 +18,22 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { View, ViewStyle, Image, ImageProps } from "react-native";
|
import AccountCircle from "@material-symbols/svg-400/rounded/account_circle-fill.svg";
|
||||||
import { useYoshiki, px, Stylable } from "yoshiki/native";
|
import { type ComponentType, type RefAttributes, forwardRef } from "react";
|
||||||
|
import { Image, type ImageProps, View, type ViewStyle } from "react-native";
|
||||||
|
import { type Stylable, px, useYoshiki } from "yoshiki/native";
|
||||||
import { Icon } from "./icons";
|
import { Icon } from "./icons";
|
||||||
import { P } from "./text";
|
import { P } from "./text";
|
||||||
import AccountCircle from "@material-symbols/svg-400/rounded/account_circle-fill.svg";
|
|
||||||
import { ComponentType, forwardRef, RefAttributes } from "react";
|
|
||||||
|
|
||||||
const stringToColor = (string: string) => {
|
const stringToColor = (string: string) => {
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
let i;
|
|
||||||
|
|
||||||
for (i = 0; i < string.length; i += 1) {
|
for (let i = 0; i < string.length; i += 1) {
|
||||||
hash = string.charCodeAt(i) + ((hash << 5) - hash);
|
hash = string.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
let color = "#";
|
let color = "#";
|
||||||
for (i = 0; i < 3; i += 1) {
|
for (let i = 0; i < 3; i += 1) {
|
||||||
const value = (hash >> (i * 8)) & 0xff;
|
const value = (hash >> (i * 8)) & 0xff;
|
||||||
color += `00${value.toString(16)}`.slice(-2);
|
color += `00${value.toString(16)}`.slice(-2);
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,12 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentType, ForwardedRef, ReactElement, forwardRef } from "react";
|
import { type ComponentType, type ForwardedRef, type ReactElement, forwardRef } from "react";
|
||||||
import { Theme, useYoshiki } from "yoshiki/native";
|
import { type Falsy, type PressableProps, View } from "react-native";
|
||||||
|
import { type Theme, useYoshiki } from "yoshiki/native";
|
||||||
import { PressableFeedback } from "./links";
|
import { PressableFeedback } from "./links";
|
||||||
import { P } from "./text";
|
import { P } from "./text";
|
||||||
import { ts } from "./utils";
|
import { ts } from "./utils";
|
||||||
import { Falsy, PressableProps, View } from "react-native";
|
|
||||||
|
|
||||||
export const Button = forwardRef(function Button<AsProps = PressableProps>(
|
export const Button = forwardRef(function Button<AsProps = PressableProps>(
|
||||||
{
|
{
|
||||||
|
@ -18,12 +18,12 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { px, rem, Theme, useYoshiki } from "yoshiki/native";
|
import type { TextProps } from "react-native";
|
||||||
|
import { type Theme, px, rem, useYoshiki } from "yoshiki/native";
|
||||||
import { Link } from "./links";
|
import { Link } from "./links";
|
||||||
|
import { Skeleton } from "./skeleton";
|
||||||
import { P } from "./text";
|
import { P } from "./text";
|
||||||
import { capitalize, ts } from "./utils";
|
import { capitalize, ts } from "./utils";
|
||||||
import { TextProps } from "react-native";
|
|
||||||
import { Skeleton } from "./skeleton";
|
|
||||||
|
|
||||||
export const Chip = ({
|
export const Chip = ({
|
||||||
color,
|
color,
|
||||||
@ -49,7 +49,7 @@ export const Chip = ({
|
|||||||
|
|
||||||
textProps ??= {};
|
textProps ??= {};
|
||||||
|
|
||||||
const sizeMult = size == "medium" ? 1 : size == "small" ? 0.5 : 1.5;
|
const sizeMult = size === "medium" ? 1 : size === "small" ? 0.5 : 1.5;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
|
@ -18,11 +18,11 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentType } from "react";
|
import type { ComponentType } from "react";
|
||||||
import { View, ViewProps } from "react-native";
|
import { View, type ViewProps } from "react-native";
|
||||||
import { percent, px, useYoshiki } from "yoshiki/native";
|
import { percent, px, useYoshiki } from "yoshiki/native";
|
||||||
|
|
||||||
export const Container = <AsProps = ViewProps,>({
|
export const Container = <AsProps = ViewProps>({
|
||||||
as,
|
as,
|
||||||
...props
|
...props
|
||||||
}: { as?: ComponentType<AsProps> } & AsProps) => {
|
}: { as?: ComponentType<AsProps> } & AsProps) => {
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { HR as EHR } from "@expo/html-elements";
|
import { HR as EHR } from "@expo/html-elements";
|
||||||
import { px, Stylable, useYoshiki } from "yoshiki/native";
|
import { type Stylable, px, useYoshiki } from "yoshiki/native";
|
||||||
import { ts } from "./utils";
|
import { ts } from "./utils";
|
||||||
|
|
||||||
export const HR = ({
|
export const HR = ({
|
||||||
|
@ -18,14 +18,15 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ComponentProps, ComponentType, ForwardedRef, forwardRef } from "react";
|
import type React from "react";
|
||||||
import { Platform, PressableProps } from "react-native";
|
import { type ComponentProps, type ComponentType, type ForwardedRef, forwardRef } from "react";
|
||||||
import { SvgProps } from "react-native-svg";
|
import { Platform, type PressableProps } from "react-native";
|
||||||
import { YoshikiStyle } from "yoshiki";
|
import type { SvgProps } from "react-native-svg";
|
||||||
import { px, Stylable, Theme, useYoshiki } from "yoshiki/native";
|
import type { YoshikiStyle } from "yoshiki";
|
||||||
|
import { type Stylable, type Theme, px, useYoshiki } from "yoshiki/native";
|
||||||
import { PressableFeedback } from "./links";
|
import { PressableFeedback } from "./links";
|
||||||
import { Breakpoint, focusReset, ts } from "./utils";
|
|
||||||
import { P } from "./text";
|
import { P } from "./text";
|
||||||
|
import { type Breakpoint, focusReset, ts } from "./utils";
|
||||||
|
|
||||||
declare module "react" {
|
declare module "react" {
|
||||||
function forwardRef<T, P = {}>(
|
function forwardRef<T, P = {}>(
|
||||||
@ -109,7 +110,7 @@ export const IconButton = forwardRef(function IconButton<AsProps = PressableProp
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const IconFab = <AsProps = PressableProps,>(
|
export const IconFab = <AsProps = PressableProps>(
|
||||||
props: ComponentProps<typeof IconButton<AsProps>>,
|
props: ComponentProps<typeof IconButton<AsProps>>,
|
||||||
) => {
|
) => {
|
||||||
const { css, theme } = useYoshiki();
|
const { css, theme } = useYoshiki();
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { KyooImage } from "@kyoo/models";
|
import type { KyooImage } from "@kyoo/models";
|
||||||
import { ReactElement } from "react";
|
import type { ReactElement } from "react";
|
||||||
import { ImageStyle } from "react-native";
|
import type { ImageStyle } from "react-native";
|
||||||
import { YoshikiStyle } from "yoshiki/src/type";
|
import type { YoshikiStyle } from "yoshiki/src/type";
|
||||||
|
|
||||||
export type YoshikiEnhanced<Style> = Style extends any
|
export type YoshikiEnhanced<Style> = Style extends any
|
||||||
? {
|
? {
|
||||||
@ -33,7 +33,7 @@ export type Props = {
|
|||||||
src?: KyooImage | null;
|
src?: KyooImage | null;
|
||||||
quality: "low" | "medium" | "high";
|
quality: "low" | "medium" | "high";
|
||||||
alt?: string;
|
alt?: string;
|
||||||
Error?: ReactElement | null;
|
Err?: ReactElement | null;
|
||||||
forcedLoading?: boolean;
|
forcedLoading?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReactElement } from "react";
|
import type { ReactElement } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { Blurhash } from "react-native-blurhash";
|
import { Blurhash } from "react-native-blurhash";
|
||||||
import { Stylable, useYoshiki } from "yoshiki/native";
|
import { type Stylable, useYoshiki } from "yoshiki/native";
|
||||||
|
|
||||||
export const BlurhashContainer = ({
|
export const BlurhashContainer = ({
|
||||||
blurhash,
|
blurhash,
|
||||||
|
@ -20,19 +20,16 @@
|
|||||||
|
|
||||||
import { decode } from "blurhash";
|
import { decode } from "blurhash";
|
||||||
import {
|
import {
|
||||||
HTMLAttributes,
|
type HTMLAttributes,
|
||||||
ReactElement,
|
type ReactElement,
|
||||||
createElement,
|
|
||||||
forwardRef,
|
forwardRef,
|
||||||
useEffect,
|
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useYoshiki } from "yoshiki";
|
import { useYoshiki } from "yoshiki";
|
||||||
import { Stylable, nativeStyleToCss } from "yoshiki/native";
|
import { nativeStyleToCss } from "yoshiki/native";
|
||||||
import { StyleList, processStyleList } from "yoshiki/src/type";
|
|
||||||
|
|
||||||
// The blurhashToUrl has been stolen from https://gist.github.com/mattiaz9/53cb67040fa135cb395b1d015a200aff
|
// The blurhashToUrl has been stolen from https://gist.github.com/mattiaz9/53cb67040fa135cb395b1d015a200aff
|
||||||
export function blurHashToDataURL(hash: string | undefined): string | undefined {
|
export function blurHashToDataURL(hash: string | undefined): string | undefined {
|
||||||
@ -53,7 +50,7 @@ function parsePixels(pixels: Uint8ClampedArray, width: number, height: number) {
|
|||||||
typeof Buffer !== "undefined"
|
typeof Buffer !== "undefined"
|
||||||
? Buffer.from(getPngArray(pngString)).toString("base64")
|
? Buffer.from(getPngArray(pngString)).toString("base64")
|
||||||
: btoa(pngString);
|
: btoa(pngString);
|
||||||
return "data:image/png;base64," + dataURL;
|
return `data:image/png;base64,${dataURL}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPngArray(pngString: string) {
|
function getPngArray(pngString: string) {
|
||||||
@ -70,6 +67,7 @@ function generatePng(width: number, height: number, rgbaString: string) {
|
|||||||
const SIGNATURE = String.fromCharCode(137, 80, 78, 71, 13, 10, 26, 10);
|
const SIGNATURE = String.fromCharCode(137, 80, 78, 71, 13, 10, 26, 10);
|
||||||
const NO_FILTER = String.fromCharCode(0);
|
const NO_FILTER = String.fromCharCode(0);
|
||||||
|
|
||||||
|
// biome-ignore lint: not gonna fix stackowerflow code that works
|
||||||
let n, c, k;
|
let n, c, k;
|
||||||
|
|
||||||
// make crc table
|
// make crc table
|
||||||
@ -89,7 +87,9 @@ function generatePng(width: number, height: number, rgbaString: string) {
|
|||||||
function inflateStore(data: string) {
|
function inflateStore(data: string) {
|
||||||
const MAX_STORE_LENGTH = 65535;
|
const MAX_STORE_LENGTH = 65535;
|
||||||
let storeBuffer = "";
|
let storeBuffer = "";
|
||||||
|
// biome-ignore lint: not gonna fix stackowerflow code that works
|
||||||
let remaining;
|
let remaining;
|
||||||
|
// biome-ignore lint: not gonna fix stackowerflow code that works
|
||||||
let blockType;
|
let blockType;
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i += MAX_STORE_LENGTH) {
|
for (let i = 0; i < data.length; i += MAX_STORE_LENGTH) {
|
||||||
@ -113,7 +113,7 @@ function generatePng(width: number, height: number, rgbaString: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function adler32(data: string) {
|
function adler32(data: string) {
|
||||||
let MOD_ADLER = 65521;
|
const MOD_ADLER = 65521;
|
||||||
let a = 1;
|
let a = 1;
|
||||||
let b = 0;
|
let b = 0;
|
||||||
|
|
||||||
@ -179,7 +179,7 @@ function generatePng(width: number, height: number, rgbaString: string) {
|
|||||||
const IHDR = createIHDR(width, height);
|
const IHDR = createIHDR(width, height);
|
||||||
|
|
||||||
let scanlines = "";
|
let scanlines = "";
|
||||||
let scanline;
|
let scanline: string;
|
||||||
|
|
||||||
for (let y = 0; y < rgbaString.length; y += width * 4) {
|
for (let y = 0; y < rgbaString.length; y += width * 4) {
|
||||||
scanline = NO_FILTER;
|
scanline = NO_FILTER;
|
||||||
|
@ -18,14 +18,14 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { FlexStyle, ImageStyle, View, ViewStyle } from "react-native";
|
|
||||||
import FastImage from "react-native-fast-image";
|
|
||||||
import { Blurhash } from "react-native-blurhash";
|
|
||||||
import { percent, useYoshiki } from "yoshiki/native";
|
|
||||||
import { Props, ImageLayout } from "./base-image";
|
|
||||||
import { Skeleton } from "../skeleton";
|
|
||||||
import { getCurrentToken } from "@kyoo/models";
|
import { getCurrentToken } from "@kyoo/models";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { type FlexStyle, type ImageStyle, View, type ViewStyle } from "react-native";
|
||||||
|
import { Blurhash } from "react-native-blurhash";
|
||||||
|
import FastImage from "react-native-fast-image";
|
||||||
|
import { percent, useYoshiki } from "yoshiki/native";
|
||||||
|
import { Skeleton } from "../skeleton";
|
||||||
|
import type { ImageLayout, Props } from "./base-image";
|
||||||
|
|
||||||
export const Image = ({
|
export const Image = ({
|
||||||
src,
|
src,
|
||||||
@ -33,7 +33,7 @@ export const Image = ({
|
|||||||
alt,
|
alt,
|
||||||
forcedLoading = false,
|
forcedLoading = false,
|
||||||
layout,
|
layout,
|
||||||
Error,
|
Err,
|
||||||
...props
|
...props
|
||||||
}: Props & { style?: ImageStyle } & { layout: ImageLayout }) => {
|
}: Props & { style?: ImageStyle } & { layout: ImageLayout }) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
@ -53,8 +53,8 @@ export const Image = ({
|
|||||||
|
|
||||||
if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />;
|
if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />;
|
||||||
if (!src || state === "errored") {
|
if (!src || state === "errored") {
|
||||||
return Error !== undefined ? (
|
return Err !== undefined ? (
|
||||||
Error
|
Err
|
||||||
) : (
|
) : (
|
||||||
<View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />
|
<View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />
|
||||||
);
|
);
|
||||||
|
@ -18,14 +18,14 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { ImageStyle, View, ViewStyle } from "react-native";
|
|
||||||
import { useYoshiki } from "yoshiki/native";
|
|
||||||
import { Props, ImageLayout } from "./base-image";
|
|
||||||
import { BlurhashContainer } from "./blurhash.web";
|
|
||||||
import { Skeleton } from "../skeleton";
|
|
||||||
import NextImage from "next/image";
|
import NextImage from "next/image";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { type ImageStyle, View, type ViewStyle } from "react-native";
|
||||||
|
import { useYoshiki } from "yoshiki/native";
|
||||||
import { imageBorderRadius } from "../constants";
|
import { imageBorderRadius } from "../constants";
|
||||||
|
import { Skeleton } from "../skeleton";
|
||||||
|
import type { ImageLayout, Props } from "./base-image";
|
||||||
|
import { BlurhashContainer } from "./blurhash.web";
|
||||||
|
|
||||||
export const Image = ({
|
export const Image = ({
|
||||||
src,
|
src,
|
||||||
@ -33,7 +33,7 @@ export const Image = ({
|
|||||||
alt,
|
alt,
|
||||||
forcedLoading = false,
|
forcedLoading = false,
|
||||||
layout,
|
layout,
|
||||||
Error,
|
Err,
|
||||||
...props
|
...props
|
||||||
}: Props & { style?: ImageStyle } & { layout: ImageLayout }) => {
|
}: Props & { style?: ImageStyle } & { layout: ImageLayout }) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
@ -45,8 +45,8 @@ export const Image = ({
|
|||||||
|
|
||||||
if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />;
|
if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />;
|
||||||
if (!src || state === "errored") {
|
if (!src || state === "errored") {
|
||||||
return Error !== undefined ? (
|
return Err !== undefined ? (
|
||||||
Error
|
Err
|
||||||
) : (
|
) : (
|
||||||
<View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />
|
<View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />
|
||||||
);
|
);
|
||||||
|
@ -18,14 +18,14 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ImageStyle, View, ViewProps, ViewStyle } from "react-native";
|
import { LinearGradient, type LinearGradientProps } from "expo-linear-gradient";
|
||||||
import { Props, ImageLayout, YoshikiEnhanced } from "./base-image";
|
import type { ComponentProps, ComponentType, ReactNode } from "react";
|
||||||
import { Image } from "./image";
|
import { type ImageStyle, View, type ViewProps, type ViewStyle } from "react-native";
|
||||||
import { ComponentProps, ComponentType, ReactNode } from "react";
|
|
||||||
import { LinearGradient, LinearGradientProps } from "expo-linear-gradient";
|
|
||||||
import { ContrastArea } from "../themes";
|
|
||||||
import { percent } from "yoshiki/native";
|
import { percent } from "yoshiki/native";
|
||||||
import { imageBorderRadius } from "../constants";
|
import { imageBorderRadius } from "../constants";
|
||||||
|
import { ContrastArea } from "../themes";
|
||||||
|
import type { ImageLayout, Props, YoshikiEnhanced } from "./base-image";
|
||||||
|
import { Image } from "./image";
|
||||||
|
|
||||||
export { Sprite } from "./sprite";
|
export { Sprite } from "./sprite";
|
||||||
export { BlurhashContainer } from "./blurhash";
|
export { BlurhashContainer } from "./blurhash";
|
||||||
@ -55,7 +55,7 @@ export const PosterBackground = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ImageBackground = <AsProps = ViewProps,>({
|
export const ImageBackground = <AsProps = ViewProps>({
|
||||||
src,
|
src,
|
||||||
alt,
|
alt,
|
||||||
quality,
|
quality,
|
||||||
@ -111,7 +111,7 @@ export const ImageBackground = <AsProps = ViewProps,>({
|
|||||||
forcedLoading={forcedLoading}
|
forcedLoading={forcedLoading}
|
||||||
alt={alt!}
|
alt={alt!}
|
||||||
layout={{ width: percent(100), height: percent(100) }}
|
layout={{ width: percent(100), height: percent(100) }}
|
||||||
Error={hideLoad ? null : undefined}
|
Err={hideLoad ? null : undefined}
|
||||||
{...(css([{ borderWidth: 0, borderRadius: 0 }, imageStyle]) as {
|
{...(css([{ borderWidth: 0, borderRadius: 0 }, imageStyle]) as {
|
||||||
style: ImageStyle;
|
style: ImageStyle;
|
||||||
})}
|
})}
|
||||||
|
@ -18,11 +18,11 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { forwardRef, ReactNode, useState } from "react";
|
import { type ReactNode, forwardRef, useState } from "react";
|
||||||
import { TextInput, TextInputProps, View, ViewStyle } from "react-native";
|
import { TextInput, type TextInputProps, View, type ViewStyle } from "react-native";
|
||||||
import { px, Theme, useYoshiki } from "yoshiki/native";
|
import { type Theme, px, useYoshiki } from "yoshiki/native";
|
||||||
|
import type { YoshikiEnhanced } from "./image/base-image";
|
||||||
import { focusReset, ts } from "./utils";
|
import { focusReset, ts } from "./utils";
|
||||||
import { YoshikiEnhanced } from "./image/base-image";
|
|
||||||
|
|
||||||
export const Input = forwardRef<
|
export const Input = forwardRef<
|
||||||
TextInput,
|
TextInput,
|
||||||
|
@ -18,13 +18,20 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { forwardRef, ReactNode } from "react";
|
import type { UrlObject } from "node:url";
|
||||||
import { Platform, Pressable, TextProps, View, PressableProps, Linking } from "react-native";
|
import { type ReactNode, forwardRef } from "react";
|
||||||
|
import {
|
||||||
|
Linking,
|
||||||
|
Platform,
|
||||||
|
Pressable,
|
||||||
|
type PressableProps,
|
||||||
|
type TextProps,
|
||||||
|
type View,
|
||||||
|
} from "react-native";
|
||||||
import { TextLink, useLink } from "solito/link";
|
import { TextLink, useLink } from "solito/link";
|
||||||
import { useTheme, useYoshiki } from "yoshiki/native";
|
|
||||||
import type { UrlObject } from "url";
|
|
||||||
import { alpha } from "./themes";
|
|
||||||
import { parseNextPath } from "solito/router";
|
import { parseNextPath } from "solito/router";
|
||||||
|
import { useTheme, useYoshiki } from "yoshiki/native";
|
||||||
|
import { alpha } from "./themes";
|
||||||
|
|
||||||
export const A = ({
|
export const A = ({
|
||||||
href,
|
href,
|
||||||
|
@ -19,29 +19,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Portal } from "@gorhom/portal";
|
import { Portal } from "@gorhom/portal";
|
||||||
|
import Check from "@material-symbols/svg-400/rounded/check-fill.svg";
|
||||||
|
import Close from "@material-symbols/svg-400/rounded/close-fill.svg";
|
||||||
import { ScrollView } from "moti";
|
import { ScrollView } from "moti";
|
||||||
import {
|
import {
|
||||||
ComponentType,
|
type ComponentType,
|
||||||
|
type ReactElement,
|
||||||
|
type ReactNode,
|
||||||
createContext,
|
createContext,
|
||||||
ReactElement,
|
|
||||||
ReactNode,
|
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { StyleSheet, Pressable, View } from "react-native";
|
import { Pressable, StyleSheet, View } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
import type { SvgProps } from "react-native-svg";
|
||||||
|
import { useRouter } from "solito/router";
|
||||||
import { percent, px, sm, useYoshiki, vh, xl } from "yoshiki/native";
|
import { percent, px, sm, useYoshiki, vh, xl } from "yoshiki/native";
|
||||||
import Close from "@material-symbols/svg-400/rounded/close-fill.svg";
|
|
||||||
import { Icon, IconButton } from "./icons";
|
import { Icon, IconButton } from "./icons";
|
||||||
import { PressableFeedback } from "./links";
|
import { PressableFeedback } from "./links";
|
||||||
import { P } from "./text";
|
import { P } from "./text";
|
||||||
import { ContrastArea, SwitchVariant } from "./themes";
|
import { ContrastArea, SwitchVariant } from "./themes";
|
||||||
import { ts } from "./utils";
|
import { ts } from "./utils";
|
||||||
import Check from "@material-symbols/svg-400/rounded/check-fill.svg";
|
|
||||||
import { useRouter } from "solito/router";
|
|
||||||
import { SvgProps } from "react-native-svg";
|
|
||||||
|
|
||||||
const MenuContext = createContext<((open: boolean) => void) | undefined>(undefined);
|
const MenuContext = createContext<((open: boolean) => void) | undefined>(undefined);
|
||||||
|
|
||||||
@ -66,7 +66,6 @@ const Menu = <AsProps,>({
|
|||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const alreadyRendered = useRef(false);
|
const alreadyRendered = useRef(false);
|
||||||
const [isOpen, setOpen] =
|
const [isOpen, setOpen] =
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
outerOpen !== undefined && outerSetOpen ? [outerOpen, outerSetOpen] : useState(false);
|
outerOpen !== undefined && outerSetOpen ? [outerOpen, outerSetOpen] : useState(false);
|
||||||
|
|
||||||
// deos the same as a useMemo but for props.
|
// deos the same as a useMemo but for props.
|
||||||
|
@ -18,18 +18,24 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import Dot from "@material-symbols/svg-400/rounded/fiber_manual_record-fill.svg";
|
||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||||
import { ComponentProps, ComponentType, forwardRef, ReactElement, ReactNode } from "react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { PressableProps } from "react-native";
|
import {
|
||||||
import { useYoshiki } from "yoshiki/web";
|
type ComponentProps,
|
||||||
|
type ComponentType,
|
||||||
|
type ReactElement,
|
||||||
|
type ReactNode,
|
||||||
|
forwardRef,
|
||||||
|
} from "react";
|
||||||
|
import type { PressableProps } from "react-native";
|
||||||
|
import type { SvgProps } from "react-native-svg";
|
||||||
import { useYoshiki as useNativeYoshiki } from "yoshiki/native";
|
import { useYoshiki as useNativeYoshiki } from "yoshiki/native";
|
||||||
|
import { useYoshiki } from "yoshiki/web";
|
||||||
|
import { Icon } from "./icons";
|
||||||
import { P } from "./text";
|
import { P } from "./text";
|
||||||
import { ContrastArea, SwitchVariant } from "./themes";
|
import { ContrastArea, SwitchVariant } from "./themes";
|
||||||
import { Icon } from "./icons";
|
|
||||||
import Dot from "@material-symbols/svg-400/rounded/fiber_manual_record-fill.svg";
|
|
||||||
import { focusReset, ts } from "./utils";
|
import { focusReset, ts } from "./utils";
|
||||||
import { SvgProps } from "react-native-svg";
|
|
||||||
|
|
||||||
type YoshikiFunc<T> = (props: ReturnType<typeof useYoshiki>) => T;
|
type YoshikiFunc<T> = (props: ReturnType<typeof useYoshiki>) => T;
|
||||||
export const YoshikiProvider = ({ children }: { children: YoshikiFunc<ReactNode> }) => {
|
export const YoshikiProvider = ({ children }: { children: YoshikiFunc<ReactNode> }) => {
|
||||||
|
@ -18,13 +18,13 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReactNode, useCallback, useEffect, useState } from "react";
|
|
||||||
import { Container } from "./container";
|
|
||||||
import { usePortal } from "@gorhom/portal";
|
import { usePortal } from "@gorhom/portal";
|
||||||
import { ContrastArea, SwitchVariant, YoshikiFunc } from "./themes";
|
import { type ReactNode, useCallback, useEffect, useState } from "react";
|
||||||
import { View, ScrollView } from "react-native";
|
import { ScrollView, View } from "react-native";
|
||||||
import { imageBorderRadius } from "./constants";
|
|
||||||
import { px, vh } from "yoshiki/native";
|
import { px, vh } from "yoshiki/native";
|
||||||
|
import { imageBorderRadius } from "./constants";
|
||||||
|
import { Container } from "./container";
|
||||||
|
import { ContrastArea, SwitchVariant, type YoshikiFunc } from "./themes";
|
||||||
import { ts } from "./utils";
|
import { ts } from "./utils";
|
||||||
|
|
||||||
export const Popup = ({ children, ...props }: { children: ReactNode | YoshikiFunc<ReactNode> }) => {
|
export const Popup = ({ children, ...props }: { children: ReactNode | YoshikiFunc<ReactNode> }) => {
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
import { ActivityIndicator, Platform, View } from "react-native";
|
import { ActivityIndicator, Platform, View } from "react-native";
|
||||||
import { Circle, Svg } from "react-native-svg";
|
import { Circle, Svg } from "react-native-svg";
|
||||||
import { px, Stylable, useYoshiki } from "yoshiki/native";
|
import { type Stylable, px, useYoshiki } from "yoshiki/native";
|
||||||
|
|
||||||
export const CircularProgress = ({
|
export const CircularProgress = ({
|
||||||
size = 48,
|
size = 48,
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Icon } from "./icons";
|
|
||||||
import ExpandMore from "@material-symbols/svg-400/rounded/expand_more-fill.svg";
|
import ExpandMore from "@material-symbols/svg-400/rounded/expand_more-fill.svg";
|
||||||
import { Menu } from "./menu";
|
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
|
import { Icon } from "./icons";
|
||||||
|
import { Menu } from "./menu";
|
||||||
|
|
||||||
export const Select = <Value extends string>({
|
export const Select = <Value extends string>({
|
||||||
label,
|
label,
|
||||||
|
@ -18,20 +18,20 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import Check from "@material-symbols/svg-400/rounded/check-fill.svg";
|
||||||
|
import ExpandLess from "@material-symbols/svg-400/rounded/expand_less-fill.svg";
|
||||||
|
import ExpandMore from "@material-symbols/svg-400/rounded/expand_more-fill.svg";
|
||||||
import * as RSelect from "@radix-ui/react-select";
|
import * as RSelect from "@radix-ui/react-select";
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
import { Icon } from "./icons";
|
|
||||||
import Check from "@material-symbols/svg-400/rounded/check-fill.svg";
|
|
||||||
import ExpandMore from "@material-symbols/svg-400/rounded/expand_more-fill.svg";
|
|
||||||
import ExpandLess from "@material-symbols/svg-400/rounded/expand_less-fill.svg";
|
|
||||||
import { ContrastArea, SwitchVariant } from "./themes";
|
|
||||||
import { InternalTriger, YoshikiProvider } from "./menu.web";
|
|
||||||
import { Theme, px, useYoshiki as useNativeYoshiki } from "yoshiki/native";
|
|
||||||
import { useYoshiki } from "yoshiki";
|
|
||||||
import { PressableFeedback } from "./links";
|
|
||||||
import { P } from "./text";
|
|
||||||
import { focusReset, ts } from "./utils";
|
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
|
import { useYoshiki } from "yoshiki";
|
||||||
|
import { type Theme, px, useYoshiki as useNativeYoshiki } from "yoshiki/native";
|
||||||
|
import { Icon } from "./icons";
|
||||||
|
import { PressableFeedback } from "./links";
|
||||||
|
import { InternalTriger, YoshikiProvider } from "./menu.web";
|
||||||
|
import { P } from "./text";
|
||||||
|
import { ContrastArea, SwitchVariant } from "./themes";
|
||||||
|
import { focusReset, ts } from "./utils";
|
||||||
|
|
||||||
export const Select = ({
|
export const Select = ({
|
||||||
label,
|
label,
|
||||||
|
@ -19,10 +19,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { LinearGradient as LG } from "expo-linear-gradient";
|
import { LinearGradient as LG } from "expo-linear-gradient";
|
||||||
import { AnimatePresence, motify, MotiView } from "moti";
|
import { AnimatePresence, MotiView, motify } from "moti";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Platform, View, ViewProps } from "react-native";
|
import { Platform, View, type ViewProps } from "react-native";
|
||||||
import { px, rem, useYoshiki, percent, em } from "yoshiki/native";
|
import { em, percent, px, rem, useYoshiki } from "yoshiki/native";
|
||||||
import { hiddenIfNoJs } from "./utils/nojs";
|
import { hiddenIfNoJs } from "./utils/nojs";
|
||||||
|
|
||||||
const LinearGradient = motify(LG)();
|
const LinearGradient = motify(LG)();
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { LinearGradient } from "expo-linear-gradient";
|
import { LinearGradient } from "expo-linear-gradient";
|
||||||
import { View, ViewProps } from "react-native";
|
import { View, type ViewProps } from "react-native";
|
||||||
import { px, rem, useYoshiki, percent, em } from "yoshiki/native";
|
import { em, percent, px, rem, useYoshiki } from "yoshiki/native";
|
||||||
import { hiddenIfNoJs } from "./utils/nojs";
|
import { hiddenIfNoJs } from "./utils/nojs";
|
||||||
|
|
||||||
export const SkeletonCss = () => (
|
export const SkeletonCss = () => (
|
||||||
|
@ -19,10 +19,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { GestureResponderEvent, Platform, View } from "react-native";
|
import { type GestureResponderEvent, Platform, View } from "react-native";
|
||||||
import { px, percent, Stylable, useYoshiki } from "yoshiki/native";
|
import type { ViewProps } from "react-native-svg/lib/typescript/fabric/utils";
|
||||||
|
import { Stylable, percent, px, useYoshiki } from "yoshiki/native";
|
||||||
import { focusReset } from "./utils";
|
import { focusReset } from "./utils";
|
||||||
import { ViewProps } from "react-native-svg/lib/typescript/fabric/utils";
|
|
||||||
|
|
||||||
export const Slider = ({
|
export const Slider = ({
|
||||||
progress,
|
progress,
|
||||||
|
@ -19,14 +19,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { usePortal } from "@gorhom/portal";
|
import { usePortal } from "@gorhom/portal";
|
||||||
import { ReactElement, createContext, useCallback, useContext, useRef } from "react";
|
import { type ReactElement, createContext, useCallback, useContext, useRef } from "react";
|
||||||
import { ContrastArea, SwitchVariant } from "./themes";
|
|
||||||
import { P } from "./text";
|
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { percent, px } from "yoshiki/native";
|
import { percent, px } from "yoshiki/native";
|
||||||
import { ts } from "./utils";
|
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
import { imageBorderRadius } from "./constants";
|
import { imageBorderRadius } from "./constants";
|
||||||
|
import { P } from "./text";
|
||||||
|
import { ContrastArea, SwitchVariant } from "./themes";
|
||||||
|
import { ts } from "./utils";
|
||||||
|
|
||||||
export type Snackbar = {
|
export type Snackbar = {
|
||||||
key?: string;
|
key?: string;
|
||||||
|
4
front/packages/primitives/src/svg.d.ts
vendored
4
front/packages/primitives/src/svg.d.ts
vendored
@ -19,8 +19,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
declare module "*.svg" {
|
declare module "*.svg" {
|
||||||
import React from "react";
|
import type React from "react";
|
||||||
import { SvgProps } from "react-native-svg";
|
import type { SvgProps } from "react-native-svg";
|
||||||
const content: React.FC<SvgProps>;
|
const content: React.FC<SvgProps>;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,6 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentType, ComponentProps } from "react";
|
|
||||||
import { Platform, Text, TextProps, TextStyle, StyleProp } from "react-native";
|
|
||||||
import { percent, rem, useYoshiki } from "yoshiki/native";
|
|
||||||
import {
|
import {
|
||||||
H1 as EH1,
|
H1 as EH1,
|
||||||
H2 as EH2,
|
H2 as EH2,
|
||||||
@ -30,6 +27,9 @@ import {
|
|||||||
H6 as EH6,
|
H6 as EH6,
|
||||||
P as EP,
|
P as EP,
|
||||||
} from "@expo/html-elements";
|
} from "@expo/html-elements";
|
||||||
|
import type { ComponentProps, ComponentType } from "react";
|
||||||
|
import { Platform, type StyleProp, Text, type TextProps, type TextStyle } from "react-native";
|
||||||
|
import { percent, rem, useYoshiki } from "yoshiki/native";
|
||||||
import { ts } from "./utils/spacing";
|
import { ts } from "./utils/spacing";
|
||||||
|
|
||||||
const styleText = (
|
const styleText = (
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ThemeBuilder } from "./theme";
|
import type { ThemeBuilder } from "./theme";
|
||||||
|
|
||||||
// Ref: https://github.com/catppuccin/catppuccin
|
// Ref: https://github.com/catppuccin/catppuccin
|
||||||
export const catppuccin: ThemeBuilder = {
|
export const catppuccin: ThemeBuilder = {
|
||||||
|
@ -18,14 +18,14 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReactNode } from "react";
|
import type { Property } from "csstype";
|
||||||
import { Property } from "csstype";
|
import type { ReactNode } from "react";
|
||||||
import { Theme, ThemeProvider, useAutomaticTheme } from "yoshiki";
|
import { Platform } from "react-native";
|
||||||
import { useTheme, useYoshiki } from "yoshiki/native";
|
import { type Theme, ThemeProvider, useAutomaticTheme } from "yoshiki";
|
||||||
import "yoshiki";
|
import "yoshiki";
|
||||||
|
import { useTheme, useYoshiki } from "yoshiki/native";
|
||||||
import "yoshiki/native";
|
import "yoshiki/native";
|
||||||
import { catppuccin } from "./catppuccin";
|
import { catppuccin } from "./catppuccin";
|
||||||
import { Platform } from "react-native";
|
|
||||||
|
|
||||||
type FontList = Partial<
|
type FontList = Partial<
|
||||||
Record<
|
Record<
|
||||||
@ -111,9 +111,7 @@ const selectMode = (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
const auto = useAutomaticTheme("theme", { light, dark });
|
const auto = useAutomaticTheme("theme", { light, dark });
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
const alternate = useAutomaticTheme("alternate", { dark: light, light: dark });
|
const alternate = useAutomaticTheme("alternate", { dark: light, light: dark });
|
||||||
return {
|
return {
|
||||||
...options,
|
...options,
|
||||||
|
@ -18,9 +18,9 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Theme } from "yoshiki/native";
|
|
||||||
import { Tooltip as RTooltip } from "react-tooltip";
|
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
|
import { Tooltip as RTooltip } from "react-tooltip";
|
||||||
|
import type { Theme } from "yoshiki/native";
|
||||||
import { ContrastArea } from "./themes";
|
import { ContrastArea } from "./themes";
|
||||||
|
|
||||||
export const tooltip = (tooltip: string, up?: boolean) => ({
|
export const tooltip = (tooltip: string, up?: boolean) => ({
|
||||||
|
2
front/packages/primitives/src/types.d.ts
vendored
2
front/packages/primitives/src/types.d.ts
vendored
@ -18,7 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import type React from "react";
|
||||||
import "react-native";
|
import "react-native";
|
||||||
|
|
||||||
declare module "react-native" {
|
declare module "react-native" {
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useWindowDimensions } from "react-native";
|
import { useWindowDimensions } from "react-native";
|
||||||
import { Breakpoints as YoshikiBreakpoint, isBreakpoints, breakpoints } from "yoshiki/native";
|
import { type Breakpoints as YoshikiBreakpoint, breakpoints, isBreakpoints } from "yoshiki/native";
|
||||||
|
|
||||||
type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];
|
type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];
|
||||||
export type Breakpoint<T> = T | AtLeastOne<YoshikiBreakpoint<T>>;
|
export type Breakpoint<T> = T | AtLeastOne<YoshikiBreakpoint<T>>;
|
||||||
|
@ -31,7 +31,7 @@ export const Head = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<NextHead>
|
<NextHead>
|
||||||
{title && <title>{title + " - Kyoo"}</title>}
|
{title && <title>{`${title} - Kyoo`}</title>}
|
||||||
{description && <meta name="description" content={description} />}
|
{description && <meta name="description" content={description} />}
|
||||||
{image && <meta property="og:image" content={image} />}
|
{image && <meta property="og:image" content={image} />}
|
||||||
</NextHead>
|
</NextHead>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ViewProps } from "react-native";
|
import type { ViewProps } from "react-native";
|
||||||
|
|
||||||
export const hiddenIfNoJs: ViewProps = { style: { $$css: true, noJs: "noJsHidden" } as any };
|
export const hiddenIfNoJs: ViewProps = { style: { $$css: true, noJs: "noJsHidden" } as any };
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Platform, ViewProps } from "react-native";
|
import { Platform, type ViewProps } from "react-native";
|
||||||
|
|
||||||
export const TouchOnlyCss = () => {
|
export const TouchOnlyCss = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -18,9 +18,9 @@
|
|||||||
"incremental": true,
|
"incremental": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["src/*"],
|
"~/*": ["src/*"]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", "**/*.tsx"],
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
"exclude": ["node_modules"],
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,12 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { QueryPage } from "@kyoo/models";
|
import type { QueryPage } from "@kyoo/models";
|
||||||
import { ts } from "@kyoo/primitives";
|
import { ts } from "@kyoo/primitives";
|
||||||
import { ScrollView } from "react-native";
|
import { ScrollView } from "react-native";
|
||||||
import { DefaultLayout } from "../layout";
|
import { DefaultLayout } from "../layout";
|
||||||
import { UserList } from "./users";
|
|
||||||
import { Scanner } from "./scanner";
|
import { Scanner } from "./scanner";
|
||||||
|
import { UserList } from "./users";
|
||||||
|
|
||||||
export const AdminPage: QueryPage = () => {
|
export const AdminPage: QueryPage = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -18,14 +18,14 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Issue, IssueP, QueryIdentifier, queryFn, useFetch } from "@kyoo/models";
|
import { type Issue, IssueP, type QueryIdentifier, queryFn, useFetch } from "@kyoo/models";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { SettingsContainer } from "../settings/base";
|
|
||||||
import { Button, Icon, P, Skeleton, tooltip, ts } from "@kyoo/primitives";
|
import { Button, Icon, P, Skeleton, tooltip, ts } from "@kyoo/primitives";
|
||||||
import { ErrorView } from "../errors";
|
import { useTranslation } from "react-i18next";
|
||||||
import { z } from "zod";
|
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { useYoshiki } from "yoshiki/native";
|
import { useYoshiki } from "yoshiki/native";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { ErrorView } from "../errors";
|
||||||
|
import { SettingsContainer } from "../settings/base";
|
||||||
|
|
||||||
import Info from "@material-symbols/svg-400/outlined/info.svg";
|
import Info from "@material-symbols/svg-400/outlined/info.svg";
|
||||||
import Scan from "@material-symbols/svg-400/outlined/sensors.svg";
|
import Scan from "@material-symbols/svg-400/outlined/sensors.svg";
|
||||||
@ -82,7 +82,7 @@ export const Scanner = () => {
|
|||||||
</View>
|
</View>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
{data != null && data.length == 0 && <P>{t("admin.scanner.empty")}</P>}
|
{data != null && data.length === 0 && <P>{t("admin.scanner.empty")}</P>}
|
||||||
</>
|
</>
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
);
|
);
|
||||||
|
@ -18,21 +18,21 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { QueryIdentifier, User, UserP, queryFn } from "@kyoo/models";
|
import { type QueryIdentifier, type User, UserP, queryFn } from "@kyoo/models";
|
||||||
import { Alert, Avatar, Icon, IconButton, Menu, P, Skeleton, tooltip, ts } from "@kyoo/primitives";
|
import { Alert, Avatar, Icon, IconButton, Menu, P, Skeleton, tooltip, ts } from "@kyoo/primitives";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { px, useYoshiki } from "yoshiki/native";
|
import { px, useYoshiki } from "yoshiki/native";
|
||||||
import { Layout, WithLoading } from "../fetch";
|
import type { Layout, WithLoading } from "../fetch";
|
||||||
import { InfiniteFetch } from "../fetch-infinite";
|
import { InfiniteFetch } from "../fetch-infinite";
|
||||||
import { SettingsContainer } from "../settings/base";
|
import { SettingsContainer } from "../settings/base";
|
||||||
|
|
||||||
import UserI from "@material-symbols/svg-400/rounded/account_circle.svg";
|
import UserI from "@material-symbols/svg-400/rounded/account_circle.svg";
|
||||||
import Delete from "@material-symbols/svg-400/rounded/delete.svg";
|
import Delete from "@material-symbols/svg-400/rounded/delete.svg";
|
||||||
import MoreVert from "@material-symbols/svg-400/rounded/more_vert.svg";
|
|
||||||
import Verifed from "@material-symbols/svg-400/rounded/verified_user.svg";
|
|
||||||
import Unverifed from "@material-symbols/svg-400/rounded/gpp_bad.svg";
|
import Unverifed from "@material-symbols/svg-400/rounded/gpp_bad.svg";
|
||||||
|
import MoreVert from "@material-symbols/svg-400/rounded/more_vert.svg";
|
||||||
import Admin from "@material-symbols/svg-400/rounded/shield_person.svg";
|
import Admin from "@material-symbols/svg-400/rounded/shield_person.svg";
|
||||||
|
import Verifed from "@material-symbols/svg-400/rounded/verified_user.svg";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
export const UserGrid = ({
|
export const UserGrid = ({
|
||||||
|
@ -18,24 +18,24 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { KyooImage, WatchStatusV } from "@kyoo/models";
|
import { type KyooImage, WatchStatusV } from "@kyoo/models";
|
||||||
import {
|
import {
|
||||||
Link,
|
|
||||||
Skeleton,
|
|
||||||
ts,
|
|
||||||
focusReset,
|
|
||||||
P,
|
|
||||||
SubP,
|
|
||||||
PosterBackground,
|
|
||||||
Icon,
|
Icon,
|
||||||
|
Link,
|
||||||
|
P,
|
||||||
|
PosterBackground,
|
||||||
|
Skeleton,
|
||||||
|
SubP,
|
||||||
|
focusReset,
|
||||||
important,
|
important,
|
||||||
|
ts,
|
||||||
} from "@kyoo/primitives";
|
} from "@kyoo/primitives";
|
||||||
import { ImageStyle, Platform, View } from "react-native";
|
|
||||||
import { max, percent, px, rem, Stylable, Theme, useYoshiki } from "yoshiki/native";
|
|
||||||
import { Layout, WithLoading } from "../fetch";
|
|
||||||
import Done from "@material-symbols/svg-400/rounded/done-fill.svg";
|
import Done from "@material-symbols/svg-400/rounded/done-fill.svg";
|
||||||
import { ItemContext } from "../components/context-menus";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { type ImageStyle, Platform, View } from "react-native";
|
||||||
|
import { type Stylable, type Theme, max, percent, px, rem, useYoshiki } from "yoshiki/native";
|
||||||
|
import { ItemContext } from "../components/context-menus";
|
||||||
|
import type { Layout, WithLoading } from "../fetch";
|
||||||
|
|
||||||
export const ItemWatchStatus = ({
|
export const ItemWatchStatus = ({
|
||||||
watchStatus,
|
watchStatus,
|
||||||
|
@ -29,17 +29,17 @@ import {
|
|||||||
tooltip,
|
tooltip,
|
||||||
ts,
|
ts,
|
||||||
} from "@kyoo/primitives";
|
} from "@kyoo/primitives";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useYoshiki } from "yoshiki/native";
|
|
||||||
import Style from "@material-symbols/svg-400/rounded/style.svg";
|
|
||||||
import GridView from "@material-symbols/svg-400/rounded/grid_view.svg";
|
|
||||||
import ViewList from "@material-symbols/svg-400/rounded/view_list.svg";
|
|
||||||
import Sort from "@material-symbols/svg-400/rounded/sort.svg";
|
|
||||||
import ArrowUpward from "@material-symbols/svg-400/rounded/arrow_upward.svg";
|
|
||||||
import ArrowDownward from "@material-symbols/svg-400/rounded/arrow_downward.svg";
|
import ArrowDownward from "@material-symbols/svg-400/rounded/arrow_downward.svg";
|
||||||
import { Layout, SearchSort, SortOrd } from "./types";
|
import ArrowUpward from "@material-symbols/svg-400/rounded/arrow_upward.svg";
|
||||||
|
import GridView from "@material-symbols/svg-400/rounded/grid_view.svg";
|
||||||
|
import Sort from "@material-symbols/svg-400/rounded/sort.svg";
|
||||||
|
import Style from "@material-symbols/svg-400/rounded/style.svg";
|
||||||
|
import ViewList from "@material-symbols/svg-400/rounded/view_list.svg";
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
import { View, PressableProps } from "react-native";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { type PressableProps, View } from "react-native";
|
||||||
|
import { useYoshiki } from "yoshiki/native";
|
||||||
|
import { Layout, SearchSort, SortOrd } from "./types";
|
||||||
|
|
||||||
const SortTrigger = forwardRef<View, PressableProps & { sortKey: string }>(function SortTrigger(
|
const SortTrigger = forwardRef<View, PressableProps & { sortKey: string }>(function SortTrigger(
|
||||||
{ sortKey, ...props },
|
{ sortKey, ...props },
|
||||||
@ -121,14 +121,14 @@ export const BrowseSettings = ({
|
|||||||
<IconButton
|
<IconButton
|
||||||
icon={GridView}
|
icon={GridView}
|
||||||
onPress={() => setLayout(Layout.Grid)}
|
onPress={() => setLayout(Layout.Grid)}
|
||||||
color={layout == Layout.Grid ? theme.accent : undefined}
|
color={layout === Layout.Grid ? theme.accent : undefined}
|
||||||
{...tooltip(t("browse.switchToGrid"))}
|
{...tooltip(t("browse.switchToGrid"))}
|
||||||
{...css({ padding: ts(0.5), marginY: "auto" })}
|
{...css({ padding: ts(0.5), marginY: "auto" })}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={ViewList}
|
icon={ViewList}
|
||||||
onPress={() => setLayout(Layout.List)}
|
onPress={() => setLayout(Layout.List)}
|
||||||
color={layout == Layout.List ? theme.accent : undefined}
|
color={layout === Layout.List ? theme.accent : undefined}
|
||||||
{...tooltip(t("browse.switchToList"))}
|
{...tooltip(t("browse.switchToList"))}
|
||||||
{...css({ padding: ts(0.5), marginY: "auto" })}
|
{...css({ padding: ts(0.5), marginY: "auto" })}
|
||||||
/>
|
/>
|
||||||
|
@ -19,21 +19,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
QueryIdentifier,
|
type LibraryItem,
|
||||||
QueryPage,
|
|
||||||
LibraryItem,
|
|
||||||
LibraryItemP,
|
LibraryItemP,
|
||||||
|
type QueryIdentifier,
|
||||||
|
type QueryPage,
|
||||||
getDisplayDate,
|
getDisplayDate,
|
||||||
} from "@kyoo/models";
|
} from "@kyoo/models";
|
||||||
import { ComponentProps, useState } from "react";
|
import { type ComponentProps, useState } from "react";
|
||||||
import { createParam } from "solito";
|
import { createParam } from "solito";
|
||||||
import { DefaultLayout } from "../layout";
|
import type { WithLoading } from "../fetch";
|
||||||
import { WithLoading } from "../fetch";
|
|
||||||
import { InfiniteFetch } from "../fetch-infinite";
|
import { InfiniteFetch } from "../fetch-infinite";
|
||||||
|
import { DefaultLayout } from "../layout";
|
||||||
import { ItemGrid } from "./grid";
|
import { ItemGrid } from "./grid";
|
||||||
import { ItemList } from "./list";
|
|
||||||
import { SortBy, SortOrd, Layout } from "./types";
|
|
||||||
import { BrowseSettings } from "./header";
|
import { BrowseSettings } from "./header";
|
||||||
|
import { ItemList } from "./list";
|
||||||
|
import { Layout, SortBy, SortOrd } from "./types";
|
||||||
|
|
||||||
const { useParam } = createParam<{ sortBy?: string }>();
|
const { useParam } = createParam<{ sortBy?: string }>();
|
||||||
|
|
||||||
|
@ -18,24 +18,24 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { KyooImage, WatchStatusV } from "@kyoo/models";
|
import type { KyooImage, WatchStatusV } from "@kyoo/models";
|
||||||
import {
|
import {
|
||||||
|
Heading,
|
||||||
|
ImageBackground,
|
||||||
Link,
|
Link,
|
||||||
P,
|
P,
|
||||||
Skeleton,
|
|
||||||
ts,
|
|
||||||
ImageBackground,
|
|
||||||
Heading,
|
|
||||||
PosterBackground,
|
PosterBackground,
|
||||||
|
Skeleton,
|
||||||
imageBorderRadius,
|
imageBorderRadius,
|
||||||
important,
|
important,
|
||||||
|
ts,
|
||||||
} from "@kyoo/primitives";
|
} from "@kyoo/primitives";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Platform, View } from "react-native";
|
import { Platform, View } from "react-native";
|
||||||
import { percent, px, rem, useYoshiki } from "yoshiki/native";
|
import { percent, px, rem, useYoshiki } from "yoshiki/native";
|
||||||
import { Layout, WithLoading } from "../fetch";
|
|
||||||
import { ItemWatchStatus } from "./grid";
|
|
||||||
import { ItemContext } from "../components/context-menus";
|
import { ItemContext } from "../components/context-menus";
|
||||||
|
import type { Layout, WithLoading } from "../fetch";
|
||||||
|
import { ItemWatchStatus } from "./grid";
|
||||||
|
|
||||||
export const ItemList = ({
|
export const ItemList = ({
|
||||||
href,
|
href,
|
||||||
|
@ -19,26 +19,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Collection,
|
type Collection,
|
||||||
CollectionP,
|
CollectionP,
|
||||||
LibraryItem,
|
type LibraryItem,
|
||||||
LibraryItemP,
|
LibraryItemP,
|
||||||
QueryIdentifier,
|
type QueryIdentifier,
|
||||||
QueryPage,
|
type QueryPage,
|
||||||
getDisplayDate,
|
getDisplayDate,
|
||||||
} from "@kyoo/models";
|
} from "@kyoo/models";
|
||||||
import { Header as ShowHeader, TitleLine } from "../details/header";
|
|
||||||
import { Container, Head, ImageBackground, P, Skeleton, ts, usePageStyle } from "@kyoo/primitives";
|
import { Container, Head, ImageBackground, P, Skeleton, ts, usePageStyle } from "@kyoo/primitives";
|
||||||
import { percent, px, useYoshiki } from "yoshiki/native";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
import { Platform, View, ViewProps } from "react-native";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Platform, View, type ViewProps } from "react-native";
|
||||||
|
import { percent, px, useYoshiki } from "yoshiki/native";
|
||||||
|
import { ItemGrid } from "../browse/grid";
|
||||||
|
import { Header as ShowHeader, TitleLine } from "../details/header";
|
||||||
|
import { SvgWave } from "../details/show";
|
||||||
import { Fetch } from "../fetch";
|
import { Fetch } from "../fetch";
|
||||||
import { InfiniteFetch } from "../fetch-infinite";
|
import { InfiniteFetch } from "../fetch-infinite";
|
||||||
import { DefaultLayout } from "../layout";
|
|
||||||
import { ItemDetails } from "../home/recommended";
|
import { ItemDetails } from "../home/recommended";
|
||||||
import { SvgWave } from "../details/show";
|
import { DefaultLayout } from "../layout";
|
||||||
import { ItemGrid } from "../browse/grid";
|
|
||||||
|
|
||||||
const Header = ({ slug }: { slug: string }) => {
|
const Header = ({ slug }: { slug: string }) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
|
@ -26,7 +26,7 @@ import Info from "@material-symbols/svg-400/rounded/info.svg";
|
|||||||
import MoreVert from "@material-symbols/svg-400/rounded/more_vert.svg";
|
import MoreVert from "@material-symbols/svg-400/rounded/more_vert.svg";
|
||||||
import MovieInfo from "@material-symbols/svg-400/rounded/movie_info.svg";
|
import MovieInfo from "@material-symbols/svg-400/rounded/movie_info.svg";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { useYoshiki } from "yoshiki/native";
|
import { useYoshiki } from "yoshiki/native";
|
||||||
|
@ -18,13 +18,19 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Audio, QueryIdentifier, Subtitle, WatchInfo, WatchInfoP } from "@kyoo/models";
|
import {
|
||||||
|
type Audio,
|
||||||
|
type QueryIdentifier,
|
||||||
|
type Subtitle,
|
||||||
|
type WatchInfo,
|
||||||
|
WatchInfoP,
|
||||||
|
} from "@kyoo/models";
|
||||||
import { Button, HR, P, Popup, Skeleton } from "@kyoo/primitives";
|
import { Button, HR, P, Popup, Skeleton } from "@kyoo/primitives";
|
||||||
import { Fetch } from "../fetch";
|
import { Fragment } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { useYoshiki } from "yoshiki/native";
|
import { useYoshiki } from "yoshiki/native";
|
||||||
import { Fragment } from "react";
|
import { Fetch } from "../fetch";
|
||||||
|
|
||||||
const MediaInfoTable = ({
|
const MediaInfoTable = ({
|
||||||
mediaInfo: { path, video, container, audios, subtitles, duration, size },
|
mediaInfo: { path, video, container, audios, subtitles, duration, size },
|
||||||
@ -35,15 +41,14 @@ const MediaInfoTable = ({
|
|||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
const formatBitrate = (b: number) => `${(b / 1000000).toFixed(2)} Mbps`;
|
const formatBitrate = (b: number) => `${(b / 1000000).toFixed(2)} Mbps`;
|
||||||
const formatTrackTable = (trackTable: (Audio | Subtitle)[], s: string) => {
|
const formatTrackTable = (trackTable: (Audio | Subtitle)[], s: string) => {
|
||||||
if (trackTable.length == 0) {
|
if (trackTable.length === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const singleTrack = trackTable.length == 1;
|
const singleTrack = trackTable.length === 1;
|
||||||
return trackTable.reduce(
|
return trackTable.reduce(
|
||||||
(collected, audioTrack, index) => ({
|
(collected, audioTrack, index) => {
|
||||||
...collected,
|
|
||||||
// If there is only one track, we do not need to show an index
|
// If there is only one track, we do not need to show an index
|
||||||
[singleTrack ? t(s) : `${t(s)} ${index + 1}`]: [
|
collected[singleTrack ? t(s) : `${t(s)} ${index + 1}`] = [
|
||||||
audioTrack.displayName,
|
audioTrack.displayName,
|
||||||
// Only show it if there is more than one track
|
// Only show it if there is more than one track
|
||||||
audioTrack.isDefault && !singleTrack ? t("mediainfo.default") : undefined,
|
audioTrack.isDefault && !singleTrack ? t("mediainfo.default") : undefined,
|
||||||
@ -51,8 +56,9 @@ const MediaInfoTable = ({
|
|||||||
audioTrack.codec,
|
audioTrack.codec,
|
||||||
]
|
]
|
||||||
.filter((x) => x !== undefined)
|
.filter((x) => x !== undefined)
|
||||||
.join(" - "),
|
.join(" - ");
|
||||||
}),
|
return collected;
|
||||||
|
},
|
||||||
{} as Record<string, string | undefined>,
|
{} as Record<string, string | undefined>,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -94,7 +100,7 @@ const MediaInfoTable = ({
|
|||||||
<Skeleton>{value ? <P>{value}</P> : undefined}</Skeleton>
|
<Skeleton>{value ? <P>{value}</P> : undefined}</Skeleton>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{index == l.length - 1 && <HR />}
|
{index === l.length - 1 && <HR />}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)),
|
)),
|
||||||
)}
|
)}
|
||||||
|
@ -18,9 +18,9 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Breakpoint, Icon, P, Skeleton, ts } from "@kyoo/primitives";
|
import { type Breakpoint, Icon, P, Skeleton, ts } from "@kyoo/primitives";
|
||||||
import { View } from "react-native";
|
|
||||||
import Star from "@material-symbols/svg-400/rounded/star-fill.svg";
|
import Star from "@material-symbols/svg-400/rounded/star-fill.svg";
|
||||||
|
import { View } from "react-native";
|
||||||
import { rem, useYoshiki } from "yoshiki/native";
|
import { rem, useYoshiki } from "yoshiki/native";
|
||||||
|
|
||||||
export const Rating = ({ rating, color }: { rating?: number; color: Breakpoint<string> }) => {
|
export const Rating = ({ rating, color }: { rating?: number; color: Breakpoint<string> }) => {
|
||||||
|
@ -18,15 +18,15 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { WatchStatusV, queryFn, useAccount } from "@kyoo/models";
|
||||||
import { IconButton, Menu, tooltip } from "@kyoo/primitives";
|
import { IconButton, Menu, tooltip } from "@kyoo/primitives";
|
||||||
import { ComponentProps } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import BookmarkAdd from "@material-symbols/svg-400/rounded/bookmark_add.svg";
|
|
||||||
import Bookmark from "@material-symbols/svg-400/rounded/bookmark-fill.svg";
|
import Bookmark from "@material-symbols/svg-400/rounded/bookmark-fill.svg";
|
||||||
|
import BookmarkAdd from "@material-symbols/svg-400/rounded/bookmark_add.svg";
|
||||||
import BookmarkAdded from "@material-symbols/svg-400/rounded/bookmark_added-fill.svg";
|
import BookmarkAdded from "@material-symbols/svg-400/rounded/bookmark_added-fill.svg";
|
||||||
import BookmarkRemove from "@material-symbols/svg-400/rounded/bookmark_remove.svg";
|
import BookmarkRemove from "@material-symbols/svg-400/rounded/bookmark_remove.svg";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { WatchStatusV, queryFn, useAccount } from "@kyoo/models";
|
import type { ComponentProps } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export const watchListIcon = (status: WatchStatusV | null) => {
|
export const watchListIcon = (status: WatchStatusV | null) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
|
@ -19,15 +19,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Collection,
|
type Collection,
|
||||||
CollectionP,
|
CollectionP,
|
||||||
KyooImage,
|
type KyooImage,
|
||||||
QueryIdentifier,
|
type QueryIdentifier,
|
||||||
useInfiniteFetch,
|
useInfiniteFetch,
|
||||||
} from "@kyoo/models";
|
} from "@kyoo/models";
|
||||||
import { Container, H2, ImageBackground, Link, P, focusReset, ts } from "@kyoo/primitives";
|
import { Container, H2, ImageBackground, Link, P, focusReset, ts } from "@kyoo/primitives";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Theme, useYoshiki } from "yoshiki/native";
|
import { type Theme, useYoshiki } from "yoshiki/native";
|
||||||
import { ErrorView } from "../errors";
|
import { ErrorView } from "../errors";
|
||||||
|
|
||||||
export const PartOf = ({
|
export const PartOf = ({
|
||||||
|
@ -18,30 +18,30 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { type KyooImage, WatchStatusV } from "@kyoo/models";
|
||||||
import {
|
import {
|
||||||
focusReset,
|
|
||||||
H6,
|
H6,
|
||||||
IconButton,
|
IconButton,
|
||||||
ImageBackground,
|
ImageBackground,
|
||||||
ImageProps,
|
type ImageProps,
|
||||||
important,
|
|
||||||
Link,
|
Link,
|
||||||
P,
|
P,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
SubP,
|
SubP,
|
||||||
|
focusReset,
|
||||||
|
important,
|
||||||
tooltip,
|
tooltip,
|
||||||
ts,
|
ts,
|
||||||
} from "@kyoo/primitives";
|
} from "@kyoo/primitives";
|
||||||
|
import ExpandLess from "@material-symbols/svg-400/rounded/expand_less-fill.svg";
|
||||||
|
import ExpandMore from "@material-symbols/svg-400/rounded/expand_more-fill.svg";
|
||||||
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ImageStyle, Platform, PressableProps, View } from "react-native";
|
import { type ImageStyle, Platform, type PressableProps, View } from "react-native";
|
||||||
import { Layout, WithLoading } from "../fetch";
|
import { type Stylable, type Theme, percent, rem, useYoshiki } from "yoshiki/native";
|
||||||
import { percent, rem, Stylable, Theme, useYoshiki } from "yoshiki/native";
|
|
||||||
import { KyooImage, WatchStatusV } from "@kyoo/models";
|
|
||||||
import { ItemProgress } from "../browse/grid";
|
import { ItemProgress } from "../browse/grid";
|
||||||
import { EpisodesContext } from "../components/context-menus";
|
import { EpisodesContext } from "../components/context-menus";
|
||||||
import { useState } from "react";
|
import type { Layout, WithLoading } from "../fetch";
|
||||||
import ExpandMore from "@material-symbols/svg-400/rounded/expand_more-fill.svg";
|
|
||||||
import ExpandLess from "@material-symbols/svg-400/rounded/expand_less-fill.svg";
|
|
||||||
|
|
||||||
export const episodeDisplayNumber = (
|
export const episodeDisplayNumber = (
|
||||||
episode: {
|
episode: {
|
||||||
@ -295,6 +295,7 @@ export const EpisodeLine = ({
|
|||||||
>
|
>
|
||||||
<Skeleton>
|
<Skeleton>
|
||||||
{isLoading || (
|
{isLoading || (
|
||||||
|
// biome-ignore lint/a11y/useValidAriaValues: simply use H6 for the style but keep a P
|
||||||
<H6 aria-level={undefined} {...css([{ flexShrink: 1 }, "title"])}>
|
<H6 aria-level={undefined} {...css([{ flexShrink: 1 }, "title"])}>
|
||||||
{[displayNumber, name ?? t("show.episodeNoMetadata")].join(" · ")}
|
{[displayNumber, name ?? t("show.episodeNoMetadata")].join(" · ")}
|
||||||
</H6>
|
</H6>
|
||||||
|
@ -19,17 +19,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Genre,
|
type Genre,
|
||||||
KyooImage,
|
type KyooImage,
|
||||||
Movie,
|
type Movie,
|
||||||
QueryIdentifier,
|
type QueryIdentifier,
|
||||||
Show,
|
type Show,
|
||||||
Studio,
|
type Studio,
|
||||||
getDisplayDate,
|
getDisplayDate,
|
||||||
queryFn,
|
queryFn,
|
||||||
useAccount,
|
useAccount,
|
||||||
} from "@kyoo/models";
|
} from "@kyoo/models";
|
||||||
import { WatchStatusV } from "@kyoo/models/src/resources/watch-status";
|
import type { WatchStatusV } from "@kyoo/models/src/resources/watch-status";
|
||||||
import {
|
import {
|
||||||
A,
|
A,
|
||||||
Chip,
|
Chip,
|
||||||
@ -63,10 +63,10 @@ import Theaters from "@material-symbols/svg-400/rounded/theaters-fill.svg";
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ImageStyle, Platform, View } from "react-native";
|
import { type ImageStyle, Platform, View } from "react-native";
|
||||||
import {
|
import {
|
||||||
Stylable,
|
type Stylable,
|
||||||
Theme,
|
type Theme,
|
||||||
em,
|
em,
|
||||||
max,
|
max,
|
||||||
md,
|
md,
|
||||||
|
@ -18,13 +18,13 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Movie, MovieP, QueryIdentifier, QueryPage } from "@kyoo/models";
|
import { type Movie, MovieP, type QueryIdentifier, type QueryPage } from "@kyoo/models";
|
||||||
|
import { usePageStyle } from "@kyoo/primitives";
|
||||||
import { Platform, ScrollView } from "react-native";
|
import { Platform, ScrollView } from "react-native";
|
||||||
import { useYoshiki } from "yoshiki/native";
|
import { useYoshiki } from "yoshiki/native";
|
||||||
import { DefaultLayout } from "../layout";
|
import { DefaultLayout } from "../layout";
|
||||||
import { Header } from "./header";
|
|
||||||
import { DetailsCollections } from "./collection";
|
import { DetailsCollections } from "./collection";
|
||||||
import { usePageStyle } from "@kyoo/primitives";
|
import { Header } from "./header";
|
||||||
|
|
||||||
const query = (slug: string): QueryIdentifier<Movie> => ({
|
const query = (slug: string): QueryIdentifier<Movie> => ({
|
||||||
parser: MovieP,
|
parser: MovieP,
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Avatar, Link, P, Skeleton, SubP } from "@kyoo/primitives";
|
import { Avatar, Link, P, Skeleton, SubP } from "@kyoo/primitives";
|
||||||
import { Stylable, useYoshiki } from "yoshiki/native";
|
import { type Stylable, useYoshiki } from "yoshiki/native";
|
||||||
|
|
||||||
export const PersonAvatar = ({
|
export const PersonAvatar = ({
|
||||||
slug,
|
slug,
|
||||||
|
@ -19,21 +19,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Episode,
|
type Episode,
|
||||||
EpisodeP,
|
EpisodeP,
|
||||||
QueryIdentifier,
|
type QueryIdentifier,
|
||||||
Season,
|
type Season,
|
||||||
SeasonP,
|
SeasonP,
|
||||||
useInfiniteFetch,
|
useInfiniteFetch,
|
||||||
} from "@kyoo/models";
|
} from "@kyoo/models";
|
||||||
import { Skeleton, H6, HR, P, ts, Menu, IconButton, tooltip, usePageStyle } from "@kyoo/primitives";
|
import { H6, HR, IconButton, Menu, P, Skeleton, tooltip, ts, usePageStyle } from "@kyoo/primitives";
|
||||||
import { rem, useYoshiki } from "yoshiki/native";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import { InfiniteFetch } from "../fetch-infinite";
|
|
||||||
import { episodeDisplayNumber, EpisodeLine } from "./episode";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { ComponentType } from "react";
|
|
||||||
import MenuIcon from "@material-symbols/svg-400/rounded/menu-fill.svg";
|
import MenuIcon from "@material-symbols/svg-400/rounded/menu-fill.svg";
|
||||||
|
import type { ComponentType } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { View } from "react-native";
|
||||||
|
import { rem, useYoshiki } from "yoshiki/native";
|
||||||
|
import { InfiniteFetch } from "../fetch-infinite";
|
||||||
|
import { EpisodeLine, episodeDisplayNumber } from "./episode";
|
||||||
|
|
||||||
type SeasonProcessed = Season & { href: string };
|
type SeasonProcessed = Season & { href: string };
|
||||||
|
|
||||||
|
@ -18,18 +18,24 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { QueryIdentifier, QueryPage, Show, ShowP, ShowWatchStatus } from "@kyoo/models";
|
import {
|
||||||
import { Platform, View, ViewProps } from "react-native";
|
type QueryIdentifier,
|
||||||
import { percent, useYoshiki } from "yoshiki/native";
|
type QueryPage,
|
||||||
import { DefaultLayout } from "../layout";
|
type Show,
|
||||||
import { EpisodeList, SeasonHeader } from "./season";
|
ShowP,
|
||||||
import { Header } from "./header";
|
type ShowWatchStatus,
|
||||||
import Svg, { Path, SvgProps } from "react-native-svg";
|
} from "@kyoo/models";
|
||||||
import { Container, H2, SwitchVariant, focusReset, ts } from "@kyoo/primitives";
|
import { Container, H2, SwitchVariant, focusReset, ts } from "@kyoo/primitives";
|
||||||
import { forwardRef, useState } from "react";
|
import { forwardRef, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Platform, View, type ViewProps } from "react-native";
|
||||||
|
import Svg, { Path, type SvgProps } from "react-native-svg";
|
||||||
|
import { percent, useYoshiki } from "yoshiki/native";
|
||||||
|
import { DefaultLayout } from "../layout";
|
||||||
import { DetailsCollections } from "./collection";
|
import { DetailsCollections } from "./collection";
|
||||||
import { EpisodeLine, episodeDisplayNumber } from "./episode";
|
import { EpisodeLine, episodeDisplayNumber } from "./episode";
|
||||||
import { useTranslation } from "react-i18next";
|
import { Header } from "./header";
|
||||||
|
import { EpisodeList, SeasonHeader } from "./season";
|
||||||
|
|
||||||
export const SvgWave = (props: SvgProps) => {
|
export const SvgWave = (props: SvgProps) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { WatchInfo, getCurrentApiUrl, queryFn, toQueryKey } from "@kyoo/models";
|
import { type WatchInfo, getCurrentApiUrl, queryFn, toQueryKey } from "@kyoo/models";
|
||||||
import { Player } from "../player";
|
|
||||||
import { getCurrentAccount } from "@kyoo/models/src/account-internal";
|
import { getCurrentAccount } from "@kyoo/models/src/account-internal";
|
||||||
import { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
import { Player } from "../player";
|
||||||
|
|
||||||
export const useDownloader = () => {
|
export const useDownloader = () => {
|
||||||
return async (type: "episode" | "movie", slug: string) => {
|
return async (type: "episode" | "movie", slug: string) => {
|
||||||
|
@ -18,9 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { State, downloadAtom } from "./state";
|
import type { KyooImage } from "@kyoo/models";
|
||||||
import { FlashList } from "@shopify/flash-list";
|
|
||||||
import { ImageStyle, View } from "react-native";
|
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
H6,
|
H6,
|
||||||
@ -34,17 +32,19 @@ import {
|
|||||||
ts,
|
ts,
|
||||||
usePageStyle,
|
usePageStyle,
|
||||||
} from "@kyoo/primitives";
|
} from "@kyoo/primitives";
|
||||||
import { EpisodeLine, displayRuntime, episodeDisplayNumber } from "../details/episode";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { EmptyView } from "../fetch";
|
|
||||||
import { percent, useYoshiki } from "yoshiki/native";
|
|
||||||
import { KyooImage } from "@kyoo/models";
|
|
||||||
import { Atom, useAtomValue } from "jotai";
|
|
||||||
import DownloadForOffline from "@material-symbols/svg-400/rounded/download_for_offline.svg";
|
import DownloadForOffline from "@material-symbols/svg-400/rounded/download_for_offline.svg";
|
||||||
import Downloading from "@material-symbols/svg-400/rounded/downloading.svg";
|
import Downloading from "@material-symbols/svg-400/rounded/downloading.svg";
|
||||||
import Error from "@material-symbols/svg-400/rounded/error.svg";
|
import ErrorIcon from "@material-symbols/svg-400/rounded/error.svg";
|
||||||
import NotStarted from "@material-symbols/svg-400/rounded/not_started.svg";
|
import NotStarted from "@material-symbols/svg-400/rounded/not_started.svg";
|
||||||
|
import { FlashList } from "@shopify/flash-list";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
|
import { type Atom, useAtomValue } from "jotai";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { type ImageStyle, View } from "react-native";
|
||||||
|
import { percent, useYoshiki } from "yoshiki/native";
|
||||||
|
import { EpisodeLine, displayRuntime, episodeDisplayNumber } from "../details/episode";
|
||||||
|
import { EmptyView } from "../fetch";
|
||||||
|
import { type State, downloadAtom } from "./state";
|
||||||
|
|
||||||
const DownloadedItem = ({
|
const DownloadedItem = ({
|
||||||
name,
|
name,
|
||||||
@ -126,6 +126,7 @@ const DownloadedItem = ({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<View {...css({ flexGrow: 1, flexShrink: 1 })}>
|
<View {...css({ flexGrow: 1, flexShrink: 1 })}>
|
||||||
|
{/* biome-ignore lint/a11y/useValidAriaValues: use h6 for style only */}
|
||||||
<H6 aria-level={undefined} {...css([{ flexShrink: 1 }, "title"])}>
|
<H6 aria-level={undefined} {...css([{ flexShrink: 1 }, "title"])}>
|
||||||
{name ?? t("show.episodeNoMetadata")}
|
{name ?? t("show.episodeNoMetadata")}
|
||||||
</H6>
|
</H6>
|
||||||
@ -193,10 +194,9 @@ const downloadIcon = (status: State["status"]) => {
|
|||||||
case "DOWNLOADING":
|
case "DOWNLOADING":
|
||||||
return Downloading;
|
return Downloading;
|
||||||
case "FAILED":
|
case "FAILED":
|
||||||
return Error;
|
return ErrorIcon;
|
||||||
case "PAUSED":
|
case "PAUSED":
|
||||||
case "STOPPED":
|
case "STOPPED":
|
||||||
default:
|
|
||||||
return NotStarted;
|
return NotStarted;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -21,27 +21,27 @@
|
|||||||
import RNBackgroundDownloader, {
|
import RNBackgroundDownloader, {
|
||||||
type DownloadTask,
|
type DownloadTask,
|
||||||
} from "@kesha-antonov/react-native-background-downloader";
|
} from "@kesha-antonov/react-native-background-downloader";
|
||||||
import { deleteAsync } from "expo-file-system";
|
|
||||||
import {
|
import {
|
||||||
Account,
|
type Account,
|
||||||
Episode,
|
type Episode,
|
||||||
EpisodeP,
|
EpisodeP,
|
||||||
Movie,
|
type Movie,
|
||||||
MovieP,
|
MovieP,
|
||||||
QueryIdentifier,
|
type QueryIdentifier,
|
||||||
WatchInfo,
|
type WatchInfo,
|
||||||
WatchInfoP,
|
WatchInfoP,
|
||||||
queryFn,
|
queryFn,
|
||||||
toQueryKey,
|
toQueryKey,
|
||||||
} from "@kyoo/models";
|
} from "@kyoo/models";
|
||||||
import { Player } from "../player";
|
|
||||||
import { atom, useSetAtom, PrimitiveAtom, useStore } from "jotai";
|
|
||||||
import { getCurrentAccount, storage } from "@kyoo/models/src/account-internal";
|
import { getCurrentAccount, storage } from "@kyoo/models/src/account-internal";
|
||||||
import { ReactNode, useEffect } from "react";
|
import { type QueryClient, useQueryClient } from "@tanstack/react-query";
|
||||||
import { Platform, ToastAndroid } from "react-native";
|
import { deleteAsync } from "expo-file-system";
|
||||||
import { QueryClient, useQueryClient } from "@tanstack/react-query";
|
import type { Router } from "expo-router/build/types";
|
||||||
import { Router } from "expo-router/build/types";
|
import { type PrimitiveAtom, atom, useSetAtom, useStore } from "jotai";
|
||||||
|
import { type ReactNode, useEffect } from "react";
|
||||||
|
import { ToastAndroid } from "react-native";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { Player } from "../player";
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
status: "DOWNLOADING" | "PAUSED" | "DONE" | "FAILED" | "STOPPED";
|
status: "DOWNLOADING" | "PAUSED" | "DONE" | "FAILED" | "STOPPED";
|
||||||
@ -257,7 +257,7 @@ export const DownloadProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
const dls: { data: Episode | Movie; info: WatchInfo; path: string; state: State }[] =
|
const dls: { data: Episode | Movie; info: WatchInfo; path: string; state: State }[] =
|
||||||
JSON.parse(storage.getString("downloads") ?? "[]");
|
JSON.parse(storage.getString("downloads") ?? "[]");
|
||||||
const downloads = dls.map((dl) => {
|
const downloads = dls.map((dl) => {
|
||||||
const t = tasks.find((x) => x.id == dl.data.id);
|
const t = tasks.find((x) => x.id === dl.data.id);
|
||||||
if (t) return setupDownloadTask(dl, t, store, queryClient);
|
if (t) return setupDownloadTask(dl, t, store, queryClient);
|
||||||
|
|
||||||
const stateAtom = atom({
|
const stateAtom = atom({
|
||||||
|
@ -20,13 +20,13 @@
|
|||||||
|
|
||||||
import { ConnectionErrorContext, useAccount } from "@kyoo/models";
|
import { ConnectionErrorContext, useAccount } from "@kyoo/models";
|
||||||
import { Button, H1, Icon, Link, P, ts } from "@kyoo/primitives";
|
import { Button, H1, Icon, Link, P, ts } from "@kyoo/primitives";
|
||||||
import { useRouter } from "solito/router";
|
import Register from "@material-symbols/svg-400/rounded/app_registration.svg";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
|
import { useRouter } from "solito/router";
|
||||||
import { useYoshiki } from "yoshiki/native";
|
import { useYoshiki } from "yoshiki/native";
|
||||||
import { DefaultLayout } from "../layout";
|
import { DefaultLayout } from "../layout";
|
||||||
import Register from "@material-symbols/svg-400/rounded/app_registration.svg";
|
|
||||||
|
|
||||||
export const ConnectionError = () => {
|
export const ConnectionError = () => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
@ -35,7 +35,7 @@ export const ConnectionError = () => {
|
|||||||
const { error, retry } = useContext(ConnectionErrorContext);
|
const { error, retry } = useContext(ConnectionErrorContext);
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
|
|
||||||
if (error && (error.status === 401 || error.status == 403)) {
|
if (error && (error.status === 401 || error.status === 403)) {
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ConnectionErrorContext, KyooErrors } from "@kyoo/models";
|
import { ConnectionErrorContext, type KyooErrors } from "@kyoo/models";
|
||||||
import { P } from "@kyoo/primitives";
|
import { P } from "@kyoo/primitives";
|
||||||
import { useContext, useLayoutEffect } from "react";
|
import { useContext, useLayoutEffect } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
@ -36,7 +36,7 @@ export const ErrorView = ({
|
|||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
// if this is a permission error, make it go up the tree to have a whole page login screen.
|
// if this is a permission error, make it go up the tree to have a whole page login screen.
|
||||||
if (!noBubble && (error.status === 401 || error.status == 403)) setError(error);
|
if (!noBubble && (error.status === 401 || error.status === 403)) setError(error);
|
||||||
}, [error, noBubble, setError]);
|
}, [error, noBubble, setError]);
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return (
|
return (
|
||||||
|
@ -18,13 +18,19 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
|
import { type Page, type QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
|
||||||
import { useBreakpointMap, HR } from "@kyoo/primitives";
|
import { HR, useBreakpointMap } from "@kyoo/primitives";
|
||||||
import { ContentStyle, FlashList } from "@shopify/flash-list";
|
import { type ContentStyle, FlashList } from "@shopify/flash-list";
|
||||||
import { ComponentProps, ComponentType, isValidElement, ReactElement, useRef } from "react";
|
import {
|
||||||
import { EmptyView, Layout, OfflineView, WithLoading, addHeader } from "./fetch";
|
type ComponentProps,
|
||||||
|
type ComponentType,
|
||||||
|
type ReactElement,
|
||||||
|
isValidElement,
|
||||||
|
useRef,
|
||||||
|
} from "react";
|
||||||
|
import { FlatList, View, type ViewStyle } from "react-native";
|
||||||
import { ErrorView } from "./errors";
|
import { ErrorView } from "./errors";
|
||||||
import { FlatList, View, ViewStyle } from "react-native";
|
import { EmptyView, type Layout, OfflineView, type WithLoading, addHeader } from "./fetch";
|
||||||
|
|
||||||
const emulateGap = (
|
const emulateGap = (
|
||||||
layout: "grid" | "vertical" | "horizontal",
|
layout: "grid" | "vertical" | "horizontal",
|
||||||
|
@ -18,22 +18,22 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
|
import { type Page, type QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
|
||||||
import { HR } from "@kyoo/primitives";
|
import { HR } from "@kyoo/primitives";
|
||||||
|
import type { ContentStyle } from "@shopify/flash-list";
|
||||||
import {
|
import {
|
||||||
ComponentProps,
|
type ComponentProps,
|
||||||
ComponentType,
|
type ComponentType,
|
||||||
Fragment,
|
Fragment,
|
||||||
|
type ReactElement,
|
||||||
isValidElement,
|
isValidElement,
|
||||||
ReactElement,
|
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { Stylable, nativeStyleToCss, useYoshiki, ysMap } from "yoshiki";
|
import { type Stylable, nativeStyleToCss, useYoshiki, ysMap } from "yoshiki";
|
||||||
import { EmptyView, Layout, WithLoading, addHeader } from "./fetch";
|
|
||||||
import { ErrorView } from "./errors";
|
import { ErrorView } from "./errors";
|
||||||
import type { ContentStyle } from "@shopify/flash-list";
|
import { EmptyView, type Layout, type WithLoading, addHeader } from "./fetch";
|
||||||
|
|
||||||
const InfiniteScroll = <Props,>({
|
const InfiniteScroll = <Props,>({
|
||||||
children,
|
children,
|
||||||
@ -78,6 +78,7 @@ const InfiniteScroll = <Props,>({
|
|||||||
|
|
||||||
// Automatically trigger a scroll check on start and after a fetch end in case the user is already
|
// Automatically trigger a scroll check on start and after a fetch end in case the user is already
|
||||||
// at the bottom of the page or if there is no scroll bar (ultrawide or something like that)
|
// at the bottom of the page or if there is no scroll bar (ultrawide or something like that)
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: Check for scroll pause after fetch ends
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onScroll();
|
onScroll();
|
||||||
}, [isFetching, onScroll]);
|
}, [isFetching, onScroll]);
|
||||||
@ -92,13 +93,13 @@ const InfiniteScroll = <Props,>({
|
|||||||
// the as any is due to differencies between css types of native and web (already accounted for in yoshiki)
|
// the as any is due to differencies between css types of native and web (already accounted for in yoshiki)
|
||||||
gridGap: layout.gap as any,
|
gridGap: layout.gap as any,
|
||||||
},
|
},
|
||||||
layout.layout == "vertical" && {
|
layout.layout === "vertical" && {
|
||||||
gridTemplateColumns: "1fr",
|
gridTemplateColumns: "1fr",
|
||||||
alignItems: "stretch",
|
alignItems: "stretch",
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
paddingY: layout.gap as any,
|
paddingY: layout.gap as any,
|
||||||
},
|
},
|
||||||
layout.layout == "horizontal" && {
|
layout.layout === "horizontal" && {
|
||||||
alignItems: "stretch",
|
alignItems: "stretch",
|
||||||
overflowX: "auto",
|
overflowX: "auto",
|
||||||
overflowY: "hidden",
|
overflowY: "hidden",
|
||||||
|
@ -18,9 +18,9 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Page, QueryIdentifier, useFetch } from "@kyoo/models";
|
import { type Page, type QueryIdentifier, useFetch } from "@kyoo/models";
|
||||||
import { Breakpoint, P } from "@kyoo/primitives";
|
import { type Breakpoint, P } from "@kyoo/primitives";
|
||||||
import { ComponentType, ReactElement, isValidElement } from "react";
|
import { type ComponentType, type ReactElement, isValidElement } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { useYoshiki } from "yoshiki/native";
|
import { useYoshiki } from "yoshiki/native";
|
||||||
@ -37,7 +37,7 @@ export type WithLoading<Item> =
|
|||||||
| (Item & { isLoading: false })
|
| (Item & { isLoading: false })
|
||||||
| (Partial<Item> & { isLoading: true });
|
| (Partial<Item> & { isLoading: true });
|
||||||
|
|
||||||
const isPage = <T = unknown,>(obj: unknown): obj is Page<T> =>
|
const isPage = <T = unknown>(obj: unknown): obj is Page<T> =>
|
||||||
(typeof obj === "object" && obj && "items" in obj) || false;
|
(typeof obj === "object" && obj && "items" in obj) || false;
|
||||||
|
|
||||||
export const Fetch = <Data,>({
|
export const Fetch = <Data,>({
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user