mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Rework events page to include timeago (#5097)
* new timeago component * added timeago to event * clock icon * clock icon. flex items center * dense prop * moved clipDuration. cleaner jsx, sm hidden * renamed clipduration => getDurationFromTimestamps * func description * duration in parenthesis
This commit is contained in:
parent
367ac28a94
commit
621aa0cf61
61
web/src/components/TimeAgo.jsx
Normal file
61
web/src/components/TimeAgo.jsx
Normal file
@ -0,0 +1,61 @@
|
||||
import { h } from 'preact';
|
||||
|
||||
const timeAgo = ({ time, dense = false }) => {
|
||||
if (!time) return 'Invalid Time';
|
||||
try {
|
||||
const currentTime = new Date();
|
||||
const pastTime = new Date(time);
|
||||
const elapsedTime = currentTime - pastTime;
|
||||
if (elapsedTime < 0) return 'Invalid Time';
|
||||
|
||||
const timeUnits = [
|
||||
{ unit: 'ye', full: 'year', value: 31536000 },
|
||||
{ unit: 'mo', full: 'month', value: 0 },
|
||||
{ unit: 'day', full: 'day', value: 86400 },
|
||||
{ unit: 'h', full: 'hour', value: 3600 },
|
||||
{ unit: 'm', full: 'minute', value: 60 },
|
||||
{ unit: 's', full: 'second', value: 1 },
|
||||
];
|
||||
|
||||
let elapsed = elapsedTime / 1000;
|
||||
if (elapsed < 60) {
|
||||
return 'just now';
|
||||
}
|
||||
|
||||
for (let i = 0; i < timeUnits.length; i++) {
|
||||
// if months
|
||||
if (i === 1) {
|
||||
// Get the month and year for the time provided
|
||||
const pastMonth = pastTime.getUTCMonth();
|
||||
const pastYear = pastTime.getUTCFullYear();
|
||||
|
||||
// get current month and year
|
||||
const currentMonth = currentTime.getUTCMonth();
|
||||
const currentYear = currentTime.getUTCFullYear();
|
||||
|
||||
let monthDiff = (currentYear - pastYear) * 12 + (currentMonth - pastMonth);
|
||||
|
||||
// check if the time provided is the previous month but not exceeded 1 month ago.
|
||||
if (currentTime.getUTCDate() < pastTime.getUTCDate()) {
|
||||
monthDiff--;
|
||||
}
|
||||
|
||||
if (monthDiff > 0) {
|
||||
const unitAmount = monthDiff;
|
||||
return `${unitAmount}${dense ? timeUnits[i].unit[0] : ` ${timeUnits[i].full}`}${dense ? '' : 's'} ago`;
|
||||
}
|
||||
} else if (elapsed >= timeUnits[i].value) {
|
||||
const unitAmount = Math.floor(elapsed / timeUnits[i].value);
|
||||
return `${unitAmount}${dense ? timeUnits[i].unit[0] : ` ${timeUnits[i].full}`}${dense ? '' : 's'} ago`;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return 'Invalid Time';
|
||||
}
|
||||
};
|
||||
|
||||
const TimeAgo = (props) => {
|
||||
return <span>{timeAgo({ ...props })}</span>;
|
||||
};
|
||||
|
||||
export default TimeAgo;
|
24
web/src/icons/Clock.jsx
Normal file
24
web/src/icons/Clock.jsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { h } from 'preact';
|
||||
import { memo } from 'preact/compat';
|
||||
|
||||
export function Clock({ className = 'h-6 w-6', stroke = 'currentColor', fill = 'none', onClick = () => {} }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
fill={fill}
|
||||
viewBox="0 0 24 24"
|
||||
stroke={stroke}
|
||||
onClick={onClick}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(Clock);
|
@ -15,6 +15,7 @@ import { UploadPlus } from '../icons/UploadPlus';
|
||||
import { Clip } from '../icons/Clip';
|
||||
import { Zone } from '../icons/Zone';
|
||||
import { Camera } from '../icons/Camera';
|
||||
import { Clock } from '../icons/Clock';
|
||||
import { Delete } from '../icons/Delete';
|
||||
import { Download } from '../icons/Download';
|
||||
import Menu, { MenuItem } from '../components/Menu';
|
||||
@ -22,8 +23,9 @@ import CalendarIcon from '../icons/Calendar';
|
||||
import Calendar from '../components/Calendar';
|
||||
import Button from '../components/Button';
|
||||
import Dialog from '../components/Dialog';
|
||||
import { fromUnixTime, intervalToDuration, formatDuration } from 'date-fns';
|
||||
import MultiSelect from '../components/MultiSelect';
|
||||
import { formatUnixTimestampToDateTime, getDurationFromTimestamps } from '../utils/dateUtil';
|
||||
import TimeAgo from '../components/TimeAgo';
|
||||
|
||||
const API_LIMIT = 25;
|
||||
|
||||
@ -39,16 +41,6 @@ const monthsAgo = (num) => {
|
||||
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() / 1000;
|
||||
};
|
||||
|
||||
const clipDuration = (start_time, end_time) => {
|
||||
const start = fromUnixTime(start_time);
|
||||
const end = fromUnixTime(end_time);
|
||||
let duration = 'In Progress';
|
||||
if (end_time) {
|
||||
duration = formatDuration(intervalToDuration({ start, end }));
|
||||
}
|
||||
return duration;
|
||||
};
|
||||
|
||||
export default function Events({ path, ...props }) {
|
||||
const apiHost = useApiHost();
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
@ -511,13 +503,19 @@ export default function Events({ path, ...props }) {
|
||||
<div className="capitalize text-lg font-bold">
|
||||
{event.sub_label
|
||||
? `${event.label.replaceAll('_', ' ')}: ${event.sub_label.replaceAll('_', ' ')}`
|
||||
: event.label.replaceAll('_', ' ')}{' '}
|
||||
: event.label.replaceAll('_', ' ')}
|
||||
({(event.top_score * 100).toFixed(0)}%)
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
{new Date(event.start_time * 1000).toLocaleDateString(locale, { timeZone: timezone })}{' '}
|
||||
{new Date(event.start_time * 1000).toLocaleTimeString(locale, { timeZone: timezone })} (
|
||||
{clipDuration(event.start_time, event.end_time)})
|
||||
<div className="text-sm flex">
|
||||
<Clock className="h-5 w-5 mr-2 inline" />
|
||||
{formatUnixTimestampToDateTime(event.start_time, locale, timezone)}
|
||||
<div className="hidden md:inline">
|
||||
<span className="m-1">-</span>
|
||||
<TimeAgo time={event.start_time * 1000} dense />
|
||||
</div>
|
||||
<div className="hidden md:inline">
|
||||
<span className="m-1" />( {getDurationFromTimestamps(event.start_time, event.end_time)} )
|
||||
</div>
|
||||
</div>
|
||||
<div className="capitalize text-sm flex align-center mt-1">
|
||||
<Camera className="h-5 w-5 mr-2 inline" />
|
||||
|
@ -1,6 +1,7 @@
|
||||
export const longToDate = (long: number): Date => new Date(long * 1000);
|
||||
export const epochToLong = (date: number): number => date / 1000;
|
||||
export const dateToLong = (date: Date): number => epochToLong(date.getTime());
|
||||
import { fromUnixTime, intervalToDuration, formatDuration } from 'date-fns';
|
||||
|
||||
const getDateTimeYesterday = (dateTime: Date): Date => {
|
||||
const twentyFourHoursInMilliseconds = 24 * 60 * 60 * 1000;
|
||||
@ -14,3 +15,75 @@ const getNowYesterday = (): Date => {
|
||||
export const getNowYesterdayInLong = (): number => {
|
||||
return dateToLong(getNowYesterday());
|
||||
};
|
||||
|
||||
/**
|
||||
* This function takes in a unix timestamp, locale, timezone,
|
||||
* and returns a dateTime string.
|
||||
* If unixTimestamp is not provided, it returns 'Invalid time'
|
||||
* @param unixTimestamp: number
|
||||
* @param locale: string
|
||||
* @param timezone: string
|
||||
* @returns string - dateTime or 'Invalid time' if unixTimestamp is not provided
|
||||
*/
|
||||
export const formatUnixTimestampToDateTime = (unixTimestamp: number, locale: string, timezone: string): string => {
|
||||
if (isNaN(unixTimestamp)) {
|
||||
return 'Invalid time';
|
||||
}
|
||||
try {
|
||||
const date = new Date(unixTimestamp * 1000);
|
||||
const formatter = new Intl.DateTimeFormat(locale, {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
timeZone: timezone,
|
||||
});
|
||||
return formatter.format(date);
|
||||
} catch (error) {
|
||||
return 'Invalid time';
|
||||
}
|
||||
};
|
||||
|
||||
interface DurationToken {
|
||||
xSeconds: string;
|
||||
xMinutes: string;
|
||||
xHours: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function takes in start and end time in unix timestamp,
|
||||
* and returns the duration between start and end time in hours, minutes and seconds.
|
||||
* If end time is not provided, it returns 'In Progress'
|
||||
* @param start_time: number - Unix timestamp for start time
|
||||
* @param end_time: number|null - Unix timestamp for end time
|
||||
* @returns string - duration or 'In Progress' if end time is not provided
|
||||
*/
|
||||
export const getDurationFromTimestamps = (start_time: number, end_time: number | null): string => {
|
||||
if (isNaN(start_time)) {
|
||||
return 'Invalid start time';
|
||||
}
|
||||
let duration = 'In Progress';
|
||||
if (end_time !== null) {
|
||||
if (isNaN(end_time)) {
|
||||
return 'Invalid end time';
|
||||
}
|
||||
const start = fromUnixTime(start_time);
|
||||
const end = fromUnixTime(end_time);
|
||||
const formatDistanceLocale: DurationToken = {
|
||||
xSeconds: '{{count}}s',
|
||||
xMinutes: '{{count}}m',
|
||||
xHours: '{{count}}h',
|
||||
};
|
||||
const shortEnLocale = {
|
||||
formatDistance: (token: keyof DurationToken, count: number) =>
|
||||
formatDistanceLocale[token].replace('{{count}}', count.toString()),
|
||||
};
|
||||
duration = formatDuration(intervalToDuration({ start, end }), {
|
||||
format: ['hours', 'minutes', 'seconds'],
|
||||
locale: shortEnLocale,
|
||||
});
|
||||
}
|
||||
return duration;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user