restyle to match Material Design List specs

This commit is contained in:
Jason Hunter 2021-06-03 01:07:26 -04:00 committed by Blake Blackshear
parent 9822d614e2
commit b53a50cd54
4 changed files with 77 additions and 71 deletions

View File

@ -534,6 +534,7 @@ def recordings(camera_name):
[
{
"date": date,
"events": sum([len(value["events"]) for value in hours.values()]),
"recordings": [
{"hour": hour, "delay": value["delay"], "events": value["events"]}
for hour, value in hours.items()

View File

@ -1,20 +0,0 @@
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,32 +0,0 @@
import { h } from 'preact';
import { addSeconds, differenceInSeconds, fromUnixTime, format, startOfHour } from 'date-fns';
import Link from '../components/Link';
import { useApiHost } from '../api';
export default function EventCard({ camera, event, delay }) {
const apiHost = useApiHost();
const start = fromUnixTime(event.start_time);
const end = fromUnixTime(event.end_time);
const duration = addSeconds(new Date(0), differenceInSeconds(end, start));
const seconds = Math.max(differenceInSeconds(start, startOfHour(start)) - delay - 10, 0);
return (
<Link className="" href={`/recordings/${camera}/${format(start, 'yyyy-MM-dd')}/${format(start, 'HH')}/${seconds}`}>
<div className="rounded-lg shadow-lg bg-gray-600 w-full flex flex-row flex-wrap p-3 antialiased mb-2">
<div className="w-1/2 md:w-1/3">
<img className="rounded-lg shadow-lg antialiased" src={`${apiHost}/api/events/${event.id}/thumbnail.jpg`} />
</div>
<div className="w-1/2 md:w-2/3 px-3 flex flex-row flex-wrap">
<div className="w-full text-right text-gray-700 font-semibold relative pt-0">
<div className="text-2xl text-white leading-tight capitalize">{event.label}</div>
<div className="text-lg text-white leading-tight">{(event.top_score * 100).toFixed(1)}%</div>
<div className="text-xs md:text-normal text-gray-300 hover:text-gray-400 cursor-pointer">
<span className="border-b border-dashed border-gray-500 pb-1">
{format(start, 'HH:mm:ss')} ({format(duration, 'mm:ss')})
</span>
</div>
</div>
</div>
</div>
</Link>
);
}

View File

@ -1,11 +1,12 @@
import { h } from 'preact';
import { h, Fragment } from 'preact';
import { useState } from 'preact/hooks';
import { format, parseISO } from 'date-fns';
import Accordion from '../components/Accordion';
import EventCard from '../components/EventCard';
import { addSeconds, differenceInSeconds, fromUnixTime, format, parseISO, startOfHour } from 'date-fns';
import ArrowDropdown from '../icons/ArrowDropdown';
import ArrowDropup from '../icons/ArrowDropup';
import Link from '../components/Link';
import Menu from '../icons/Menu';
import MenuOpen from '../icons/MenuOpen';
import { useApiHost } from '../api';
export default function RecordingPlaylist({ camera, recordings, selectedDate, selectedHour }) {
const [active, setActive] = useState(true);
@ -15,32 +16,45 @@ export default function RecordingPlaylist({ camera, recordings, selectedDate, se
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="mb-2">
<div className="text-white bg-black bg-opacity-50 border-b border-gray-500 py-2 px-4 mb-1">
{recording.date === selectedDate && item.hour === selectedHour ? (
<span className="text-green-500">{item.hour}:00</span>
<ExpandableList
title={format(date, 'MMM d, yyyy')}
events={recording.events}
selected={recording.date === selectedDate}
>
{recording.recordings.map((item, i) => (
<div className="mb-2 w-full">
<div
className={`flex w-full text-md text-white px-8 py-2 mb-2 ${
i === 0 ? 'border-t border-white border-opacity-50' : ''
}`}
>
{selectedDate == recording.date && selectedHour === item.hour ? (
<Fragment>
<div className="flex-1 text-green-500">{item.hour}:00</div>
<div className="flex-1 text-green-500">Now Playing</div>
</Fragment>
) : (
<Link href={`/recordings/${camera}/${recording.date}/${item.hour}`} type="text">
{item.hour}:00
</Link>
<div className="flex-1">
<Link href={`/recordings/${camera}/${recording.date}/${item.hour}`} type="text">
{item.hour}:00
</Link>
</div>
)}
<span className="float-right">{item.events.length} Events</span>
<div className="flex-1 text-right">{item.events.length} Events</div>
</div>
{item.events.map((event) => (
<EventCard camera={camera} event={event} delay={item.delay} />
))}
</div>
))}
</Accordion>
</ExpandableList>
);
}
const openClass = active ? '-left-6' : 'right-0';
return (
<div className="flex absolute inset-y-0 right-0 w-9/12 md:w-1/3 max-w-xl min-w-lg text-base text-white font-sans">
<div className="flex absolute inset-y-0 right-0 w-9/12 md:w-1/2 lg:w-3/5 max-w-md 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`}
@ -48,7 +62,7 @@ export default function RecordingPlaylist({ camera, recordings, selectedDate, se
{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${
className={`w-full h-full bg-gray-800 bg-opacity-70 border-l overflow-x-hidden overflow-y-auto${
active ? '' : ' hidden'
}`}
>
@ -58,6 +72,49 @@ export default function RecordingPlaylist({ camera, recordings, selectedDate, se
);
}
export function Heading({ title }) {
return <div>{title}</div>;
export function ExpandableList({ title, events = 0, children, selected = false }) {
const [active, setActive] = useState(selected);
const toggle = () => setActive(!active);
return (
<div className={`w-full text-sm ${active ? 'border-b border-white border-opacity-50' : ''}`}>
<div className="flex items-center w-full p-2 cursor-pointer md:text-lg" onClick={toggle}>
<div className="flex-1 font-bold">{title}</div>
<div className="flex-1 text-right mr-4">{events} Events</div>
<div className="w-6 md:w-10 h-6 md:h-10">{active ? <ArrowDropup /> : <ArrowDropdown />}</div>
</div>
<div className={`bg-gray-800 bg-opacity-50 ${active ? '' : 'hidden'}`}>{children}</div>
</div>
);
}
export function EventCard({ camera, event, delay }) {
const apiHost = useApiHost();
const start = fromUnixTime(event.start_time);
const end = fromUnixTime(event.end_time);
const duration = addSeconds(new Date(0), differenceInSeconds(end, start));
const seconds = Math.max(differenceInSeconds(start, startOfHour(start)) - delay - 10, 0);
return (
<Link className="" href={`/recordings/${camera}/${format(start, 'yyyy-MM-dd')}/${format(start, 'HH')}/${seconds}`}>
<div className="flex flex-row mb-2">
<div className="w-28 mr-4">
<img className="antialiased" src={`${apiHost}/api/events/${event.id}/thumbnail.jpg`} />
</div>
<div className="flex flex-row w-full border-b">
<div className="w-full text-gray-700 font-semibold relative pt-0">
<div className="flex flex-row items-center">
<div className="flex-1">
<div className="text-2xl text-white leading-tight capitalize">{event.label}</div>
<div className="text-sm md:text-normal text-gray-300">
{format(start, 'HH:mm:ss')} - {format(end, 'HH:mm:ss')}
</div>
<div className="text-sm md:text-normal text-gray-300">Duration: {format(duration, 'mm:ss')}</div>
</div>
<div className="text-lg text-white text-right leading-tight">{(event.top_score * 100).toFixed(1)}%</div>
</div>
</div>
</div>
<div className="w-6"></div>
</div>
</Link>
);
}