mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-31 12:14:46 -04:00
Add blurhash container component
This commit is contained in:
parent
7ad1383acb
commit
a04bffbec9
43
front/packages/primitives/src/image/blurhash.tsx
Normal file
43
front/packages/primitives/src/image/blurhash.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Kyoo - A portable and vast media library solution.
|
||||||
|
* Copyright (c) Kyoo.
|
||||||
|
*
|
||||||
|
* See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
*
|
||||||
|
* Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* Kyoo is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
import { View } from "react-native";
|
||||||
|
import { Blurhash } from "react-native-blurhash";
|
||||||
|
import { Stylable, useYoshiki } from "yoshiki/native";
|
||||||
|
|
||||||
|
export const BlurhashContainer = ({
|
||||||
|
blurhash,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: { blurhash: string; children?: ReactElement | ReactElement[] } & Stylable) => {
|
||||||
|
const { css } = useYoshiki();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View {...props}>
|
||||||
|
<Blurhash
|
||||||
|
blurhash={blurhash}
|
||||||
|
resizeMode="cover"
|
||||||
|
{...css({ position: "absolute", top: 0, bottom: 0, left: 0, right: 0 })}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
@ -19,6 +19,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { decode } from "blurhash";
|
import { decode } from "blurhash";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
import { useYoshiki } from "yoshiki";
|
||||||
|
import { Stylable } from "yoshiki/native";
|
||||||
|
import { StyleList, processStyleList } from "yoshiki/src/type";
|
||||||
|
|
||||||
// The blurhashToUrl has been stolen from https://gist.github.com/mattiaz9/53cb67040fa135cb395b1d015a200aff
|
// The blurhashToUrl has been stolen from https://gist.github.com/mattiaz9/53cb67040fa135cb395b1d015a200aff
|
||||||
export function blurHashToDataURL(hash: string | undefined): string | undefined {
|
export function blurHashToDataURL(hash: string | undefined): string | undefined {
|
||||||
@ -186,3 +190,45 @@ function generatePng(width: number, height: number, rgbaString: string) {
|
|||||||
const pngString = SIGNATURE + IHDR + IDAT + IEND;
|
const pngString = SIGNATURE + IHDR + IDAT + IEND;
|
||||||
return pngString;
|
return pngString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract classnames from leftover props using yoshiki's internal.
|
||||||
|
const extractClassNames = <Style,>(props: {
|
||||||
|
style?: StyleList<{ $$css?: true; yoshiki?: string } | Style>;
|
||||||
|
}) => {
|
||||||
|
const inline = processStyleList(props.style);
|
||||||
|
return "$$css" in inline && inline.$$css ? inline.yoshiki : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BlurhashContainer = ({
|
||||||
|
blurhash,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: { blurhash: string; children?: ReactElement | ReactElement[] } & Stylable) => {
|
||||||
|
const { css } = useYoshiki();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
// To reproduce view's behavior
|
||||||
|
boxSizing: "border-box",
|
||||||
|
overflow: "hidden",
|
||||||
|
|
||||||
|
// Use a blurhash here to nicely fade the NextImage when it is loaded completly
|
||||||
|
// (this prevents loading the image line by line which is ugly and buggy on firefox)
|
||||||
|
backgroundImage: `url(${blurHashToDataURL(blurhash)})`,
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
backgroundPosition: "50% 50%",
|
||||||
|
}}
|
||||||
|
// This should be in yoshiki but this allows this component to be styled like a native component.
|
||||||
|
{...css(
|
||||||
|
{ position: "relative" },
|
||||||
|
{
|
||||||
|
className: extractClassNames(props),
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -20,22 +20,12 @@
|
|||||||
|
|
||||||
import { useLayoutEffect, useState } from "react";
|
import { useLayoutEffect, useState } from "react";
|
||||||
import { ImageStyle, View, ViewStyle } from "react-native";
|
import { ImageStyle, View, ViewStyle } from "react-native";
|
||||||
import { StyleList, processStyleList } from "yoshiki/src/type";
|
|
||||||
import { useYoshiki as useWebYoshiki } from "yoshiki/web";
|
|
||||||
import { useYoshiki } from "yoshiki/native";
|
import { useYoshiki } from "yoshiki/native";
|
||||||
import { Props, ImageLayout } from "./base-image";
|
import { Props, ImageLayout } from "./base-image";
|
||||||
import { blurHashToDataURL } from "./blurhash-web";
|
import { BlurhashContainer, blurHashToDataURL } from "./blurhash.web";
|
||||||
import { Skeleton } from "../skeleton";
|
import { Skeleton } from "../skeleton";
|
||||||
import NextImage from "next/image";
|
import NextImage from "next/image";
|
||||||
|
|
||||||
// Extract classnames from leftover props using yoshiki's internal.
|
|
||||||
const extractClassNames = <Style,>(props: {
|
|
||||||
style?: StyleList<{ $$css?: true; yoshiki?: string } | Style>;
|
|
||||||
}) => {
|
|
||||||
const inline = processStyleList(props.style);
|
|
||||||
return "$$css" in inline && inline.$$css ? inline.yoshiki : undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Image = ({
|
export const Image = ({
|
||||||
src,
|
src,
|
||||||
quality,
|
quality,
|
||||||
@ -46,7 +36,6 @@ export const Image = ({
|
|||||||
...props
|
...props
|
||||||
}: Props & { style?: ImageStyle } & { layout: ImageLayout }) => {
|
}: Props & { style?: ImageStyle } & { layout: ImageLayout }) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
const { css: wCss } = useWebYoshiki();
|
|
||||||
const [state, setState] = useState<"loading" | "errored" | "finished">(
|
const [state, setState] = useState<"loading" | "errored" | "finished">(
|
||||||
src ? "finished" : "errored",
|
src ? "finished" : "errored",
|
||||||
);
|
);
|
||||||
@ -66,26 +55,8 @@ export const Image = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const blurhash = blurHashToDataURL(src.blurhash);
|
|
||||||
return (
|
return (
|
||||||
<div
|
<BlurhashContainer blurhash={src.blurhash} {...css([layout, border], props)}>
|
||||||
style={{
|
|
||||||
// To reproduce view's behavior
|
|
||||||
boxSizing: "border-box",
|
|
||||||
overflow: "hidden",
|
|
||||||
|
|
||||||
// Use a blurhash here to nicely fade the NextImage when it is loaded completly
|
|
||||||
// (this prevents loading the image line by line which is ugly and buggy on firefox)
|
|
||||||
backgroundImage: `url(${blurhash})`,
|
|
||||||
backgroundSize: "cover",
|
|
||||||
backgroundRepeat: "no-repeat",
|
|
||||||
backgroundPosition: "50% 50%",
|
|
||||||
}}
|
|
||||||
{...wCss([layout as any, { ...border, borderRadius: "6px", position: "relative" }], {
|
|
||||||
// Gather classnames from props (to support parent's hover for example).
|
|
||||||
className: extractClassNames(props),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<NextImage
|
<NextImage
|
||||||
src={src[quality ?? "high"]}
|
src={src[quality ?? "high"]}
|
||||||
priority={quality === "high"}
|
priority={quality === "high"}
|
||||||
@ -96,13 +67,13 @@ export const Image = ({
|
|||||||
opacity: state === "loading" ? 0 : 1,
|
opacity: state === "loading" ? 0 : 1,
|
||||||
transition: "opacity .2s ease-out",
|
transition: "opacity .2s ease-out",
|
||||||
}}
|
}}
|
||||||
blurDataURL={blurhash}
|
blurDataURL={blurHashToDataURL(src.blurhash)}
|
||||||
placeholder="blur"
|
placeholder="blur"
|
||||||
// Don't use next's server to reprocess images, they are already optimized by kyoo.
|
// Don't use next's server to reprocess images, they are already optimized by kyoo.
|
||||||
unoptimized={true}
|
unoptimized={true}
|
||||||
onLoadingComplete={() => setState("finished")}
|
onLoadingComplete={() => setState("finished")}
|
||||||
onError={() => setState("errored")}
|
onError={() => setState("errored")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</BlurhashContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -18,14 +18,15 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ImageProps, ImageStyle, Platform, View, ViewProps, ViewStyle } from "react-native";
|
import { ImageStyle, View, ViewProps, ViewStyle } from "react-native";
|
||||||
import { Props, YoshikiEnhanced } from "./base-image";
|
import { Props, YoshikiEnhanced } from "./base-image";
|
||||||
import { Image } from "./image";
|
import { Image } from "./image";
|
||||||
import { ComponentType, ReactNode, useState } from "react";
|
import { ComponentType, ReactNode } from "react";
|
||||||
import { LinearGradient, LinearGradientProps } from "expo-linear-gradient";
|
import { LinearGradient, LinearGradientProps } from "expo-linear-gradient";
|
||||||
import { ContrastArea, alpha } from "../themes";
|
import { ContrastArea, alpha } from "../themes";
|
||||||
import { percent } from "yoshiki/native";
|
import { percent } from "yoshiki/native";
|
||||||
|
|
||||||
|
export { BlurhashContainer } from "./blurhash";
|
||||||
export { type Props as ImageProps, Image };
|
export { type Props as ImageProps, Image };
|
||||||
|
|
||||||
export const Poster = ({
|
export const Poster = ({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user