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:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
- name: Setup Biome
|
||||
uses: biomejs/setup-biome@v2
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: yarn
|
||||
cache-dependency-path: front/yarn.lock
|
||||
version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint && yarn format
|
||||
- name: Run Biome
|
||||
run: biome ci .
|
||||
|
||||
scanner:
|
||||
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",
|
||||
userInterfaceStyle: "automatic",
|
||||
splash,
|
||||
updates: {
|
||||
fallbackToCacheTimeout: 0,
|
||||
},
|
||||
assetBundlePatterns: ["**/*"],
|
||||
ios: {
|
||||
supportsTablet: true,
|
||||
@ -59,6 +56,7 @@ const config = {
|
||||
},
|
||||
updates: {
|
||||
url: "https://u.expo.dev/55de6b52-c649-4a15-9a45-569ff5ed036c",
|
||||
fallbackToCacheTimeout: 0,
|
||||
},
|
||||
runtimeVersion: {
|
||||
policy: "sdkVersion",
|
||||
|
@ -19,10 +19,10 @@
|
||||
*/
|
||||
|
||||
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 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() {
|
||||
return (
|
||||
|
@ -18,7 +18,7 @@
|
||||
* 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 { Redirect, Stack } from "expo-router";
|
||||
import { useContext, useRef } from "react";
|
||||
@ -30,7 +30,7 @@ export default function PublicLayout() {
|
||||
const { error } = useContext(ConnectionErrorContext);
|
||||
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;
|
||||
|
||||
return (
|
||||
|
@ -20,39 +20,39 @@
|
||||
|
||||
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 {
|
||||
useFonts,
|
||||
Poppins_300Light,
|
||||
Poppins_400Regular,
|
||||
Poppins_900Black,
|
||||
useFonts,
|
||||
} 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/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.
|
||||
import en from "../../../translations/en.json";
|
||||
import fr from "../../../translations/fr.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) => {
|
||||
return NetInfo.addEventListener((state) => {
|
||||
|
@ -18,14 +18,14 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Stack, useLocalSearchParams } from "expo-router";
|
||||
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 { type QueryPage, useHasPermission } from "@kyoo/models";
|
||||
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 = () => {
|
||||
useEffect(() => {
|
||||
|
@ -19,7 +19,7 @@
|
||||
*/
|
||||
|
||||
const { getDefaultConfig } = require("expo/metro-config");
|
||||
const path = require("path");
|
||||
const path = require("node:path");
|
||||
|
||||
const projectRoot = __dirname;
|
||||
const defaultConfig = getDefaultConfig(projectRoot);
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
},
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
* 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 DefinePlugin = require("webpack").DefinePlugin;
|
||||
|
||||
|
@ -2,9 +2,7 @@
|
||||
"name": "web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"sideEffects": [
|
||||
"./src/polyfill.ts"
|
||||
],
|
||||
"sideEffects": ["./src/polyfill.ts"],
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
@ -56,8 +54,6 @@
|
||||
"@types/react": "18.2.48",
|
||||
"@types/react-dom": "18.2.18",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-next": "14.1.0",
|
||||
"react-native": "0.73.2",
|
||||
"typescript": "^5.3.3",
|
||||
"webpack": "^5.90.0"
|
||||
|
@ -19,7 +19,7 @@
|
||||
*/
|
||||
|
||||
import "i18next";
|
||||
import en from "../../../translations/en.json";
|
||||
import type en from "../../../translations/en.json";
|
||||
|
||||
declare module "i18next" {
|
||||
interface CustomTypeOptions {
|
||||
|
@ -18,10 +18,10 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ComponentType, useMemo } from "react";
|
||||
import i18next, { InitOptions } from "i18next";
|
||||
import i18next, { type InitOptions } from "i18next";
|
||||
import type { AppContext, AppInitialProps, AppProps } from "next/app";
|
||||
import { type ComponentType, useMemo } from "react";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { AppContext, AppInitialProps, type AppProps } from "next/app";
|
||||
|
||||
import en from "../../../translations/en.json";
|
||||
import fr from "../../../translations/fr.json";
|
||||
@ -40,18 +40,15 @@ export const withTranslations = (
|
||||
};
|
||||
|
||||
const AppWithTranslations = (props: AppProps) => {
|
||||
const li18n = useMemo(
|
||||
() =>
|
||||
typeof window === "undefined"
|
||||
? i18n
|
||||
: (i18next.init({
|
||||
const li18n = useMemo(() => {
|
||||
if (typeof window === "undefined") return i18n;
|
||||
i18next.init({
|
||||
...commonOptions,
|
||||
lng: props.pageProps.__lang,
|
||||
resources: props.pageProps.__resources,
|
||||
}),
|
||||
i18next),
|
||||
[props.pageProps.__lang, props.pageProps.__resources],
|
||||
);
|
||||
});
|
||||
return i18next;
|
||||
}, [props.pageProps.__lang, props.pageProps.__resources]);
|
||||
|
||||
return (
|
||||
<I18nextProvider i18n={li18n}>
|
||||
|
@ -20,42 +20,42 @@
|
||||
|
||||
import "../polyfill";
|
||||
|
||||
import { HydrationBoundary, QueryClientProvider, dehydrate } from "@tanstack/react-query";
|
||||
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 { PortalProvider } from "@gorhom/portal";
|
||||
import {
|
||||
AccountP,
|
||||
AccountProvider,
|
||||
ConnectionErrorContext,
|
||||
type QueryIdentifier,
|
||||
type QueryPage,
|
||||
ServerInfoP,
|
||||
UserP,
|
||||
createQueryClient,
|
||||
fetchQuery,
|
||||
getTokenWJ,
|
||||
QueryIdentifier,
|
||||
QueryPage,
|
||||
ServerInfoP,
|
||||
setSsrApiUrl,
|
||||
UserP,
|
||||
useUserTheme,
|
||||
} 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 { 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 { 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" });
|
||||
|
||||
@ -114,7 +114,6 @@ const GlobalCssTheme = () => {
|
||||
|
||||
const YoshikiDebug = ({ children }: { children: JSX.Element }) => {
|
||||
if (typeof window === "undefined") return children;
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const registry = useStyleRegistry();
|
||||
return <StyleRegistryProvider registry={registry}>{children}</StyleRegistryProvider>;
|
||||
};
|
||||
|
@ -18,9 +18,9 @@
|
||||
* 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 { Html, Main, Head, NextScript, DocumentContext } from "next/document";
|
||||
import { createStyleRegistry, StyleRegistryProvider } from "yoshiki/web";
|
||||
import { StyleRegistryProvider, createStyleRegistry } from "yoshiki/web";
|
||||
|
||||
export const style = `
|
||||
/**
|
||||
|
@ -18,10 +18,10 @@
|
||||
* 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 { useRouter } from "next/router";
|
||||
import { ComponentType } from "react";
|
||||
import type { ComponentType } from "react";
|
||||
|
||||
export const withRoute = <Props,>(
|
||||
Component: ComponentType<Props>,
|
||||
|
@ -16,9 +16,9 @@
|
||||
"incremental": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["src/*"],
|
||||
},
|
||||
"~/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"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:dev": "yarn workspace mobile build:dev",
|
||||
"update": "yarn workspace mobile update",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier -c .",
|
||||
"format:fix": "prettier -w ."
|
||||
},
|
||||
"eslintIgnore": [
|
||||
"next-env.d.ts"
|
||||
],
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
],
|
||||
"prettier": {
|
||||
"useTabs": true,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "all"
|
||||
"lint": "biome lint .",
|
||||
"format": "biome format .",
|
||||
"format:fix": "biome format . --write"
|
||||
},
|
||||
"workspaces": ["apps/*", "packages/*"],
|
||||
"devDependencies": {
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-next": "14.1.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-header": "^3.1.1",
|
||||
"prettier": "^3.2.4",
|
||||
"@biomejs/biome": "1.7.3",
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
"packageManager": "yarn@3.2.4"
|
||||
|
@ -18,10 +18,10 @@
|
||||
* 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 { MMKV } from "react-native-mmkv";
|
||||
import { type ZodTypeAny, z } from "zod";
|
||||
import { type Account, AccountP } from "./accounts";
|
||||
|
||||
export const storage = new MMKV();
|
||||
|
||||
@ -49,8 +49,8 @@ export const setCookie = (key: string, val?: unknown) => {
|
||||
const d = new Date();
|
||||
// A year
|
||||
d.setTime(d.getTime() + 365 * 24 * 60 * 60 * 1000);
|
||||
const expires = value ? "expires=" + d.toUTCString() : "expires=Thu, 01 Jan 1970 00:00:01 GMT";
|
||||
document.cookie = key + "=" + value + ";" + expires + ";path=/;samesite=strict";
|
||||
const expires = value ? `expires=${d.toUTCString()}` : "expires=Thu, 01 Jan 1970 00:00:01 GMT";
|
||||
document.cookie = `${key}=${value};${expires};path=/;samesite=strict`;
|
||||
return null;
|
||||
};
|
||||
|
||||
@ -65,10 +65,10 @@ export const readCookie = <T extends ZodTypeAny>(
|
||||
const ca = decodedCookie.split(";");
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) == " ") {
|
||||
while (c.charAt(0) === " ") {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
if (c.indexOf(name) === 0) {
|
||||
const str = c.substring(name.length, c.length);
|
||||
return parser ? parser.parse(JSON.parse(str)) : str;
|
||||
}
|
||||
@ -85,7 +85,7 @@ export const addAccount = (account: Account) => {
|
||||
const accounts = readAccounts();
|
||||
|
||||
// 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);
|
||||
return;
|
||||
}
|
||||
@ -106,7 +106,7 @@ export const removeAccounts = (filter: (acc: Account) => boolean) => {
|
||||
|
||||
export const updateAccount = (id: string, account: Account) => {
|
||||
const accounts = readAccounts();
|
||||
const idx = accounts.findIndex((x) => x.id == id);
|
||||
const idx = accounts.findIndex((x) => x.id === id);
|
||||
if (idx === -1) return;
|
||||
|
||||
const selected = account.selected;
|
||||
|
@ -18,17 +18,25 @@
|
||||
* 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 { 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 { KyooErrors } from "./kyoo-errors";
|
||||
import { ServerInfoP, type User, UserP } from "./resources";
|
||||
import { zdate } from "./utils";
|
||||
|
||||
export const TokenP = z.object({
|
||||
token_type: z.literal("Bearer"),
|
||||
@ -72,7 +80,6 @@ export const ConnectionErrorContext = createContext<{
|
||||
setError: (error: KyooErrors) => void;
|
||||
}>({ error: null, loading: true, setError: () => {} });
|
||||
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
export const AccountProvider = ({
|
||||
children,
|
||||
ssrAccount,
|
||||
@ -115,7 +122,7 @@ export const AccountProvider = ({
|
||||
acc?.map((account) => ({
|
||||
...account,
|
||||
select: () => updateAccount(account.id, { ...account, selected: true }),
|
||||
remove: () => removeAccounts((x) => x.id == x.id),
|
||||
remove: () => removeAccounts((x) => x.id === account.id),
|
||||
})) ?? [],
|
||||
[acc],
|
||||
);
|
||||
@ -151,6 +158,7 @@ export const AccountProvider = ({
|
||||
useEffect(() => {
|
||||
// if the user change account (or connect/disconnect), reset query cache.
|
||||
if (
|
||||
// biome-ignore lint/suspicious/noDoubleEquals: id can be an id, null or undefined
|
||||
selected?.id != oldSelected.current?.id ||
|
||||
(userIsError && selected?.token.access_token !== oldSelected.current?.token)
|
||||
) {
|
||||
|
@ -18,13 +18,13 @@
|
||||
* 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 { 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> =
|
||||
| { ok: true; value: A; error?: undefined }
|
||||
@ -94,7 +94,7 @@ let running: ReturnType<typeof getTokenWJ> | null = null;
|
||||
|
||||
export const getTokenWJ = async (
|
||||
acc?: Account | null,
|
||||
forceRefresh: boolean = false,
|
||||
forceRefresh = false,
|
||||
): Promise<readonly [string, Token, null] | readonly [null, null, KyooErrors | null]> => {
|
||||
if (acc === undefined) acc = getCurrentAccount();
|
||||
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,
|
||||
);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Refresh token when account change
|
||||
useEffect(() => {
|
||||
async function run() {
|
||||
const nToken = await getTokenWJ();
|
||||
|
@ -18,18 +18,18 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ComponentType, ReactElement } from "react";
|
||||
import {
|
||||
QueryClient,
|
||||
QueryFunctionContext,
|
||||
type QueryFunctionContext,
|
||||
useInfiniteQuery,
|
||||
useQuery,
|
||||
} from "@tanstack/react-query";
|
||||
import { z } from "zod";
|
||||
import { KyooErrors } from "./kyoo-errors";
|
||||
import { Page, Paged } from "./page";
|
||||
import { getToken, getTokenWJ } from "./login";
|
||||
import type { ComponentType, ReactElement } from "react";
|
||||
import type { z } from "zod";
|
||||
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!;
|
||||
|
||||
@ -66,7 +66,7 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
|
||||
.join("/")
|
||||
.replace("//", "/")
|
||||
.replace("/?", "?");
|
||||
let resp;
|
||||
let resp: Response;
|
||||
try {
|
||||
resp = await fetch(path, {
|
||||
method: context.method,
|
||||
@ -97,11 +97,11 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
|
||||
if (resp.status === 403 && iToken === undefined && token) {
|
||||
const [newToken, _, error] = await getTokenWJ(undefined, true);
|
||||
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) {
|
||||
const error = await resp.text();
|
||||
let data;
|
||||
let data: Record<string, any>;
|
||||
try {
|
||||
data = JSON.parse(error);
|
||||
} 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;
|
||||
|
||||
let data;
|
||||
let data: Record<string, any>;
|
||||
try {
|
||||
data = await resp.json();
|
||||
} catch (e) {
|
||||
@ -204,11 +204,10 @@ export const toQueryKey = (query: {
|
||||
query.options?.apiUrl,
|
||||
...query.path,
|
||||
query.params
|
||||
? "?" +
|
||||
Object.entries(query.params)
|
||||
? `?${Object.entries(query.params)
|
||||
.filter(([_, v]) => v !== undefined)
|
||||
.map(([k, v]) => `${k}=${Array.isArray(v) ? v.join(",") : v}`)
|
||||
.join("&")
|
||||
.join("&")}`
|
||||
: null,
|
||||
].filter((x) => x);
|
||||
};
|
||||
@ -267,12 +266,11 @@ export const fetchQuery = async (queries: QueryIdentifier[], authToken?: string
|
||||
queryFn: (ctx) => queryFn(ctx, Paged(query.parser), authToken),
|
||||
initialPageParam: undefined,
|
||||
});
|
||||
} else {
|
||||
}
|
||||
return client.prefetchQuery({
|
||||
queryKey: toQueryKey(query),
|
||||
queryFn: (ctx) => queryFn(ctx, query.parser, authToken),
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
return client;
|
||||
|
@ -19,9 +19,9 @@
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { zdate } from "../utils";
|
||||
import { ImagesP, imageFn } from "../traits";
|
||||
import { ResourceP } from "../traits/resource";
|
||||
import { zdate } from "../utils";
|
||||
|
||||
export const BaseEpisodeP = ResourceP("episode")
|
||||
.merge(ImagesP)
|
||||
|
@ -19,8 +19,8 @@
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { ShowP } from "./show";
|
||||
import { BaseEpisodeP } from "./episode.base";
|
||||
import { ShowP } from "./show";
|
||||
import { WatchStatusP } from "./watch-status";
|
||||
|
||||
export const EpisodeP = BaseEpisodeP.and(
|
||||
|
@ -19,13 +19,13 @@
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { zdate } from "../utils";
|
||||
import { ImagesP, ResourceP, imageFn } from "../traits";
|
||||
import { Genre } from "./genre";
|
||||
import { StudioP } from "./studio";
|
||||
import { Status } from "./show";
|
||||
import { zdate } from "../utils";
|
||||
import { CollectionP } from "./collection";
|
||||
import { Genre } from "./genre";
|
||||
import { MetadataP } from "./metadata";
|
||||
import { Status } from "./show";
|
||||
import { StudioP } from "./studio";
|
||||
import { WatchStatusP } from "./watch-status";
|
||||
|
||||
export const MovieP = ResourceP("movie")
|
||||
|
@ -19,8 +19,8 @@
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { MovieP } from "./movie";
|
||||
import { EpisodeP } from "./episode";
|
||||
import { MovieP } from "./movie";
|
||||
|
||||
export const NewsP = z.union([
|
||||
/*
|
||||
|
@ -19,8 +19,8 @@
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { zdate } from "../utils";
|
||||
import { ImagesP, ResourceP } from "../traits";
|
||||
import { zdate } from "../utils";
|
||||
|
||||
export const SeasonP = ResourceP("season").merge(ImagesP).extend({
|
||||
/**
|
||||
|
@ -18,9 +18,9 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Platform } from "react-native";
|
||||
import { z } from "zod";
|
||||
import { imageFn } from "..";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
export const OidcInfoP = z.object({
|
||||
/*
|
||||
|
@ -19,12 +19,12 @@
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { zdate } from "../utils";
|
||||
import { ImagesP, ResourceP } from "../traits";
|
||||
import { Genre } from "./genre";
|
||||
import { StudioP } from "./studio";
|
||||
import { zdate } from "../utils";
|
||||
import { BaseEpisodeP } from "./episode.base";
|
||||
import { Genre } from "./genre";
|
||||
import { MetadataP } from "./metadata";
|
||||
import { StudioP } from "./studio";
|
||||
import { ShowWatchStatusP } from "./watch-status";
|
||||
|
||||
/**
|
||||
|
@ -19,8 +19,8 @@
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { ResourceP } from "../traits/resource";
|
||||
import { imageFn } from "../traits/images";
|
||||
import { ResourceP } from "../traits/resource";
|
||||
|
||||
export const UserP = ResourceP("user")
|
||||
.extend({
|
||||
|
@ -18,9 +18,9 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import i18next from "i18next";
|
||||
import { z } from "zod";
|
||||
import { imageFn } from "../traits";
|
||||
import i18next from "i18next";
|
||||
import { QualityP } from "./quality";
|
||||
|
||||
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
|
||||
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.
|
||||
// biome-ignore lint: same as above
|
||||
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/>.
|
||||
*/
|
||||
|
||||
import { Platform } from "react-native";
|
||||
import { useMMKVString } from "react-native-mmkv";
|
||||
import { setCookie, storage } from "./account-internal";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
export const useUserTheme = (ssrTheme?: "light" | "dark" | "auto") => {
|
||||
if (Platform.OS === "web" && typeof window === "undefined" && ssrTheme) return ssrTheme;
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const [value] = useMMKVString("theme", storage);
|
||||
if (!value) return "auto";
|
||||
return value as "light" | "dark" | "auto";
|
||||
|
@ -19,10 +19,10 @@
|
||||
*/
|
||||
|
||||
import { Platform } from "react-native";
|
||||
import { Movie, Show } from "./resources";
|
||||
import { z } from "zod";
|
||||
import { useMMKVString } from "react-native-mmkv";
|
||||
import { z } from "zod";
|
||||
import { storage } from "./account-internal";
|
||||
import type { Movie, Show } from "./resources";
|
||||
|
||||
export const zdate = z.coerce.date;
|
||||
|
||||
@ -38,7 +38,8 @@ export const getDisplayDate = (data: Show | Movie) => {
|
||||
return startAir.getFullYear().toString();
|
||||
}
|
||||
return startAir.getFullYear() + (endAir ? ` - ${endAir.getFullYear()}` : "");
|
||||
} else if (airDate) {
|
||||
}
|
||||
if (airDate) {
|
||||
return airDate.getFullYear().toString();
|
||||
}
|
||||
};
|
||||
|
@ -18,9 +18,9 @@
|
||||
"incremental": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["src/*"],
|
||||
},
|
||||
"~/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"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
|
||||
|
||||
import { Alert as RNAlert, type AlertOptions, type AlertButton } from "react-native";
|
||||
import { type SweetAlertIcon } from "sweetalert2";
|
||||
import { type AlertButton, type AlertOptions, Alert as RNAlert } from "react-native";
|
||||
import type { SweetAlertIcon } from "sweetalert2";
|
||||
|
||||
export interface ExtendedAlertStatic {
|
||||
alert: (
|
||||
|
@ -20,9 +20,10 @@
|
||||
|
||||
// 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";
|
||||
|
||||
// biome-ignore lint/complexity/noStaticOnlyClass: Compatibility with rn
|
||||
export class Alert {
|
||||
static alert(
|
||||
title: string,
|
||||
|
@ -18,23 +18,22 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { View, ViewStyle, Image, ImageProps } from "react-native";
|
||||
import { useYoshiki, px, Stylable } from "yoshiki/native";
|
||||
import AccountCircle from "@material-symbols/svg-400/rounded/account_circle-fill.svg";
|
||||
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 { 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) => {
|
||||
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);
|
||||
}
|
||||
|
||||
let color = "#";
|
||||
for (i = 0; i < 3; i += 1) {
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
const value = (hash >> (i * 8)) & 0xff;
|
||||
color += `00${value.toString(16)}`.slice(-2);
|
||||
}
|
||||
|
@ -18,12 +18,12 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ComponentType, ForwardedRef, ReactElement, forwardRef } from "react";
|
||||
import { Theme, useYoshiki } from "yoshiki/native";
|
||||
import { type ComponentType, type ForwardedRef, type ReactElement, forwardRef } from "react";
|
||||
import { type Falsy, type PressableProps, View } from "react-native";
|
||||
import { type Theme, useYoshiki } from "yoshiki/native";
|
||||
import { PressableFeedback } from "./links";
|
||||
import { P } from "./text";
|
||||
import { ts } from "./utils";
|
||||
import { Falsy, PressableProps, View } from "react-native";
|
||||
|
||||
export const Button = forwardRef(function Button<AsProps = PressableProps>(
|
||||
{
|
||||
|
@ -18,12 +18,12 @@
|
||||
* 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 { Skeleton } from "./skeleton";
|
||||
import { P } from "./text";
|
||||
import { capitalize, ts } from "./utils";
|
||||
import { TextProps } from "react-native";
|
||||
import { Skeleton } from "./skeleton";
|
||||
|
||||
export const Chip = ({
|
||||
color,
|
||||
@ -49,7 +49,7 @@ export const Chip = ({
|
||||
|
||||
textProps ??= {};
|
||||
|
||||
const sizeMult = size == "medium" ? 1 : size == "small" ? 0.5 : 1.5;
|
||||
const sizeMult = size === "medium" ? 1 : size === "small" ? 0.5 : 1.5;
|
||||
|
||||
return (
|
||||
<Link
|
||||
|
@ -18,11 +18,11 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ComponentType } from "react";
|
||||
import { View, ViewProps } from "react-native";
|
||||
import type { ComponentType } from "react";
|
||||
import { View, type ViewProps } from "react-native";
|
||||
import { percent, px, useYoshiki } from "yoshiki/native";
|
||||
|
||||
export const Container = <AsProps = ViewProps,>({
|
||||
export const Container = <AsProps = ViewProps>({
|
||||
as,
|
||||
...props
|
||||
}: { as?: ComponentType<AsProps> } & AsProps) => {
|
||||
|
@ -19,7 +19,7 @@
|
||||
*/
|
||||
|
||||
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";
|
||||
|
||||
export const HR = ({
|
||||
|
@ -18,14 +18,15 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { ComponentProps, ComponentType, ForwardedRef, forwardRef } from "react";
|
||||
import { Platform, PressableProps } from "react-native";
|
||||
import { SvgProps } from "react-native-svg";
|
||||
import { YoshikiStyle } from "yoshiki";
|
||||
import { px, Stylable, Theme, useYoshiki } from "yoshiki/native";
|
||||
import type React from "react";
|
||||
import { type ComponentProps, type ComponentType, type ForwardedRef, forwardRef } from "react";
|
||||
import { Platform, type PressableProps } from "react-native";
|
||||
import type { SvgProps } from "react-native-svg";
|
||||
import type { YoshikiStyle } from "yoshiki";
|
||||
import { type Stylable, type Theme, px, useYoshiki } from "yoshiki/native";
|
||||
import { PressableFeedback } from "./links";
|
||||
import { Breakpoint, focusReset, ts } from "./utils";
|
||||
import { P } from "./text";
|
||||
import { type Breakpoint, focusReset, ts } from "./utils";
|
||||
|
||||
declare module "react" {
|
||||
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>>,
|
||||
) => {
|
||||
const { css, theme } = useYoshiki();
|
||||
|
@ -18,10 +18,10 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { KyooImage } from "@kyoo/models";
|
||||
import { ReactElement } from "react";
|
||||
import { ImageStyle } from "react-native";
|
||||
import { YoshikiStyle } from "yoshiki/src/type";
|
||||
import type { KyooImage } from "@kyoo/models";
|
||||
import type { ReactElement } from "react";
|
||||
import type { ImageStyle } from "react-native";
|
||||
import type { YoshikiStyle } from "yoshiki/src/type";
|
||||
|
||||
export type YoshikiEnhanced<Style> = Style extends any
|
||||
? {
|
||||
@ -33,7 +33,7 @@ export type Props = {
|
||||
src?: KyooImage | null;
|
||||
quality: "low" | "medium" | "high";
|
||||
alt?: string;
|
||||
Error?: ReactElement | null;
|
||||
Err?: ReactElement | null;
|
||||
forcedLoading?: boolean;
|
||||
};
|
||||
|
||||
|
@ -18,10 +18,10 @@
|
||||
* 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 { Blurhash } from "react-native-blurhash";
|
||||
import { Stylable, useYoshiki } from "yoshiki/native";
|
||||
import { type Stylable, useYoshiki } from "yoshiki/native";
|
||||
|
||||
export const BlurhashContainer = ({
|
||||
blurhash,
|
||||
|
@ -20,19 +20,16 @@
|
||||
|
||||
import { decode } from "blurhash";
|
||||
import {
|
||||
HTMLAttributes,
|
||||
ReactElement,
|
||||
createElement,
|
||||
type HTMLAttributes,
|
||||
type ReactElement,
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useYoshiki } from "yoshiki";
|
||||
import { Stylable, nativeStyleToCss } from "yoshiki/native";
|
||||
import { StyleList, processStyleList } from "yoshiki/src/type";
|
||||
import { nativeStyleToCss } from "yoshiki/native";
|
||||
|
||||
// The blurhashToUrl has been stolen from https://gist.github.com/mattiaz9/53cb67040fa135cb395b1d015a200aff
|
||||
export function blurHashToDataURL(hash: string | undefined): string | undefined {
|
||||
@ -53,7 +50,7 @@ function parsePixels(pixels: Uint8ClampedArray, width: number, height: number) {
|
||||
typeof Buffer !== "undefined"
|
||||
? Buffer.from(getPngArray(pngString)).toString("base64")
|
||||
: btoa(pngString);
|
||||
return "data:image/png;base64," + dataURL;
|
||||
return `data:image/png;base64,${dataURL}`;
|
||||
}
|
||||
|
||||
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 NO_FILTER = String.fromCharCode(0);
|
||||
|
||||
// biome-ignore lint: not gonna fix stackowerflow code that works
|
||||
let n, c, k;
|
||||
|
||||
// make crc table
|
||||
@ -89,7 +87,9 @@ function generatePng(width: number, height: number, rgbaString: string) {
|
||||
function inflateStore(data: string) {
|
||||
const MAX_STORE_LENGTH = 65535;
|
||||
let storeBuffer = "";
|
||||
// biome-ignore lint: not gonna fix stackowerflow code that works
|
||||
let remaining;
|
||||
// biome-ignore lint: not gonna fix stackowerflow code that works
|
||||
let blockType;
|
||||
|
||||
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) {
|
||||
let MOD_ADLER = 65521;
|
||||
const MOD_ADLER = 65521;
|
||||
let a = 1;
|
||||
let b = 0;
|
||||
|
||||
@ -179,7 +179,7 @@ function generatePng(width: number, height: number, rgbaString: string) {
|
||||
const IHDR = createIHDR(width, height);
|
||||
|
||||
let scanlines = "";
|
||||
let scanline;
|
||||
let scanline: string;
|
||||
|
||||
for (let y = 0; y < rgbaString.length; y += width * 4) {
|
||||
scanline = NO_FILTER;
|
||||
|
@ -18,14 +18,14 @@
|
||||
* 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 { 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 = ({
|
||||
src,
|
||||
@ -33,7 +33,7 @@ export const Image = ({
|
||||
alt,
|
||||
forcedLoading = false,
|
||||
layout,
|
||||
Error,
|
||||
Err,
|
||||
...props
|
||||
}: Props & { style?: ImageStyle } & { layout: ImageLayout }) => {
|
||||
const { css } = useYoshiki();
|
||||
@ -53,8 +53,8 @@ export const Image = ({
|
||||
|
||||
if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />;
|
||||
if (!src || state === "errored") {
|
||||
return Error !== undefined ? (
|
||||
Error
|
||||
return Err !== undefined ? (
|
||||
Err
|
||||
) : (
|
||||
<View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />
|
||||
);
|
||||
|
@ -18,14 +18,14 @@
|
||||
* 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 { useState } from "react";
|
||||
import { type ImageStyle, View, type ViewStyle } from "react-native";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
import { imageBorderRadius } from "../constants";
|
||||
import { Skeleton } from "../skeleton";
|
||||
import type { ImageLayout, Props } from "./base-image";
|
||||
import { BlurhashContainer } from "./blurhash.web";
|
||||
|
||||
export const Image = ({
|
||||
src,
|
||||
@ -33,7 +33,7 @@ export const Image = ({
|
||||
alt,
|
||||
forcedLoading = false,
|
||||
layout,
|
||||
Error,
|
||||
Err,
|
||||
...props
|
||||
}: Props & { style?: ImageStyle } & { layout: ImageLayout }) => {
|
||||
const { css } = useYoshiki();
|
||||
@ -45,8 +45,8 @@ export const Image = ({
|
||||
|
||||
if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />;
|
||||
if (!src || state === "errored") {
|
||||
return Error !== undefined ? (
|
||||
Error
|
||||
return Err !== undefined ? (
|
||||
Err
|
||||
) : (
|
||||
<View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />
|
||||
);
|
||||
|
@ -18,14 +18,14 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ImageStyle, View, ViewProps, ViewStyle } from "react-native";
|
||||
import { Props, ImageLayout, YoshikiEnhanced } from "./base-image";
|
||||
import { Image } from "./image";
|
||||
import { ComponentProps, ComponentType, ReactNode } from "react";
|
||||
import { LinearGradient, LinearGradientProps } from "expo-linear-gradient";
|
||||
import { ContrastArea } from "../themes";
|
||||
import { LinearGradient, type LinearGradientProps } from "expo-linear-gradient";
|
||||
import type { ComponentProps, ComponentType, ReactNode } from "react";
|
||||
import { type ImageStyle, View, type ViewProps, type ViewStyle } from "react-native";
|
||||
import { percent } from "yoshiki/native";
|
||||
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 { BlurhashContainer } from "./blurhash";
|
||||
@ -55,7 +55,7 @@ export const PosterBackground = ({
|
||||
/>
|
||||
);
|
||||
|
||||
export const ImageBackground = <AsProps = ViewProps,>({
|
||||
export const ImageBackground = <AsProps = ViewProps>({
|
||||
src,
|
||||
alt,
|
||||
quality,
|
||||
@ -111,7 +111,7 @@ export const ImageBackground = <AsProps = ViewProps,>({
|
||||
forcedLoading={forcedLoading}
|
||||
alt={alt!}
|
||||
layout={{ width: percent(100), height: percent(100) }}
|
||||
Error={hideLoad ? null : undefined}
|
||||
Err={hideLoad ? null : undefined}
|
||||
{...(css([{ borderWidth: 0, borderRadius: 0 }, imageStyle]) as {
|
||||
style: ImageStyle;
|
||||
})}
|
||||
|
@ -18,11 +18,11 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { forwardRef, ReactNode, useState } from "react";
|
||||
import { TextInput, TextInputProps, View, ViewStyle } from "react-native";
|
||||
import { px, Theme, useYoshiki } from "yoshiki/native";
|
||||
import { type ReactNode, forwardRef, useState } from "react";
|
||||
import { TextInput, type TextInputProps, View, type ViewStyle } from "react-native";
|
||||
import { type Theme, px, useYoshiki } from "yoshiki/native";
|
||||
import type { YoshikiEnhanced } from "./image/base-image";
|
||||
import { focusReset, ts } from "./utils";
|
||||
import { YoshikiEnhanced } from "./image/base-image";
|
||||
|
||||
export const Input = forwardRef<
|
||||
TextInput,
|
||||
|
@ -18,13 +18,20 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { forwardRef, ReactNode } from "react";
|
||||
import { Platform, Pressable, TextProps, View, PressableProps, Linking } from "react-native";
|
||||
import type { UrlObject } from "node:url";
|
||||
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 { useTheme, useYoshiki } from "yoshiki/native";
|
||||
import type { UrlObject } from "url";
|
||||
import { alpha } from "./themes";
|
||||
import { parseNextPath } from "solito/router";
|
||||
import { useTheme, useYoshiki } from "yoshiki/native";
|
||||
import { alpha } from "./themes";
|
||||
|
||||
export const A = ({
|
||||
href,
|
||||
|
@ -19,29 +19,29 @@
|
||||
*/
|
||||
|
||||
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 {
|
||||
ComponentType,
|
||||
type ComponentType,
|
||||
type ReactElement,
|
||||
type ReactNode,
|
||||
createContext,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} 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 type { SvgProps } from "react-native-svg";
|
||||
import { useRouter } from "solito/router";
|
||||
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 { PressableFeedback } from "./links";
|
||||
import { P } from "./text";
|
||||
import { ContrastArea, SwitchVariant } from "./themes";
|
||||
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);
|
||||
|
||||
@ -66,7 +66,6 @@ const Menu = <AsProps,>({
|
||||
const insets = useSafeAreaInsets();
|
||||
const alreadyRendered = useRef(false);
|
||||
const [isOpen, setOpen] =
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
outerOpen !== undefined && outerSetOpen ? [outerOpen, outerSetOpen] : useState(false);
|
||||
|
||||
// deos the same as a useMemo but for props.
|
||||
|
@ -18,18 +18,24 @@
|
||||
* 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 { ComponentProps, ComponentType, forwardRef, ReactElement, ReactNode } from "react";
|
||||
import Link from "next/link";
|
||||
import { PressableProps } from "react-native";
|
||||
import { useYoshiki } from "yoshiki/web";
|
||||
import {
|
||||
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 } from "yoshiki/web";
|
||||
import { Icon } from "./icons";
|
||||
import { P } from "./text";
|
||||
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 { SvgProps } from "react-native-svg";
|
||||
|
||||
type YoshikiFunc<T> = (props: ReturnType<typeof useYoshiki>) => T;
|
||||
export const YoshikiProvider = ({ children }: { children: YoshikiFunc<ReactNode> }) => {
|
||||
|
@ -18,13 +18,13 @@
|
||||
* 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 { ContrastArea, SwitchVariant, YoshikiFunc } from "./themes";
|
||||
import { View, ScrollView } from "react-native";
|
||||
import { imageBorderRadius } from "./constants";
|
||||
import { type ReactNode, useCallback, useEffect, useState } from "react";
|
||||
import { ScrollView, View } from "react-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";
|
||||
|
||||
export const Popup = ({ children, ...props }: { children: ReactNode | YoshikiFunc<ReactNode> }) => {
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
import { ActivityIndicator, Platform, View } from "react-native";
|
||||
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 = ({
|
||||
size = 48,
|
||||
|
@ -18,10 +18,10 @@
|
||||
* 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 { Menu } from "./menu";
|
||||
import { Button } from "./button";
|
||||
import { Icon } from "./icons";
|
||||
import { Menu } from "./menu";
|
||||
|
||||
export const Select = <Value extends string>({
|
||||
label,
|
||||
|
@ -18,20 +18,20 @@
|
||||
* 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 { 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 { 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 = ({
|
||||
label,
|
||||
|
@ -19,10 +19,10 @@
|
||||
*/
|
||||
|
||||
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 { Platform, View, ViewProps } from "react-native";
|
||||
import { px, rem, useYoshiki, percent, em } from "yoshiki/native";
|
||||
import { Platform, View, type ViewProps } from "react-native";
|
||||
import { em, percent, px, rem, useYoshiki } from "yoshiki/native";
|
||||
import { hiddenIfNoJs } from "./utils/nojs";
|
||||
|
||||
const LinearGradient = motify(LG)();
|
||||
|
@ -19,8 +19,8 @@
|
||||
*/
|
||||
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { View, ViewProps } from "react-native";
|
||||
import { px, rem, useYoshiki, percent, em } from "yoshiki/native";
|
||||
import { View, type ViewProps } from "react-native";
|
||||
import { em, percent, px, rem, useYoshiki } from "yoshiki/native";
|
||||
import { hiddenIfNoJs } from "./utils/nojs";
|
||||
|
||||
export const SkeletonCss = () => (
|
||||
|
@ -19,10 +19,10 @@
|
||||
*/
|
||||
|
||||
import { useRef, useState } from "react";
|
||||
import { GestureResponderEvent, Platform, View } from "react-native";
|
||||
import { px, percent, Stylable, useYoshiki } from "yoshiki/native";
|
||||
import { type GestureResponderEvent, Platform, View } from "react-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 { ViewProps } from "react-native-svg/lib/typescript/fabric/utils";
|
||||
|
||||
export const Slider = ({
|
||||
progress,
|
||||
|
@ -19,14 +19,14 @@
|
||||
*/
|
||||
|
||||
import { usePortal } from "@gorhom/portal";
|
||||
import { ReactElement, createContext, useCallback, useContext, useRef } from "react";
|
||||
import { ContrastArea, SwitchVariant } from "./themes";
|
||||
import { P } from "./text";
|
||||
import { type ReactElement, createContext, useCallback, useContext, useRef } from "react";
|
||||
import { View } from "react-native";
|
||||
import { percent, px } from "yoshiki/native";
|
||||
import { ts } from "./utils";
|
||||
import { Button } from "./button";
|
||||
import { imageBorderRadius } from "./constants";
|
||||
import { P } from "./text";
|
||||
import { ContrastArea, SwitchVariant } from "./themes";
|
||||
import { ts } from "./utils";
|
||||
|
||||
export type Snackbar = {
|
||||
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" {
|
||||
import React from "react";
|
||||
import { SvgProps } from "react-native-svg";
|
||||
import type React from "react";
|
||||
import type { SvgProps } from "react-native-svg";
|
||||
const content: React.FC<SvgProps>;
|
||||
export default content;
|
||||
}
|
||||
|
@ -18,9 +18,6 @@
|
||||
* 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 {
|
||||
H1 as EH1,
|
||||
H2 as EH2,
|
||||
@ -30,6 +27,9 @@ import {
|
||||
H6 as EH6,
|
||||
P as EP,
|
||||
} 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";
|
||||
|
||||
const styleText = (
|
||||
|
@ -18,7 +18,7 @@
|
||||
* 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
|
||||
export const catppuccin: ThemeBuilder = {
|
||||
|
@ -18,14 +18,14 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import { Property } from "csstype";
|
||||
import { Theme, ThemeProvider, useAutomaticTheme } from "yoshiki";
|
||||
import { useTheme, useYoshiki } from "yoshiki/native";
|
||||
import type { Property } from "csstype";
|
||||
import type { ReactNode } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { type Theme, ThemeProvider, useAutomaticTheme } from "yoshiki";
|
||||
import "yoshiki";
|
||||
import { useTheme, useYoshiki } from "yoshiki/native";
|
||||
import "yoshiki/native";
|
||||
import { catppuccin } from "./catppuccin";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
type FontList = Partial<
|
||||
Record<
|
||||
@ -111,9 +111,7 @@ const selectMode = (
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const auto = useAutomaticTheme("theme", { light, dark });
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const alternate = useAutomaticTheme("alternate", { dark: light, light: dark });
|
||||
return {
|
||||
...options,
|
||||
|
@ -18,9 +18,9 @@
|
||||
* 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 { Tooltip as RTooltip } from "react-tooltip";
|
||||
import type { Theme } from "yoshiki/native";
|
||||
import { ContrastArea } from "./themes";
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import type React from "react";
|
||||
import "react-native";
|
||||
|
||||
declare module "react-native" {
|
||||
|
@ -19,7 +19,7 @@
|
||||
*/
|
||||
|
||||
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];
|
||||
export type Breakpoint<T> = T | AtLeastOne<YoshikiBreakpoint<T>>;
|
||||
|
@ -31,7 +31,7 @@ export const Head = ({
|
||||
}) => {
|
||||
return (
|
||||
<NextHead>
|
||||
{title && <title>{title + " - Kyoo"}</title>}
|
||||
{title && <title>{`${title} - Kyoo`}</title>}
|
||||
{description && <meta name="description" content={description} />}
|
||||
{image && <meta property="og:image" content={image} />}
|
||||
</NextHead>
|
||||
|
@ -18,7 +18,7 @@
|
||||
* 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 };
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
*/
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { Platform, ViewProps } from "react-native";
|
||||
import { Platform, type ViewProps } from "react-native";
|
||||
|
||||
export const TouchOnlyCss = () => {
|
||||
return (
|
||||
|
@ -18,9 +18,9 @@
|
||||
"incremental": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["src/*"],
|
||||
},
|
||||
"~/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
@ -18,12 +18,12 @@
|
||||
* 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 { ScrollView } from "react-native";
|
||||
import { DefaultLayout } from "../layout";
|
||||
import { UserList } from "./users";
|
||||
import { Scanner } from "./scanner";
|
||||
import { UserList } from "./users";
|
||||
|
||||
export const AdminPage: QueryPage = () => {
|
||||
return (
|
||||
|
@ -18,14 +18,14 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Issue, IssueP, QueryIdentifier, queryFn, useFetch } from "@kyoo/models";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SettingsContainer } from "../settings/base";
|
||||
import { type Issue, IssueP, type QueryIdentifier, queryFn, useFetch } from "@kyoo/models";
|
||||
import { Button, Icon, P, Skeleton, tooltip, ts } from "@kyoo/primitives";
|
||||
import { ErrorView } from "../errors";
|
||||
import { z } from "zod";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { View } from "react-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 Scan from "@material-symbols/svg-400/outlined/sensors.svg";
|
||||
@ -82,7 +82,7 @@ export const Scanner = () => {
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
{data != null && data.length == 0 && <P>{t("admin.scanner.empty")}</P>}
|
||||
{data != null && data.length === 0 && <P>{t("admin.scanner.empty")}</P>}
|
||||
</>
|
||||
</SettingsContainer>
|
||||
);
|
||||
|
@ -18,21 +18,21 @@
|
||||
* 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 { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { px, useYoshiki } from "yoshiki/native";
|
||||
import { Layout, WithLoading } from "../fetch";
|
||||
import type { Layout, WithLoading } from "../fetch";
|
||||
import { InfiniteFetch } from "../fetch-infinite";
|
||||
import { SettingsContainer } from "../settings/base";
|
||||
|
||||
import UserI from "@material-symbols/svg-400/rounded/account_circle.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 MoreVert from "@material-symbols/svg-400/rounded/more_vert.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";
|
||||
|
||||
export const UserGrid = ({
|
||||
|
@ -18,24 +18,24 @@
|
||||
* 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 {
|
||||
Link,
|
||||
Skeleton,
|
||||
ts,
|
||||
focusReset,
|
||||
P,
|
||||
SubP,
|
||||
PosterBackground,
|
||||
Icon,
|
||||
Link,
|
||||
P,
|
||||
PosterBackground,
|
||||
Skeleton,
|
||||
SubP,
|
||||
focusReset,
|
||||
important,
|
||||
ts,
|
||||
} 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 { ItemContext } from "../components/context-menus";
|
||||
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 = ({
|
||||
watchStatus,
|
||||
|
@ -29,17 +29,17 @@ import {
|
||||
tooltip,
|
||||
ts,
|
||||
} 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 { 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 { 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(
|
||||
{ sortKey, ...props },
|
||||
@ -121,14 +121,14 @@ export const BrowseSettings = ({
|
||||
<IconButton
|
||||
icon={GridView}
|
||||
onPress={() => setLayout(Layout.Grid)}
|
||||
color={layout == Layout.Grid ? theme.accent : undefined}
|
||||
color={layout === Layout.Grid ? theme.accent : undefined}
|
||||
{...tooltip(t("browse.switchToGrid"))}
|
||||
{...css({ padding: ts(0.5), marginY: "auto" })}
|
||||
/>
|
||||
<IconButton
|
||||
icon={ViewList}
|
||||
onPress={() => setLayout(Layout.List)}
|
||||
color={layout == Layout.List ? theme.accent : undefined}
|
||||
color={layout === Layout.List ? theme.accent : undefined}
|
||||
{...tooltip(t("browse.switchToList"))}
|
||||
{...css({ padding: ts(0.5), marginY: "auto" })}
|
||||
/>
|
||||
|
@ -19,21 +19,21 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
QueryIdentifier,
|
||||
QueryPage,
|
||||
LibraryItem,
|
||||
type LibraryItem,
|
||||
LibraryItemP,
|
||||
type QueryIdentifier,
|
||||
type QueryPage,
|
||||
getDisplayDate,
|
||||
} from "@kyoo/models";
|
||||
import { ComponentProps, useState } from "react";
|
||||
import { type ComponentProps, useState } from "react";
|
||||
import { createParam } from "solito";
|
||||
import { DefaultLayout } from "../layout";
|
||||
import { WithLoading } from "../fetch";
|
||||
import type { WithLoading } from "../fetch";
|
||||
import { InfiniteFetch } from "../fetch-infinite";
|
||||
import { DefaultLayout } from "../layout";
|
||||
import { ItemGrid } from "./grid";
|
||||
import { ItemList } from "./list";
|
||||
import { SortBy, SortOrd, Layout } from "./types";
|
||||
import { BrowseSettings } from "./header";
|
||||
import { ItemList } from "./list";
|
||||
import { Layout, SortBy, SortOrd } from "./types";
|
||||
|
||||
const { useParam } = createParam<{ sortBy?: string }>();
|
||||
|
||||
|
@ -18,24 +18,24 @@
|
||||
* 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 {
|
||||
Heading,
|
||||
ImageBackground,
|
||||
Link,
|
||||
P,
|
||||
Skeleton,
|
||||
ts,
|
||||
ImageBackground,
|
||||
Heading,
|
||||
PosterBackground,
|
||||
Skeleton,
|
||||
imageBorderRadius,
|
||||
important,
|
||||
ts,
|
||||
} from "@kyoo/primitives";
|
||||
import { useState } from "react";
|
||||
import { Platform, View } from "react-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 type { Layout, WithLoading } from "../fetch";
|
||||
import { ItemWatchStatus } from "./grid";
|
||||
|
||||
export const ItemList = ({
|
||||
href,
|
||||
|
@ -19,26 +19,26 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
Collection,
|
||||
type Collection,
|
||||
CollectionP,
|
||||
LibraryItem,
|
||||
type LibraryItem,
|
||||
LibraryItemP,
|
||||
QueryIdentifier,
|
||||
QueryPage,
|
||||
type QueryIdentifier,
|
||||
type QueryPage,
|
||||
getDisplayDate,
|
||||
} from "@kyoo/models";
|
||||
import { Header as ShowHeader, TitleLine } from "../details/header";
|
||||
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 { 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 { InfiniteFetch } from "../fetch-infinite";
|
||||
import { DefaultLayout } from "../layout";
|
||||
import { ItemDetails } from "../home/recommended";
|
||||
import { SvgWave } from "../details/show";
|
||||
import { ItemGrid } from "../browse/grid";
|
||||
import { DefaultLayout } from "../layout";
|
||||
|
||||
const Header = ({ slug }: { slug: string }) => {
|
||||
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 MovieInfo from "@material-symbols/svg-400/rounded/movie_info.svg";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { ComponentProps } from "react";
|
||||
import type { ComponentProps } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Platform } from "react-native";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
|
@ -18,13 +18,19 @@
|
||||
* 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 { Fetch } from "../fetch";
|
||||
import { Fragment } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
import { Fragment } from "react";
|
||||
import { Fetch } from "../fetch";
|
||||
|
||||
const MediaInfoTable = ({
|
||||
mediaInfo: { path, video, container, audios, subtitles, duration, size },
|
||||
@ -35,15 +41,14 @@ const MediaInfoTable = ({
|
||||
const { css } = useYoshiki();
|
||||
const formatBitrate = (b: number) => `${(b / 1000000).toFixed(2)} Mbps`;
|
||||
const formatTrackTable = (trackTable: (Audio | Subtitle)[], s: string) => {
|
||||
if (trackTable.length == 0) {
|
||||
if (trackTable.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const singleTrack = trackTable.length == 1;
|
||||
const singleTrack = trackTable.length === 1;
|
||||
return trackTable.reduce(
|
||||
(collected, audioTrack, index) => ({
|
||||
...collected,
|
||||
(collected, audioTrack, 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,
|
||||
// Only show it if there is more than one track
|
||||
audioTrack.isDefault && !singleTrack ? t("mediainfo.default") : undefined,
|
||||
@ -51,8 +56,9 @@ const MediaInfoTable = ({
|
||||
audioTrack.codec,
|
||||
]
|
||||
.filter((x) => x !== undefined)
|
||||
.join(" - "),
|
||||
}),
|
||||
.join(" - ");
|
||||
return collected;
|
||||
},
|
||||
{} as Record<string, string | undefined>,
|
||||
);
|
||||
};
|
||||
@ -94,7 +100,7 @@ const MediaInfoTable = ({
|
||||
<Skeleton>{value ? <P>{value}</P> : undefined}</Skeleton>
|
||||
</View>
|
||||
</View>
|
||||
{index == l.length - 1 && <HR />}
|
||||
{index === l.length - 1 && <HR />}
|
||||
</Fragment>
|
||||
)),
|
||||
)}
|
||||
|
@ -18,9 +18,9 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Breakpoint, Icon, P, Skeleton, ts } from "@kyoo/primitives";
|
||||
import { View } from "react-native";
|
||||
import { type Breakpoint, Icon, P, Skeleton, ts } from "@kyoo/primitives";
|
||||
import Star from "@material-symbols/svg-400/rounded/star-fill.svg";
|
||||
import { View } from "react-native";
|
||||
import { rem, useYoshiki } from "yoshiki/native";
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
import { WatchStatusV, queryFn, useAccount } from "@kyoo/models";
|
||||
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 BookmarkAdd from "@material-symbols/svg-400/rounded/bookmark_add.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 { 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) => {
|
||||
switch (status) {
|
||||
|
@ -19,15 +19,15 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
Collection,
|
||||
type Collection,
|
||||
CollectionP,
|
||||
KyooImage,
|
||||
QueryIdentifier,
|
||||
type KyooImage,
|
||||
type QueryIdentifier,
|
||||
useInfiniteFetch,
|
||||
} from "@kyoo/models";
|
||||
import { Container, H2, ImageBackground, Link, P, focusReset, ts } from "@kyoo/primitives";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Theme, useYoshiki } from "yoshiki/native";
|
||||
import { type Theme, useYoshiki } from "yoshiki/native";
|
||||
import { ErrorView } from "../errors";
|
||||
|
||||
export const PartOf = ({
|
||||
|
@ -18,30 +18,30 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { type KyooImage, WatchStatusV } from "@kyoo/models";
|
||||
import {
|
||||
focusReset,
|
||||
H6,
|
||||
IconButton,
|
||||
ImageBackground,
|
||||
ImageProps,
|
||||
important,
|
||||
type ImageProps,
|
||||
Link,
|
||||
P,
|
||||
Skeleton,
|
||||
SubP,
|
||||
focusReset,
|
||||
important,
|
||||
tooltip,
|
||||
ts,
|
||||
} 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 { ImageStyle, Platform, PressableProps, View } from "react-native";
|
||||
import { Layout, WithLoading } from "../fetch";
|
||||
import { percent, rem, Stylable, Theme, useYoshiki } from "yoshiki/native";
|
||||
import { KyooImage, WatchStatusV } from "@kyoo/models";
|
||||
import { type ImageStyle, Platform, type PressableProps, View } from "react-native";
|
||||
import { type Stylable, type Theme, percent, rem, useYoshiki } from "yoshiki/native";
|
||||
import { ItemProgress } from "../browse/grid";
|
||||
import { EpisodesContext } from "../components/context-menus";
|
||||
import { useState } from "react";
|
||||
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 type { Layout, WithLoading } from "../fetch";
|
||||
|
||||
export const episodeDisplayNumber = (
|
||||
episode: {
|
||||
@ -295,6 +295,7 @@ export const EpisodeLine = ({
|
||||
>
|
||||
<Skeleton>
|
||||
{isLoading || (
|
||||
// biome-ignore lint/a11y/useValidAriaValues: simply use H6 for the style but keep a P
|
||||
<H6 aria-level={undefined} {...css([{ flexShrink: 1 }, "title"])}>
|
||||
{[displayNumber, name ?? t("show.episodeNoMetadata")].join(" · ")}
|
||||
</H6>
|
||||
|
@ -19,17 +19,17 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
Genre,
|
||||
KyooImage,
|
||||
Movie,
|
||||
QueryIdentifier,
|
||||
Show,
|
||||
Studio,
|
||||
type Genre,
|
||||
type KyooImage,
|
||||
type Movie,
|
||||
type QueryIdentifier,
|
||||
type Show,
|
||||
type Studio,
|
||||
getDisplayDate,
|
||||
queryFn,
|
||||
useAccount,
|
||||
} from "@kyoo/models";
|
||||
import { WatchStatusV } from "@kyoo/models/src/resources/watch-status";
|
||||
import type { WatchStatusV } from "@kyoo/models/src/resources/watch-status";
|
||||
import {
|
||||
A,
|
||||
Chip,
|
||||
@ -63,10 +63,10 @@ import Theaters from "@material-symbols/svg-400/rounded/theaters-fill.svg";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { Fragment } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ImageStyle, Platform, View } from "react-native";
|
||||
import { type ImageStyle, Platform, View } from "react-native";
|
||||
import {
|
||||
Stylable,
|
||||
Theme,
|
||||
type Stylable,
|
||||
type Theme,
|
||||
em,
|
||||
max,
|
||||
md,
|
||||
|
@ -18,13 +18,13 @@
|
||||
* 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 { useYoshiki } from "yoshiki/native";
|
||||
import { DefaultLayout } from "../layout";
|
||||
import { Header } from "./header";
|
||||
import { DetailsCollections } from "./collection";
|
||||
import { usePageStyle } from "@kyoo/primitives";
|
||||
import { Header } from "./header";
|
||||
|
||||
const query = (slug: string): QueryIdentifier<Movie> => ({
|
||||
parser: MovieP,
|
||||
|
@ -19,7 +19,7 @@
|
||||
*/
|
||||
|
||||
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 = ({
|
||||
slug,
|
||||
|
@ -19,21 +19,21 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
Episode,
|
||||
type Episode,
|
||||
EpisodeP,
|
||||
QueryIdentifier,
|
||||
Season,
|
||||
type QueryIdentifier,
|
||||
type Season,
|
||||
SeasonP,
|
||||
useInfiniteFetch,
|
||||
} from "@kyoo/models";
|
||||
import { Skeleton, H6, HR, P, ts, Menu, IconButton, tooltip, 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 { H6, HR, IconButton, Menu, P, Skeleton, tooltip, ts, usePageStyle } from "@kyoo/primitives";
|
||||
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 };
|
||||
|
||||
|
@ -18,18 +18,24 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { QueryIdentifier, QueryPage, Show, ShowP, ShowWatchStatus } from "@kyoo/models";
|
||||
import { Platform, View, ViewProps } from "react-native";
|
||||
import { percent, useYoshiki } from "yoshiki/native";
|
||||
import { DefaultLayout } from "../layout";
|
||||
import { EpisodeList, SeasonHeader } from "./season";
|
||||
import { Header } from "./header";
|
||||
import Svg, { Path, SvgProps } from "react-native-svg";
|
||||
import {
|
||||
type QueryIdentifier,
|
||||
type QueryPage,
|
||||
type Show,
|
||||
ShowP,
|
||||
type ShowWatchStatus,
|
||||
} from "@kyoo/models";
|
||||
import { Container, H2, SwitchVariant, focusReset, ts } from "@kyoo/primitives";
|
||||
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 { EpisodeLine, episodeDisplayNumber } from "./episode";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Header } from "./header";
|
||||
import { EpisodeList, SeasonHeader } from "./season";
|
||||
|
||||
export const SvgWave = (props: SvgProps) => {
|
||||
const { css } = useYoshiki();
|
||||
|
@ -18,10 +18,10 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { WatchInfo, getCurrentApiUrl, queryFn, toQueryKey } from "@kyoo/models";
|
||||
import { Player } from "../player";
|
||||
import { type WatchInfo, getCurrentApiUrl, queryFn, toQueryKey } from "@kyoo/models";
|
||||
import { getCurrentAccount } from "@kyoo/models/src/account-internal";
|
||||
import { ReactNode } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { Player } from "../player";
|
||||
|
||||
export const useDownloader = () => {
|
||||
return async (type: "episode" | "movie", slug: string) => {
|
||||
|
@ -18,9 +18,7 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { State, downloadAtom } from "./state";
|
||||
import { FlashList } from "@shopify/flash-list";
|
||||
import { ImageStyle, View } from "react-native";
|
||||
import type { KyooImage } from "@kyoo/models";
|
||||
import {
|
||||
Alert,
|
||||
H6,
|
||||
@ -34,17 +32,19 @@ import {
|
||||
ts,
|
||||
usePageStyle,
|
||||
} 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 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 { FlashList } from "@shopify/flash-list";
|
||||
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 = ({
|
||||
name,
|
||||
@ -126,6 +126,7 @@ const DownloadedItem = ({
|
||||
})}
|
||||
>
|
||||
<View {...css({ flexGrow: 1, flexShrink: 1 })}>
|
||||
{/* biome-ignore lint/a11y/useValidAriaValues: use h6 for style only */}
|
||||
<H6 aria-level={undefined} {...css([{ flexShrink: 1 }, "title"])}>
|
||||
{name ?? t("show.episodeNoMetadata")}
|
||||
</H6>
|
||||
@ -193,10 +194,9 @@ const downloadIcon = (status: State["status"]) => {
|
||||
case "DOWNLOADING":
|
||||
return Downloading;
|
||||
case "FAILED":
|
||||
return Error;
|
||||
return ErrorIcon;
|
||||
case "PAUSED":
|
||||
case "STOPPED":
|
||||
default:
|
||||
return NotStarted;
|
||||
}
|
||||
};
|
||||
|
@ -21,27 +21,27 @@
|
||||
import RNBackgroundDownloader, {
|
||||
type DownloadTask,
|
||||
} from "@kesha-antonov/react-native-background-downloader";
|
||||
import { deleteAsync } from "expo-file-system";
|
||||
import {
|
||||
Account,
|
||||
Episode,
|
||||
type Account,
|
||||
type Episode,
|
||||
EpisodeP,
|
||||
Movie,
|
||||
type Movie,
|
||||
MovieP,
|
||||
QueryIdentifier,
|
||||
WatchInfo,
|
||||
type QueryIdentifier,
|
||||
type WatchInfo,
|
||||
WatchInfoP,
|
||||
queryFn,
|
||||
toQueryKey,
|
||||
} from "@kyoo/models";
|
||||
import { Player } from "../player";
|
||||
import { atom, useSetAtom, PrimitiveAtom, useStore } from "jotai";
|
||||
import { getCurrentAccount, storage } from "@kyoo/models/src/account-internal";
|
||||
import { ReactNode, useEffect } from "react";
|
||||
import { Platform, ToastAndroid } from "react-native";
|
||||
import { QueryClient, useQueryClient } from "@tanstack/react-query";
|
||||
import { Router } from "expo-router/build/types";
|
||||
import { type QueryClient, useQueryClient } from "@tanstack/react-query";
|
||||
import { deleteAsync } from "expo-file-system";
|
||||
import type { 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 { Player } from "../player";
|
||||
|
||||
export type State = {
|
||||
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 }[] =
|
||||
JSON.parse(storage.getString("downloads") ?? "[]");
|
||||
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);
|
||||
|
||||
const stateAtom = atom({
|
||||
|
@ -20,13 +20,13 @@
|
||||
|
||||
import { ConnectionErrorContext, useAccount } from "@kyoo/models";
|
||||
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 { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { useRouter } from "solito/router";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
import { DefaultLayout } from "../layout";
|
||||
import Register from "@material-symbols/svg-400/rounded/app_registration.svg";
|
||||
|
||||
export const ConnectionError = () => {
|
||||
const { css } = useYoshiki();
|
||||
@ -35,7 +35,7 @@ export const ConnectionError = () => {
|
||||
const { error, retry } = useContext(ConnectionErrorContext);
|
||||
const account = useAccount();
|
||||
|
||||
if (error && (error.status === 401 || error.status == 403)) {
|
||||
if (error && (error.status === 401 || error.status === 403)) {
|
||||
if (!account) {
|
||||
return (
|
||||
<View
|
||||
|
@ -18,7 +18,7 @@
|
||||
* 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 { useContext, useLayoutEffect } from "react";
|
||||
import { View } from "react-native";
|
||||
@ -36,7 +36,7 @@ export const ErrorView = ({
|
||||
|
||||
useLayoutEffect(() => {
|
||||
// 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]);
|
||||
console.log(error);
|
||||
return (
|
||||
|
@ -18,13 +18,19 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
|
||||
import { useBreakpointMap, HR } from "@kyoo/primitives";
|
||||
import { ContentStyle, FlashList } from "@shopify/flash-list";
|
||||
import { ComponentProps, ComponentType, isValidElement, ReactElement, useRef } from "react";
|
||||
import { EmptyView, Layout, OfflineView, WithLoading, addHeader } from "./fetch";
|
||||
import { type Page, type QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
|
||||
import { HR, useBreakpointMap } from "@kyoo/primitives";
|
||||
import { type ContentStyle, FlashList } from "@shopify/flash-list";
|
||||
import {
|
||||
type ComponentProps,
|
||||
type ComponentType,
|
||||
type ReactElement,
|
||||
isValidElement,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { FlatList, View, type ViewStyle } from "react-native";
|
||||
import { ErrorView } from "./errors";
|
||||
import { FlatList, View, ViewStyle } from "react-native";
|
||||
import { EmptyView, type Layout, OfflineView, type WithLoading, addHeader } from "./fetch";
|
||||
|
||||
const emulateGap = (
|
||||
layout: "grid" | "vertical" | "horizontal",
|
||||
|
@ -18,22 +18,22 @@
|
||||
* 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 type { ContentStyle } from "@shopify/flash-list";
|
||||
import {
|
||||
ComponentProps,
|
||||
ComponentType,
|
||||
type ComponentProps,
|
||||
type ComponentType,
|
||||
Fragment,
|
||||
type ReactElement,
|
||||
isValidElement,
|
||||
ReactElement,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { Stylable, nativeStyleToCss, useYoshiki, ysMap } from "yoshiki";
|
||||
import { EmptyView, Layout, WithLoading, addHeader } from "./fetch";
|
||||
import { type Stylable, nativeStyleToCss, useYoshiki, ysMap } from "yoshiki";
|
||||
import { ErrorView } from "./errors";
|
||||
import type { ContentStyle } from "@shopify/flash-list";
|
||||
import { EmptyView, type Layout, type WithLoading, addHeader } from "./fetch";
|
||||
|
||||
const InfiniteScroll = <Props,>({
|
||||
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
|
||||
// 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(() => {
|
||||
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)
|
||||
gridGap: layout.gap as any,
|
||||
},
|
||||
layout.layout == "vertical" && {
|
||||
layout.layout === "vertical" && {
|
||||
gridTemplateColumns: "1fr",
|
||||
alignItems: "stretch",
|
||||
overflowY: "auto",
|
||||
paddingY: layout.gap as any,
|
||||
},
|
||||
layout.layout == "horizontal" && {
|
||||
layout.layout === "horizontal" && {
|
||||
alignItems: "stretch",
|
||||
overflowX: "auto",
|
||||
overflowY: "hidden",
|
||||
|
@ -18,9 +18,9 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Page, QueryIdentifier, useFetch } from "@kyoo/models";
|
||||
import { Breakpoint, P } from "@kyoo/primitives";
|
||||
import { ComponentType, ReactElement, isValidElement } from "react";
|
||||
import { type Page, type QueryIdentifier, useFetch } from "@kyoo/models";
|
||||
import { type Breakpoint, P } from "@kyoo/primitives";
|
||||
import { type ComponentType, type ReactElement, isValidElement } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
@ -37,7 +37,7 @@ export type WithLoading<Item> =
|
||||
| (Item & { isLoading: false })
|
||||
| (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;
|
||||
|
||||
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