From a33057651d09d91c05be0f76fa6b2f8c0f850b25 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Fri, 14 Jan 2022 12:54:54 +0100 Subject: [PATCH] Update frontend with Tailwind. - Replace inline CSS with Tailwind classes - Improve page layout and scaling - Add icons to ControlBar - Small refactor of play/pause logic - Add basic (not by any means final) colours --- frontend/package.json | 1 + frontend/public/manifest.json | 5 +- frontend/src/App.css | 7 -- frontend/src/App.tsx | 121 ++++++++++++++++++-------------- frontend/src/ControlBar.tsx | 64 +++++++++++------ frontend/src/HudCanvas.tsx | 29 +++----- frontend/src/Overview.tsx | 15 +--- frontend/src/SeekBar.tsx | 15 ++-- frontend/src/VideoPreview.tsx | 21 +----- frontend/src/Waveform.tsx | 13 +--- frontend/src/WaveformCanvas.tsx | 18 +---- frontend/yarn.lock | 5 ++ 12 files changed, 138 insertions(+), 176 deletions(-) delete mode 100644 frontend/src/App.css diff --git a/frontend/package.json b/frontend/package.json index 1609da5..53a0e04 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@heroicons/react": "^1.0.5", "@improbable-eng/grpc-web": "^0.14.1", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json index 080d6c7..2f9149f 100644 --- a/frontend/public/manifest.json +++ b/frontend/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "Clipper", + "name": "Clipper", "icons": [ { "src": "favicon.ico", @@ -20,6 +20,7 @@ ], "start_url": ".", "display": "standalone", + "orientation": "landscape", "theme_color": "#000000", "background_color": "#ffffff" } diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index 5a5b8cf..0000000 --- a/frontend/src/App.css +++ /dev/null @@ -1,7 +0,0 @@ -body { - background-color: #333; -} - -.App { - text-align: center; -} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8a10f75..3ad6217 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -13,15 +13,15 @@ import { Overview, CanvasLogicalWidth } from './Overview'; import { Waveform } from './Waveform'; import { ControlBar } from './ControlBar'; import { SeekBar } from './SeekBar'; -import './App.css'; 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'; + // ported from backend, where should they live? -const thumbnailWidth = 177; -const thumbnailHeight = 100; +const thumbnailWidth = 177; // height 100 const initialViewportCanvasPixels = 100; @@ -40,6 +40,11 @@ export interface VideoPosition { percent: number; } +export enum PlayState { + Paused, + Playing, +} + const video = document.createElement('video'); const audio = document.createElement('audio'); @@ -50,6 +55,7 @@ function App(): JSX.Element { const [overviewPeaks, setOverviewPeaks] = useState>( from([]) ); + const [playState, setPlayState] = useState(PlayState.Paused); // position stores the current playback position. positionRef makes it // available inside a setInterval callback. @@ -98,7 +104,7 @@ function App(): JSX.Element { currentTimeToFrame(position.currentTime) < selection.end && currentTimeToFrame(currTime) >= selection.end ) { - handlePause(); + pause(); } // update the current position @@ -114,7 +120,7 @@ function App(): JSX.Element { useEffect(() => { document.addEventListener('keypress', handleKeyPress); return () => document.removeEventListener('keypress', handleKeyPress); - }, [selection]); + }, [playState]); // load audio when MediaSet is loaded: useEffect(() => { @@ -189,11 +195,7 @@ function App(): JSX.Element { return; } - if (audio.paused) { - handlePlay(); - } else { - handlePause(); - } + handleTogglePlay(); }; // handler called when the selection in the overview (zoom setting) is changed. @@ -227,18 +229,30 @@ function App(): JSX.Element { video.currentTime = currentTime; }; - const handlePlay = () => { - audio.play(); - video.play(); + const handleTogglePlay = () => { + if (playState == PlayState.Paused) { + play(); + } else { + pause(); + } }; - const handlePause = () => { + const play = () => { + audio.play(); + video.play(); + + setPlayState(PlayState.Playing); + }; + + const pause = () => { video.pause(); audio.pause(); if (selection.start != selection.end) { setPositionFromFrame(selection.start); } + + setPlayState(PlayState.Paused); }; const handleClip = () => { @@ -370,17 +384,8 @@ function App(): JSX.Element { // render component - const containerStyles = { - border: '1px solid black', - width: '90%', - margin: '1em auto', - minHeight: '500px', - height: '700px', - display: 'flex', - flexDirection: 'column', - } as React.CSSProperties; - const offsetPixels = Math.floor(thumbnailWidth / 2); + const marginClass = 'mx-[88px]'; // offsetPixels if (mediaSet == null) { // TODO: improve @@ -389,33 +394,48 @@ function App(): JSX.Element { return ( <> -
-
- +
+
+

Clipper

+
+
+
+
+ {mediaSet.author} + / + {mediaSet.title} + + + +
+ - + - + +
-
    +
    • Frames: {mediaSet.audioFrames}
    • Viewport (frames): {viewport.start} to {viewport.end} diff --git a/frontend/src/ControlBar.tsx b/frontend/src/ControlBar.tsx index aa81195..89d3393 100644 --- a/frontend/src/ControlBar.tsx +++ b/frontend/src/ControlBar.tsx @@ -1,42 +1,60 @@ import React from 'react'; +import { PlayState } from './App'; +import { + CloudDownloadIcon, + FastForwardIcon, + PauseIcon, + PlayIcon, + RewindIcon, + ZoomInIcon, + ZoomOutIcon, +} from '@heroicons/react/solid'; interface Props { - onPlay: () => void; - onPause: () => void; + playState: PlayState; + onTogglePlay: () => void; onClip: () => void; onZoomIn: () => void; onZoomOut: () => void; } const ControlBar: React.FC = React.memo((props: Props) => { - const styles = { width: '100%', flexGrow: 0 }; - const buttonStyles = { - cursor: 'pointer', - background: 'black', - outline: 'none', - border: 'none', - color: 'green', - display: 'inline-block', - margin: '0 2px', - }; + 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 iconStyle = 'inline h-6 w-6 text-white-500'; + + const playPauseComponent = + props.playState == PlayState.Playing ? ( + + ) : ( + + ); return ( <> -
      - - - - - +
      diff --git a/frontend/src/HudCanvas.tsx b/frontend/src/HudCanvas.tsx index 82d9128..6cf83c1 100644 --- a/frontend/src/HudCanvas.tsx +++ b/frontend/src/HudCanvas.tsx @@ -10,7 +10,6 @@ interface Styles { interface Props { width: number; height: number; - zIndex: number; emptySelectionAction: EmptySelectionAction; styles: Styles; position: number | null; @@ -48,7 +47,6 @@ const emptySelection: Selection = { start: 0, end: 0 }; export const HudCanvas: React.FC = ({ width, height, - zIndex, emptySelectionAction, styles: { borderLineWidth, @@ -66,7 +64,7 @@ export const HudCanvas: React.FC = ({ }); const [mode, setMode] = useState(Mode.Normal); const [hoverState, setHoverState] = useState(HoverState.Normal); - const [cursor, setCursor] = useState('auto'); + const [cursor, setCursor] = useState('cursor-auto'); const canvasRef = useRef(null); const moveOffsetX = useRef(0); @@ -189,11 +187,11 @@ export const HudCanvas: React.FC = ({ moveOffsetX.current = x; } else if (isHoveringSelection(x)) { setMode(Mode.Dragging); - setCursor('pointer'); + setCursor('cursor-pointer'); moveOffsetX.current = x; } else { setMode(Mode.Selecting); - setCursor('col-resize'); + setCursor('cursor-col-resize'); moveOffsetX.current = x; setNewSelection({ start: x, end: x }); } @@ -206,15 +204,15 @@ export const HudCanvas: React.FC = ({ case Mode.Normal: { if (isHoveringSelectionStart(x)) { setHoverState(HoverState.OverSelectionStart); - setCursor('col-resize'); + setCursor('cursor-col-resize'); } else if (isHoveringSelectionEnd(x)) { setHoverState(HoverState.OverSelectionEnd); - setCursor('col-resize'); + setCursor('cursor-col-resize'); } else if (isHoveringSelection(x)) { setHoverState(HoverState.OverSelection); - setCursor('pointer'); + setCursor('cursor-pointer'); } else { - setCursor('auto'); + setCursor('cursor-auto'); } break; } @@ -275,7 +273,7 @@ export const HudCanvas: React.FC = ({ } setMode(Mode.Normal); - setCursor('auto'); + setCursor('cursor-auto'); if (newSelection.start == newSelection.end) { handleEmptySelectionAction(); @@ -300,22 +298,13 @@ export const HudCanvas: React.FC = ({ setHoverState(HoverState.Normal); }; - const canvasStyles = { - display: 'block', - position: 'absolute', - width: '100%', - height: '100%', - zIndex: zIndex, - cursor: cursor, - } as React.CSSProperties; - return ( <> ; mediaSet: MediaSet; - height: number; - offsetPixels: number; position: VideoPosition; viewport: Frames; onSelectionChange: (selection: Selection) => void; @@ -26,8 +24,6 @@ export const CanvasLogicalHeight = 500; export const Overview: React.FC = ({ peaks, mediaSet, - height, - offsetPixels, position, viewport, onSelectionChange, @@ -69,13 +65,6 @@ export const Overview: React.FC = ({ // render component - const containerStyles = { - flexGrow: 0, - position: 'relative', - margin: `0 ${offsetPixels}px`, - height: `${height}px`, - } as React.CSSProperties; - const hudStyles = { borderLineWidth: 4, borderStrokeStyle: 'red', @@ -85,7 +74,7 @@ export const Overview: React.FC = ({ return ( <> -
      +
      = ({ height={CanvasLogicalHeight} strokeStyle="black" fillStyle="#003300" - zIndex={1} alpha={1} > = ({ onPositionChanged, }: Props) => { const [mode, setMode] = useState(Mode.Normal); - const [cursor, setCursor] = useState('auto'); + const [cursor, setCursor] = useState('cursor-auto'); const canvasRef = useRef(null); // render canvas @@ -98,9 +98,9 @@ export const SeekBar: React.FC = ({ // TODO: improve mouse detection around knob. if (y > InnerMargin && y < LogicalHeight - InnerMargin) { - setCursor('pointer'); + setCursor('cursor-pointer'); } else { - setCursor('auto'); + setCursor('cursor-auto'); } if (mode == Mode.Normal) return; @@ -116,17 +116,10 @@ export const SeekBar: React.FC = ({ // render component - const styles = { - width: '100%', - height: '30px', - margin: '0 auto', - cursor: cursor, - }; - return ( <> = ({ mediaSet, position, duration, - height, video, }: Props) => { const videoCanvasRef = useRef(null); @@ -76,30 +74,15 @@ export const VideoPreview: React.FC = ({ // render component - const containerStyles = { - height: height + 'px', - position: 'relative', - flexGrow: 0, - } as React.CSSProperties; - - const canvasStyles = { - position: 'absolute', - width: '100%', - height: '100%', - display: 'block', - zIndex: 1, - } as React.CSSProperties; - return ( <> -
      +
      -
      ); diff --git a/frontend/src/Waveform.tsx b/frontend/src/Waveform.tsx index 7ecb579..a2be14a 100644 --- a/frontend/src/Waveform.tsx +++ b/frontend/src/Waveform.tsx @@ -10,7 +10,6 @@ interface Props { mediaSet: MediaSet; position: VideoPosition; viewport: Frames; - offsetPixels: number; onSelectionChange: (selection: Selection) => void; } @@ -21,7 +20,6 @@ export const Waveform: React.FC = ({ mediaSet, position, viewport, - offsetPixels, onSelectionChange, }: Props) => { const [peaks, setPeaks] = useState>(from([])); @@ -115,13 +113,6 @@ export const Waveform: React.FC = ({ // render component - const containerStyles = { - background: 'black', - margin: '0 ' + offsetPixels + 'px', - flexGrow: 1, - position: 'relative', - } as React.CSSProperties; - const hudStyles = { borderLineWidth: 0, borderStrokeStyle: 'transparent', @@ -131,7 +122,7 @@ export const Waveform: React.FC = ({ return ( <> -
      +
      = ({ height={CanvasLogicalHeight} strokeStyle="green" fillStyle="black" - zIndex={0} alpha={1} > = React.memo((props: Props) => { const canvasRef = useRef(null); @@ -71,21 +63,13 @@ const WaveformCanvas: React.FC = React.memo((props: Props) => { })(); }, [props.peaks]); - const canvasStyles = { - display: 'block', - position: 'absolute', - width: '100%', - height: '100%', - zIndex: props.zIndex, - } as React.CSSProperties; - return ( <> ); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index b0fb4ec..e215c9e 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1102,6 +1102,11 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@heroicons/react@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.5.tgz#2fe4df9d33eb6ce6d5178a0f862e97b61c01e27d" + integrity sha512-UDMyLM2KavIu2vlWfMspapw9yii7aoLwzI2Hudx4fyoPwfKfxU8r3cL8dEBXOjcLG0/oOONZzbT14M1HoNtEcg== + "@humanwhocodes/config-array@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9"