ya-webadb/apps/demo/src/components/device-view.tsx
2023-02-23 14:02:01 +08:00

161 lines
4.8 KiB
TypeScript

import { StackItem } from "@fluentui/react";
import { makeStyles } from "@griffel/react";
import {
CSSProperties,
ComponentType,
HTMLAttributes,
ReactNode,
useImperativeHandle,
useMemo,
useRef,
useState,
} from "react";
import { forwardRef } from "../utils/with-display-name";
import { ResizeObserver, Size } from "./resize-observer";
export interface DeviceViewProps extends HTMLAttributes<HTMLDivElement> {
width: number;
height: number;
BottomElement?: ComponentType<{
className: string;
style: CSSProperties;
children: ReactNode;
}>;
children?: ReactNode;
}
export interface DeviceViewRef {
enterFullscreen(): void;
}
const useClasses = makeStyles({
outer: {
width: "100%",
height: "100%",
backgroundColor: "black",
},
inner: {
position: "absolute",
transformOrigin: "top left",
},
bottom: {
position: "absolute",
},
});
export const DeviceView = forwardRef<DeviceViewRef>("DeviceView")(
(
{ width, height, BottomElement, children, ...props }: DeviceViewProps,
ref
) => {
const classes = useClasses();
const [containerSize, setContainerSize] = useState<Size>({
width: 0,
height: 0,
});
const [bottomSize, setBottomSize] = useState<Size>({
width: 0,
height: 0,
});
// Container size minus bottom element size
const usableSize = useMemo(
() => ({
width: containerSize.width,
height: containerSize.height - bottomSize.height,
}),
[containerSize, bottomSize]
);
// Compute sizes after scaling
const childrenStyle = useMemo(() => {
let scale: number;
let childrenWidth: number;
let childrenHeight: number;
let childrenTop: number;
let childrenLeft: number;
if (width === 0 || usableSize.width === 0) {
scale = 1;
childrenWidth = 0;
childrenHeight = 0;
childrenTop = 0;
childrenLeft = 0;
} else {
const videoRatio = width / height;
const containerRatio = usableSize.width / usableSize.height;
if (videoRatio > containerRatio) {
scale = usableSize.width / width;
childrenWidth = usableSize.width;
childrenHeight = height * scale;
childrenTop = (usableSize.height - childrenHeight) / 2;
childrenLeft = 0;
} else {
scale = usableSize.height / height;
childrenWidth = width * scale;
childrenHeight = usableSize.height;
childrenTop = 0;
childrenLeft = (usableSize.width - childrenWidth) / 2;
}
}
return {
scale,
width: childrenWidth,
height: childrenHeight,
top: childrenTop,
left: childrenLeft,
};
}, [width, height, usableSize]);
const containerRef = useRef<HTMLDivElement | null>(null);
useImperativeHandle(
ref,
() => ({
enterFullscreen() {
containerRef.current!.requestFullscreen();
},
}),
[]
);
return (
<StackItem grow styles={{ root: { position: "relative" } }}>
<div ref={containerRef} className={classes.outer} {...props}>
<ResizeObserver onResize={setContainerSize} />
<div
className={classes.inner}
style={{
top: childrenStyle.top,
left: childrenStyle.left,
width,
height,
transform: `scale(${childrenStyle.scale})`,
}}
>
{children}
</div>
{!!width && !!BottomElement && (
<BottomElement
className={classes.bottom}
style={{
top: childrenStyle.top + childrenStyle.height,
left: childrenStyle.left,
width: childrenStyle.width,
}}
>
<ResizeObserver onResize={setBottomSize} />
</BottomElement>
)}
</div>
</StackItem>
);
}
);