mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	restyle to match Material Design List specs
This commit is contained in:
		
							parent
							
								
									9822d614e2
								
							
						
					
					
						commit
						b53a50cd54
					
				| @ -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() | ||||
|  | ||||
| @ -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> | ||||
|   ); | ||||
| } | ||||
| @ -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> | ||||
|   ); | ||||
| } | ||||
| @ -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> | ||||
|               ) : ( | ||||
|                 <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> | ||||
|   ); | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user