mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-31 14:33:50 -04:00
Update code for biome v2
This commit is contained in:
parent
bbe1ad4ef1
commit
7b3f3cc1c1
@ -1,4 +1,4 @@
|
||||
import { type SQL, and, desc, eq, isNotNull, ne, sql } from "drizzle-orm";
|
||||
import { and, desc, eq, isNotNull, ne, type SQL, sql } from "drizzle-orm";
|
||||
import { Elysia, t } from "elysia";
|
||||
import { auth } from "~/auth";
|
||||
import { db } from "~/db";
|
||||
@ -31,14 +31,14 @@ import { KError } from "~/models/error";
|
||||
import { madeInAbyss } from "~/models/examples";
|
||||
import {
|
||||
AcceptLanguage,
|
||||
createPage,
|
||||
Filter,
|
||||
type FilterDef,
|
||||
Page,
|
||||
Sort,
|
||||
createPage,
|
||||
isUuid,
|
||||
keysetPaginate,
|
||||
Page,
|
||||
processLanguages,
|
||||
Sort,
|
||||
sortToSql,
|
||||
} from "~/models/utils";
|
||||
import { desc as description } from "~/models/utils/descriptions";
|
||||
|
@ -11,10 +11,10 @@ import { KError } from "~/models/error";
|
||||
import { SeedHistory } from "~/models/history";
|
||||
import {
|
||||
AcceptLanguage,
|
||||
Filter,
|
||||
Page,
|
||||
createPage,
|
||||
Filter,
|
||||
isUuid,
|
||||
Page,
|
||||
processLanguages,
|
||||
} from "~/models/utils";
|
||||
import { desc } from "~/models/utils/descriptions";
|
||||
|
@ -8,13 +8,13 @@ import { getColumns, sqlarr } from "~/db/utils";
|
||||
import { Entry } from "~/models/entry";
|
||||
import {
|
||||
AcceptLanguage,
|
||||
createPage,
|
||||
Filter,
|
||||
type FilterDef,
|
||||
Page,
|
||||
Sort,
|
||||
createPage,
|
||||
keysetPaginate,
|
||||
Page,
|
||||
processLanguages,
|
||||
Sort,
|
||||
sortToSql,
|
||||
} from "~/models/utils";
|
||||
import { desc } from "~/models/utils/descriptions";
|
||||
|
@ -18,11 +18,11 @@ import { Movie } from "~/models/movie";
|
||||
import { Serie } from "~/models/serie";
|
||||
import {
|
||||
AcceptLanguage,
|
||||
createPage,
|
||||
DbMetadata,
|
||||
Filter,
|
||||
Page,
|
||||
createPage,
|
||||
isUuid,
|
||||
Page,
|
||||
processLanguages,
|
||||
} from "~/models/utils";
|
||||
import { desc } from "~/models/utils/descriptions";
|
||||
|
@ -1,20 +1,20 @@
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { Elysia, t } from "elysia";
|
||||
import { db } from "~/db";
|
||||
import { seasonTranslations, seasons, shows } from "~/db/schema";
|
||||
import { seasons, seasonTranslations, shows } from "~/db/schema";
|
||||
import { getColumns, sqlarr } from "~/db/utils";
|
||||
import { KError } from "~/models/error";
|
||||
import { madeInAbyss } from "~/models/examples";
|
||||
import {
|
||||
AcceptLanguage,
|
||||
createPage,
|
||||
Filter,
|
||||
type FilterDef,
|
||||
Page,
|
||||
Sort,
|
||||
createPage,
|
||||
isUuid,
|
||||
keysetPaginate,
|
||||
Page,
|
||||
processLanguages,
|
||||
Sort,
|
||||
sortToSql,
|
||||
} from "~/models/utils";
|
||||
import { desc } from "~/models/utils/descriptions";
|
||||
|
@ -1,11 +1,11 @@
|
||||
import path from "node:path";
|
||||
import { encode } from "blurhash";
|
||||
import { type SQL, and, eq, is, lt, sql } from "drizzle-orm";
|
||||
import { and, eq, is, lt, type SQL, sql } from "drizzle-orm";
|
||||
import { PgColumn, type PgTable } from "drizzle-orm/pg-core";
|
||||
import { version } from "package.json";
|
||||
import type { PoolClient } from "pg";
|
||||
import sharp from "sharp";
|
||||
import { type Transaction, db } from "~/db";
|
||||
import { db, type Transaction } from "~/db";
|
||||
import { mqueue } from "~/db/schema/mqueue";
|
||||
import type { Image } from "~/models/utils";
|
||||
import { getFile } from "~/utils";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { db } from "~/db";
|
||||
import { showTranslations, shows } from "~/db/schema";
|
||||
import { shows, showTranslations } from "~/db/schema";
|
||||
import { conflictUpdateAllExcept } from "~/db/utils";
|
||||
import type { SeedCollection } from "~/models/collections";
|
||||
import type { SeedMovie } from "~/models/movie";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { type Column, type SQL, eq, sql } from "drizzle-orm";
|
||||
import { type Column, eq, type SQL, sql } from "drizzle-orm";
|
||||
import { db } from "~/db";
|
||||
import {
|
||||
entries,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { db } from "~/db";
|
||||
import { seasonTranslations, seasons } from "~/db/schema";
|
||||
import { seasons, seasonTranslations } from "~/db/schema";
|
||||
import { conflictUpdateAllExcept } from "~/db/utils";
|
||||
import type { SeedSeason } from "~/models/season";
|
||||
import { enqueueOptImage } from "../images";
|
||||
|
@ -1,15 +1,15 @@
|
||||
import {
|
||||
type SQLWrapper,
|
||||
and,
|
||||
count,
|
||||
eq,
|
||||
exists,
|
||||
isNull,
|
||||
ne,
|
||||
type SQLWrapper,
|
||||
sql,
|
||||
} from "drizzle-orm";
|
||||
import { type Transaction, db } from "~/db";
|
||||
import { entries, entryVideoJoin, showTranslations, shows } from "~/db/schema";
|
||||
import { db, type Transaction } from "~/db";
|
||||
import { entries, entryVideoJoin, shows, showTranslations } from "~/db/schema";
|
||||
import { conflictUpdateAllExcept, sqlarr } from "~/db/utils";
|
||||
import type { SeedCollection } from "~/models/collections";
|
||||
import type { SeedMovie } from "~/models/movie";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { db } from "~/db";
|
||||
import { showStudioJoin, studioTranslations, studios } from "~/db/schema";
|
||||
import { showStudioJoin, studios, studioTranslations } from "~/db/schema";
|
||||
import { conflictUpdateAllExcept } from "~/db/utils";
|
||||
import type { SeedStudio } from "~/models/studio";
|
||||
import { enqueueOptImage } from "../images";
|
||||
|
@ -1,7 +1,7 @@
|
||||
// oh i hate js dates so much.
|
||||
export const guessNextRefresh = (airDate: Date | string) => {
|
||||
if (typeof airDate === "string") airDate = new Date(airDate);
|
||||
const diff = new Date().getTime() - airDate.getTime();
|
||||
const diff = Date.now() - airDate.getTime();
|
||||
const days = diff / (24 * 60 * 60 * 1000);
|
||||
|
||||
const ret = new Date();
|
||||
|
@ -16,10 +16,10 @@ import { Serie } from "~/models/serie";
|
||||
import { Show } from "~/models/show";
|
||||
import {
|
||||
AcceptLanguage,
|
||||
Filter,
|
||||
Page,
|
||||
createPage,
|
||||
Filter,
|
||||
isUuid,
|
||||
Page,
|
||||
processLanguages,
|
||||
} from "~/models/utils";
|
||||
import { desc } from "~/models/utils/descriptions";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { and, eq, exists, gt, ne, type SQL, sql } from "drizzle-orm";
|
||||
import { and, eq, exists, ne, type SQL, sql } from "drizzle-orm";
|
||||
import { db } from "~/db";
|
||||
import {
|
||||
entries,
|
||||
|
@ -9,10 +9,10 @@ import { bubble } from "~/models/examples";
|
||||
import { FullMovie, Movie, MovieTranslation } from "~/models/movie";
|
||||
import {
|
||||
AcceptLanguage,
|
||||
Filter,
|
||||
Page,
|
||||
createPage,
|
||||
Filter,
|
||||
isUuid,
|
||||
Page,
|
||||
processLanguages,
|
||||
} from "~/models/utils";
|
||||
import { desc } from "~/models/utils/descriptions";
|
||||
|
@ -9,10 +9,10 @@ import { madeInAbyss } from "~/models/examples";
|
||||
import { FullSerie, Serie, SerieTranslation } from "~/models/serie";
|
||||
import {
|
||||
AcceptLanguage,
|
||||
Filter,
|
||||
Page,
|
||||
createPage,
|
||||
Filter,
|
||||
isUuid,
|
||||
Page,
|
||||
processLanguages,
|
||||
} from "~/models/utils";
|
||||
import { desc } from "~/models/utils/descriptions";
|
||||
|
@ -8,9 +8,9 @@ import { KError } from "~/models/error";
|
||||
import { Show } from "~/models/show";
|
||||
import {
|
||||
AcceptLanguage,
|
||||
createPage,
|
||||
Filter,
|
||||
Page,
|
||||
createPage,
|
||||
processLanguages,
|
||||
} from "~/models/utils";
|
||||
import { desc } from "~/models/utils/descriptions";
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { type SQL, and, eq, sql } from "drizzle-orm";
|
||||
import { and, eq, type SQL, sql } from "drizzle-orm";
|
||||
import Elysia, { t } from "elysia";
|
||||
import { auth } from "~/auth";
|
||||
import { prefix } from "~/base";
|
||||
import { db } from "~/db";
|
||||
import { profiles, showTranslations, shows } from "~/db/schema";
|
||||
import { profiles, shows, showTranslations } from "~/db/schema";
|
||||
import { roles, staff } from "~/db/schema/staff";
|
||||
import { watchlist } from "~/db/schema/watchlist";
|
||||
import { getColumns, jsonbBuildObject, sqlarr } from "~/db/utils";
|
||||
@ -13,15 +13,15 @@ import { Role, Staff } from "~/models/staff";
|
||||
import { RoleWShow, RoleWStaff } from "~/models/staff-roles";
|
||||
import {
|
||||
AcceptLanguage,
|
||||
createPage,
|
||||
Filter,
|
||||
type FilterDef,
|
||||
type Image,
|
||||
Page,
|
||||
Sort,
|
||||
createPage,
|
||||
isUuid,
|
||||
keysetPaginate,
|
||||
Page,
|
||||
processLanguages,
|
||||
Sort,
|
||||
sortToSql,
|
||||
} from "~/models/utils";
|
||||
import { desc } from "~/models/utils/descriptions";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { type SQL, and, eq, exists, sql } from "drizzle-orm";
|
||||
import { and, eq, exists, type SQL, sql } from "drizzle-orm";
|
||||
import Elysia, { t } from "elysia";
|
||||
import { auth } from "~/auth";
|
||||
import { prefix } from "~/base";
|
||||
@ -6,8 +6,8 @@ import { db } from "~/db";
|
||||
import {
|
||||
showStudioJoin,
|
||||
shows,
|
||||
studioTranslations,
|
||||
studios,
|
||||
studioTranslations,
|
||||
} from "~/db/schema";
|
||||
import {
|
||||
getColumns,
|
||||
@ -22,14 +22,14 @@ import { Show } from "~/models/show";
|
||||
import { Studio, StudioTranslation } from "~/models/studio";
|
||||
import {
|
||||
AcceptLanguage,
|
||||
Filter,
|
||||
Page,
|
||||
Sort,
|
||||
buildRelations,
|
||||
createPage,
|
||||
Filter,
|
||||
isUuid,
|
||||
keysetPaginate,
|
||||
Page,
|
||||
processLanguages,
|
||||
Sort,
|
||||
sortToSql,
|
||||
} from "~/models/utils";
|
||||
import { desc } from "~/models/utils/descriptions";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { and, eq, notExists, or, sql } from "drizzle-orm";
|
||||
import { Elysia, t } from "elysia";
|
||||
import { type Transaction, db } from "~/db";
|
||||
import { db, type Transaction } from "~/db";
|
||||
import { entries, entryVideoJoin, shows, videos } from "~/db/schema";
|
||||
import {
|
||||
conflictUpdateAllExcept,
|
||||
@ -13,12 +13,12 @@ import {
|
||||
import { KError } from "~/models/error";
|
||||
import { bubbleVideo } from "~/models/examples";
|
||||
import {
|
||||
Page,
|
||||
type Resource,
|
||||
Sort,
|
||||
createPage,
|
||||
isUuid,
|
||||
keysetPaginate,
|
||||
Page,
|
||||
type Resource,
|
||||
Sort,
|
||||
sortToSql,
|
||||
} from "~/models/utils";
|
||||
import { desc as description } from "~/models/utils/descriptions";
|
||||
|
@ -1,9 +1,9 @@
|
||||
export * from "./entries";
|
||||
export * from "./seasons";
|
||||
export * from "./shows";
|
||||
export * from "./studios";
|
||||
export * from "./staff";
|
||||
export * from "./videos";
|
||||
export * from "./profiles";
|
||||
export * from "./history";
|
||||
export * from "./mqueue";
|
||||
export * from "./profiles";
|
||||
export * from "./seasons";
|
||||
export * from "./shows";
|
||||
export * from "./staff";
|
||||
export * from "./studios";
|
||||
export * from "./videos";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { t } from "elysia";
|
||||
import { type Prettify, comment } from "~/utils";
|
||||
import { comment, type Prettify } from "~/utils";
|
||||
import { madeInAbyss, registerExamples } from "../examples";
|
||||
import { Progress } from "../history";
|
||||
import { DbMetadata, SeedImage } from "../utils";
|
||||
|
@ -13,6 +13,6 @@ export type SeedEntry = SeedEpisode | SeedMovieEntry | SeedSpecial;
|
||||
export type EntryKind = Entry["kind"] | Extra["kind"];
|
||||
|
||||
export * from "./episode";
|
||||
export * from "./extra";
|
||||
export * from "./movie-entry";
|
||||
export * from "./special";
|
||||
export * from "./extra";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { t } from "elysia";
|
||||
import { type Prettify, comment } from "~/utils";
|
||||
import { comment, type Prettify } from "~/utils";
|
||||
import { bubbleImages, madeInAbyss, registerExamples } from "../examples";
|
||||
import { Progress } from "../history";
|
||||
import {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { t } from "elysia";
|
||||
import { type Prettify, comment } from "~/utils";
|
||||
import { comment, type Prettify } from "~/utils";
|
||||
import { bubbleImages, madeInAbyss, registerExamples } from "../examples";
|
||||
import { Progress } from "../history";
|
||||
import {
|
||||
|
@ -31,7 +31,7 @@ export const registerExamples = <T extends TSchema>(
|
||||
};
|
||||
|
||||
export * from "./bubble";
|
||||
export * from "./made-in-abyss";
|
||||
export * from "./dune-1984";
|
||||
export * from "./dune-2021";
|
||||
export * from "./dune-collection";
|
||||
export * from "./made-in-abyss";
|
||||
|
@ -23,7 +23,10 @@ export type FilterDef = {
|
||||
export const Filter = ({
|
||||
def,
|
||||
description = "Filters to apply to the query.",
|
||||
}: { def: FilterDef; description?: string }) =>
|
||||
}: {
|
||||
def: FilterDef;
|
||||
description?: string;
|
||||
}) =>
|
||||
t
|
||||
.Transform(
|
||||
t.String({
|
||||
|
@ -1,11 +1,11 @@
|
||||
import {
|
||||
type Parjser,
|
||||
anyStringOf,
|
||||
digit,
|
||||
float,
|
||||
int,
|
||||
letter,
|
||||
noCharOf,
|
||||
type Parjser,
|
||||
string,
|
||||
} from "parjs";
|
||||
import {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {
|
||||
type BinaryOperator,
|
||||
type SQL,
|
||||
and,
|
||||
type BinaryOperator,
|
||||
eq,
|
||||
gt,
|
||||
gte,
|
||||
@ -10,6 +9,7 @@ import {
|
||||
ne,
|
||||
not,
|
||||
or,
|
||||
type SQL,
|
||||
sql,
|
||||
} from "drizzle-orm";
|
||||
import { KErrorT } from "~/models/error";
|
||||
|
@ -1,12 +1,12 @@
|
||||
export * from "./db-metadata";
|
||||
export * from "./external-id";
|
||||
export * from "./filters";
|
||||
export * from "./genres";
|
||||
export * from "./image";
|
||||
export * from "./language";
|
||||
export * from "./resource";
|
||||
export * from "./filters";
|
||||
export * from "./page";
|
||||
export * from "./sort";
|
||||
export * from "./keyset-paginate";
|
||||
export * from "./db-metadata";
|
||||
export * from "./language";
|
||||
export * from "./original";
|
||||
export * from "./page";
|
||||
export * from "./relations";
|
||||
export * from "./resource";
|
||||
export * from "./sort";
|
||||
|
@ -4,9 +4,7 @@ import {
|
||||
type TSchema,
|
||||
type TString,
|
||||
} from "@sinclair/typebox";
|
||||
import { type Column, eq, sql, type Table } from "drizzle-orm";
|
||||
import { t } from "elysia";
|
||||
import { sqlarr } from "~/db/utils";
|
||||
import { comment } from "../../utils";
|
||||
import { KErrorT } from "../error";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { PatternStringExact, type TSchema } from "@sinclair/typebox";
|
||||
import { t } from "elysia";
|
||||
import { type Prettify, comment } from "~/utils";
|
||||
import { comment, type Prettify } from "~/utils";
|
||||
import { ExtraType } from "./entry/extra";
|
||||
import { bubble, bubbleVideo, registerExamples } from "./examples";
|
||||
import { DbMetadata, EpisodeId, ExternalId, Resource } from "./utils";
|
||||
|
@ -1,8 +1,7 @@
|
||||
export * from "~/base";
|
||||
export * from "./movies-helper";
|
||||
export * from "./series-helper";
|
||||
export * from "./shows-helper";
|
||||
export * from "./studio-helper";
|
||||
export * from "./staff-helper";
|
||||
export * from "./studio-helper";
|
||||
export * from "./videos-helper";
|
||||
|
||||
export * from "~/base";
|
||||
|
@ -45,7 +45,11 @@ export const getSerie = async (
|
||||
export const getSeries = async ({
|
||||
langs,
|
||||
...query
|
||||
}: { langs?: string; preferOriginal?: boolean; with?: string[] }) => {
|
||||
}: {
|
||||
langs?: string;
|
||||
preferOriginal?: boolean;
|
||||
with?: string[];
|
||||
}) => {
|
||||
const resp = await handlers.handle(
|
||||
new Request(buildUrl("series", query), {
|
||||
method: "GET",
|
||||
|
@ -2,7 +2,7 @@ import { beforeAll, describe, expect, it } from "bun:test";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { expectStatus } from "tests/utils";
|
||||
import { db } from "~/db";
|
||||
import { showTranslations, shows, videos } from "~/db/schema";
|
||||
import { shows, showTranslations, videos } from "~/db/schema";
|
||||
import { bubble } from "~/models/examples";
|
||||
import { dune, duneVideo } from "~/models/examples/dune-2021";
|
||||
import { createMovie, createVideo } from "../helpers";
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
} from "tests/helpers";
|
||||
import { expectStatus } from "tests/utils";
|
||||
import { db } from "~/db";
|
||||
import { entries, entryVideoJoin, shows, videos } from "~/db/schema";
|
||||
import { entries, shows, videos } from "~/db/schema";
|
||||
import { bubble, madeInAbyss } from "~/models/examples";
|
||||
|
||||
beforeAll(async () => {
|
||||
|
@ -21,13 +21,18 @@
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off",
|
||||
"noArrayIndexKey": "off"
|
||||
"noArrayIndexKey": "off",
|
||||
"noTemplateCurlyInString": "off"
|
||||
},
|
||||
"security": {
|
||||
"noDangerouslySetInnerHtml": "off"
|
||||
},
|
||||
"complexity": {
|
||||
"noBannedTypes": "off"
|
||||
"noBannedTypes": "off",
|
||||
"noUselessUndefinedInitialization": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"noUnusedVariables": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1,3 +1,6 @@
|
||||
{
|
||||
"extends": "//"
|
||||
"extends": "//",
|
||||
"files": {
|
||||
"includes": ["src/**"]
|
||||
}
|
||||
}
|
||||
|
@ -8,15 +8,46 @@ export default function Root({ children }: PropsWithChildren) {
|
||||
<head>
|
||||
<title>Kyoo</title>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="description" content="A portable and vast media library solution." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<meta
|
||||
name="description"
|
||||
content="A portable and vast media library solution."
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/icon-16x16.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/icon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="/icon-64x64.png" />
|
||||
<link rel="icon" type="image/png" sizes="128x128" href="/icon-128x128.png" />
|
||||
<link rel="icon" type="image/png" sizes="256x256" href="/icon-256x256.png" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/icon-16x16.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/icon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="64x64"
|
||||
href="/icon-64x64.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="128x128"
|
||||
href="/icon-128x128.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="256x256"
|
||||
href="/icon-256x256.png"
|
||||
/>
|
||||
|
||||
<ScrollViewStyleReset />
|
||||
</head>
|
||||
|
@ -1,12 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { type ImageStyle, Platform, View } from "react-native";
|
||||
import {
|
||||
percent,
|
||||
px,
|
||||
type Stylable,
|
||||
type Theme,
|
||||
useYoshiki,
|
||||
} from "yoshiki/native";
|
||||
import { percent, type Stylable, type Theme, useYoshiki } from "yoshiki/native";
|
||||
import type { KImage, WatchStatusV } from "~/models";
|
||||
import {
|
||||
focusReset,
|
||||
|
@ -14,7 +14,8 @@ export const ItemWatchStatus = ({
|
||||
}) => {
|
||||
const { css } = useYoshiki();
|
||||
|
||||
if (watchStatus !== WatchStatusV.Completed && !unseenEpisodesCount) return null;
|
||||
if (watchStatus !== WatchStatusV.Completed && !unseenEpisodesCount)
|
||||
return null;
|
||||
|
||||
return (
|
||||
<View
|
||||
@ -38,7 +39,13 @@ export const ItemWatchStatus = ({
|
||||
{watchStatus === WatchStatusV.Completed ? (
|
||||
<Icon icon={Done} size={16} />
|
||||
) : (
|
||||
<P {...css({ marginVertical: 0, verticalAlign: "middle", textAlign: "center" })}>
|
||||
<P
|
||||
{...css({
|
||||
marginVertical: 0,
|
||||
verticalAlign: "middle",
|
||||
textAlign: "center",
|
||||
})}
|
||||
>
|
||||
{unseenEpisodesCount}
|
||||
</P>
|
||||
)}
|
||||
|
@ -1,7 +1,7 @@
|
||||
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 Bookmark from "@material-symbols/svg-400/rounded/bookmark-fill.svg";
|
||||
import type { ComponentProps } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { Serie } from "~/models";
|
||||
@ -10,7 +10,13 @@ import { useAccount } from "~/providers/account-context";
|
||||
import { useMutation } from "~/query";
|
||||
|
||||
type WatchStatus = NonNullable<Serie["watchStatus"]>["status"];
|
||||
const WatchStatus = ["completed", "watching", "rewatching", "dropped", "planned"] as const;
|
||||
const WatchStatus = [
|
||||
"completed",
|
||||
"watching",
|
||||
"rewatching",
|
||||
"dropped",
|
||||
"planned",
|
||||
] as const;
|
||||
|
||||
export const watchListIcon = (status: WatchStatus | null) => {
|
||||
switch (status) {
|
||||
@ -51,7 +57,12 @@ export const WatchListInfo = ({
|
||||
|
||||
if (account == null) {
|
||||
return (
|
||||
<IconButton icon={BookmarkAdd} disabled {...tooltip(t("show.watchlistLogin"))} {...props} />
|
||||
<IconButton
|
||||
icon={BookmarkAdd}
|
||||
disabled
|
||||
{...tooltip(t("show.watchlistLogin"))}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -93,7 +104,10 @@ export const WatchListInfo = ({
|
||||
selected={x === status}
|
||||
/>
|
||||
))}
|
||||
<Menu.Item label={t("show.watchlistMark.null")} onSelect={() => mutation.mutate(null)} />
|
||||
<Menu.Item
|
||||
label={t("show.watchlistMark.null")}
|
||||
onSelect={() => mutation.mutate(null)}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
default:
|
||||
|
@ -1,15 +1,12 @@
|
||||
export * from "./utils/page";
|
||||
export * from "./collection";
|
||||
export * from "./entry";
|
||||
export * from "./kyoo-error";
|
||||
|
||||
export * from "./movie";
|
||||
export * from "./serie";
|
||||
export * from "./collection";
|
||||
export * from "./show";
|
||||
export * from "./entry";
|
||||
export * from "./studio";
|
||||
export * from "./video";
|
||||
|
||||
export * from "./user";
|
||||
|
||||
export * from "./utils/images";
|
||||
export * from "./utils/genre";
|
||||
export * from "./utils/images";
|
||||
export * from "./utils/page";
|
||||
export * from "./video";
|
||||
|
@ -23,7 +23,8 @@ export const EpisodeP = BaseEpisodeP.and(
|
||||
watchStatus: WatchStatusP.optional().nullable(),
|
||||
}),
|
||||
).transform((x) => {
|
||||
if (x.show && !x.thumbnail && x.show.thumbnail) x.thumbnail = x.show.thumbnail;
|
||||
if (x.show && !x.thumbnail && x.show.thumbnail)
|
||||
x.thumbnail = x.show.thumbnail;
|
||||
return x;
|
||||
});
|
||||
|
||||
|
@ -169,9 +169,11 @@ export const WatchInfoP = z
|
||||
// from https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
|
||||
const humanFileSize = (size: number): string => {
|
||||
const i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
|
||||
return (
|
||||
// @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];
|
||||
// biome-ignore lint/style/useTemplate: same as above
|
||||
(size / 1024 ** i).toFixed(2) * 1 + " " + ["B", "kB", "MB", "GB", "TB"][i]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -11,4 +11,10 @@ export const Show = z.union([
|
||||
export type Show = z.infer<typeof Show>;
|
||||
|
||||
export type WatchStatusV = NonNullable<Serie["watchStatus"]>["status"];
|
||||
export const WatchStatusV = ["completed", "watching", "rewatching", "dropped", "planned"] as const;
|
||||
export const WatchStatusV = [
|
||||
"completed",
|
||||
"watching",
|
||||
"rewatching",
|
||||
"dropped",
|
||||
"planned",
|
||||
] as const;
|
||||
|
@ -1,6 +1,10 @@
|
||||
// Stolen from https://github.com/necolas/react-native-web/issues/1026#issuecomment-1458279681
|
||||
|
||||
import { type AlertButton, type AlertOptions, Alert as RNAlert } from "react-native";
|
||||
import {
|
||||
type AlertButton,
|
||||
type AlertOptions,
|
||||
Alert as RNAlert,
|
||||
} from "react-native";
|
||||
import type { SweetAlertIcon } from "sweetalert2";
|
||||
|
||||
export interface ExtendedAlertStatic {
|
||||
|
@ -37,7 +37,9 @@ export class Alert {
|
||||
const denyButton = buttons
|
||||
? buttons.find((button) => button.style === "destructive")
|
||||
: undefined;
|
||||
const cancelButton = buttons ? buttons.find((button) => button.style === "cancel") : undefined;
|
||||
const cancelButton = buttons
|
||||
? buttons.find((button) => button.style === "cancel")
|
||||
: undefined;
|
||||
|
||||
Swal.fire({
|
||||
title: title,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import AccountCircle from "@material-symbols/svg-400/rounded/account_circle-fill.svg";
|
||||
import { type ComponentType, type RefAttributes, forwardRef } from "react";
|
||||
import { type ComponentType, forwardRef, type RefAttributes } from "react";
|
||||
import { Image, type ImageProps, View, type ViewStyle } from "react-native";
|
||||
import { type Stylable, px, useYoshiki } from "yoshiki/native";
|
||||
import { px, type Stylable, useYoshiki } from "yoshiki/native";
|
||||
import { Icon } from "./icons";
|
||||
import { Skeleton } from "./skeleton";
|
||||
import { P } from "./text";
|
||||
@ -73,7 +73,11 @@ const AvatarC = forwardRef<
|
||||
{placeholder[0]}
|
||||
</P>
|
||||
) : (
|
||||
<Icon icon={AccountCircle} size={size} color={fill ? col : theme.colors.white} />
|
||||
<Icon
|
||||
icon={AccountCircle}
|
||||
size={size}
|
||||
color={fill ? col : theme.colors.white}
|
||||
/>
|
||||
)}
|
||||
<Image
|
||||
resizeMode="cover"
|
||||
|
@ -1,4 +1,9 @@
|
||||
import { type ComponentType, type ForwardedRef, type ReactElement, forwardRef } from "react";
|
||||
import {
|
||||
type ComponentType,
|
||||
type ForwardedRef,
|
||||
forwardRef,
|
||||
type ReactElement,
|
||||
} from "react";
|
||||
import { type Falsy, type PressableProps, View } from "react-native";
|
||||
import { type Theme, useYoshiki } from "yoshiki/native";
|
||||
import { PressableFeedback } from "./links";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { HR as EHR } from "@expo/html-elements";
|
||||
import { type Stylable, px, useYoshiki } from "yoshiki/native";
|
||||
import { px, type Stylable, useYoshiki } from "yoshiki/native";
|
||||
import { ts } from "./utils";
|
||||
|
||||
export const HR = ({
|
||||
|
@ -43,7 +43,9 @@ export const Sprite = ({
|
||||
style?: object;
|
||||
}) => {
|
||||
return (
|
||||
<View style={{ width, height, overflow: "hidden", flexGrow: 0, flexShrink: 0 }}>
|
||||
<View
|
||||
style={{ width, height, overflow: "hidden", flexGrow: 0, flexShrink: 0 }}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: src }}
|
||||
alt={alt}
|
||||
|
@ -1,24 +1,24 @@
|
||||
export { Header, Main, Nav, Footer, UL } from "@expo/html-elements";
|
||||
export * from "./text";
|
||||
export * from "./theme";
|
||||
export * from "./icons";
|
||||
export * from "./links";
|
||||
export { Footer, Header, Main, Nav, UL } from "@expo/html-elements";
|
||||
export * from "./avatar";
|
||||
export * from "./image";
|
||||
export * from "./image-background";
|
||||
export * from "./skeleton";
|
||||
export * from "./tooltip";
|
||||
export * from "./button";
|
||||
export * from "./chip";
|
||||
export * from "./container";
|
||||
export * from "./divider";
|
||||
export * from "./icons";
|
||||
export * from "./image";
|
||||
export * from "./image-background";
|
||||
// export * from "./popup";
|
||||
// export * from "./select";
|
||||
export * from "./input";
|
||||
export * from "./links";
|
||||
// export * from "./progress";
|
||||
// export * from "./slider";
|
||||
// export * from "./snackbar";
|
||||
// export * from "./alert";
|
||||
export * from "./menu";
|
||||
// export * from "./popup";
|
||||
// export * from "./select";
|
||||
export * from "./input";
|
||||
export * from "./button";
|
||||
export * from "./chip";
|
||||
export * from "./skeleton";
|
||||
export * from "./text";
|
||||
export * from "./theme";
|
||||
export * from "./tooltip";
|
||||
|
||||
export * from "./utils";
|
||||
|
@ -1,6 +1,11 @@
|
||||
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 { forwardRef, type ReactNode, useState } from "react";
|
||||
import {
|
||||
TextInput,
|
||||
type TextInputProps,
|
||||
View,
|
||||
type ViewStyle,
|
||||
} from "react-native";
|
||||
import { px, type Theme, useYoshiki } from "yoshiki/native";
|
||||
import type { YoshikiEnhanced } from "./image/base-image";
|
||||
import { focusReset, ts } from "./utils";
|
||||
|
||||
|
@ -4,9 +4,9 @@ import Close from "@material-symbols/svg-400/rounded/close-fill.svg";
|
||||
import { useRouter } from "expo-router";
|
||||
import {
|
||||
type ComponentType,
|
||||
createContext,
|
||||
type ReactElement,
|
||||
type ReactNode,
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
@ -22,7 +22,9 @@ import { P } from "./text";
|
||||
import { ContrastArea, SwitchVariant } from "./theme";
|
||||
import { ts } from "./utils";
|
||||
|
||||
const MenuContext = createContext<((open: boolean) => void) | undefined>(undefined);
|
||||
const MenuContext = createContext<((open: boolean) => void) | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
type Optional<T, K extends keyof any> = Omit<T, K> & Partial<T>;
|
||||
|
||||
@ -45,7 +47,10 @@ const Menu = <AsProps,>({
|
||||
const insets = useSafeAreaInsets();
|
||||
const alreadyRendered = useRef(false);
|
||||
const [isOpen, setOpen] =
|
||||
outerOpen !== undefined && outerSetOpen ? [outerOpen, outerSetOpen] : useState(false);
|
||||
outerOpen !== undefined && outerSetOpen
|
||||
? [outerOpen, outerSetOpen]
|
||||
: // biome-ignore lint/correctness/useHookAtTopLevel: const
|
||||
useState(false);
|
||||
|
||||
// does the same as a useMemo but for props.
|
||||
const memoRef = useRef({ onMenuOpen, onMenuClose });
|
||||
@ -73,7 +78,11 @@ const Menu = <AsProps,>({
|
||||
<Pressable
|
||||
onPress={() => setOpen(false)}
|
||||
tabIndex={-1}
|
||||
{...css({ ...StyleSheet.absoluteFillObject, flexGrow: 1, bg: "transparent" })}
|
||||
{...css({
|
||||
...StyleSheet.absoluteFillObject,
|
||||
flexGrow: 1,
|
||||
bg: "transparent",
|
||||
})}
|
||||
/>
|
||||
<View
|
||||
{...css([
|
||||
@ -107,7 +116,10 @@ const Menu = <AsProps,>({
|
||||
icon={Close}
|
||||
color={theme.colors.black}
|
||||
onPress={() => setOpen(false)}
|
||||
{...css({ alignSelf: "flex-end", display: { xs: "none", xl: "flex" } })}
|
||||
{...css({
|
||||
alignSelf: "flex-end",
|
||||
display: { xs: "none", xl: "flex" },
|
||||
})}
|
||||
/>
|
||||
{children}
|
||||
</ScrollView>
|
||||
@ -137,7 +149,10 @@ const MenuItem = ({
|
||||
left?: ReactElement;
|
||||
disabled?: boolean;
|
||||
icon?: ComponentType<SvgProps>;
|
||||
} & ({ onSelect: () => void; href?: undefined } | { href: string; onSelect?: undefined })) => {
|
||||
} & (
|
||||
| { onSelect: () => void; href?: undefined }
|
||||
| { href: string; onSelect?: undefined }
|
||||
)) => {
|
||||
const { css, theme } = useYoshiki();
|
||||
const setOpen = useContext(MenuContext);
|
||||
const router = useRouter();
|
||||
@ -174,7 +189,10 @@ const MenuItem = ({
|
||||
{!left && icn && icn}
|
||||
<P
|
||||
{...css([
|
||||
{ paddingLeft: ts(2) + +!(icon || selected || left) * px(24), flexGrow: 1 },
|
||||
{
|
||||
paddingLeft: ts(2) + +!(icon || selected || left) * px(24),
|
||||
flexGrow: 1,
|
||||
},
|
||||
disabled && { color: theme.overlay0 },
|
||||
])}
|
||||
>
|
||||
|
@ -27,7 +27,12 @@ import { Container } from "./container";
|
||||
import { ContrastArea, SwitchVariant, type YoshikiFunc } from "./themes";
|
||||
import { ts } from "./utils";
|
||||
|
||||
export const Popup = ({ children, ...props }: { children: ReactNode | YoshikiFunc<ReactNode> }) => {
|
||||
export const Popup = ({
|
||||
children,
|
||||
...props
|
||||
}: {
|
||||
children: ReactNode | YoshikiFunc<ReactNode>;
|
||||
}) => {
|
||||
return (
|
||||
<ContrastArea mode="user">
|
||||
<SwitchVariant>
|
||||
@ -67,7 +72,9 @@ export const Popup = ({ children, ...props }: { children: ReactNode | YoshikiFun
|
||||
flexShrink: 1,
|
||||
})}
|
||||
>
|
||||
{typeof children === "function" ? children({ css, theme }) : children}
|
||||
{typeof children === "function"
|
||||
? children({ css, theme })
|
||||
: children}
|
||||
</ScrollView>
|
||||
</Container>
|
||||
</View>
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
import { ActivityIndicator, Platform, View } from "react-native";
|
||||
import { Circle, Svg } from "react-native-svg";
|
||||
import { type Stylable, px, useYoshiki } from "yoshiki/native";
|
||||
import { px, type Stylable, useYoshiki } from "yoshiki/native";
|
||||
|
||||
export const CircularProgress = ({
|
||||
size = 48,
|
||||
@ -31,7 +31,9 @@ export const CircularProgress = ({
|
||||
const { css, theme } = useYoshiki();
|
||||
|
||||
if (Platform.OS !== "web")
|
||||
return <ActivityIndicator size={size} color={color ?? theme.accent} {...props} />;
|
||||
return (
|
||||
<ActivityIndicator size={size} color={color ?? theme.accent} {...props} />
|
||||
);
|
||||
|
||||
return (
|
||||
<View {...css({ width: size, height: size, overflow: "hidden" }, props)}>
|
||||
@ -63,7 +65,9 @@ export const CircularProgress = ({
|
||||
viewBox={`${size / 2} ${size / 2} ${size} ${size}`}
|
||||
{...css(
|
||||
// @ts-ignore Web only
|
||||
Platform.OS === "web" && { animation: "circularProgress-svg 1.4s ease-in-out infinite" },
|
||||
Platform.OS === "web" && {
|
||||
animation: "circularProgress-svg 1.4s ease-in-out infinite",
|
||||
},
|
||||
)}
|
||||
>
|
||||
<Circle
|
||||
|
@ -24,7 +24,6 @@ import { Icon } from "./icons";
|
||||
import { Menu } from "./menu";
|
||||
|
||||
export const Select = <Value extends string>({
|
||||
label,
|
||||
value,
|
||||
onValueChange,
|
||||
values,
|
||||
@ -37,7 +36,11 @@ export const Select = <Value extends string>({
|
||||
getLabel: (key: Value) => string;
|
||||
}) => {
|
||||
return (
|
||||
<Menu Trigger={Button} text={getLabel(value)} icon={<Icon icon={ExpandMore} />}>
|
||||
<Menu
|
||||
Trigger={Button}
|
||||
text={getLabel(value)}
|
||||
icon={<Icon icon={ExpandMore} />}
|
||||
>
|
||||
{values.map((x) => (
|
||||
<Menu.Item
|
||||
key={x}
|
||||
|
@ -25,7 +25,7 @@ import * as RSelect from "@radix-ui/react-select";
|
||||
import { forwardRef } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useYoshiki } from "yoshiki";
|
||||
import { type Theme, px, useYoshiki as useNativeYoshiki } from "yoshiki/native";
|
||||
import { px, type Theme, useYoshiki as useNativeYoshiki } from "yoshiki/native";
|
||||
import { Icon } from "./icons";
|
||||
import { PressableFeedback } from "./links";
|
||||
import { InternalTriger, YoshikiProvider } from "./menu.web";
|
||||
@ -78,7 +78,9 @@ export const Select = ({
|
||||
})}
|
||||
>
|
||||
<P {...css({ textAlign: "center" }, "text")}>{<RSelect.Value />}</P>
|
||||
<RSelect.Icon {...wCss({ display: "flex", justifyContent: "center" })}>
|
||||
<RSelect.Icon
|
||||
{...wCss({ display: "flex", justifyContent: "center" })}
|
||||
>
|
||||
<Icon icon={ExpandMore} />
|
||||
</RSelect.Icon>
|
||||
</View>
|
||||
@ -98,7 +100,8 @@ export const Select = ({
|
||||
boxShadow:
|
||||
"0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)",
|
||||
zIndex: 2,
|
||||
maxHeight: "calc(var(--radix-dropdown-menu-content-available-height) * 0.8)",
|
||||
maxHeight:
|
||||
"calc(var(--radix-dropdown-menu-content-available-height) * 0.8)",
|
||||
})}
|
||||
>
|
||||
<RSelect.ScrollUpButton>
|
||||
@ -122,10 +125,8 @@ export const Select = ({
|
||||
);
|
||||
};
|
||||
|
||||
const Item = forwardRef<HTMLDivElement, { label: string; value: string }>(function Item(
|
||||
{ label, value, ...props },
|
||||
ref,
|
||||
) {
|
||||
const Item = forwardRef<HTMLDivElement, { label: string; value: string }>(
|
||||
function Item({ label, value, ...props }, ref) {
|
||||
const { css, theme } = useYoshiki();
|
||||
const { css: nCss } = useNativeYoshiki();
|
||||
return (
|
||||
@ -156,7 +157,9 @@ const Item = forwardRef<HTMLDivElement, { label: string; value: string }>(functi
|
||||
props as any,
|
||||
)}
|
||||
>
|
||||
<RSelect.ItemText {...css({ color: (theme) => theme.paragraph })}>{label}</RSelect.ItemText>
|
||||
<RSelect.ItemText {...css({ color: (theme) => theme.paragraph })}>
|
||||
{label}
|
||||
</RSelect.ItemText>
|
||||
<RSelect.ItemIndicator asChild>
|
||||
<InternalTriger
|
||||
Component={Icon}
|
||||
@ -173,4 +176,5 @@ const Item = forwardRef<HTMLDivElement, { label: string; value: string }>(functi
|
||||
</RSelect.Item>
|
||||
</>
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
@ -32,7 +32,10 @@ export const Skeleton = ({
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
mult.value = withRepeat(withDelay(800, withTiming(1, { duration: 800 })), 0);
|
||||
mult.value = withRepeat(
|
||||
withDelay(800, withTiming(1, { duration: 800 })),
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -21,7 +21,7 @@
|
||||
import { useRef, useState } from "react";
|
||||
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 { percent, px, useYoshiki } from "yoshiki/native";
|
||||
import { focusReset } from "./utils";
|
||||
|
||||
export const Slider = ({
|
||||
@ -80,7 +80,10 @@ export const Slider = ({
|
||||
}}
|
||||
// @ts-ignore Web only
|
||||
onMouseMove={(e) =>
|
||||
onHover?.(Math.max(0, Math.min((e.clientX - layout.x) / layout.width, 1) * max), layout)
|
||||
onHover?.(
|
||||
Math.max(0, Math.min((e.clientX - layout.x) / layout.width, 1) * max),
|
||||
layout,
|
||||
)
|
||||
}
|
||||
tabIndex={0}
|
||||
onFocus={() => setFocus(true)}
|
||||
|
@ -19,7 +19,13 @@
|
||||
*/
|
||||
|
||||
import { usePortal } from "@gorhom/portal";
|
||||
import { type ReactElement, createContext, useCallback, useContext, useRef } from "react";
|
||||
import {
|
||||
createContext,
|
||||
type ReactElement,
|
||||
useCallback,
|
||||
useContext,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { View } from "react-native";
|
||||
import { percent, px } from "yoshiki/native";
|
||||
import { Button } from "./button";
|
||||
@ -43,14 +49,21 @@ export type Action = {
|
||||
|
||||
const SnackbarContext = createContext<(snackbar: Snackbar) => void>(null!);
|
||||
|
||||
export const SnackbarProvider = ({ children }: { children: ReactElement | ReactElement[] }) => {
|
||||
export const SnackbarProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactElement | ReactElement[];
|
||||
}) => {
|
||||
const { addPortal, removePortal } = usePortal();
|
||||
const snackbars = useRef<Snackbar[]>([]);
|
||||
const timeout = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const createSnackbar = useCallback(
|
||||
(snackbar: Snackbar) => {
|
||||
if (snackbar.key) snackbars.current = snackbars.current.filter((x) => snackbar.key !== x.key);
|
||||
if (snackbar.key)
|
||||
snackbars.current = snackbars.current.filter(
|
||||
(x) => snackbar.key !== x.key,
|
||||
);
|
||||
snackbars.current.unshift(snackbar);
|
||||
|
||||
if (timeout.current) return;
|
||||
@ -73,7 +86,11 @@ export const SnackbarProvider = ({ children }: { children: ReactElement | ReactE
|
||||
[addPortal, removePortal],
|
||||
);
|
||||
|
||||
return <SnackbarContext.Provider value={createSnackbar}>{children}</SnackbarContext.Provider>;
|
||||
return (
|
||||
<SnackbarContext.Provider value={createSnackbar}>
|
||||
{children}
|
||||
</SnackbarContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useSnackbar = () => {
|
||||
|
@ -8,7 +8,13 @@ import {
|
||||
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 {
|
||||
Platform,
|
||||
type StyleProp,
|
||||
Text,
|
||||
type TextProps,
|
||||
type TextStyle,
|
||||
} from "react-native";
|
||||
import { percent, rem, useYoshiki } from "yoshiki/native";
|
||||
import { ts } from "./utils/spacing";
|
||||
|
||||
@ -55,7 +61,10 @@ const styleText = (
|
||||
return Text;
|
||||
};
|
||||
|
||||
export const H1 = styleText(EH1, "header", { fontSize: rem(3), fontWeight: "900" });
|
||||
export const H1 = styleText(EH1, "header", {
|
||||
fontSize: rem(3),
|
||||
fontWeight: "900",
|
||||
});
|
||||
export const H2 = styleText(EH2, "header", { fontSize: rem(2) });
|
||||
export const H3 = styleText(EH3, "header");
|
||||
export const H4 = styleText(EH4, "header");
|
||||
|
@ -1,2 +1,2 @@
|
||||
export * from "./theme";
|
||||
export * from "./catppuccin";
|
||||
export * from "./theme";
|
||||
|
@ -1,7 +1,11 @@
|
||||
import type { Property } from "csstype";
|
||||
import type { ReactNode } from "react";
|
||||
import { Platform, type TextStyle } from "react-native";
|
||||
import { type Theme, ThemeProvider as WebThemeProvider, useAutomaticTheme } from "yoshiki";
|
||||
import {
|
||||
type Theme,
|
||||
useAutomaticTheme,
|
||||
ThemeProvider as WebThemeProvider,
|
||||
} from "yoshiki";
|
||||
import "yoshiki";
|
||||
import { ThemeProvider, useTheme, useYoshiki } from "yoshiki/native";
|
||||
import "yoshiki/native";
|
||||
@ -52,7 +56,9 @@ declare module "yoshiki" {
|
||||
|
||||
export type { Theme } from "yoshiki";
|
||||
export type ThemeBuilder = {
|
||||
light: Omit<Mode, "contrast" | "mode" | "themeOverlay"> & { default: Variant };
|
||||
light: Omit<Mode, "contrast" | "mode" | "themeOverlay"> & {
|
||||
default: Variant;
|
||||
};
|
||||
dark: Omit<Mode, "contrast" | "mode" | "themeOverlay"> & { default: Variant };
|
||||
};
|
||||
|
||||
@ -88,8 +94,13 @@ const selectMode = (
|
||||
};
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/useHookAtTopLevel: const
|
||||
const auto = useAutomaticTheme("theme", { light, dark });
|
||||
const alternate = useAutomaticTheme("alternate", { dark: light, light: dark });
|
||||
// biome-ignore lint/correctness/useHookAtTopLevel: const
|
||||
const alternate = useAutomaticTheme("alternate", {
|
||||
dark: light,
|
||||
light: dark,
|
||||
});
|
||||
return {
|
||||
...options,
|
||||
...auto,
|
||||
@ -136,12 +147,20 @@ export const ThemeSelector = ({
|
||||
|
||||
export type YoshikiFunc<T> = (props: ReturnType<typeof useYoshiki>) => T;
|
||||
|
||||
const YoshikiProvider = ({ children }: { children: YoshikiFunc<ReactNode> }) => {
|
||||
const YoshikiProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: YoshikiFunc<ReactNode>;
|
||||
}) => {
|
||||
const yoshiki = useYoshiki();
|
||||
return <>{children(yoshiki)}</>;
|
||||
};
|
||||
|
||||
export const SwitchVariant = ({ children }: { children: ReactNode | YoshikiFunc<ReactNode> }) => {
|
||||
export const SwitchVariant = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode | YoshikiFunc<ReactNode>;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { useWindowDimensions } from "react-native";
|
||||
import { breakpoints, isBreakpoints, type Breakpoints as YoshikiBreakpoint } from "yoshiki/native";
|
||||
import {
|
||||
breakpoints,
|
||||
isBreakpoints,
|
||||
type Breakpoints as YoshikiBreakpoint,
|
||||
} from "yoshiki/native";
|
||||
|
||||
type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];
|
||||
type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> &
|
||||
U[keyof U];
|
||||
export type Breakpoint<T> = T | AtLeastOne<YoshikiBreakpoint<T>>;
|
||||
|
||||
// copied from yoshiki.
|
||||
@ -36,6 +41,9 @@ export const useBreakpointMap = <T extends Record<string, unknown>>(
|
||||
const breakpoint = useBreakpoint();
|
||||
// @ts-ignore
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, val]) => [key, getBreakpointValue(val, breakpoint)]),
|
||||
Object.entries(value).map(([key, val]) => [
|
||||
key,
|
||||
getBreakpointValue(val, breakpoint),
|
||||
]),
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
export * from "./nojs";
|
||||
export * from "./head";
|
||||
export * from "./spacing";
|
||||
export * from "./capitalize";
|
||||
export * from "./touchonly";
|
||||
export * from "./page-style";
|
||||
export * from "./breakpoint";
|
||||
export * from "./capitalize";
|
||||
export * from "./head";
|
||||
export * from "./nojs";
|
||||
export * from "./page-style";
|
||||
export * from "./spacing";
|
||||
export * from "./touchonly";
|
||||
|
@ -1,6 +1,8 @@
|
||||
import type { ViewProps } from "react-native";
|
||||
|
||||
export const hiddenIfNoJs: ViewProps = { style: { $$css: true, noJs: "noJsHidden" } as any };
|
||||
export const hiddenIfNoJs: ViewProps = {
|
||||
style: { $$css: true, noJs: "noJsHidden" } as any,
|
||||
};
|
||||
|
||||
export const HiddenIfNoJs = () => (
|
||||
<noscript>
|
||||
|
@ -14,10 +14,16 @@ export const TouchOnlyCss = () => {
|
||||
};
|
||||
|
||||
export const touchOnly: ViewProps = {
|
||||
style: Platform.OS === "web" ? ({ $$css: true, touchOnly: "touchOnly" } as any) : {},
|
||||
style:
|
||||
Platform.OS === "web"
|
||||
? ({ $$css: true, touchOnly: "touchOnly" } as any)
|
||||
: {},
|
||||
};
|
||||
export const noTouch: ViewProps = {
|
||||
style: Platform.OS === "web" ? ({ $$css: true, noTouch: "noTouch" } as any) : { display: "none" },
|
||||
style:
|
||||
Platform.OS === "web"
|
||||
? ({ $$css: true, noTouch: "noTouch" } as any)
|
||||
: { display: "none" },
|
||||
};
|
||||
|
||||
export const useIsTouch = () => {
|
||||
|
@ -2,7 +2,13 @@ import { type ReactNode, useContext } from "react";
|
||||
import { ErrorView, errorHandlers } from "~/ui/errors";
|
||||
import { ErrorContext } from "./error-provider";
|
||||
|
||||
export const ErrorConsumer = ({ children, scope }: { children: ReactNode; scope: string }) => {
|
||||
export const ErrorConsumer = ({
|
||||
children,
|
||||
scope,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
scope: string;
|
||||
}) => {
|
||||
const error = useContext(ErrorContext);
|
||||
if (!error) return children;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {
|
||||
type ReactNode,
|
||||
createContext,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
@ -46,7 +46,10 @@ export const ErrorProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
export const useSetError = (key: string) => {
|
||||
const { setError, clearError } = useContext(ErrorSetterContext);
|
||||
const set = ({ key: nKey, ...obj }: Omit<Error, "key"> & { key?: Error["key"] } = {}) =>
|
||||
const set = ({
|
||||
key: nKey,
|
||||
...obj
|
||||
}: Omit<Error, "key"> & { key?: Error["key"] } = {}) =>
|
||||
setError({ key: nKey ?? key, ...obj });
|
||||
const clear = () => clearError(key);
|
||||
return [set, clear] as const;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useFetch } from "~/query";
|
||||
import { useAccount } from "./account-context";
|
||||
// import { useFetch } from "~/query";
|
||||
// import { useAccount } from "./account-context";
|
||||
// import { ServerInfoP } from "~/models/resources/server-info";
|
||||
|
||||
// export const useHasPermission = (perms?: string[]) => {
|
||||
|
@ -23,6 +23,7 @@ export const setCookie = (key: string, val?: unknown) => {
|
||||
const expires = value
|
||||
? `expires=${d.toUTCString()}`
|
||||
: "expires=Thu, 01 Jan 1970 00:00:01 GMT";
|
||||
// biome-ignore lint/suspicious/noDocumentCookie: idk
|
||||
document.cookie = `${key}=${value};${expires};path=/;samesite=strict`;
|
||||
};
|
||||
|
||||
@ -43,6 +44,7 @@ export const useStoreValue = <T extends ZodType>(key: string, parser: T) => {
|
||||
if (Platform.OS === "web" && typeof window === "undefined") {
|
||||
return readCookie(key, parser);
|
||||
}
|
||||
// biome-ignore lint/correctness/useHookAtTopLevel: constant
|
||||
const [val] = useMMKVString(key);
|
||||
if (val === undefined) return val;
|
||||
return parser.parse(JSON.parse(val)) as z.infer<T>;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import i18next from "i18next";
|
||||
import { type ReactNode, useMemo } from "react";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { resources, supportedLanguages } from "./translations.compile";
|
||||
import { setServerData } from "~/utils";
|
||||
import { resources, supportedLanguages } from "./translations.compile";
|
||||
|
||||
export const TranslationsProvider = ({ children }: { children: ReactNode }) => {
|
||||
const val = useMemo(() => {
|
||||
|
@ -2,8 +2,8 @@ import i18next from "i18next";
|
||||
import HttpApi, { type HttpBackendOptions } from "i18next-http-backend";
|
||||
import { type ReactNode, useMemo } from "react";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { supportedLanguages } from "./translations.compile";
|
||||
import { getServerData } from "~/utils";
|
||||
import { supportedLanguages } from "./translations.compile";
|
||||
|
||||
export const TranslationsProvider = ({ children }: { children: ReactNode }) => {
|
||||
const val = useMemo(() => {
|
||||
|
@ -22,7 +22,7 @@ export const Fetch = <Data,>({
|
||||
if (error && (error.status === 401 || error.status === 403)) {
|
||||
setError({ key: "unauthorized", error });
|
||||
}
|
||||
}, [error, isPaused]);
|
||||
}, [error, isPaused, setError]);
|
||||
|
||||
if (error) {
|
||||
return <ErrorView error={error} />;
|
||||
|
@ -1,3 +1,3 @@
|
||||
export * from "./query";
|
||||
export * from "./fetch";
|
||||
export * from "./fetch-infinite";
|
||||
export * from "./query";
|
||||
|
@ -1,3 +1,9 @@
|
||||
export const availableSorts = ["name", "startAir", "endAir", "createdAt", "rating"] as const;
|
||||
export const availableSorts = [
|
||||
"name",
|
||||
"startAir",
|
||||
"endAir",
|
||||
"createdAt",
|
||||
"rating",
|
||||
] as const;
|
||||
export type SortBy = (typeof availableSorts)[number];
|
||||
export type SortOrd = "asc" | "desc";
|
||||
|
@ -18,6 +18,8 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** biome-ignore-all lint/correctness/noUnusedImports: TODO */
|
||||
|
||||
import {
|
||||
type Collection,
|
||||
CollectionP,
|
||||
@ -27,12 +29,12 @@ import {
|
||||
} from "@kyoo/models";
|
||||
import {
|
||||
Container,
|
||||
focusReset,
|
||||
GradientImageBackground,
|
||||
H2,
|
||||
ImageBackground,
|
||||
Link,
|
||||
P,
|
||||
focusReset,
|
||||
ts,
|
||||
} from "@kyoo/primitives";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@ -90,8 +92,16 @@ export const PartOf = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const DetailsCollections = ({ type, slug }: { type: "movie" | "show"; slug: string }) => {
|
||||
const { items, error } = useInfiniteFetch(DetailsCollections.query(type, slug));
|
||||
export const DetailsCollections = ({
|
||||
type,
|
||||
slug,
|
||||
}: {
|
||||
type: "movie" | "show";
|
||||
slug: string;
|
||||
}) => {
|
||||
const { items, error } = useInfiniteFetch(
|
||||
DetailsCollections.query(type, slug),
|
||||
);
|
||||
const { css } = useYoshiki();
|
||||
|
||||
if (error) return <ErrorView error={error} />;
|
||||
@ -114,7 +124,10 @@ export const DetailsCollections = ({ type, slug }: { type: "movie" | "show"; slu
|
||||
);
|
||||
};
|
||||
|
||||
DetailsCollections.query = (type: "movie" | "show", slug: string): QueryIdentifier<Collection> => ({
|
||||
DetailsCollections.query = (
|
||||
type: "movie" | "show",
|
||||
slug: string,
|
||||
): QueryIdentifier<Collection> => ({
|
||||
parser: CollectionP,
|
||||
path: [type, slug, "collections"],
|
||||
params: {
|
||||
|
@ -18,20 +18,22 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** biome-ignore-all lint/correctness/noUnusedImports: TODO */
|
||||
|
||||
import { type KyooImage, WatchStatusV } from "@kyoo/models";
|
||||
import {
|
||||
focusReset,
|
||||
H6,
|
||||
IconButton,
|
||||
Image,
|
||||
ImageBackground,
|
||||
type ImageProps,
|
||||
imageBorderRadius,
|
||||
important,
|
||||
Link,
|
||||
P,
|
||||
Skeleton,
|
||||
SubP,
|
||||
focusReset,
|
||||
imageBorderRadius,
|
||||
important,
|
||||
tooltip,
|
||||
ts,
|
||||
} from "@kyoo/primitives";
|
||||
@ -39,8 +41,19 @@ import ExpandMore from "@material-symbols/svg-400/rounded/keyboard_arrow_down-fi
|
||||
import ExpandLess from "@material-symbols/svg-400/rounded/keyboard_arrow_up-fill.svg";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { type ImageStyle, Platform, type PressableProps, View } from "react-native";
|
||||
import { type Stylable, type Theme, percent, rem, useYoshiki } from "yoshiki/native";
|
||||
import {
|
||||
type ImageStyle,
|
||||
Platform,
|
||||
type PressableProps,
|
||||
View,
|
||||
} from "react-native";
|
||||
import {
|
||||
percent,
|
||||
rem,
|
||||
type Stylable,
|
||||
type Theme,
|
||||
useYoshiki,
|
||||
} from "yoshiki/native";
|
||||
import { ItemProgress } from "../browse/grid";
|
||||
import { EpisodesContext } from "../components/context-menus";
|
||||
import type { Layout } from "../fetch";
|
||||
@ -50,7 +63,10 @@ export const episodeDisplayNumber = (episode: {
|
||||
episodeNumber?: number | null;
|
||||
absoluteNumber?: number | null;
|
||||
}) => {
|
||||
if (typeof episode.seasonNumber === "number" && typeof episode.episodeNumber === "number")
|
||||
if (
|
||||
typeof episode.seasonNumber === "number" &&
|
||||
typeof episode.episodeNumber === "number"
|
||||
)
|
||||
return `S${episode.seasonNumber}:E${episode.episodeNumber}`;
|
||||
if (episode.absoluteNumber) return episode.absoluteNumber.toString();
|
||||
return "??";
|
||||
@ -139,7 +155,8 @@ export const EpisodeBox = ({
|
||||
bg: (theme) => theme.darkOverlay,
|
||||
},
|
||||
"more",
|
||||
Platform.OS === "web" && moreOpened && { display: important("flex") },
|
||||
Platform.OS === "web" &&
|
||||
moreOpened && { display: important("flex") },
|
||||
])}
|
||||
/>
|
||||
</ImageBackground>
|
||||
@ -295,7 +312,9 @@ export const EpisodeLine = ({
|
||||
<SubP>
|
||||
{[
|
||||
// @ts-ignore Source https://www.i18next.com/translation-function/formatting#datetime
|
||||
releaseDate ? t("{{val, datetime}}", { val: releaseDate }) : null,
|
||||
releaseDate
|
||||
? t("{{val, datetime}}", { val: releaseDate })
|
||||
: null,
|
||||
displayRuntime(runtime),
|
||||
]
|
||||
.filter((item) => item != null)
|
||||
@ -310,17 +329,22 @@ export const EpisodeLine = ({
|
||||
{...css([
|
||||
"more",
|
||||
{ display: "flex", marginLeft: ts(3) },
|
||||
Platform.OS === "web" && moreOpened && { display: important("flex") },
|
||||
Platform.OS === "web" &&
|
||||
moreOpened && { display: important("flex") },
|
||||
])}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View {...css({ flexDirection: "row", justifyContent: "space-between" })}>
|
||||
<View
|
||||
{...css({ flexDirection: "row", justifyContent: "space-between" })}
|
||||
>
|
||||
<P numberOfLines={descriptionExpanded ? undefined : 3}>{overview}</P>
|
||||
<IconButton
|
||||
{...css(["more", Platform.OS !== "web" && { opacity: 1 }])}
|
||||
icon={descriptionExpanded ? ExpandLess : ExpandMore}
|
||||
{...tooltip(t(descriptionExpanded ? "misc.collapse" : "misc.expand"))}
|
||||
{...tooltip(
|
||||
t(descriptionExpanded ? "misc.collapse" : "misc.expand"),
|
||||
)}
|
||||
onPress={(e) => {
|
||||
e.preventDefault();
|
||||
setDescriptionExpanded((isExpanded) => !isExpanded);
|
||||
|
@ -1,6 +1,5 @@
|
||||
import Refresh from "@material-symbols/svg-400/rounded/autorenew.svg";
|
||||
import BookmarkAdd from "@material-symbols/svg-400/rounded/bookmark_add.svg";
|
||||
import Download from "@material-symbols/svg-400/rounded/download.svg";
|
||||
import MoreHoriz from "@material-symbols/svg-400/rounded/more_horiz.svg";
|
||||
import MovieInfo from "@material-symbols/svg-400/rounded/movie_info.svg";
|
||||
import PlayArrow from "@material-symbols/svg-400/rounded/play_arrow-fill.svg";
|
||||
|
@ -77,7 +77,7 @@ export const ShowWatchStatusCard = ({
|
||||
);
|
||||
};
|
||||
|
||||
const ShowHeader = ({ children, slug, ...props }) => {
|
||||
const ShowHeader = ({ children, slug, ...props }: any) => {
|
||||
const { css, theme } = useYoshiki();
|
||||
|
||||
return (
|
||||
@ -107,8 +107,7 @@ const ShowHeader = ({ children, slug, ...props }) => {
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
export const ShowDetails = () => {
|
||||
const { css, theme } = useYoshiki();
|
||||
|
@ -4,7 +4,13 @@ import { useYoshiki } from "yoshiki/native";
|
||||
import type { KyooError } from "~/models";
|
||||
import { Button, H1, Link, P, ts } from "~/primitives";
|
||||
|
||||
export const ConnectionError = ({ error, retry }: { error: KyooError; retry: () => void }) => {
|
||||
export const ConnectionError = ({
|
||||
error,
|
||||
retry,
|
||||
}: {
|
||||
error: KyooError;
|
||||
retry: () => void;
|
||||
}) => {
|
||||
const { css } = useYoshiki();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -13,8 +19,17 @@ export const ConnectionError = ({ error, retry }: { error: KyooError; retry: ()
|
||||
<H1 {...css({ textAlign: "center" })}>{t("errors.connection")}</H1>
|
||||
<P>{error?.message ?? t("errors.unknown")}</P>
|
||||
<P>{t("errors.connection-tips")}</P>
|
||||
<Button onPress={retry} text={t("errors.try-again")} {...css({ m: ts(1) })} />
|
||||
<Button as={Link} href="/login" text={t("errors.re-login")} {...css({ m: ts(1) })} />
|
||||
<Button
|
||||
onPress={retry}
|
||||
text={t("errors.try-again")}
|
||||
{...css({ m: ts(1) })}
|
||||
/>
|
||||
<Button
|
||||
as={Link}
|
||||
href="/login"
|
||||
text={t("errors.re-login")}
|
||||
{...css({ m: ts(1) })}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -3,11 +3,7 @@ import { useYoshiki } from "yoshiki/native";
|
||||
import type { KyooError } from "~/models";
|
||||
import { P } from "~/primitives";
|
||||
|
||||
export const ErrorView = ({
|
||||
error,
|
||||
}: {
|
||||
error: KyooError;
|
||||
}) => {
|
||||
export const ErrorView = ({ error }: { error: KyooError }) => {
|
||||
const { css } = useYoshiki();
|
||||
|
||||
return (
|
||||
|
@ -16,7 +16,9 @@ export const OfflineView = () => {
|
||||
alignItems: "center",
|
||||
})}
|
||||
>
|
||||
<P {...css({ color: (theme) => theme.colors.white })}>{t("errors.offline")}</P>
|
||||
<P {...css({ color: (theme) => theme.colors.white })}>
|
||||
{t("errors.offline")}
|
||||
</P>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -13,7 +13,12 @@ export const Unauthorized = ({ missing }: { missing: string[] }) => {
|
||||
if (!account) {
|
||||
return (
|
||||
<View
|
||||
{...css({ flexGrow: 1, flexShrink: 1, justifyContent: "center", alignItems: "center" })}
|
||||
{...css({
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
})}
|
||||
>
|
||||
<P>{t("errors.needAccount")}</P>
|
||||
<Button
|
||||
@ -29,7 +34,12 @@ export const Unauthorized = ({ missing }: { missing: string[] }) => {
|
||||
if (!account.isVerified) {
|
||||
return (
|
||||
<View
|
||||
{...css({ flexGrow: 1, flexShrink: 1, justifyContent: "center", alignItems: "center" })}
|
||||
{...css({
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
})}
|
||||
>
|
||||
<P>{t("errors.needVerification")}</P>
|
||||
</View>
|
||||
@ -45,7 +55,9 @@ export const Unauthorized = ({ missing }: { missing: string[] }) => {
|
||||
alignItems: "center",
|
||||
})}
|
||||
>
|
||||
<P>{t("errors.unauthorized", { permission: missing?.join(", ") ?? "" })}</P>
|
||||
<P>
|
||||
{t("errors.unauthorized", { permission: missing?.join(", ") ?? "" })}
|
||||
</P>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {
|
||||
oidcLogin,
|
||||
type QueryIdentifier,
|
||||
type QueryPage,
|
||||
type ServerInfo,
|
||||
ServerInfoP,
|
||||
oidcLogin,
|
||||
useFetch,
|
||||
} from "@kyoo/models";
|
||||
import { Button, HR, Link, P, Skeleton, ts } from "@kyoo/primitives";
|
||||
@ -14,12 +14,24 @@ import { useRouter } from "solito/router";
|
||||
import { percent, rem, useYoshiki } from "yoshiki/native";
|
||||
import { ErrorView } from "../errors";
|
||||
|
||||
export const OidcLogin = ({ apiUrl, hideOr }: { apiUrl?: string; hideOr?: boolean }) => {
|
||||
export const OidcLogin = ({
|
||||
apiUrl,
|
||||
hideOr,
|
||||
}: {
|
||||
apiUrl?: string;
|
||||
hideOr?: boolean;
|
||||
}) => {
|
||||
const { css } = useYoshiki();
|
||||
const { t } = useTranslation();
|
||||
const { data, error } = useFetch({ options: { apiUrl }, ...OidcLogin.query() });
|
||||
const { data, error } = useFetch({
|
||||
options: { apiUrl },
|
||||
...OidcLogin.query(),
|
||||
});
|
||||
|
||||
const btn = css({ width: { xs: percent(100), sm: percent(75) }, marginY: ts(1) });
|
||||
const btn = css({
|
||||
width: { xs: percent(100), sm: percent(75) },
|
||||
marginY: ts(1),
|
||||
});
|
||||
|
||||
return (
|
||||
<View {...css({ alignItems: "center", marginY: ts(1) })}>
|
||||
@ -86,16 +98,26 @@ export const OidcCallbackPage: QueryPage<{
|
||||
hasRun.current = true;
|
||||
|
||||
function onError(error: string) {
|
||||
router.replace({ pathname: "/login", query: { error, apiUrl } }, undefined, {
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||
});
|
||||
router.replace(
|
||||
{ pathname: "/login", query: { error, apiUrl } },
|
||||
undefined,
|
||||
{
|
||||
experimental: {
|
||||
nativeBehavior: "stack-replace",
|
||||
isNestedNavigator: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
async function run() {
|
||||
const { error: loginError } = await oidcLogin(provider, code, apiUrl);
|
||||
if (loginError) onError(loginError);
|
||||
else {
|
||||
router.replace("/", undefined, {
|
||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||
experimental: {
|
||||
nativeBehavior: "stack-replace",
|
||||
isNestedNavigator: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Svg, { type SvgProps, Path } from "react-native-svg";
|
||||
import Svg, { Path, type SvgProps } from "react-native-svg";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
|
||||
/* export const KyooLogo = (props: SvgProps) => ( */
|
||||
@ -24,7 +24,12 @@ export const KyooLongLogo = ({
|
||||
const { theme } = useYoshiki();
|
||||
const textColor = theme.contrast;
|
||||
return (
|
||||
<Svg viewBox="49.954 131.833 318.13 108.676" height={height} width={height * 2.9272} {...props}>
|
||||
<Svg
|
||||
viewBox="49.954 131.833 318.13 108.676"
|
||||
height={height}
|
||||
width={height * 2.9272}
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
d="m164.844 186.759-114.89-53.76v107.51l114.89-53.75Z"
|
||||
fill="#121327"
|
||||
|
@ -2,7 +2,7 @@ import { NavigationContext, useRoute } from "@react-navigation/native";
|
||||
import { useContext } from "react";
|
||||
import type { Movie, Show } from "~/models";
|
||||
|
||||
export function setServerData(key: string, val: any) {}
|
||||
export function setServerData(_key: string, _val: any) {}
|
||||
export function getServerData(key: string) {
|
||||
return key;
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": [
|
||||
"./src/*"
|
||||
]
|
||||
"~/*": ["./src/*"]
|
||||
},
|
||||
"strict": true,
|
||||
"rootDir": ".",
|
||||
@ -16,21 +14,10 @@
|
||||
"skipLibCheck": true,
|
||||
"jsx": "react-jsx",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"types": [
|
||||
"node",
|
||||
"react",
|
||||
],
|
||||
"lib": [
|
||||
"dom",
|
||||
"esnext"
|
||||
]
|
||||
"types": ["node", "react"],
|
||||
"lib": ["dom", "esnext"]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".expo/types/**/*.ts",
|
||||
"expo-env.d.ts"
|
||||
],
|
||||
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".expo",
|
||||
|
Loading…
x
Reference in New Issue
Block a user