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

View File

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

View File

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

View File

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

View File

@ -45,7 +45,7 @@ export const SeekBar: React.FC<Props> = ({
canvas.width = canvas.height * (canvas.clientWidth / canvas.clientHeight); canvas.width = canvas.height * (canvas.clientWidth / canvas.clientHeight);
// background // background
ctx.fillStyle = '#444444'; ctx.fillStyle = 'transparent';
ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillRect(0, 0, canvas.width, canvas.height);
// seek bar // seek bar
@ -119,7 +119,7 @@ export const SeekBar: React.FC<Props> = ({
return ( return (
<> <>
<canvas <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} ref={canvasRef}
width={LogicalWidth} width={LogicalWidth}
height={LogicalHeight} height={LogicalHeight}