Update duration display on selection change

This commit is contained in:
Rob Watson 2022-01-17 18:58:13 +01:00
parent f386e12f72
commit a4e9ebca3b
5 changed files with 49 additions and 16 deletions

View File

@ -17,8 +17,9 @@ import { firstValueFrom, from, Observable } from 'rxjs';
import { first, map } from 'rxjs/operators';
import millisFromDuration from './helpers/millisFromDuration';
import { zoomViewportIn, zoomViewportOut } from './helpers/zoom';
import { ExternalLinkIcon } from '@heroicons/react/solid';
import toHHMMSS from './helpers/toHHMMSS';
import framesToDuration from './helpers/framesToDuration';
import { ClockIcon, ExternalLinkIcon } from '@heroicons/react/solid';
// ported from backend, where should they live?
const thumbnailWidth = 177; // height 100
@ -330,8 +331,6 @@ function App(): JSX.Element {
})();
};
const zoomMultiplier = 1.777;
const handleZoomIn = () => {
if (mediaSet == null) {
return;
@ -392,6 +391,26 @@ function App(): JSX.Element {
[mediaSet]
);
const durationString = useCallback((): string => {
if (!mediaSet || !mediaSet.videoDuration) {
return '';
}
const totalDur = toHHMMSS(mediaSet.videoDuration);
if (selection.start == selection.end) {
return totalDur;
}
const clipDur = toHHMMSS(
framesToDuration(
selection.end - selection.start,
mediaSet.audioSampleRate
)
);
return `Selected ${clipDur} of ${totalDur}`;
}, [mediaSet, selection]);
// render component
const offsetPixels = Math.floor(thumbnailWidth / 2);
@ -408,7 +427,7 @@ function App(): JSX.Element {
<header className="bg-green-900 h-16 grow-0 flex items-center mb-12 px-[88px]">
<h1 className="text-3xl font-bold">Clipper</h1>
</header>
<div className="flex flex-col grow-1 bg-gray-800 w-full h-full mx-auto">
<div className="flex flex-col grow bg-gray-800 w-full h-full mx-auto">
<div className={`flex flex-col grow ${marginClass}`}>
<div className="flex grow-0 h-8 pt-4 pb-2 items-center space-x-2 text-white">
<span className="text-gray-300">{mediaSet.author}</span>
@ -422,6 +441,10 @@ function App(): JSX.Element {
>
<ExternalLinkIcon className="h-6 w-6 text-gray-500 hover:text-gray-200" />
</a>
<span className="flex grow justify-end text-gray-500">
<ClockIcon className="h-5 w-5 mr-1 mt-0.5" />
{durationString()}
</span>
</div>
<ControlBar
playState={playState}
@ -429,8 +452,10 @@ function App(): JSX.Element {
onClip={handleClip}
onZoomIn={handleZoomIn}
onZoomOut={handleZoomOut}
downloadClipEnabled={selection.start != selection.end}
/>
<div className="w-full bg-gray-600 h-6"></div>
<Overview
peaks={overviewPeaks}
mediaSet={mediaSet}

View File

@ -16,16 +16,18 @@ interface Props {
onClip: () => void;
onZoomIn: () => void;
onZoomOut: () => void;
downloadClipEnabled: boolean;
}
const ControlBar: React.FC<Props> = React.memo((props: Props) => {
const buttonStyle =
'bg-gray-700 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded';
const largeButtonStyle =
'bg-green-700 hover:bg-green-600 text-white font-bold py-2 px-4 rounded absolute right-0';
const downloadButtonStyle = props.downloadClipEnabled
? 'bg-green-700 hover:bg-green-600 text-white font-bold py-2 px-4 rounded absolute right-0'
: 'bg-gray-700 hover:cursor-not-allowed text-gray-500 font-bold py-2 px-4 rounded absolute right-0';
const iconStyle = 'inline h-6 w-6 text-white-500';
const iconStyle = 'inline h-7 w-7 text-white-500';
const playPauseComponent =
props.playState == PlayState.Playing ? (
@ -34,6 +36,12 @@ const ControlBar: React.FC<Props> = React.memo((props: Props) => {
<PlayIcon className={iconStyle} />
);
const handleClip = () => {
if (props.downloadClipEnabled) {
props.onClip();
}
};
// Detect if the space bar has been used to trigger this event, and ignore
// it if so. This conflicts with the player interface.
const filterMouseEvent = (evt: React.MouseEvent, cb: () => void) => {
@ -71,11 +79,11 @@ const ControlBar: React.FC<Props> = React.memo((props: Props) => {
<ZoomOutIcon className={iconStyle} />
</button>
<button
className={largeButtonStyle}
onClick={(evt) => filterMouseEvent(evt, props.onClip)}
className={downloadButtonStyle}
onClick={(evt) => filterMouseEvent(evt, handleClip)}
>
<CloudDownloadIcon className={`${iconStyle} mr-2`} />
Download clip
Download clip as MP3
</button>
</div>
</>

View File

@ -136,8 +136,8 @@ export const HudCanvas: React.FC<Props> = ({
ctx.strokeStyle = positionStrokeStyle;
ctx.lineWidth = positionLineWidth;
ctx.moveTo(position, 0);
ctx.lineWidth = 4;
ctx.lineTo(position, canvas.height - 4);
ctx.lineWidth = position == 0 ? 8 : 4;
ctx.lineTo(position, canvas.height);
ctx.stroke();
});
}, [selection, newSelection, position]);

View File

@ -77,7 +77,7 @@ export const Overview: React.FC<Props> = ({
return (
<>
<div className={`relative grow-0 h-[80px]`}>
<div className={`relative grow-0 h-16`}>
<WaveformCanvas
peaks={peaks}
channels={mediaSet.audioChannels}

View File

@ -45,7 +45,7 @@ export const SeekBar: React.FC<Props> = ({
canvas.width = canvas.height * (canvas.clientWidth / canvas.clientHeight);
// background
ctx.fillStyle = '#444444';
ctx.fillStyle = 'transparent';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// seek bar
@ -119,7 +119,7 @@ export const SeekBar: React.FC<Props> = ({
return (
<>
<canvas
className={`w-full h-[30px] mx-0 my-auto ${cursor}`}
className={`w-full bg-gray-700 h-10 mx-0 my-auto ${cursor}`}
ref={canvasRef}
width={LogicalWidth}
height={LogicalHeight}