File size: 3,651 Bytes
7e3630c 862182b 7e3630c 862182b 7e3630c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | import { Box, useIsScreenReaderEnabled, useStdout } from "src/ink";
import React, { useMemo } from "react";
import { useMeasuredSize } from "../../hooks/useMeasuredSize.js";
import usePosition from "../../hooks/usePosition.js";
import useProtocol from "../../hooks/useProtocol.js";
import type { GetVisibility, Visibility } from "../../hooks/useVisibility.js";
import { useVisibility } from "../../hooks/useVisibility.js";
import { useTerminalInfo } from "../../InkPictureProvider.js";
import AsciiImage from "./Ascii.js";
import BrailleImage from "./Braille.js";
import HalfBlockImage from "./HalfBlock.js";
import ITerm2Image from "./ITerm2.js";
import KittyImage from "./Kitty.js";
import type { ImageProps } from "./protocol.js";
import SixelImage from "./Sixel.js";
const imageProtocols = {
ascii: AsciiImage,
braille: BrailleImage,
halfBlock: HalfBlockImage,
iterm2: ITerm2Image,
kitty: KittyImage,
sixel: SixelImage,
};
export type ImageProtocolName = keyof typeof imageProtocols;
export type ImageProtocolHint = Partial<Record<Visibility, ImageProtocolName>>;
const ImageRenderer = (props: ImageProps & { protocol: ImageProtocolName }) => {
const ProtocolComponent = imageProtocols[props.protocol];
return <ProtocolComponent {...props} />;
};
type ImageComponentProps = Omit<ImageProps, "width" | "height"> & {
width?: number | string;
height?: number | string;
protocol?: ImageProtocolName | ImageProtocolHint;
getVisibility?: GetVisibility;
};
function Image({
protocol: specifiedProtocol,
getVisibility,
width = "100%",
height = "100%",
pixelWidth,
pixelHeight,
...props
}: ImageComponentProps) {
const isScreenReaderEnabled = useIsScreenReaderEnabled();
const terminalInfo = useTerminalInfo();
const { stdout } = useStdout();
const protocol = useProtocol(
typeof specifiedProtocol === "string" ? specifiedProtocol : undefined,
);
const { containerRef, resolvedWidth, resolvedHeight } = useMeasuredSize(
width,
height,
);
const position = usePosition(containerRef);
const visibility = useVisibility(
position,
stdout.rows,
stdout.columns,
getVisibility,
);
const effectiveProtocol = useMemo((): ImageProtocolName => {
if (typeof specifiedProtocol === "string") return specifiedProtocol;
const hints: ImageProtocolHint = specifiedProtocol ?? {};
const hint = hints[visibility];
if (hint) return hint;
if (visibility === "full") return protocol;
if (
protocol === "ascii" ||
protocol === "braille" ||
protocol === "halfBlock"
)
return protocol;
if (terminalInfo.supportsUnicode && terminalInfo.supportsColor)
return "halfBlock";
if (terminalInfo.supportsUnicode) return "braille";
return "ascii";
}, [
visibility,
protocol,
specifiedProtocol,
terminalInfo.supportsUnicode,
terminalInfo.supportsColor,
]);
if (isScreenReaderEnabled) {
const { src, alt } = props;
const label = `image: ${alt || (typeof src === "string" ? src : "binary image data")}`;
return (
<Box
ref={containerRef}
width={width}
height={height}
aria-label={label}
flexDirection="column"
/>
);
}
return (
<Box
ref={containerRef}
width={width}
height={height}
flexDirection="column"
>
<ImageRenderer
protocol={effectiveProtocol}
key={effectiveProtocol}
width={resolvedWidth}
height={resolvedHeight}
pixelWidth={pixelWidth}
pixelHeight={pixelHeight}
{...props}
/>
</Box>
);
}
export default Image;
|