mirror of
https://github.com/zoriya/Kyoo.git
synced 2026-03-17 06:59:15 -04:00
Remove yoshiki, fix ts issues
This commit is contained in:
parent
f4be92f190
commit
e771ca591f
@ -322,7 +322,7 @@ export async function getVideos({
|
||||
.with(...cte)
|
||||
.select({
|
||||
...getColumns(videos),
|
||||
...buildRelations(["slugs", ...relations], videoRelations, {
|
||||
...buildRelations(["slugs", "progress", ...relations], videoRelations, {
|
||||
languages,
|
||||
preferOriginal,
|
||||
}),
|
||||
|
||||
@ -64,7 +64,6 @@
|
||||
"uniwind": "^1.2.6",
|
||||
"uuid": "^13.0.0",
|
||||
"video.js": "^8.23.4",
|
||||
"yoshiki": "1.2.14",
|
||||
"zod": "^4.3.5",
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -629,8 +628,6 @@
|
||||
|
||||
"@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="],
|
||||
|
||||
"@types/inline-style-prefixer": ["@types/inline-style-prefixer@5.0.3", "", {}, "sha512-GOiSoBwH2U8LmbCnOLU6ZRPtm+qycO9sNXCvP+ahG0abpHrYTd1rm6ZPX4qYTFf1mTB6tqTQ9fYaJPcQWGFMSQ=="],
|
||||
|
||||
"@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="],
|
||||
|
||||
"@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="],
|
||||
@ -641,8 +638,6 @@
|
||||
|
||||
"@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
|
||||
|
||||
"@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.17", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.1.11", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-3BKc/yGdNTYQVVw4idqHtSOcFsgGuBbMveKCOgF8wQ5QtrYOc3jDIlzg3jef04zcXFIHLelyGlj0T+BJ8+KN+w=="],
|
||||
@ -1675,8 +1670,6 @@
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"yoshiki": ["yoshiki@1.2.14", "", { "dependencies": { "@types/inline-style-prefixer": "^5.0.0", "@types/node": "18.x.x", "@types/react": "18.x.x", "inline-style-prefixer": "^7.0.0" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native", "react-native-web"] }, "sha512-TQoaB1C8/rUCuz/856eDv1BUxN+/OYL0f7RG+MwqIv260BEQQeUrsGGaMfN2GHdy86geNkn9wQArJqsjut/3Lg=="],
|
||||
|
||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
||||
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
@ -1857,8 +1850,6 @@
|
||||
|
||||
"xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
|
||||
|
||||
"yoshiki/@types/react": ["@types/react@18.3.27", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w=="],
|
||||
|
||||
"@babel/highlight/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
|
||||
|
||||
"@babel/highlight/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
|
||||
|
||||
@ -74,7 +74,6 @@
|
||||
"uniwind": "^1.2.6",
|
||||
"uuid": "^13.0.0",
|
||||
"video.js": "^8.23.4",
|
||||
"yoshiki": "1.2.14",
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { HomePage, loader } from "~/ui/home";
|
||||
import { HomePage } from "~/ui/home";
|
||||
|
||||
export { ErrorBoundary } from "~/ui/error-boundary";
|
||||
|
||||
export { loader };
|
||||
export default HomePage;
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
import { z } from "zod";
|
||||
import { ImagesP, ResourceP } from "../traits";
|
||||
|
||||
export const PersonP = ResourceP("people").merge(ImagesP).extend({
|
||||
/**
|
||||
* The name of this person.
|
||||
*/
|
||||
name: z.string(),
|
||||
/**
|
||||
* The type of work the person has done for the show. That can be something like "Actor",
|
||||
* "Writer", "Music", "Voice Actor"...
|
||||
*/
|
||||
type: z.string().optional(),
|
||||
|
||||
/**
|
||||
* The role the People played. This is mostly used to inform witch character was played for actor
|
||||
* and voice actors.
|
||||
*/
|
||||
role: z.string().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* A studio that make shows.
|
||||
*/
|
||||
export type Person = z.infer<typeof PersonP>;
|
||||
@ -1,71 +0,0 @@
|
||||
import { Platform } from "react-native";
|
||||
import { z } from "zod";
|
||||
|
||||
export const OidcInfoP = z.object({
|
||||
/*
|
||||
* The name of this oidc service. Human readable.
|
||||
*/
|
||||
displayName: z.string(),
|
||||
/*
|
||||
* A url returning a square logo for this provider.
|
||||
*/
|
||||
logoUrl: z.string().nullable(),
|
||||
});
|
||||
|
||||
export enum SetupStep {
|
||||
MissingAdminAccount = "MissingAdminAccount",
|
||||
NoVideoFound = "NoVideoFound",
|
||||
Done = "Done",
|
||||
}
|
||||
|
||||
export const ServerInfoP = z
|
||||
.object({
|
||||
/*
|
||||
* True if guest accounts are allowed on this instance.
|
||||
*/
|
||||
allowGuests: z.boolean(),
|
||||
/*
|
||||
* The list of permissions available for the guest account.
|
||||
*/
|
||||
guestPermissions: z.array(z.string()),
|
||||
/*
|
||||
* The url to reach the homepage of kyoo (add /api for the api).
|
||||
*/
|
||||
publicUrl: z.string(),
|
||||
/*
|
||||
* The list of oidc providers configured for this instance of kyoo.
|
||||
*/
|
||||
oidc: z.record(z.string(), OidcInfoP),
|
||||
/*
|
||||
* Check if kyoo's setup is finished.
|
||||
*/
|
||||
setupStatus: z.nativeEnum(SetupStep),
|
||||
/*
|
||||
* True if password login is enabled on this instance.
|
||||
*/
|
||||
passwordLoginEnabled: z.boolean(),
|
||||
/*
|
||||
* True if registration is enabled on this instance.
|
||||
*/
|
||||
registrationEnabled: z.boolean(),
|
||||
})
|
||||
.transform((x) => {
|
||||
const baseUrl = Platform.OS === "web" ? x.publicUrl : "kyoo://";
|
||||
return {
|
||||
...x,
|
||||
oidc: Object.fromEntries(
|
||||
Object.entries(x.oidc).map(([provider, info]) => [
|
||||
provider,
|
||||
{
|
||||
...info,
|
||||
link: `/auth/login/${provider}?redirectUrl=${baseUrl}/login/callback`,
|
||||
},
|
||||
]),
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* A season of a Show.
|
||||
*/
|
||||
export type ServerInfo = z.infer<typeof ServerInfoP>;
|
||||
@ -42,14 +42,12 @@ export const FullVideo = Video.extend({
|
||||
Special.omit({ progress: true, videos: true }),
|
||||
]),
|
||||
),
|
||||
progress: z.optional(
|
||||
z.object({
|
||||
percent: z.int().min(0).max(100),
|
||||
time: z.int().min(0),
|
||||
playedDate: zdate().nullable(),
|
||||
videoId: z.string().nullable(),
|
||||
}),
|
||||
),
|
||||
progress: z.object({
|
||||
percent: z.int().min(0).max(100),
|
||||
time: z.int().min(0),
|
||||
playedDate: zdate().nullable(),
|
||||
videoId: z.string().nullable(),
|
||||
}),
|
||||
previous: z.object({ video: z.string(), entry: Entry }).nullable().optional(),
|
||||
next: z.object({ video: z.string(), entry: Entry }).nullable().optional(),
|
||||
show: Show.optional(),
|
||||
|
||||
@ -3,18 +3,11 @@ import KyooLogo from "public/icon.svg";
|
||||
import type { ComponentProps } from "react";
|
||||
import { type ImageStyle, Platform, View, type ViewProps } from "react-native";
|
||||
import { withUniwind } from "uniwind";
|
||||
import type { YoshikiStyle } from "yoshiki/src/type";
|
||||
import type { KImage } from "~/models";
|
||||
import { useToken } from "~/providers/account-context";
|
||||
import { cn } from "~/utils";
|
||||
import { Skeleton } from "../skeleton";
|
||||
|
||||
export type YoshikiEnhanced<Style> = Style extends any
|
||||
? {
|
||||
[key in keyof Style]: YoshikiStyle<Style[key]>;
|
||||
}
|
||||
: never;
|
||||
|
||||
const Img = withUniwind(EImage);
|
||||
|
||||
// This should stay in think with `ImageBackground`.
|
||||
|
||||
@ -20,7 +20,6 @@ export * from "./select";
|
||||
export * from "./skeleton";
|
||||
export * from "./slider";
|
||||
export * from "./text";
|
||||
export * from "./theme";
|
||||
export * from "./tooltip";
|
||||
|
||||
export * from "./utils";
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
import type { ThemeBuilder } from "./theme";
|
||||
|
||||
// Ref: https://github.com/catppuccin/catppuccin
|
||||
export const catppuccin: ThemeBuilder = {
|
||||
light: {
|
||||
// Catppuccin latte
|
||||
overlay0: "#9ca0b0",
|
||||
overlay1: "#7c7f93",
|
||||
lightOverlay: "#eff1f599",
|
||||
darkOverlay: "#4c4f6999",
|
||||
link: "#1e66f5",
|
||||
default: {
|
||||
background: "#eff1f5",
|
||||
accent: "#e64553",
|
||||
divider: "#8c8fa1",
|
||||
heading: "#4c4f69",
|
||||
paragraph: "#5c5f77",
|
||||
subtext: "#6c6f85",
|
||||
},
|
||||
variant: {
|
||||
background: "#e6e9ef",
|
||||
accent: "#d20f39",
|
||||
divider: "#dd7878",
|
||||
heading: "#4c4f69",
|
||||
paragraph: "#5c5f77",
|
||||
subtext: "#6c6f85",
|
||||
},
|
||||
colors: {
|
||||
red: "#d20f39",
|
||||
green: "#40a02b",
|
||||
blue: "#1e66f5",
|
||||
yellow: "#df8e1d",
|
||||
black: "#4c4f69",
|
||||
white: "#eff1f5",
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
// Catppuccin mocha
|
||||
overlay0: "#6c7086",
|
||||
overlay1: "#9399b2",
|
||||
lightOverlay: "#f5f0f899",
|
||||
darkOverlay: "#11111b99",
|
||||
link: "#89b4fa",
|
||||
default: {
|
||||
background: "#1e1e2e",
|
||||
accent: "#89b4fa",
|
||||
divider: "#7f849c",
|
||||
heading: "#cdd6f4",
|
||||
paragraph: "#bac2de",
|
||||
subtext: "#a6adc8",
|
||||
},
|
||||
variant: {
|
||||
background: "#181825",
|
||||
accent: "#74c7ec",
|
||||
divider: "#1e1e2e",
|
||||
heading: "#cdd6f4",
|
||||
paragraph: "#bac2de",
|
||||
subtext: "#a6adc8",
|
||||
},
|
||||
colors: {
|
||||
red: "#f38ba8",
|
||||
green: "#a6e3a1",
|
||||
blue: "#89b4fa",
|
||||
yellow: "#f9e2af",
|
||||
black: "#11111b",
|
||||
white: "#f5f0f8",
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./catppuccin";
|
||||
export * from "./theme";
|
||||
@ -1,215 +0,0 @@
|
||||
import type { Property } from "csstype";
|
||||
import type { ReactNode } from "react";
|
||||
import { Platform, type TextStyle } from "react-native";
|
||||
import {
|
||||
type Theme,
|
||||
useAutomaticTheme,
|
||||
ThemeProvider as WebThemeProvider,
|
||||
} from "yoshiki";
|
||||
import "yoshiki";
|
||||
import { ThemeProvider, useTheme, useYoshiki } from "yoshiki/native";
|
||||
import "yoshiki/native";
|
||||
import { catppuccin } from "./catppuccin";
|
||||
|
||||
type FontList = Partial<
|
||||
Record<Exclude<TextStyle["fontWeight"], null | undefined | number>, string>
|
||||
>;
|
||||
|
||||
type Mode = {
|
||||
mode: "light" | "dark" | "auto";
|
||||
overlay0: Property.Color;
|
||||
overlay1: Property.Color;
|
||||
lightOverlay: Property.Color;
|
||||
darkOverlay: Property.Color;
|
||||
themeOverlay: Property.Color;
|
||||
link: Property.Color;
|
||||
contrast: Property.Color;
|
||||
variant: Variant;
|
||||
colors: {
|
||||
red: Property.Color;
|
||||
green: Property.Color;
|
||||
blue: Property.Color;
|
||||
yellow: Property.Color;
|
||||
white: Property.Color;
|
||||
black: Property.Color;
|
||||
};
|
||||
};
|
||||
|
||||
type Variant = {
|
||||
background: Property.Color;
|
||||
accent: Property.Color;
|
||||
divider: Property.Color;
|
||||
heading: Property.Color;
|
||||
paragraph: Property.Color;
|
||||
subtext: Property.Color;
|
||||
};
|
||||
|
||||
declare module "yoshiki" {
|
||||
export interface Theme extends Mode, Variant {
|
||||
light: Mode & Variant;
|
||||
dark: Mode & Variant;
|
||||
user: Mode & Variant;
|
||||
alternate: Mode & Variant;
|
||||
font: FontList;
|
||||
}
|
||||
}
|
||||
|
||||
export type { Theme } from "yoshiki";
|
||||
export type ThemeBuilder = {
|
||||
light: Omit<Mode, "contrast" | "mode" | "themeOverlay"> & {
|
||||
default: Variant;
|
||||
};
|
||||
dark: Omit<Mode, "contrast" | "mode" | "themeOverlay"> & { default: Variant };
|
||||
};
|
||||
|
||||
const selectMode = (
|
||||
theme: ThemeBuilder & { font: FontList },
|
||||
mode: "light" | "dark" | "auto",
|
||||
): Theme => {
|
||||
const { light: lightBuilder, dark: darkBuilder, ...options } = theme;
|
||||
const light: Mode & Variant = {
|
||||
...lightBuilder,
|
||||
...lightBuilder.default,
|
||||
contrast: lightBuilder.colors.black,
|
||||
themeOverlay: lightBuilder.lightOverlay,
|
||||
mode: "light",
|
||||
};
|
||||
const dark: Mode & Variant = {
|
||||
...darkBuilder,
|
||||
...darkBuilder.default,
|
||||
contrast: darkBuilder.colors.white,
|
||||
themeOverlay: darkBuilder.darkOverlay,
|
||||
mode: "dark",
|
||||
};
|
||||
if (Platform.OS !== "web" || mode !== "auto") {
|
||||
const value = mode === "light" ? light : dark;
|
||||
const alternate = mode === "light" ? dark : light;
|
||||
return {
|
||||
...options,
|
||||
...value,
|
||||
light,
|
||||
dark,
|
||||
user: value,
|
||||
alternate,
|
||||
};
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/useHookAtTopLevel: const
|
||||
const auto = useAutomaticTheme("theme", { light, dark });
|
||||
// biome-ignore lint/correctness/useHookAtTopLevel: const
|
||||
const alternate = useAutomaticTheme("alternate", {
|
||||
dark: light,
|
||||
light: dark,
|
||||
});
|
||||
return {
|
||||
...options,
|
||||
...auto,
|
||||
mode: "auto",
|
||||
light,
|
||||
dark,
|
||||
user: { ...auto, mode: "auto" },
|
||||
alternate: { ...alternate, mode: "auto" },
|
||||
};
|
||||
};
|
||||
|
||||
const switchVariant = (theme: Theme) => {
|
||||
return {
|
||||
...theme,
|
||||
...theme.variant,
|
||||
variant: {
|
||||
background: theme.background,
|
||||
accent: theme.accent,
|
||||
divider: theme.divider,
|
||||
heading: theme.heading,
|
||||
paragraph: theme.paragraph,
|
||||
subtext: theme.subtext,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const ThemeSelector = ({
|
||||
children,
|
||||
theme,
|
||||
font,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
theme: "light" | "dark" | "auto";
|
||||
font: FontList;
|
||||
}) => {
|
||||
const newTheme = selectMode({ ...catppuccin, font }, theme);
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={newTheme}>
|
||||
<WebThemeProvider theme={newTheme}>{children as any}</WebThemeProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export type YoshikiFunc<T> = (props: ReturnType<typeof useYoshiki>) => T;
|
||||
|
||||
const YoshikiProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: YoshikiFunc<ReactNode>;
|
||||
}) => {
|
||||
const yoshiki = useYoshiki();
|
||||
return <>{children(yoshiki)}</>;
|
||||
};
|
||||
|
||||
export const SwitchVariant = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode | YoshikiFunc<ReactNode>;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={switchVariant(theme)}>
|
||||
{typeof children === "function" ? (
|
||||
<YoshikiProvider>{children}</YoshikiProvider>
|
||||
) : (
|
||||
(children as any)
|
||||
)}
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const ContrastArea = ({
|
||||
children,
|
||||
mode = "dark",
|
||||
contrastText,
|
||||
}: {
|
||||
children: ReactNode | YoshikiFunc<ReactNode>;
|
||||
mode?: "light" | "dark" | "user" | "alternate";
|
||||
contrastText?: boolean;
|
||||
}) => {
|
||||
const oldTheme = useTheme();
|
||||
const theme: Theme = { ...oldTheme, ...oldTheme[mode] };
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
theme={
|
||||
contrastText
|
||||
? {
|
||||
...theme,
|
||||
// Keep the same skeletons, it looks weird otherwise.
|
||||
overlay0: theme.user.overlay0,
|
||||
overlay1: theme.user.overlay1,
|
||||
heading: theme.contrast,
|
||||
paragraph: theme.heading,
|
||||
}
|
||||
: theme
|
||||
}
|
||||
>
|
||||
{typeof children === "function" ? (
|
||||
<YoshikiProvider>{children}</YoshikiProvider>
|
||||
) : (
|
||||
(children as any)
|
||||
)}
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const alpha = (color: Property.Color, alpha: number) => {
|
||||
return color + Math.round(alpha * 255).toString(16);
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
import type { ComponentProps } from "react";
|
||||
import { Tooltip as RTooltip } from "react-tooltip";
|
||||
import { useTheme } from "yoshiki/native";
|
||||
import { useResolveClassNames } from "uniwind";
|
||||
|
||||
export const tooltip = (tooltip: string, up?: boolean) => ({
|
||||
dataSet: {
|
||||
@ -12,14 +12,20 @@ export const tooltip = (tooltip: string, up?: boolean) => ({
|
||||
});
|
||||
|
||||
export const Tooltip = (props: ComponentProps<typeof RTooltip>) => {
|
||||
const theme = useTheme();
|
||||
const { color: background } = useResolveClassNames(
|
||||
"text-color-dark dark:text-color-light",
|
||||
);
|
||||
const { color } = useResolveClassNames(
|
||||
"text-color-light dark:text-color-dark",
|
||||
);
|
||||
|
||||
return (
|
||||
<RTooltip
|
||||
id="tooltip"
|
||||
opacity={0.9}
|
||||
style={{
|
||||
background: theme.contrast,
|
||||
color: theme.alternate.contrast,
|
||||
background: background as string,
|
||||
color: color as string,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@ -1,15 +1,31 @@
|
||||
import { useWindowDimensions } from "react-native";
|
||||
import {
|
||||
breakpoints,
|
||||
isBreakpoints,
|
||||
type Breakpoints as YoshikiBreakpoint,
|
||||
} from "yoshiki/native";
|
||||
|
||||
export const breakpoints = {
|
||||
xs: 0,
|
||||
sm: 600,
|
||||
md: 900,
|
||||
lg: 1200,
|
||||
xl: 1600,
|
||||
};
|
||||
|
||||
type Breakpoints<Property> = {
|
||||
[key in keyof typeof breakpoints]?: Property;
|
||||
};
|
||||
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<Breakpoints<T>>;
|
||||
|
||||
// copied from yoshiki.
|
||||
const isBreakpoints = <T>(value: unknown): value is Breakpoints<T> => {
|
||||
if (typeof value !== "object" || !value) return false;
|
||||
for (const v of Object.keys(value)) {
|
||||
if (!(v in breakpoints)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const useBreakpoint = () => {
|
||||
const { width } = useWindowDimensions();
|
||||
const idx = Object.values(breakpoints).findLastIndex((x) => x <= width);
|
||||
@ -19,7 +35,7 @@ const useBreakpoint = () => {
|
||||
|
||||
const getBreakpointValue = <T>(value: Breakpoint<T>, breakpoint: number): T => {
|
||||
if (!isBreakpoints(value)) return value;
|
||||
const bpKeys = Object.keys(breakpoints) as Array<keyof YoshikiBreakpoint<T>>;
|
||||
const bpKeys = Object.keys(breakpoints) as Array<keyof Breakpoints<T>>;
|
||||
for (let i = breakpoint; i >= 0; i--) {
|
||||
if (bpKeys[i] in value) {
|
||||
const val = value[bpKeys[i]];
|
||||
|
||||
@ -5,7 +5,6 @@ import {
|
||||
} from "@react-navigation/native";
|
||||
import { HydrationBoundary, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { useColorScheme } from "react-native";
|
||||
import { SafeAreaListener } from "react-native-safe-area-context";
|
||||
import {
|
||||
Uniwind,
|
||||
@ -13,12 +12,11 @@ import {
|
||||
useResolveClassNames,
|
||||
useUniwind,
|
||||
} from "uniwind";
|
||||
import { ThemeSelector } from "~/primitives/theme";
|
||||
import { createQueryClient } from "~/query";
|
||||
import { AccountProvider } from "./account-provider";
|
||||
import { TranslationsProvider } from "./translations.native";
|
||||
|
||||
function getServerData(_key: string) {}
|
||||
function getServerData(_key: string): any {}
|
||||
|
||||
const QueryProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [queryClient] = useState(() => createQueryClient());
|
||||
@ -31,17 +29,6 @@ const QueryProvider = ({ children }: { children: ReactNode }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ThemeProvider = ({ children }: { children: ReactNode }) => {
|
||||
// we can't use "auto" here because it breaks the `RnThemeProvider`
|
||||
const userTheme = useColorScheme();
|
||||
|
||||
return (
|
||||
<ThemeSelector theme={userTheme ?? "light"} font={{ normal: "inherit" }}>
|
||||
{children}
|
||||
</ThemeSelector>
|
||||
);
|
||||
};
|
||||
|
||||
const RnTheme = ({ children }: { children: ReactNode }) => {
|
||||
const { theme } = useUniwind();
|
||||
const [accent, background, card, popover] = useCSSVariable([
|
||||
@ -81,15 +68,13 @@ const RnTheme = ({ children }: { children: ReactNode }) => {
|
||||
export const Providers = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<QueryProvider>
|
||||
<ThemeProvider>
|
||||
<RnTheme>
|
||||
<TranslationsProvider>
|
||||
<AccountProvider>
|
||||
<PortalProvider>{children}</PortalProvider>
|
||||
</AccountProvider>
|
||||
</TranslationsProvider>
|
||||
</RnTheme>
|
||||
</ThemeProvider>
|
||||
<RnTheme>
|
||||
<TranslationsProvider>
|
||||
<AccountProvider>
|
||||
<PortalProvider>{children}</PortalProvider>
|
||||
</AccountProvider>
|
||||
</TranslationsProvider>
|
||||
</RnTheme>
|
||||
</QueryProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -42,18 +42,18 @@ export const InfiniteFetch = <Data, Type extends string = string>({
|
||||
stickyHeaderConfig?: LegendListProps["stickyHeaderConfig"];
|
||||
Render: (props: { item: Data; index: number }) => ReactElement | null;
|
||||
Loader: (props: { index: number }) => ReactElement | null;
|
||||
Empty?: JSX.Element;
|
||||
Empty?: ReactElement;
|
||||
incremental?: boolean;
|
||||
Divider?: true | ComponentType;
|
||||
Header?: ComponentType<{ children: JSX.Element }> | ReactElement;
|
||||
Footer?: ComponentType<{ children: JSX.Element }> | ReactElement;
|
||||
Header?: ComponentType<{ children: ReactElement }> | ReactElement;
|
||||
Footer?: ComponentType<{ children: ReactElement }> | ReactElement;
|
||||
fetchMore?: boolean;
|
||||
contentContainerStyle?: ViewStyle;
|
||||
onScroll?: LegendListProps["onScroll"];
|
||||
scrollEventThrottle?: LegendListProps["scrollEventThrottle"];
|
||||
columnWrapperStyle?: ViewStyle;
|
||||
outerGap?: boolean;
|
||||
}): JSX.Element | null => {
|
||||
}): ReactElement | null => {
|
||||
const { numColumns, size, gap } = useBreakpointMap(layout);
|
||||
const oldItems = useRef<Data[] | undefined>(undefined);
|
||||
let { items, fetchNextPage, hasNextPage, isFetching, refetch, isRefetching } =
|
||||
@ -109,6 +109,7 @@ export const InfiniteFetch = <Data, Type extends string = string>({
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={contentContainerStyle}
|
||||
columnWrapperStyle={{
|
||||
// @ts-expect-error ts is dumb
|
||||
gap,
|
||||
...(Platform.OS === "web" && columnWrapperStyle
|
||||
? { display: "flex", margin: "auto" }
|
||||
|
||||
@ -9,7 +9,7 @@ export const Fetch = <Data,>({
|
||||
query: QueryIdentifier<Data>;
|
||||
Render: (item: Data) => ReactElement | null;
|
||||
Loader: () => ReactElement | null;
|
||||
}): JSX.Element | null => {
|
||||
}): ReactElement | null => {
|
||||
const { data } = useFetch(query);
|
||||
|
||||
if (!data) return <Loader />;
|
||||
|
||||
@ -35,7 +35,7 @@ export const BrowsePage = () => {
|
||||
/>
|
||||
}
|
||||
Render={({ item }) => <LayoutComponent {...itemMap(item)} />}
|
||||
Loader={LayoutComponent.Loader}
|
||||
Loader={() => <LayoutComponent.Loader />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,128 +1,126 @@
|
||||
import {
|
||||
oidcLogin,
|
||||
type QueryIdentifier,
|
||||
type QueryPage,
|
||||
type ServerInfo,
|
||||
ServerInfoP,
|
||||
useFetch,
|
||||
} from "@kyoo/models";
|
||||
import { Button, HR, Link, P, Skeleton, ts } from "@kyoo/primitives";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ImageBackground, View } from "react-native";
|
||||
import { useRouter } from "solito/router";
|
||||
import { percent, rem, useYoshiki } from "yoshiki/native";
|
||||
|
||||
export const OidcLogin = ({
|
||||
apiUrl,
|
||||
hideOr,
|
||||
}: {
|
||||
apiUrl?: string;
|
||||
hideOr?: boolean;
|
||||
}) => {
|
||||
const { css } = useYoshiki();
|
||||
const { t } = useTranslation();
|
||||
const { data } = useFetch({
|
||||
options: { apiUrl },
|
||||
...OidcLogin.query(),
|
||||
});
|
||||
|
||||
const btn = css({
|
||||
width: { xs: percent(100), sm: percent(75) },
|
||||
marginY: ts(1),
|
||||
});
|
||||
|
||||
return (
|
||||
<View {...css({ alignItems: "center", marginY: ts(1) })}>
|
||||
{data
|
||||
? Object.values(data.oidc).map((x) => (
|
||||
<Button
|
||||
as={Link}
|
||||
href={{ pathname: x.link, query: { apiUrl } }}
|
||||
key={x.displayName}
|
||||
licon={
|
||||
x.logoUrl != null && (
|
||||
<ImageBackground
|
||||
source={{ uri: x.logoUrl }}
|
||||
{...css({
|
||||
width: ts(3),
|
||||
height: ts(3),
|
||||
marginRight: ts(2),
|
||||
})}
|
||||
/>
|
||||
)
|
||||
}
|
||||
text={t("login.via", { provider: x.displayName })}
|
||||
{...btn}
|
||||
/>
|
||||
))
|
||||
: [...Array(3)].map((_, i) => (
|
||||
<Button key={i} {...btn}>
|
||||
<Skeleton {...css({ width: percent(66), marginY: rem(0.5) })} />
|
||||
</Button>
|
||||
))}
|
||||
<View
|
||||
{...css({
|
||||
marginY: ts(1),
|
||||
flexDirection: "row",
|
||||
width: percent(100),
|
||||
alignItems: "center",
|
||||
display: hideOr ? "none" : undefined,
|
||||
})}
|
||||
>
|
||||
<HR {...css({ flexGrow: 1 })} />
|
||||
<P>{t("misc.or")}</P>
|
||||
<HR {...css({ flexGrow: 1 })} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
OidcLogin.query = (): QueryIdentifier<ServerInfo> => ({
|
||||
path: ["info"],
|
||||
parser: ServerInfoP,
|
||||
});
|
||||
|
||||
export const OidcCallbackPage: QueryPage<{
|
||||
apiUrl?: string;
|
||||
provider: string;
|
||||
code: string;
|
||||
error?: string;
|
||||
}> = ({ apiUrl, provider, code, error }) => {
|
||||
const hasRun = useRef(false);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (hasRun.current) return;
|
||||
hasRun.current = true;
|
||||
|
||||
function onError(error: string) {
|
||||
router.replace(
|
||||
{ pathname: "/login", query: { error, apiUrl } },
|
||||
undefined,
|
||||
{
|
||||
experimental: {
|
||||
nativeBehavior: "stack-replace",
|
||||
isNestedNavigator: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
async function run() {
|
||||
const { error: loginError } = await oidcLogin(provider, code, apiUrl);
|
||||
if (loginError) onError(loginError);
|
||||
else {
|
||||
router.replace("/", undefined, {
|
||||
experimental: {
|
||||
nativeBehavior: "stack-replace",
|
||||
isNestedNavigator: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (error) onError(error);
|
||||
else run();
|
||||
}, [provider, code, apiUrl, router, error]);
|
||||
return <P>{"Loading"}</P>;
|
||||
};
|
||||
// import {
|
||||
// oidcLogin,
|
||||
// type QueryIdentifier,
|
||||
// type QueryPage,
|
||||
// type ServerInfo,
|
||||
// ServerInfoP,
|
||||
// useFetch,
|
||||
// } from "@kyoo/models";
|
||||
// import { Button, HR, Link, P, Skeleton, ts } from "@kyoo/primitives";
|
||||
// import { useEffect, useRef } from "react";
|
||||
// import { useTranslation } from "react-i18next";
|
||||
// import { ImageBackground, View } from "react-native";
|
||||
// import { useRouter } from "solito/router";
|
||||
//
|
||||
// export const OidcLogin = ({
|
||||
// apiUrl,
|
||||
// hideOr,
|
||||
// }: {
|
||||
// apiUrl?: string;
|
||||
// hideOr?: boolean;
|
||||
// }) => {
|
||||
// const { t } = useTranslation();
|
||||
// const { data } = useFetch({
|
||||
// options: { apiUrl },
|
||||
// ...OidcLogin.query(),
|
||||
// });
|
||||
//
|
||||
// const btn = css({
|
||||
// width: { xs: percent(100), sm: percent(75) },
|
||||
// marginY: ts(1),
|
||||
// });
|
||||
//
|
||||
// return (
|
||||
// <View {...css({ alignItems: "center", marginY: ts(1) })}>
|
||||
// {data
|
||||
// ? Object.values(data.oidc).map((x) => (
|
||||
// <Button
|
||||
// as={Link}
|
||||
// href={{ pathname: x.link, query: { apiUrl } }}
|
||||
// key={x.displayName}
|
||||
// licon={
|
||||
// x.logoUrl != null && (
|
||||
// <ImageBackground
|
||||
// source={{ uri: x.logoUrl }}
|
||||
// {...css({
|
||||
// width: ts(3),
|
||||
// height: ts(3),
|
||||
// marginRight: ts(2),
|
||||
// })}
|
||||
// />
|
||||
// )
|
||||
// }
|
||||
// text={t("login.via", { provider: x.displayName })}
|
||||
// {...btn}
|
||||
// />
|
||||
// ))
|
||||
// : [...Array(3)].map((_, i) => (
|
||||
// <Button key={i} {...btn}>
|
||||
// <Skeleton {...css({ width: percent(66), marginY: rem(0.5) })} />
|
||||
// </Button>
|
||||
// ))}
|
||||
// <View
|
||||
// {...css({
|
||||
// marginY: ts(1),
|
||||
// flexDirection: "row",
|
||||
// width: percent(100),
|
||||
// alignItems: "center",
|
||||
// display: hideOr ? "none" : undefined,
|
||||
// })}
|
||||
// >
|
||||
// <HR {...css({ flexGrow: 1 })} />
|
||||
// <P>{t("misc.or")}</P>
|
||||
// <HR {...css({ flexGrow: 1 })} />
|
||||
// </View>
|
||||
// </View>
|
||||
// );
|
||||
// };
|
||||
//
|
||||
// OidcLogin.query = (): QueryIdentifier<ServerInfo> => ({
|
||||
// path: ["info"],
|
||||
// parser: ServerInfoP,
|
||||
// });
|
||||
//
|
||||
// export const OidcCallbackPage: QueryPage<{
|
||||
// apiUrl?: string;
|
||||
// provider: string;
|
||||
// code: string;
|
||||
// error?: string;
|
||||
// }> = ({ apiUrl, provider, code, error }) => {
|
||||
// const hasRun = useRef(false);
|
||||
// const router = useRouter();
|
||||
//
|
||||
// useEffect(() => {
|
||||
// if (hasRun.current) return;
|
||||
// hasRun.current = true;
|
||||
//
|
||||
// function onError(error: string) {
|
||||
// router.replace(
|
||||
// { pathname: "/login", query: { error, apiUrl } },
|
||||
// undefined,
|
||||
// {
|
||||
// experimental: {
|
||||
// nativeBehavior: "stack-replace",
|
||||
// isNestedNavigator: false,
|
||||
// },
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// async function run() {
|
||||
// const { error: loginError } = await oidcLogin(provider, code, apiUrl);
|
||||
// if (loginError) onError(loginError);
|
||||
// else {
|
||||
// router.replace("/", undefined, {
|
||||
// experimental: {
|
||||
// nativeBehavior: "stack-replace",
|
||||
// isNestedNavigator: false,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (error) onError(error);
|
||||
// else run();
|
||||
// }, [provider, code, apiUrl, router, error]);
|
||||
// return <P>{"Loading"}</P>;
|
||||
// };
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
// import { Button, IconButton, Link, Skeleton, tooltip, ts } from "@kyoo/primitives";
|
||||
// import { useTranslation } from "react-i18next";
|
||||
// import { ImageBackground } from "react-native";
|
||||
// import { rem, useYoshiki } from "yoshiki/native";
|
||||
// import { Preference, SettingsContainer } from "./base";
|
||||
//
|
||||
// import Badge from "@material-symbols/svg-400/outlined/badge.svg";
|
||||
@ -19,7 +18,6 @@
|
||||
//
|
||||
// export const OidcSettings = () => {
|
||||
// const account = useAccount()!;
|
||||
// const { css } = useYoshiki();
|
||||
// const { t } = useTranslation();
|
||||
// const { data, error } = useFetch(OidcSettings.query());
|
||||
// const queryClient = useQueryClient();
|
||||
|
||||
@ -12,7 +12,7 @@ export const useForceRerender = () => {
|
||||
};
|
||||
|
||||
export function setServerData(_key: string, _val: any) {}
|
||||
export function getServerData(key: string) {
|
||||
export function getServerData(key: string): any {
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,9 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
"~/*": [
|
||||
"./src/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"rootDir": ".",
|
||||
@ -15,12 +17,23 @@
|
||||
"jsx": "react-jsx",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"types": ["node", "react"],
|
||||
"lib": ["dom", "esnext"]
|
||||
"types": [
|
||||
"node",
|
||||
"react"
|
||||
],
|
||||
"lib": [
|
||||
"dom",
|
||||
"esnext"
|
||||
]
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".expo/types/**/*.ts",
|
||||
"expo-env.d.ts",
|
||||
"node_modules/uniwind/types.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".expo",
|
||||
"scripts",
|
||||
"**/test",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user