video overlay

This commit is contained in:
Jason Hunter
2021-06-02 02:41:26 -04:00
committed by Blake Blackshear
parent 28a2a3816a
commit d3dc018260
8 changed files with 89 additions and 164 deletions

View File

@@ -0,0 +1,20 @@
import { h } from 'preact';
import { useState } from 'preact/hooks';
import ArrowDropdown from '../icons/ArrowDropdown';
import ArrowDropup from '../icons/ArrowDropup';
export default function Accordion({ title, children, selected = false }) {
const [active, setActive] = useState(selected);
const toggle = () => setActive(!active);
return (
<div className="w-full border border-white border-opacity-20 rounded-md mb-4 text-xs">
<div className="relative w-full cursor-pointer md:text-lg" onClick={toggle}>
<div className="w-90 py-1 px-2 text-center font-bold">{title}</div>
<div className="absolute top-0 md:-top-1 right-0 md:right-2 w-6 md:w-10 h-6 md:h-10">
{active ? <ArrowDropup /> : <ArrowDropdown />}
</div>
</div>
<div className={`bg-white bg-opacity-20 rounded-b-md p-2 ${active ? '' : 'hidden'}`}>{children}</div>
</div>
);
}

View File

@@ -1,65 +0,0 @@
import { h, Component } from 'preact';
import Flickity from 'flickity';
import 'flickity/css/flickity.css';
export default class Carousel extends Component {
constructor(props) {
super(props);
this.carousel = null;
this.flkty = null;
}
create() {
if (this.carousel) {
this.flkty = new Flickity(this.carousel, this.props.options);
if (this.props.flickityRef) {
this.props.flickityRef(this.flkty);
}
}
}
destroy() {
if (this.flkty) {
this.flkty.destroy();
this.flkty = null;
this.carousel = null;
}
}
componentWillUpdate() {
this.destroy();
}
componentDidUpdate() {
this.create();
}
componentWillUnmount() {
this.destroy();
}
componentDidMount() {
this.create();
}
render(props) {
return h(
this.props.elementType,
{
className: this.props.className,
ref: (c) => {
this.carousel = c;
},
},
this.props.children
);
}
}
Carousel.defaultProps = {
options: {},
className: '',
elementType: 'div',
};

View File

@@ -0,0 +1,53 @@
import { h } from 'preact';
import { useState } from 'preact/hooks';
import { format, parseISO } from 'date-fns';
import Accordion from '../components/Accordion';
import Link from '../components/Link';
import Menu from '../icons/Menu';
import MenuOpen from '../icons/MenuOpen';
export default function RecordingPlaylist({ camera, recordings, selectedDate }) {
const [active, setActive] = useState(true);
const toggle = () => setActive(!active);
const result = [];
for (const recording of recordings.slice().reverse()) {
const date = parseISO(recording.date);
result.push(
<Accordion title={format(date, 'MMM d, yyyy')} selected={recording.date === selectedDate}>
{recording.recordings.map((item) => (
<div className="text-white bg-black bg-opacity-50 border-b border-gray-500 py-2 px-4 mb-1">
<Link href={`/recordings/${camera}/${recording.date}/${item.hour}`} type="text">
{item.hour}:00
</Link>
<span className="float-right">{item.events} Events</span>
</div>
))}
</Accordion>
);
}
const openClass = active ? '-left-6' : 'right-0';
return (
<div className="flex absolute inset-y-0 right-0 w-1/2 md:w-1/3 max-w-xl min-w-lg text-base text-white font-sans">
<div
onClick={toggle}
className={`absolute ${openClass} cursor-pointer items-center self-center rounded-tl-lg rounded-bl-lg border border-r-0 w-6 h-20 py-7 bg-gray-800 bg-opacity-70`}
>
{active ? <Menu /> : <MenuOpen />}
</div>
<div
className={`w-full h-full p-1 md:p-4 bg-gray-800 bg-opacity-70 border-l overflow-x-hidden overflow-y-auto${
active ? '' : ' hidden'
}`}
>
{result}
</div>
</div>
);
}
export function Heading({ title }) {
return <div>{title}</div>;
}

View File

@@ -31,12 +31,12 @@ export default class VideoPlayer extends Component {
}
render() {
const { style } = this.props;
const { style, children } = this.props;
return (
<div style={style}>
<div data-vjs-player>
<video playsinline ref={(node) => (this.videoNode = node)} className="video-js" />
<div className="vjs-playlist" />
<video ref={(node) => (this.videoNode = node)} className="video-js vjs-default-skin" controls playsinline />
{children}
</div>
</div>
);

View File

@@ -25,3 +25,11 @@
transform: rotate(360deg);
}
}
.vjs-playlist-modal {
position: absolute;
top: 0;
right: 0;
width: 33%;
height: 100%;
}

View File

@@ -1,11 +1,8 @@
import { h } from 'preact';
import { Link } from 'preact-router/match';
import { closestTo, format, isEqual, parseISO } from 'date-fns';
import { closestTo, format, parseISO } from 'date-fns';
import ActivityIndicator from '../components/ActivityIndicator';
import Button from '../components/Button';
import Calendar from '../components/Calendar';
import Carousel from '../components/Carousel';
import Heading from '../components/Heading';
import RecordingPlaylist from '../components/RecordingPlaylist';
import VideoPlayer from '../components/VideoPlayer';
import { FetchStatus, useApiHost, useRecording } from '../api';
@@ -24,32 +21,12 @@ export default function Recording({ camera, date, hour }) {
);
const selectedKey = format(selectedDate, 'yyyy-MM-dd');
const [year, month, day] = selectedKey.split('-');
const calendar = [];
const buttons = [];
const playlist = [];
const hours = [];
for (const item of data) {
const date = parseISO(item.date);
const events = item.recordings.map((i) => i.events);
calendar.push(
<Link href={`/recordings/${camera}/${item.date}`}>
<Calendar
date={date}
hours={events.length}
events={events.reduce((a, b) => a + b)}
selected={isEqual(selectedDate, date)}
/>
</Link>
);
if (item.date === selectedKey) {
for (const recording of item.recordings) {
buttons.push(
<Button href={`/recordings/${camera}/${item.date}/${recording.hour}`} type="text">
{recording.hour}:00
</Button>
);
playlist.push({
name: `${selectedKey} ${recording.hour}:00`,
description: `${camera} recording @ ${recording.hour}:00.`,
@@ -76,24 +53,11 @@ export default function Recording({ camera, date, hour }) {
}
}
const selectDate = (flkty) => {
flkty.select(recordingDates.indexOf(selectedKey), false, true);
};
const selectHour = (flkty) => {
flkty.select(selectedHour, false, true);
};
return (
<div className="space-y-4">
<Heading>{camera} Recordings</Heading>
<Carousel flickityRef={selectDate} options={{ pageDots: false }}>
{calendar}
</Carousel>
<VideoPlayer
date={selectedKey}
onReady={(player) => {
if (player.playlist) {
player.playlist(playlist);
@@ -104,11 +68,9 @@ export default function Recording({ camera, date, hour }) {
this.player = player;
}
}}
/>
<Carousel flickityRef={selectHour} options={{ pageDots: false }}>
{buttons}
</Carousel>
>
<RecordingPlaylist camera={camera} recordings={data} selectedDate={selectedKey} />
</VideoPlayer>
</div>
);
}