Fix layout (#454)

* player layout autosize

* fix video layout

* improve style
This commit is contained in:
an-lee
2024-03-28 10:35:32 +08:00
committed by GitHub
parent 97f970ed5b
commit e63d77cd82
12 changed files with 239 additions and 139 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect, useContext, useRef } from "react";
import { useEffect, useContext, useState } from "react";
import { MediaPlayerProviderContext } from "@renderer/context";
import {
MediaLoadingModal,
@@ -7,43 +7,45 @@ import {
MediaTabs,
MediaCurrentRecording,
MediaPlayer,
LoaderSpin,
} from "@renderer/components";
import { useAudio } from "@renderer/hooks";
export const AudioPlayer = (props: { id?: string; md5?: string }) => {
const { id, md5 } = props;
const { setMedia } = useContext(MediaPlayerProviderContext);
const { setMedia, layout } = useContext(MediaPlayerProviderContext);
const { audio } = useAudio({ id, md5 });
useEffect(() => {
if (!audio) return;
setMedia(audio);
}, [audio]);
if (!layout) return <LoaderSpin />;
return (
<div data-testid="audio-player">
<div className="h-[calc(100vh-37.5rem)] mb-4">
<div className="grid grid-cols-3 gap-6 px-6 h-full">
<div className="col-span-1 rounded-lg border shadow-lg h-[calc(100vh-37.5rem)]">
<div className={`${layout.upperWrapper} mb-4`}>
<div className="grid grid-cols-5 xl:grid-cols-3 gap-6 px-6 h-full">
<div className={`col-span-2 xl:col-span-1 rounded-lg border shadow-lg ${layout.upperWrapper}`}>
<MediaTabs />
</div>
<div className="col-span-2 h-[calc(100vh-37.5rem)]">
<div className={`col-span-3 xl:col-span-2 ${layout.upperWrapper}`}>
<MediaCaption />
</div>
</div>
</div>
<div className="h-[33rem] flex flex-col">
<div className="h-[13rem] py-2 px-6 mb-4">
<MediaCurrentRecording />
<div className={`${layout.lowerWrapper} flex flex-col`}>
<div className={`${layout.playerWrapper} py-2 px-6`}>
<MediaCurrentRecording height={layout.playerHeight} />
</div>
<div className="w-full h-[13rem] px-6 py-2 mb-4">
<div className={`${layout.playerWrapper} py-2 px-6`}>
<MediaPlayer />
</div>
<div className="w-full bg-background z-10 shadow-xl">
<div className={`${layout.panelWrapper} w-full bg-background z-10 shadow-xl`}>
<MediaPlayerControls />
</div>
</div>

View File

@@ -46,8 +46,9 @@ import { formatDuration } from "@renderer/lib/utils";
import { useHotkeys } from "react-hotkeys-hook";
export const MediaCurrentRecording = (props: { height?: number }) => {
const { height = 192 } = props;
const { height } = props;
const {
layout,
isRecording,
setIsRecording,
currentRecording,
@@ -281,9 +282,9 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
});
return () => {
ws.destroy();
ws?.destroy();
};
}, [ref, currentRecording, isRecording]);
}, [ref, currentRecording, isRecording, height]);
useEffect(() => {
setIsComparing(false);
@@ -315,7 +316,7 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
}
const subscriptions = [
regions.on("region-created", () => {}),
regions.on("region-created", () => { }),
regions.on("region-clicked", (region, e) => {
e.stopPropagation();
@@ -383,16 +384,7 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
useEffect(() => {
calContainerWidth();
window.addEventListener("resize", () => {
calContainerWidth();
});
return () => {
window.removeEventListener("resize", () => {
calContainerWidth();
});
};
}, [currentRecording, isRecording]);
}, [currentRecording, isRecording, layout?.width]);
useHotkeys(
["Ctrl+R", "Meta+R"],
@@ -410,7 +402,7 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
[player]
);
if (isRecording) return <MediaRecorder />;
if (isRecording) return <MediaRecorder height={height} />;
if (!currentRecording?.src)
return (
<div className="h-full w-full flex items-center space-x-4">
@@ -480,27 +472,31 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
setIsRecording={setIsRecording}
/>
<Button
variant={isComparing ? "secondary" : "outline"}
size="icon"
data-tooltip-id="media-player-tooltip"
data-tooltip-content={t("compare")}
className="rounded-full w-8 h-8 p-0"
onClick={toggleCompare}
>
<GitCompareIcon className="w-4 h-4" />
</Button>
{
height >= 192 && <>
<Button
variant={isComparing ? "secondary" : "outline"}
size="icon"
data-tooltip-id="media-player-tooltip"
data-tooltip-content={t("compare")}
className="rounded-full w-8 h-8 p-0"
onClick={toggleCompare}
>
<GitCompareIcon className="w-4 h-4" />
</Button>
<Button
variant={isSelectingRegion ? "secondary" : "outline"}
size="icon"
data-tooltip-id="media-player-tooltip"
data-tooltip-content={t("selectRegion")}
className="rounded-full w-8 h-8 p-0"
onClick={() => setIsSelectingRegion(!isSelectingRegion)}
>
<TextCursorInputIcon className="w-4 h-4" />
</Button>
<Button
variant={isSelectingRegion ? "secondary" : "outline"}
size="icon"
data-tooltip-id="media-player-tooltip"
data-tooltip-content={t("selectRegion")}
className="rounded-full w-8 h-8 p-0"
onClick={() => setIsSelectingRegion(!isSelectingRegion)}
>
<TextCursorInputIcon className="w-4 h-4" />
</Button>
</>
}
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -522,17 +518,16 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
>
<GaugeCircleIcon
className={`w-4 h-4 mr-4
${
currentRecording.pronunciationAssessment
? currentRecording.pronunciationAssessment
.pronunciationScore >= 80
? "text-green-500"
: currentRecording.pronunciationAssessment
.pronunciationScore >= 60
? "text-yellow-600"
: "text-red-500"
: ""
}
${currentRecording.pronunciationAssessment
? currentRecording.pronunciationAssessment
.pronunciationScore >= 80
? "text-green-500"
: currentRecording.pronunciationAssessment
.pronunciationScore >= 60
? "text-yellow-600"
: "text-red-500"
: ""
}
`}
/>
<span>{t("pronunciationAssessment")}</span>

View File

@@ -456,7 +456,7 @@ export const MediaPlayerControls = () => {
}, [grouping]);
return (
<div className="w-full h-20 flex items-center justify-center px-6">
<div className="w-full h-full flex items-center justify-center px-6">
<div className="flex items-center justify-center space-x-2">
<Popover>
<PopoverTrigger asChild>
@@ -480,11 +480,10 @@ export const MediaPlayerControls = () => {
{PLAYBACK_RATE_OPTIONS.map((rate, i) => (
<div
key={i}
className={`cursor-pointer h-10 w-10 leading-10 rounded-full flex items-center justify-center ${
rate === playbackRate
className={`cursor-pointer h-10 w-10 leading-10 rounded-full flex items-center justify-center ${rate === playbackRate
? "bg-primary text-white text-md"
: "text-black/70 text-xs"
}`}
}`}
onClick={() => {
setPlaybackRate(rate);
}}

View File

@@ -41,6 +41,7 @@ const MAX_ZOOM_RATIO = 4.0;
export const MediaPlayer = () => {
const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext);
const {
layout,
media,
currentTime,
setRef,
@@ -132,16 +133,7 @@ export const MediaPlayer = () => {
useEffect(() => {
calContainerWidth();
window.addEventListener("resize", () => {
calContainerWidth();
});
return () => {
window.removeEventListener("resize", () => {
calContainerWidth();
});
};
}, []);
}, [layout.width]);
return (
<div className="flex space-x-4 media-player-wrapper">
@@ -192,38 +184,44 @@ export const MediaPlayer = () => {
<ZoomInIcon className="w-4 h-4" />
</Button>
<Button
variant={`${zoomRatio < 1.0 ? "secondary" : "outline"}`}
data-tooltip-id="media-player-tooltip"
data-tooltip-content={t("zoomOut")}
className="relative aspect-square rounded-full p-0 h-8"
onClick={() => {
if (zoomRatio > MIN_ZOOM_RATIO) {
const nextZoomRatio = ZOOM_RATIO_OPTIONS.reverse().find(
(rate) => rate < zoomRatio
);
setZoomRatio(nextZoomRatio || MIN_ZOOM_RATIO);
}
}}
>
<ZoomOutIcon className="w-4 h-4" />
</Button>
{
layout.name === "lg" && (
<>
<Button
variant={`${zoomRatio < 1.0 ? "secondary" : "outline"}`}
data-tooltip-id="media-player-tooltip"
data-tooltip-content={t("zoomOut")}
className="relative aspect-square rounded-full p-0 h-8"
onClick={() => {
if (zoomRatio > MIN_ZOOM_RATIO) {
const nextZoomRatio = ZOOM_RATIO_OPTIONS.reverse().find(
(rate) => rate < zoomRatio
);
setZoomRatio(nextZoomRatio || MIN_ZOOM_RATIO);
}
}}
>
<ZoomOutIcon className="w-4 h-4" />
</Button>
<Button
variant={`${displayInlineCaption ? "secondary" : "outline"}`}
data-tooltip-id="media-player-tooltip"
data-tooltip-content={t("inlineCaption")}
className="relative aspect-square rounded-full p-0 h-8"
onClick={() => {
setDisplayInlineCaption(!displayInlineCaption);
if (pitchChart) {
pitchChart.options.scales.x.display = !displayInlineCaption;
pitchChart.update();
}
}}
>
<SpellCheckIcon className="w-4 h-4" />
</Button>
<Button
variant={`${displayInlineCaption ? "secondary" : "outline"}`}
data-tooltip-id="media-player-tooltip"
data-tooltip-content={t("inlineCaption")}
className="relative aspect-square rounded-full p-0 h-8"
onClick={() => {
setDisplayInlineCaption(!displayInlineCaption);
if (pitchChart) {
pitchChart.options.scales.x.display = !displayInlineCaption;
pitchChart.update();
}
}}
>
<SpellCheckIcon className="w-4 h-4" />
</Button>
</>
)
}
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -239,6 +237,40 @@ export const MediaPlayer = () => {
</DropdownMenuTrigger>
<DropdownMenuContent>
{
layout.name === "sm" && (
<>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {
if (zoomRatio > MIN_ZOOM_RATIO) {
const nextZoomRatio = ZOOM_RATIO_OPTIONS.reverse().find(
(rate) => rate < zoomRatio
);
setZoomRatio(nextZoomRatio || MIN_ZOOM_RATIO);
}
}}
>
<ZoomOutIcon className="w-4 h-4 mr-4" />
<span>{t("zoomOut")}</span>
</DropdownMenuItem>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {
setDisplayInlineCaption(!displayInlineCaption);
if (pitchChart) {
pitchChart.options.scales.x.display = !displayInlineCaption;
pitchChart.update();
}
}}
>
<SpellCheckIcon className="w-4 h-4 mr-4" />
<span>{t("inlineCaption")}</span>
</DropdownMenuItem>
</>
)
}
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {

View File

@@ -20,8 +20,9 @@ export const MediaProvider = () => {
if (!media?.src) return null;
return (
<div className="px-4" data-testid="media-player">
<div className="px-2 py-4" data-testid="media-player">
<VidstackMediaPlayer
className="my-auto"
controls
src={media.src}
onCanPlayThrough={(detail, nativeEvent) => {

View File

@@ -24,15 +24,13 @@ export const MediaTabs = () => {
return (
<ScrollArea className="h-full">
<div
className={`p-1 bg-muted rounded-t-lg mb-2 text-sm sticky top-0 z-10 grid gap-4 ${
media?.mediaType === "Video" ? "grid-cols-4" : "grid-cols-3"
}`}
className={`p-1 bg-muted rounded-t-lg mb-2 text-sm sticky top-0 z-10 grid gap-4 ${media?.mediaType === "Video" ? "grid-cols-4" : "grid-cols-3"
}`}
>
{media.mediaType === "Video" && (
<div
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize ${
tab === "provider" ? "bg-background" : ""
}`}
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${tab === "provider" ? "bg-background" : ""
}`}
onClick={() => setTab("provider")}
>
{t("player")}
@@ -40,25 +38,22 @@ export const MediaTabs = () => {
)}
<div
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize ${
tab === "transcription" ? "bg-background" : ""
}`}
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${tab === "transcription" ? "bg-background" : ""
}`}
onClick={() => setTab("transcription")}
>
{t("transcription")}
</div>
<div
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize ${
tab === "recordings" ? "bg-background" : ""
}`}
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${tab === "recordings" ? "bg-background" : ""
}`}
onClick={() => setTab("recordings")}
>
{t("myRecordings")}
</div>
<div
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize ${
tab === "info" ? "bg-background" : ""
}`}
className={`rounded cursor-pointer px-2 py-1 text-sm text-center capitalize truncate ${tab === "info" ? "bg-background" : ""
}`}
onClick={() => setTab("info")}
>
{t("mediaInfo")}

View File

@@ -7,12 +7,13 @@ import {
MediaTabs,
MediaCurrentRecording,
MediaPlayer,
LoaderSpin,
} from "@renderer/components";
import { useVideo } from "@renderer/hooks";
export const VideoPlayer = (props: { id?: string; md5?: string }) => {
const { id, md5 } = props;
const { setMedia } = useContext(MediaPlayerProviderContext);
const { setMedia, layout } = useContext(MediaPlayerProviderContext);
const { video } = useVideo({ id, md5 });
useEffect(() => {
@@ -21,29 +22,31 @@ export const VideoPlayer = (props: { id?: string; md5?: string }) => {
setMedia(video);
}, [video]);
if (!layout) return <LoaderSpin />;
return (
<div data-testid="video-player">
<div className="h-[calc(100vh-37.5rem)] mb-4">
<div className="grid grid-cols-3 gap-4 px-6 h-full">
<div className="col-span-1 rounded-lg border shadow-lg h-[calc(100vh-37.5rem)]">
<div className={`${layout.upperWrapper} mb-4`}>
<div className="grid grid-cols-5 xl:grid-cols-3 gap-6 px-6 h-full">
<div className={`col-span-2 xl:col-span-1 rounded-lg border shadow-lg ${layout.upperWrapper}`}>
<MediaTabs />
</div>
<div className="col-span-2 h-[calc(100vh-37.5rem)]">
<div className={`col-span-3 xl:col-span-2 ${layout.upperWrapper}`}>
<MediaCaption />
</div>
</div>
</div>
<div className="h-[33rem] flex flex-col">
<div className="h-[13rem] py-2 px-6 mb-4">
<MediaCurrentRecording />
<div className={`${layout.lowerWrapper} flex flex-col`}>
<div className={`${layout.playerWrapper} py-2 px-6`}>
<MediaCurrentRecording height={layout.playerHeight} />
</div>
<div className="w-full h-[13rem] px-6 py-2 mb-4">
<div className={`${layout.playerWrapper} py-2 px-6`}>
<MediaPlayer />
</div>
<div className="w-full bg-background z-10 shadow-xl">
<div className={`${layout.panelWrapper} w-full bg-background z-10 shadow-xl`}>
<MediaPlayerControls />
</div>
</div>