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
This commit is contained in:
parent
ec3ac8996d
commit
a33057651d
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
body {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
|
@ -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<Observable<number[]>>(
|
||||
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 (
|
||||
<>
|
||||
<div className="App">
|
||||
<div style={containerStyles}>
|
||||
<ControlBar
|
||||
onPlay={handlePlay}
|
||||
onPause={handlePause}
|
||||
onClip={handleClip}
|
||||
onZoomIn={handleZoomIn}
|
||||
onZoomOut={handleZoomOut}
|
||||
/>
|
||||
<div className="App bg-gray-800 h-screen flex flex-col">
|
||||
<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 ${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>
|
||||
<span>/</span>
|
||||
<span>{mediaSet.title}</span>
|
||||
<a
|
||||
href={`https://www.youtube.com/watch?v=${mediaSet.youtubeId}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
title="Open in YouTube"
|
||||
>
|
||||
<ExternalLinkIcon className="h-6 w-6 text-gray-500 hover:text-gray-200" />
|
||||
</a>
|
||||
</div>
|
||||
<ControlBar
|
||||
playState={playState}
|
||||
onTogglePlay={handleTogglePlay}
|
||||
onClip={handleClip}
|
||||
onZoomIn={handleZoomIn}
|
||||
onZoomOut={handleZoomOut}
|
||||
/>
|
||||
|
||||
<Overview
|
||||
peaks={overviewPeaks}
|
||||
mediaSet={mediaSet}
|
||||
offsetPixels={offsetPixels}
|
||||
height={80}
|
||||
viewport={viewport}
|
||||
position={position}
|
||||
onSelectionChange={handleOverviewSelectionChange}
|
||||
/>
|
||||
<Overview
|
||||
peaks={overviewPeaks}
|
||||
mediaSet={mediaSet}
|
||||
viewport={viewport}
|
||||
position={position}
|
||||
onSelectionChange={handleOverviewSelectionChange}
|
||||
/>
|
||||
|
||||
<Waveform
|
||||
mediaSet={mediaSet}
|
||||
position={position}
|
||||
viewport={viewport}
|
||||
offsetPixels={offsetPixels}
|
||||
onSelectionChange={handleWaveformSelectionChange}
|
||||
/>
|
||||
<Waveform
|
||||
mediaSet={mediaSet}
|
||||
position={position}
|
||||
viewport={viewport}
|
||||
onSelectionChange={handleWaveformSelectionChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SeekBar
|
||||
position={video.currentTime}
|
||||
|
@ -432,10 +452,9 @@ function App(): JSX.Element {
|
|||
video={video}
|
||||
position={position}
|
||||
duration={millisFromDuration(mediaSet.videoDuration)}
|
||||
height={thumbnailHeight}
|
||||
/>
|
||||
</div>
|
||||
<ul style={{ listStyleType: 'none' } as React.CSSProperties}>
|
||||
<ul className="hidden">
|
||||
<li>Frames: {mediaSet.audioFrames}</li>
|
||||
<li>
|
||||
Viewport (frames): {viewport.start} to {viewport.end}
|
||||
|
|
|
@ -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<Props> = 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 ? (
|
||||
<PauseIcon className={iconStyle} />
|
||||
) : (
|
||||
<PlayIcon className={iconStyle} />
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={styles}>
|
||||
<button style={buttonStyles} onClick={props.onPlay}>
|
||||
Play
|
||||
<div className="relative grow-0 w-full py-2 space-x-2">
|
||||
<button className={buttonStyle}>
|
||||
<RewindIcon className={iconStyle} />
|
||||
</button>
|
||||
<button style={buttonStyles} onClick={props.onPause}>
|
||||
Pause
|
||||
<button className={buttonStyle} onClick={props.onTogglePlay}>
|
||||
{playPauseComponent}
|
||||
</button>
|
||||
<button style={buttonStyles} onClick={props.onClip}>
|
||||
Clip
|
||||
<button className={buttonStyle}>
|
||||
<FastForwardIcon className={iconStyle} />
|
||||
</button>
|
||||
<button style={buttonStyles} onClick={props.onZoomIn}>
|
||||
Zoom In
|
||||
<button className={buttonStyle} onClick={props.onZoomIn}>
|
||||
<ZoomInIcon className={iconStyle} />
|
||||
</button>
|
||||
<button style={buttonStyles} onClick={props.onZoomOut}>
|
||||
Zoom Out
|
||||
<button className={buttonStyle} onClick={props.onZoomOut}>
|
||||
<ZoomOutIcon className={iconStyle} />
|
||||
</button>
|
||||
<button className={largeButtonStyle} onClick={props.onClip}>
|
||||
<CloudDownloadIcon className={`${iconStyle} mr-2`} />
|
||||
Download clip
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -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<Props> = ({
|
||||
width,
|
||||
height,
|
||||
zIndex,
|
||||
emptySelectionAction,
|
||||
styles: {
|
||||
borderLineWidth,
|
||||
|
@ -66,7 +64,7 @@ export const HudCanvas: React.FC<Props> = ({
|
|||
});
|
||||
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<HTMLCanvasElement>(null);
|
||||
const moveOffsetX = useRef(0);
|
||||
|
@ -189,11 +187,11 @@ export const HudCanvas: React.FC<Props> = ({
|
|||
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<Props> = ({
|
|||
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<Props> = ({
|
|||
}
|
||||
|
||||
setMode(Mode.Normal);
|
||||
setCursor('auto');
|
||||
setCursor('cursor-auto');
|
||||
|
||||
if (newSelection.start == newSelection.end) {
|
||||
handleEmptySelectionAction();
|
||||
|
@ -300,22 +298,13 @@ export const HudCanvas: React.FC<Props> = ({
|
|||
setHoverState(HoverState.Normal);
|
||||
};
|
||||
|
||||
const canvasStyles = {
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
zIndex: zIndex,
|
||||
cursor: cursor,
|
||||
} as React.CSSProperties;
|
||||
|
||||
return (
|
||||
<>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className={`block absolute w-full h-full ${cursor} z-20`}
|
||||
width={width}
|
||||
height={height}
|
||||
style={canvasStyles}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
|
|
|
@ -13,8 +13,6 @@ export interface Selection {
|
|||
interface Props {
|
||||
peaks: Observable<number[]>;
|
||||
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<Props> = ({
|
||||
peaks,
|
||||
mediaSet,
|
||||
height,
|
||||
offsetPixels,
|
||||
position,
|
||||
viewport,
|
||||
onSelectionChange,
|
||||
|
@ -69,13 +65,6 @@ export const Overview: React.FC<Props> = ({
|
|||
|
||||
// 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<Props> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div style={containerStyles}>
|
||||
<div className={`relative grow-0 h-[80px]`}>
|
||||
<WaveformCanvas
|
||||
peaks={peaks}
|
||||
channels={mediaSet.audioChannels}
|
||||
|
@ -93,13 +82,11 @@ export const Overview: React.FC<Props> = ({
|
|||
height={CanvasLogicalHeight}
|
||||
strokeStyle="black"
|
||||
fillStyle="#003300"
|
||||
zIndex={1}
|
||||
alpha={1}
|
||||
></WaveformCanvas>
|
||||
<HudCanvas
|
||||
width={CanvasLogicalWidth}
|
||||
height={CanvasLogicalHeight}
|
||||
zIndex={1}
|
||||
emptySelectionAction={EmptySelectionAction.SelectPrevious}
|
||||
styles={hudStyles}
|
||||
position={positionPixels}
|
||||
|
|
|
@ -24,7 +24,7 @@ export const SeekBar: React.FC<Props> = ({
|
|||
onPositionChanged,
|
||||
}: Props) => {
|
||||
const [mode, setMode] = useState(Mode.Normal);
|
||||
const [cursor, setCursor] = useState('auto');
|
||||
const [cursor, setCursor] = useState('cursor-auto');
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
// render canvas
|
||||
|
@ -98,9 +98,9 @@ export const SeekBar: React.FC<Props> = ({
|
|||
|
||||
// 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<Props> = ({
|
|||
|
||||
// render component
|
||||
|
||||
const styles = {
|
||||
width: '100%',
|
||||
height: '30px',
|
||||
margin: '0 auto',
|
||||
cursor: cursor,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<canvas
|
||||
style={styles}
|
||||
className={`w-full h-[30px] mx-0 my-auto ${cursor}`}
|
||||
ref={canvasRef}
|
||||
width={LogicalWidth}
|
||||
height={LogicalHeight}
|
||||
|
|
|
@ -6,7 +6,6 @@ interface Props {
|
|||
mediaSet: MediaSet;
|
||||
position: VideoPosition;
|
||||
duration: number;
|
||||
height: number;
|
||||
video: HTMLVideoElement;
|
||||
}
|
||||
|
||||
|
@ -14,7 +13,6 @@ export const VideoPreview: React.FC<Props> = ({
|
|||
mediaSet,
|
||||
position,
|
||||
duration,
|
||||
height,
|
||||
video,
|
||||
}: Props) => {
|
||||
const videoCanvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
@ -76,30 +74,15 @@ export const VideoPreview: React.FC<Props> = ({
|
|||
|
||||
// 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 (
|
||||
<>
|
||||
<div style={containerStyles}>
|
||||
<div className={`relative grow-0 h-[100px]`}>
|
||||
<canvas
|
||||
className="absolute block w-full h-full"
|
||||
width="500"
|
||||
height="100"
|
||||
ref={videoCanvasRef}
|
||||
style={canvasStyles}
|
||||
></canvas>
|
||||
<canvas style={canvasStyles}></canvas>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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<Props> = ({
|
|||
mediaSet,
|
||||
position,
|
||||
viewport,
|
||||
offsetPixels,
|
||||
onSelectionChange,
|
||||
}: Props) => {
|
||||
const [peaks, setPeaks] = useState<Observable<number[]>>(from([]));
|
||||
|
@ -115,13 +113,6 @@ export const Waveform: React.FC<Props> = ({
|
|||
|
||||
// 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<Props> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div style={containerStyles}>
|
||||
<div className={`relative grow`}>
|
||||
<WaveformCanvas
|
||||
peaks={peaks}
|
||||
channels={mediaSet.audioChannels}
|
||||
|
@ -139,13 +130,11 @@ export const Waveform: React.FC<Props> = ({
|
|||
height={CanvasLogicalHeight}
|
||||
strokeStyle="green"
|
||||
fillStyle="black"
|
||||
zIndex={0}
|
||||
alpha={1}
|
||||
></WaveformCanvas>
|
||||
<HudCanvas
|
||||
width={CanvasLogicalWidth}
|
||||
height={CanvasLogicalHeight}
|
||||
zIndex={1}
|
||||
emptySelectionAction={EmptySelectionAction.SelectNothing}
|
||||
styles={hudStyles}
|
||||
position={positionPixels}
|
||||
|
|
|
@ -10,18 +10,10 @@ interface Props {
|
|||
channels: number;
|
||||
strokeStyle: string;
|
||||
fillStyle: string;
|
||||
zIndex: number;
|
||||
alpha: number;
|
||||
}
|
||||
|
||||
// Canvas is a generic component that renders a waveform to a canvas.
|
||||
//
|
||||
// Properties:
|
||||
//
|
||||
// peaks: a 2d array of uint16s representing the peak values. Each inner array length should match logicalWidth
|
||||
// strokeStyle: waveform style
|
||||
// fillStyle: background style
|
||||
// style: React.CSSProperties applied to canvas element
|
||||
const WaveformCanvas: React.FC<Props> = React.memo((props: Props) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
|
@ -71,21 +63,13 @@ const WaveformCanvas: React.FC<Props> = React.memo((props: Props) => {
|
|||
})();
|
||||
}, [props.peaks]);
|
||||
|
||||
const canvasStyles = {
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
zIndex: props.zIndex,
|
||||
} as React.CSSProperties;
|
||||
|
||||
return (
|
||||
<>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className={`block absolute w-full h-full z-10`}
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
style={canvasStyles}
|
||||
></canvas>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue