Events Page: Added option to override browser time format and style (#5538)

* use12hour optional config

* use12hour config arg

* added use12HourFormat arg to format function

* dateStyle & timeStyle option

* moved timezone & locales to format function

* added dateStyle & timeStyle

* re-formatted

* added strftime_fmt config entry

* strftime package

* added strftime option

* underscore instead of camelCase

* underscore props instead of camelCase
This commit is contained in:
Bernt Christian Egeland 2023-02-22 14:54:16 +01:00 committed by GitHub
parent fbf29667d4
commit 3611e874ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 105 additions and 23 deletions

View File

@ -66,12 +66,29 @@ class LiveModeEnum(str, Enum):
webrtc = "webrtc"
class DateTimeStyleEnum(str, Enum):
full = "full"
long = "long"
medium = "medium"
short = "short"
class UIConfig(FrigateBaseModel):
live_mode: LiveModeEnum = Field(
default=LiveModeEnum.mse, title="Default Live Mode."
)
timezone: Optional[str] = Field(title="Override UI timezone.")
use_experimental: bool = Field(default=False, title="Experimental UI")
use12hour: Optional[bool] = Field(title="Override UI time format.")
date_style: DateTimeStyleEnum = Field(
default=DateTimeStyleEnum.short, title="Override UI dateStyle."
)
time_style: DateTimeStyleEnum = Field(
default=DateTimeStyleEnum.medium, title="Override UI timeStyle."
)
strftime_fmt: Optional[str] = Field(
default=None, title="Override date and time format using strftime syntax."
)
class TelemetryConfig(FrigateBaseModel):

41
web/package-lock.json generated
View File

@ -10,6 +10,7 @@
"dependencies": {
"@cycjimmy/jsmpeg-player": "^6.0.5",
"axios": "^1.2.2",
"copy-to-clipboard": "3.3.3",
"date-fns": "^2.29.3",
"idb-keyval": "^6.2.0",
"immer": "^9.0.16",
@ -19,6 +20,7 @@
"preact-router": "^4.1.0",
"react": "npm:@preact/compat@^17.1.2",
"react-dom": "npm:@preact/compat@^17.1.2",
"strftime": "^0.10.1",
"swr": "^1.3.0",
"video.js": "^7.20.3",
"videojs-playlist": "^5.0.0",
@ -3369,6 +3371,14 @@
"node": ">= 0.6"
}
},
"node_modules/copy-to-clipboard": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
"integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
"dependencies": {
"toggle-selection": "^1.0.6"
}
},
"node_modules/core-js": {
"version": "3.26.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz",
@ -8547,6 +8557,14 @@
"node": ">=8"
}
},
"node_modules/strftime": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/strftime/-/strftime-0.10.1.tgz",
"integrity": "sha512-nVvH6JG8KlXFPC0f8lojLgEsPA18lRpLZ+RrJh/NkQV2tqOgZfbas8gcU8SFgnnqR3rWzZPYu6N2A3xzs/8rQg==",
"engines": {
"node": ">=0.2.0"
}
},
"node_modules/strict-event-emitter": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz",
@ -8901,6 +8919,11 @@
"node": ">=8.0"
}
},
"node_modules/toggle-selection": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
},
"node_modules/totalist": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.0.tgz",
@ -12151,6 +12174,14 @@
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"dev": true
},
"copy-to-clipboard": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
"integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
"requires": {
"toggle-selection": "^1.0.6"
}
},
"core-js": {
"version": "3.26.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz",
@ -15868,6 +15899,11 @@
}
}
},
"strftime": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/strftime/-/strftime-0.10.1.tgz",
"integrity": "sha512-nVvH6JG8KlXFPC0f8lojLgEsPA18lRpLZ+RrJh/NkQV2tqOgZfbas8gcU8SFgnnqR3rWzZPYu6N2A3xzs/8rQg=="
},
"strict-event-emitter": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz",
@ -16154,6 +16190,11 @@
"is-number": "^7.0.0"
}
},
"toggle-selection": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
},
"totalist": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.0.tgz",

View File

@ -18,17 +18,18 @@
"date-fns": "^2.29.3",
"idb-keyval": "^6.2.0",
"immer": "^9.0.16",
"monaco-yaml": "^4.0.2",
"preact": "^10.11.3",
"preact-async-route": "^2.2.1",
"preact-router": "^4.1.0",
"react": "npm:@preact/compat@^17.1.2",
"react-dom": "npm:@preact/compat@^17.1.2",
"vite-plugin-monaco-editor": "^1.1.0",
"monaco-yaml": "^4.0.2",
"strftime": "^0.10.1",
"swr": "^1.3.0",
"video.js": "^7.20.3",
"videojs-playlist": "^5.0.0",
"videojs-seek-buttons": "^3.0.1"
"videojs-seek-buttons": "^3.0.1",
"vite-plugin-monaco-editor": "^1.1.0"
},
"devDependencies": {
"@preact/preset-vite": "^2.5.0",

View File

@ -287,9 +287,6 @@ export default function Events({ path, ...props }) {
return <ActivityIndicator />;
}
const timezone = config.ui?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
const locale = window.navigator?.language || 'en-US';
return (
<div className="space-y-4 p-2 px-4 w-full">
<Heading>Events</Heading>
@ -508,7 +505,7 @@ export default function Events({ path, ...props }) {
</div>
<div className="text-sm flex">
<Clock className="h-5 w-5 mr-2 inline" />
{formatUnixTimestampToDateTime(event.start_time, locale, timezone)}
{formatUnixTimestampToDateTime(event.start_time, { ...config.ui })}
<div className="hidden md:inline">
<span className="m-1">-</span>
<TimeAgo time={event.start_time * 1000} dense />

View File

@ -1,7 +1,8 @@
import strftime from 'strftime';
import { fromUnixTime, intervalToDuration, formatDuration } from 'date-fns';
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;
@ -17,28 +18,53 @@ export const getNowYesterdayInLong = (): number => {
};
/**
* 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
* This function takes in a Unix timestamp, configuration options for date/time display, and an optional strftime format string,
* and returns a formatted date/time string.
*
* If the Unix timestamp is not provided, it returns "Invalid time".
*
* The configuration options determine how the date and time are formatted.
* The `timezone` option allows you to specify a specific timezone for the output, otherwise the user's browser timezone will be used.
* The `use12hour` option allows you to display time in a 12-hour format if true, and 24-hour format if false.
* The `dateStyle` and `timeStyle` options allow you to specify pre-defined formats for displaying the date and time.
* The `strftime_fmt` option allows you to specify a custom format using the strftime syntax.
*
* If both `strftime_fmt` and `dateStyle`/`timeStyle` are provided, `strftime_fmt` takes precedence.
*
* @param unixTimestamp The Unix timestamp to format
* @param config An object containing the configuration options for date/time display
* @returns The formatted date/time string, or "Invalid time" if the Unix timestamp is not provided or invalid.
*/
export const formatUnixTimestampToDateTime = (unixTimestamp: number, locale: string, timezone: string): string => {
interface DateTimeStyle {
timezone: string;
use12hour: boolean | undefined;
date_style: 'full' | 'long' | 'medium' | 'short';
time_style: 'full' | 'long' | 'medium' | 'short';
strftime_fmt: string;
}
export const formatUnixTimestampToDateTime = (unixTimestamp: number, config: DateTimeStyle): string => {
const { timezone, use12hour, date_style, time_style, strftime_fmt } = config;
const locale = window.navigator?.language || 'en-US';
if (isNaN(unixTimestamp)) {
return 'Invalid time';
}
try {
const date = new Date(unixTimestamp * 1000);
// use strftime_fmt if defined in config file
if (strftime_fmt) {
const strftime_locale = strftime.localizeByIdentifier(locale);
return strftime_locale(strftime_fmt, date);
}
// else use Intl.DateTimeFormat
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,
dateStyle: date_style,
timeStyle: time_style,
timeZone: timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
hour12: use12hour !== null ? use12hour : undefined,
});
return formatter.format(date);
} catch (error) {