diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 0f980d2..8a4d63c 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -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 {
-
+
{mediaSet.author}
@@ -422,6 +441,10 @@ function App(): JSX.Element {
>
+
+
+ {durationString()}
+
+
void;
onZoomIn: () => void;
onZoomOut: () => void;
+ downloadClipEnabled: boolean;
}
const ControlBar: React.FC = 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 = React.memo((props: Props) => {
);
+ 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 = React.memo((props: Props) => {
>
diff --git a/frontend/src/HudCanvas.tsx b/frontend/src/HudCanvas.tsx
index d71ff40..2835d48 100644
--- a/frontend/src/HudCanvas.tsx
+++ b/frontend/src/HudCanvas.tsx
@@ -136,8 +136,8 @@ export const HudCanvas: React.FC
= ({
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]);
diff --git a/frontend/src/Overview.tsx b/frontend/src/Overview.tsx
index a755d82..1e578ad 100644
--- a/frontend/src/Overview.tsx
+++ b/frontend/src/Overview.tsx
@@ -77,7 +77,7 @@ export const Overview: React.FC = ({
return (
<>
-
+
= ({
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 = ({
return (
<>