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