Use prettier-plugin-tailwindcss (#11373)

* use prettier-plugin-tailwindcss to keep class names organized

* use prettierrc file to ensure formatting on save works with vscode

* classname reorder with prettier-plugin-tailwindcss
This commit is contained in:
Josh Hawkins 2024-05-14 10:06:44 -05:00 committed by GitHub
parent b10ae68c1f
commit 1757f4cb04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
80 changed files with 682 additions and 597 deletions

View File

@ -44,6 +44,12 @@ module.exports = {
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
], ],
"no-console": "error", "no-console": "error",
"prettier/prettier": [
"warn",
{
plugins: ["prettier-plugin-tailwindcss"],
},
],
}, },
overrides: [ overrides: [
{ {

3
web/.prettierrc Normal file
View File

@ -0,0 +1,3 @@
{
"plugins": ["prettier-plugin-tailwindcss"]
}

91
web/package-lock.json generated
View File

@ -96,9 +96,10 @@
"fake-indexeddb": "^5.0.2", "fake-indexeddb": "^5.0.2",
"jest-websocket-mock": "^2.5.0", "jest-websocket-mock": "^2.5.0",
"jsdom": "^24.0.0", "jsdom": "^24.0.0",
"msw": "^2.2.14", "msw": "^2.3.0",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.14",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"vite": "^5.2.11", "vite": "^5.2.11",
@ -848,9 +849,9 @@
} }
}, },
"node_modules/@mswjs/interceptors": { "node_modules/@mswjs/interceptors": {
"version": "0.26.15", "version": "0.29.1",
"resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.26.15.tgz", "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.29.1.tgz",
"integrity": "sha512-HM47Lu1YFmnYHKMBynFfjCp0U/yRskHj/8QEJW0CBEPOlw8Gkmjfll+S9b8M7V5CNDw2/ciRxjjnWeaCiblSIQ==", "integrity": "sha512-3rDakgJZ77+RiQUuSK69t1F0m8BQKA8Vh5DCS5V0DWvNY67zob2JhhQrhCO0AKLGINTRSFd1tBaHcJTkhefoSw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@open-draft/deferred-promise": "^2.2.0", "@open-draft/deferred-promise": "^2.2.0",
@ -5595,9 +5596,9 @@
"dev": true "dev": true
}, },
"node_modules/msw": { "node_modules/msw": {
"version": "2.2.14", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/msw/-/msw-2.2.14.tgz", "resolved": "https://registry.npmjs.org/msw/-/msw-2.3.0.tgz",
"integrity": "sha512-64i8rNCa1xzDK8ZYsTrVMli05D687jty8+Th+PU5VTbJ2/4P7fkQFVyDQ6ZFT5FrNR8z2BHhbY47fKNvfHrumA==", "integrity": "sha512-cDr1q/QTMzaWhY8n9lpGhceY209k29UZtdTgJ3P8Bzne3TSMchX2EM/ldvn4ATLOktpCefCU2gcEgzHc31GTPw==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
@ -5605,7 +5606,7 @@
"@bundled-es-modules/statuses": "^1.0.1", "@bundled-es-modules/statuses": "^1.0.1",
"@inquirer/confirm": "^3.0.0", "@inquirer/confirm": "^3.0.0",
"@mswjs/cookies": "^1.1.0", "@mswjs/cookies": "^1.1.0",
"@mswjs/interceptors": "^0.26.14", "@mswjs/interceptors": "^0.29.0",
"@open-draft/until": "^2.1.0", "@open-draft/until": "^2.1.0",
"@types/cookie": "^0.6.0", "@types/cookie": "^0.6.0",
"@types/statuses": "^2.0.4", "@types/statuses": "^2.0.4",
@ -6181,6 +6182,80 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/prettier-plugin-tailwindcss": {
"version": "0.5.14",
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.14.tgz",
"integrity": "sha512-Puaz+wPUAhFp8Lo9HuciYKM2Y2XExESjeT+9NQoVFXZsPPnc9VYss2SpxdQ6vbatmt8/4+SN0oe0I1cPDABg9Q==",
"dev": true,
"engines": {
"node": ">=14.21.3"
},
"peerDependencies": {
"@ianvs/prettier-plugin-sort-imports": "*",
"@prettier/plugin-pug": "*",
"@shopify/prettier-plugin-liquid": "*",
"@trivago/prettier-plugin-sort-imports": "*",
"@zackad/prettier-plugin-twig-melody": "*",
"prettier": "^3.0",
"prettier-plugin-astro": "*",
"prettier-plugin-css-order": "*",
"prettier-plugin-import-sort": "*",
"prettier-plugin-jsdoc": "*",
"prettier-plugin-marko": "*",
"prettier-plugin-organize-attributes": "*",
"prettier-plugin-organize-imports": "*",
"prettier-plugin-sort-imports": "*",
"prettier-plugin-style-order": "*",
"prettier-plugin-svelte": "*"
},
"peerDependenciesMeta": {
"@ianvs/prettier-plugin-sort-imports": {
"optional": true
},
"@prettier/plugin-pug": {
"optional": true
},
"@shopify/prettier-plugin-liquid": {
"optional": true
},
"@trivago/prettier-plugin-sort-imports": {
"optional": true
},
"@zackad/prettier-plugin-twig-melody": {
"optional": true
},
"prettier-plugin-astro": {
"optional": true
},
"prettier-plugin-css-order": {
"optional": true
},
"prettier-plugin-import-sort": {
"optional": true
},
"prettier-plugin-jsdoc": {
"optional": true
},
"prettier-plugin-marko": {
"optional": true
},
"prettier-plugin-organize-attributes": {
"optional": true
},
"prettier-plugin-organize-imports": {
"optional": true
},
"prettier-plugin-sort-imports": {
"optional": true
},
"prettier-plugin-style-order": {
"optional": true
},
"prettier-plugin-svelte": {
"optional": true
}
}
},
"node_modules/pretty-format": { "node_modules/pretty-format": {
"version": "29.7.0", "version": "29.7.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",

View File

@ -104,6 +104,7 @@
"msw": "^2.3.0", "msw": "^2.3.0",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.14",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"vite": "^5.2.11", "vite": "^5.2.11",

View File

@ -31,7 +31,7 @@ function App() {
{isMobile && <Bottombar />} {isMobile && <Bottombar />}
<div <div
id="pageRoot" id="pageRoot"
className={`absolute top-0 right-0 overflow-hidden ${isMobile ? "left-0 bottom-16" : "left-[52px] bottom-8"}`} className={`absolute right-0 top-0 overflow-hidden ${isMobile ? "bottom-16 left-0" : "bottom-8 left-[52px]"}`}
> >
<Suspense> <Suspense>
<Routes> <Routes>

View File

@ -55,11 +55,11 @@ export default function Statusbar() {
}, [potentialProblems, addMessage, clearMessages]); }, [potentialProblems, addMessage, clearMessages]);
return ( return (
<div className="absolute left-0 bottom-0 right-0 w-full h-8 flex justify-between items-center px-4 bg-background_alt z-10 dark:text-secondary-foreground border-t border-secondary-highlight"> <div className="absolute bottom-0 left-0 right-0 z-10 flex h-8 w-full items-center justify-between border-t border-secondary-highlight bg-background_alt px-4 dark:text-secondary-foreground">
<div className="h-full flex items-center gap-2"> <div className="flex h-full items-center gap-2">
{cpuPercent && ( {cpuPercent && (
<Link to="/system#general"> <Link to="/system#general">
<div className="flex items-center text-sm gap-2 cursor-pointer hover:underline"> <div className="flex cursor-pointer items-center gap-2 text-sm hover:underline">
<MdCircle <MdCircle
className={`size-2 ${ className={`size-2 ${
cpuPercent < 50 cpuPercent < 50
@ -99,7 +99,7 @@ export default function Statusbar() {
{" "} {" "}
<div <div
key={gpuTitle} key={gpuTitle}
className="flex items-center text-sm gap-2 cursor-pointer hover:underline" className="flex cursor-pointer items-center gap-2 text-sm hover:underline"
> >
<MdCircle <MdCircle
className={`size-2 ${ className={`size-2 ${
@ -116,20 +116,20 @@ export default function Statusbar() {
); );
})} })}
</div> </div>
<div className="h-full flex items-center gap-2"> <div className="flex h-full items-center gap-2">
{Object.entries(messages).length === 0 ? ( {Object.entries(messages).length === 0 ? (
<div className="flex items-center text-sm gap-2"> <div className="flex items-center gap-2 text-sm">
<FaCheck className="size-3 text-green-500" /> <FaCheck className="size-3 text-green-500" />
System is healthy System is healthy
</div> </div>
) : ( ) : (
Object.entries(messages).map(([key, messageArray]) => ( Object.entries(messages).map(([key, messageArray]) => (
<div key={key} className="h-full flex items-center gap-2"> <div key={key} className="flex h-full items-center gap-2">
{messageArray.map(({ id, text, color, link }: StatusMessage) => { {messageArray.map(({ id, text, color, link }: StatusMessage) => {
const message = ( const message = (
<div <div
key={id} key={id}
className={`flex items-center text-sm gap-2 ${link ? "hover:underline cursor-pointer" : ""}`} className={`flex items-center gap-2 text-sm ${link ? "cursor-pointer hover:underline" : ""}`}
> >
<IoIosWarning <IoIosWarning
className={`size-5 ${color || "text-danger"}`} className={`size-5 ${color || "text-danger"}`}

View File

@ -5,7 +5,7 @@ type TWrapperProps = {
}; };
const Wrapper = ({ children }: TWrapperProps) => { const Wrapper = ({ children }: TWrapperProps) => {
return <main className="w-screen h-dvh overflow-hidden">{children}</main>; return <main className="h-dvh w-screen overflow-hidden">{children}</main>;
}; };
export default Wrapper; export default Wrapper;

View File

@ -27,11 +27,11 @@ export default function TimelineBar({
return ( return (
<div <div
className="my-1 p-1 w-full h-18 border rounded cursor-pointer hover:bg-secondary hover:bg-opacity-30" className="h-18 my-1 w-full cursor-pointer rounded border p-1 hover:bg-secondary hover:bg-opacity-30"
onClick={onClick} onClick={onClick}
> >
{graphData != undefined && ( {graphData != undefined && (
<div className="relative w-full h-8 flex"> <div className="relative flex h-8 w-full">
{getHourBlocks().map((idx) => { {getHourBlocks().map((idx) => {
return ( return (
<div <div
@ -46,8 +46,8 @@ export default function TimelineBar({
/> />
); );
})} })}
<div className="absolute left-0 top-0 bottom-0 align-bottom border-l border-gray-500"> <div className="absolute bottom-0 left-0 top-0 border-l border-gray-500 align-bottom">
<div className="absolute ml-1 bottom-0 text-sm text-gray-500"> <div className="absolute bottom-0 ml-1 text-sm text-gray-500">
{formatUnixTimestampToDateTime(startTime, { {formatUnixTimestampToDateTime(startTime, {
strftime_fmt: strftime_fmt:
config?.ui.time_format == "24hour" ? "%H:00" : "%I:00%P", config?.ui.time_format == "24hour" ? "%H:00" : "%I:00%P",
@ -56,8 +56,8 @@ export default function TimelineBar({
})} })}
</div> </div>
</div> </div>
<div className="absolute left-[8.3%] top-0 bottom-0 align-bottom border-l border-gray-500"> <div className="absolute bottom-0 left-[8.3%] top-0 border-l border-gray-500 align-bottom">
<div className="absolute ml-1 bottom-0 text-sm text-gray-500"> <div className="absolute bottom-0 ml-1 text-sm text-gray-500">
{formatUnixTimestampToDateTime(startTime, { {formatUnixTimestampToDateTime(startTime, {
strftime_fmt: strftime_fmt:
config?.ui.time_format == "24hour" ? "%H:05" : "%I:05%P", config?.ui.time_format == "24hour" ? "%H:05" : "%I:05%P",
@ -66,8 +66,8 @@ export default function TimelineBar({
})} })}
</div> </div>
</div> </div>
<div className="absolute left-[16.7%] top-0 bottom-0 align-bottom border-l border-gray-500"> <div className="absolute bottom-0 left-[16.7%] top-0 border-l border-gray-500 align-bottom">
<div className="absolute ml-1 bottom-0 text-sm text-gray-500"> <div className="absolute bottom-0 ml-1 text-sm text-gray-500">
{formatUnixTimestampToDateTime(startTime, { {formatUnixTimestampToDateTime(startTime, {
strftime_fmt: strftime_fmt:
config?.ui.time_format == "24hour" ? "%H:10" : "%I:10%P", config?.ui.time_format == "24hour" ? "%H:10" : "%I:10%P",
@ -76,8 +76,8 @@ export default function TimelineBar({
})} })}
</div> </div>
</div> </div>
<div className="absolute left-[25%] top-0 bottom-0 align-bottom border-l border-gray-500"> <div className="absolute bottom-0 left-[25%] top-0 border-l border-gray-500 align-bottom">
<div className="absolute ml-1 bottom-0 text-sm text-gray-500"> <div className="absolute bottom-0 ml-1 text-sm text-gray-500">
{formatUnixTimestampToDateTime(startTime, { {formatUnixTimestampToDateTime(startTime, {
strftime_fmt: strftime_fmt:
config?.ui.time_format == "24hour" ? "%H:15" : "%I:15%P", config?.ui.time_format == "24hour" ? "%H:15" : "%I:15%P",
@ -86,8 +86,8 @@ export default function TimelineBar({
})} })}
</div> </div>
</div> </div>
<div className="absolute left-[33.3%] top-0 bottom-0 align-bottom border-l border-gray-500"> <div className="absolute bottom-0 left-[33.3%] top-0 border-l border-gray-500 align-bottom">
<div className="absolute ml-1 bottom-0 text-sm text-gray-500"> <div className="absolute bottom-0 ml-1 text-sm text-gray-500">
{formatUnixTimestampToDateTime(startTime, { {formatUnixTimestampToDateTime(startTime, {
strftime_fmt: strftime_fmt:
config?.ui.time_format == "24hour" ? "%H:20" : "%I:20%P", config?.ui.time_format == "24hour" ? "%H:20" : "%I:20%P",
@ -96,8 +96,8 @@ export default function TimelineBar({
})} })}
</div> </div>
</div> </div>
<div className="absolute left-[41.7%] top-0 bottom-0 align-bottom border-l border-gray-500"> <div className="absolute bottom-0 left-[41.7%] top-0 border-l border-gray-500 align-bottom">
<div className="absolute ml-1 bottom-0 text-sm text-gray-500"> <div className="absolute bottom-0 ml-1 text-sm text-gray-500">
{formatUnixTimestampToDateTime(startTime, { {formatUnixTimestampToDateTime(startTime, {
strftime_fmt: strftime_fmt:
config?.ui.time_format == "24hour" ? "%H:25" : "%I:25%P", config?.ui.time_format == "24hour" ? "%H:25" : "%I:25%P",
@ -106,8 +106,8 @@ export default function TimelineBar({
})} })}
</div> </div>
</div> </div>
<div className="absolute left-[50%] top-0 bottom-0 align-bottom border-l border-gray-500"> <div className="absolute bottom-0 left-[50%] top-0 border-l border-gray-500 align-bottom">
<div className="absolute ml-1 bottom-0 text-sm text-gray-500"> <div className="absolute bottom-0 ml-1 text-sm text-gray-500">
{formatUnixTimestampToDateTime(startTime, { {formatUnixTimestampToDateTime(startTime, {
strftime_fmt: strftime_fmt:
config?.ui.time_format == "24hour" ? "%H:30" : "%I:30%P", config?.ui.time_format == "24hour" ? "%H:30" : "%I:30%P",
@ -116,8 +116,8 @@ export default function TimelineBar({
})} })}
</div> </div>
</div> </div>
<div className="absolute left-[58.3%] top-0 bottom-0 align-bottom border-l border-gray-500"> <div className="absolute bottom-0 left-[58.3%] top-0 border-l border-gray-500 align-bottom">
<div className="absolute ml-1 bottom-0 text-sm text-gray-500"> <div className="absolute bottom-0 ml-1 text-sm text-gray-500">
{formatUnixTimestampToDateTime(startTime, { {formatUnixTimestampToDateTime(startTime, {
strftime_fmt: strftime_fmt:
config?.ui.time_format == "24hour" ? "%H:35" : "%I:35%P", config?.ui.time_format == "24hour" ? "%H:35" : "%I:35%P",
@ -126,8 +126,8 @@ export default function TimelineBar({
})} })}
</div> </div>
</div> </div>
<div className="absolute left-[66.7%] top-0 bottom-0 align-bottom border-l border-gray-500"> <div className="absolute bottom-0 left-[66.7%] top-0 border-l border-gray-500 align-bottom">
<div className="absolute ml-1 bottom-0 text-sm text-gray-500"> <div className="absolute bottom-0 ml-1 text-sm text-gray-500">
{formatUnixTimestampToDateTime(startTime, { {formatUnixTimestampToDateTime(startTime, {
strftime_fmt: strftime_fmt:
config?.ui.time_format == "24hour" ? "%H:40" : "%I:40%P", config?.ui.time_format == "24hour" ? "%H:40" : "%I:40%P",
@ -136,8 +136,8 @@ export default function TimelineBar({
})} })}
</div> </div>
</div> </div>
<div className="absolute left-[75%] top-0 bottom-0 align-bottom border-l border-gray-500"> <div className="absolute bottom-0 left-[75%] top-0 border-l border-gray-500 align-bottom">
<div className="absolute ml-1 bottom-0 text-sm text-gray-500"> <div className="absolute bottom-0 ml-1 text-sm text-gray-500">
{formatUnixTimestampToDateTime(startTime, { {formatUnixTimestampToDateTime(startTime, {
strftime_fmt: strftime_fmt:
config?.ui.time_format == "24hour" ? "%H:45" : "%I:45%P", config?.ui.time_format == "24hour" ? "%H:45" : "%I:45%P",
@ -146,8 +146,8 @@ export default function TimelineBar({
})} })}
</div> </div>
</div> </div>
<div className="absolute left-[83.3%] top-0 bottom-0 align-bottom border-l border-gray-500"> <div className="absolute bottom-0 left-[83.3%] top-0 border-l border-gray-500 align-bottom">
<div className="absolute ml-1 bottom-0 text-sm text-gray-500"> <div className="absolute bottom-0 ml-1 text-sm text-gray-500">
{formatUnixTimestampToDateTime(startTime, { {formatUnixTimestampToDateTime(startTime, {
strftime_fmt: strftime_fmt:
config?.ui.time_format == "24hour" ? "%H:50" : "%I:50%P", config?.ui.time_format == "24hour" ? "%H:50" : "%I:50%P",
@ -156,8 +156,8 @@ export default function TimelineBar({
})} })}
</div> </div>
</div> </div>
<div className="absolute left-[91.7%] top-0 bottom-0 align-bottom border-l border-gray-500"> <div className="absolute bottom-0 left-[91.7%] top-0 border-l border-gray-500 align-bottom">
<div className="absolute ml-1 bottom-0 text-sm text-gray-500"> <div className="absolute bottom-0 ml-1 text-sm text-gray-500">
{formatUnixTimestampToDateTime(startTime, { {formatUnixTimestampToDateTime(startTime, {
strftime_fmt: strftime_fmt:
config?.ui.time_format == "24hour" ? "%H:55" : "%I:55%P", config?.ui.time_format == "24hour" ? "%H:55" : "%I:55%P",

View File

@ -45,7 +45,7 @@ export default function CameraImage({
{enabled ? ( {enabled ? (
<img <img
ref={imgRef} ref={imgRef}
className={`object-contain ${isPortraitImage ? "h-full w-auto" : "w-full h-auto"} rounded-lg md:rounded-2xl`} className={`object-contain ${isPortraitImage ? "h-full w-auto" : "h-auto w-full"} rounded-lg md:rounded-2xl`}
onLoad={() => { onLoad={() => {
setHasLoaded(true); setHasLoaded(true);
@ -62,12 +62,12 @@ export default function CameraImage({
}} }}
/> />
) : ( ) : (
<div className="text-center pt-6"> <div className="pt-6 text-center">
Camera is disabled in config, no stream or snapshot available! Camera is disabled in config, no stream or snapshot available!
</div> </div>
)} )}
{!hasLoaded && enabled ? ( {!hasLoaded && enabled ? (
<div className="absolute left-0 right-0 top-0 bottom-0 flex justify-center items-center"> <div className="absolute bottom-0 left-0 right-0 top-0 flex items-center justify-center">
<ActivityIndicator /> <ActivityIndicator />
</div> </div>
) : null} ) : null}

View File

@ -56,7 +56,7 @@ export default function DebugCameraImage({
cameraClasses="relative w-full h-full flex justify-center" cameraClasses="relative w-full h-full flex justify-center"
/> />
<Button onClick={handleToggleSettings} variant="link" size="sm"> <Button onClick={handleToggleSettings} variant="link" size="sm">
<span className="w-5 h-5"> <span className="h-5 w-5">
<LuSettings /> <LuSettings />
</span>{" "} </span>{" "}
<span>{showSettings ? "Hide" : "Show"} Options</span> <span>{showSettings ? "Hide" : "Show"} Options</span>
@ -85,7 +85,7 @@ type DebugSettingsProps = {
function DebugSettings({ handleSetOption, options }: DebugSettingsProps) { function DebugSettings({ handleSetOption, options }: DebugSettingsProps) {
return ( return (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"> <div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Switch <Switch
id="bbox" id="bbox"

View File

@ -96,7 +96,7 @@ export default function CameraImage({
return ( return (
<div <div
className={cn("relative w-full h-full flex justify-center", className)} className={cn("relative flex h-full w-full justify-center", className)}
ref={containerRef} ref={containerRef}
> >
{enabled ? ( {enabled ? (
@ -108,7 +108,7 @@ export default function CameraImage({
width={scaledWidth} width={scaledWidth}
/> />
) : ( ) : (
<div className="text-center pt-6"> <div className="pt-6 text-center">
Camera is disabled in config, no stream or snapshot available! Camera is disabled in config, no stream or snapshot available!
</div> </div>
)} )}

View File

@ -67,13 +67,13 @@ export function AnimatedEventCard({ event }: AnimatedEventCardProps) {
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div <div
className="h-24 4k:h-32 relative" className="relative h-24 4k:h-32"
style={{ style={{
aspectRatio: aspectRatio, aspectRatio: aspectRatio,
}} }}
> >
<div <div
className="size-full rounded md:rounded-lg cursor-pointer overflow-hidden" className="size-full cursor-pointer overflow-hidden rounded md:rounded-lg"
onClick={onOpenReview} onClick={onOpenReview}
> >
{previews ? ( {previews ? (
@ -102,8 +102,8 @@ export function AnimatedEventCard({ event }: AnimatedEventCardProps) {
/> />
)} )}
</div> </div>
<div className="absolute bottom-0 inset-x-0 h-6 bg-gradient-to-t from-slate-900/50 to-transparent rounded"> <div className="absolute inset-x-0 bottom-0 h-6 rounded bg-gradient-to-t from-slate-900/50 to-transparent">
<div className="w-full absolute left-1 bottom-0 text-xs text-white"> <div className="absolute bottom-0 left-1 w-full text-xs text-white">
<TimeAgo time={event.start_time * 1000} dense /> <TimeAgo time={event.start_time * 1000} dense />
</div> </div>
</div> </div>

View File

@ -106,7 +106,7 @@ export default function ExportCard({
<div <div
className={cn( className={cn(
"relative aspect-video bg-black rounded-lg md:rounded-2xl flex justify-center items-center", "relative flex aspect-video items-center justify-center rounded-lg bg-black md:rounded-2xl",
className, className,
)} )}
onMouseEnter={ onMouseEnter={
@ -127,19 +127,19 @@ export default function ExportCard({
> >
{hovered && ( {hovered && (
<> <>
<div className="absolute inset-0 z-10 bg-black bg-opacity-60 rounded-lg md:rounded-2xl" /> <div className="absolute inset-0 z-10 rounded-lg bg-black bg-opacity-60 md:rounded-2xl" />
<div className="absolute top-1 right-1 flex items-center gap-2"> <div className="absolute right-1 top-1 flex items-center gap-2">
<a <a
className="z-20" className="z-20"
download download
href={`${baseUrl}${exportedRecording.video_path.replace("/media/frigate/", "")}`} href={`${baseUrl}${exportedRecording.video_path.replace("/media/frigate/", "")}`}
> >
<Chip className="bg-gradient-to-br from-gray-400 to-gray-500 bg-gray-500 rounded-md cursor-pointer"> <Chip className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500">
<FaDownload className="size-4 text-white" /> <FaDownload className="size-4 text-white" />
</Chip> </Chip>
</a> </a>
<Chip <Chip
className="bg-gradient-to-br from-gray-400 to-gray-500 bg-gray-500 rounded-md cursor-pointer" className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"
onClick={() => onClick={() =>
setEditName({ original: exportedRecording.name, update: "" }) setEditName({ original: exportedRecording.name, update: "" })
} }
@ -147,7 +147,7 @@ export default function ExportCard({
<MdEditSquare className="size-4 text-white" /> <MdEditSquare className="size-4 text-white" />
</Chip> </Chip>
<Chip <Chip
className="bg-gradient-to-br from-gray-400 to-gray-500 bg-gray-500 rounded-md cursor-pointer" className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"
onClick={() => onClick={() =>
onDelete({ onDelete({
file: exportedRecording.id, file: exportedRecording.id,
@ -155,12 +155,12 @@ export default function ExportCard({
}) })
} }
> >
<LuTrash className="size-4 text-destructive fill-destructive" /> <LuTrash className="size-4 fill-destructive text-destructive" />
</Chip> </Chip>
</div> </div>
<Button <Button
className="absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2 w-20 h-20 z-20 text-white hover:text-white hover:bg-transparent cursor-pointer" className="absolute left-1/2 top-1/2 z-20 h-20 w-20 -translate-x-1/2 -translate-y-1/2 cursor-pointer text-white hover:bg-transparent hover:text-white"
variant="ghost" variant="ghost"
onClick={() => { onClick={() => {
onSelect(exportedRecording); onSelect(exportedRecording);
@ -176,20 +176,20 @@ export default function ExportCard({
<> <>
{exportedRecording.thumb_path.length > 0 ? ( {exportedRecording.thumb_path.length > 0 ? (
<img <img
className="size-full absolute inset-0 object-contain aspect-video rounded-lg md:rounded-2xl" className="absolute inset-0 aspect-video size-full rounded-lg object-contain md:rounded-2xl"
src={exportedRecording.thumb_path.replace("/media/frigate", "")} src={exportedRecording.thumb_path.replace("/media/frigate", "")}
onLoad={() => setLoading(false)} onLoad={() => setLoading(false)}
/> />
) : ( ) : (
<div className="absolute inset-0 bg-secondary rounded-lg md:rounded-2xl" /> <div className="absolute inset-0 rounded-lg bg-secondary md:rounded-2xl" />
)} )}
</> </>
)} )}
{loading && ( {loading && (
<Skeleton className="absolute inset-0 aspect-video rounded-lg md:rounded-2xl" /> <Skeleton className="absolute inset-0 aspect-video rounded-lg md:rounded-2xl" />
)} )}
<div className="absolute bottom-0 inset-x-0 rounded-b-l z-10 h-[20%] bg-gradient-to-t from-black/60 to-transparent pointer-events-none rounded-lg md:rounded-2xl"> <div className="rounded-b-l pointer-events-none absolute inset-x-0 bottom-0 z-10 h-[20%] rounded-lg bg-gradient-to-t from-black/60 to-transparent md:rounded-2xl">
<div className="flex h-full justify-between items-end mx-3 pb-1 text-white text-sm capitalize"> <div className="mx-3 flex h-full items-end justify-between pb-1 text-sm capitalize text-white">
{exportedRecording.name.replaceAll("_", " ")} {exportedRecording.name.replaceAll("_", " ")}
</div> </div>
</div> </div>

View File

@ -35,7 +35,7 @@ export default function ReviewCard({
return ( return (
<div <div
className="w-full relative flex flex-col gap-1.5 cursor-pointer" className="relative flex w-full cursor-pointer flex-col gap-1.5"
onClick={onClick} onClick={onClick}
> >
<ImageLoadingIndicator <ImageLoadingIndicator
@ -51,8 +51,8 @@ export default function ReviewCard({
onImgLoad(); onImgLoad();
}} }}
/> />
<div className="flex justify-between items-center"> <div className="flex items-center justify-between">
<div className="flex justify-evenly items-center gap-1"> <div className="flex items-center justify-evenly gap-1">
{event.data.objects.map((object) => { {event.data.objects.map((object) => {
return getIconForLabel(object, "size-3 text-white"); return getIconForLabel(object, "size-3 text-white");
})} })}

View File

@ -41,7 +41,7 @@ export default function CameraFeatureToggle({
onClick={onClick} onClick={onClick}
className={cn( className={cn(
className, className,
"flex flex-col justify-center items-center", "flex flex-col items-center justify-center",
variants[variant][isActive ? "active" : "inactive"], variants[variant][isActive ? "active" : "inactive"],
)} )}
> >

View File

@ -27,13 +27,13 @@ export default function NewReviewData({
return ( return (
<div className={className}> <div className={className}>
<div className="flex justify-center items-center mr-[65px] md:mr-[115px] pointer-events-auto"> <div className="pointer-events-auto mr-[65px] flex items-center justify-center md:mr-[115px]">
<Button <Button
className={`${ className={`${
hasUpdate hasUpdate
? "animate-in slide-in-from-top duration-500" ? "duration-500 animate-in slide-in-from-top"
: "invisible" : "invisible"
} text-center mt-5 mx-auto bg-gray-400 text-white`} } mx-auto mt-5 bg-gray-400 text-center text-white`}
onClick={() => { onClick={() => {
pullLatestData(); pullLatestData();
if (contentRef.current) { if (contentRef.current) {
@ -44,7 +44,7 @@ export default function NewReviewData({
} }
}} }}
> >
<LuRefreshCcw className="w-4 h-4 mr-2" /> <LuRefreshCcw className="mr-2 h-4 w-4" />
New Items To Review New Items To Review
</Button> </Button>
</div> </div>

View File

@ -125,8 +125,8 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
<Button <Button
className={ className={
group == "default" group == "default"
? "text-selected bg-blue-900 focus:bg-blue-900 bg-opacity-60 focus:bg-opacity-60" ? "bg-blue-900 bg-opacity-60 text-selected focus:bg-blue-900 focus:bg-opacity-60"
: "text-secondary-foreground bg-secondary focus:text-secondary-foreground focus:bg-secondary" : "bg-secondary text-secondary-foreground focus:bg-secondary focus:text-secondary-foreground"
} }
size="xs" size="xs"
onClick={() => (group ? setGroup("default", true) : null)} onClick={() => (group ? setGroup("default", true) : null)}
@ -149,8 +149,8 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
<Button <Button
className={ className={
group == name group == name
? "text-selected bg-blue-900 focus:bg-blue-900 bg-opacity-60 focus:bg-opacity-60" ? "bg-blue-900 bg-opacity-60 text-selected focus:bg-blue-900 focus:bg-opacity-60"
: "text-secondary-foreground bg-secondary" : "bg-secondary text-secondary-foreground"
} }
size="xs" size="xs"
onClick={() => setGroup(name, group != "default")} onClick={() => setGroup(name, group != "default")}
@ -177,7 +177,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
})} })}
<Button <Button
className="text-muted-foreground bg-secondary" className="bg-secondary text-muted-foreground"
size="xs" size="xs"
onClick={() => setAddGroup(true)} onClick={() => setAddGroup(true)}
> >
@ -308,16 +308,16 @@ function NewGroupDialog({
}} }}
> >
<Content <Content
className={`min-w-0 ${isMobile ? "w-full p-3 rounded-t-2xl max-h-[90%]" : "w-6/12 max-h-dvh overflow-y-hidden"}`} className={`min-w-0 ${isMobile ? "max-h-[90%] w-full rounded-t-2xl p-3" : "max-h-dvh w-6/12 overflow-y-hidden"}`}
> >
<div className="flex flex-col my-4 overflow-y-auto"> <div className="my-4 flex flex-col overflow-y-auto">
{editState === "none" && ( {editState === "none" && (
<> <>
<div className="flex flex-row justify-between items-center py-2"> <div className="flex flex-row items-center justify-between py-2">
<DialogTitle>Camera Groups</DialogTitle> <DialogTitle>Camera Groups</DialogTitle>
<Button <Button
variant="secondary" variant="secondary"
className="size-6 p-1 rounded-md text-background bg-secondary-foreground" className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
onClick={() => { onClick={() => {
setEditState("add"); setEditState("add");
}} }}
@ -338,7 +338,7 @@ function NewGroupDialog({
{editState != "none" && ( {editState != "none" && (
<> <>
<div className="flex flex-row justify-between items-center mb-3"> <div className="mb-3 flex flex-row items-center justify-between">
<DialogTitle> <DialogTitle>
{editState == "add" ? "Add" : "Edit"} Camera Group {editState == "add" ? "Add" : "Edit"} Camera Group
</DialogTitle> </DialogTitle>
@ -398,10 +398,10 @@ export function EditGroupDialog({
}} }}
> >
<DialogContent <DialogContent
className={`min-w-0 ${isMobile ? "w-full p-3 rounded-t-2xl max-h-[90%]" : "w-6/12 max-h-dvh overflow-y-hidden"}`} className={`min-w-0 ${isMobile ? "max-h-[90%] w-full rounded-t-2xl p-3" : "max-h-dvh w-6/12 overflow-y-hidden"}`}
> >
<div className="flex flex-col my-4 overflow-y-auto"> <div className="my-4 flex flex-col overflow-y-auto">
<div className="flex flex-row justify-between items-center mb-3"> <div className="mb-3 flex flex-row items-center justify-between">
<DialogTitle>Edit Camera Group</DialogTitle> <DialogTitle>Edit Camera Group</DialogTitle>
</div> </div>
<CameraGroupEdit <CameraGroupEdit
@ -440,7 +440,7 @@ export function CameraGroupRow({
<> <>
<div <div
key={group[0]} key={group[0]}
className="flex md:p-1 rounded-lg flex-row items-center justify-between my-1.5 transition-background duration-100" className="transition-background my-1.5 flex flex-row items-center justify-between rounded-lg duration-100 md:p-1"
> >
<div className={`flex items-center`}> <div className={`flex items-center`}>
<p className="cursor-default">{group[0]}</p> <p className="cursor-default">{group[0]}</p>
@ -482,7 +482,7 @@ export function CameraGroupRow({
</> </>
)} )}
{!isMobile && ( {!isMobile && (
<div className="flex flex-row gap-2 items-center"> <div className="flex flex-row items-center gap-2">
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<IconWrapper <IconWrapper
@ -641,7 +641,7 @@ export function CameraGroupEdit({
<FormLabel>Name</FormLabel> <FormLabel>Name</FormLabel>
<FormControl> <FormControl>
<Input <Input
className="w-full p-2 border border-input bg-background hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
placeholder="Enter a name..." placeholder="Enter a name..."
disabled={editingGroup !== undefined} disabled={editingGroup !== undefined}
{...field} {...field}
@ -652,8 +652,8 @@ export function CameraGroupEdit({
)} )}
/> />
<Separator className="flex my-2 bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<div className="max-h-[25dvh] md:max-h-[40dvh] overflow-y-auto"> <div className="max-h-[25dvh] overflow-y-auto md:max-h-[40dvh]">
<FormField <FormField
control={form.control} control={form.control}
name="cameras" name="cameras"
@ -686,7 +686,7 @@ export function CameraGroupEdit({
/> />
</div> </div>
<Separator className="flex my-2 bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<FormField <FormField
control={form.control} control={form.control}
name="icon" name="icon"
@ -711,7 +711,7 @@ export function CameraGroupEdit({
)} )}
/> />
<Separator className="flex my-2 bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<div className="flex flex-row gap-2 py-5 md:pb-0"> <div className="flex flex-row gap-2 py-5 md:pb-0">
<Button type="button" className="flex flex-1" onClick={onCancel}> <Button type="button" className="flex flex-1" onClick={onCancel}>

View File

@ -14,9 +14,9 @@ export default function FilterSwitch({
onCheckedChange, onCheckedChange,
}: FilterSwitchProps) { }: FilterSwitchProps) {
return ( return (
<div className="flex justify-between items-center gap-1"> <div className="flex items-center justify-between gap-1">
<Label <Label
className={`w-full mx-2 text-primary capitalize cursor-pointer ${disabled ? "text-secondary-foreground" : ""}`} className={`mx-2 w-full cursor-pointer capitalize text-primary ${disabled ? "text-secondary-foreground" : ""}`}
htmlFor={label} htmlFor={label}
> >
{label} {label}

View File

@ -19,7 +19,7 @@ export function LogLevelFilterButton({
const trigger = ( const trigger = (
<Button size="sm" className="flex items-center gap-2"> <Button size="sm" className="flex items-center gap-2">
<FaFilter className="text-secondary-foreground" /> <FaFilter className="text-secondary-foreground" />
<div className="hidden md:block text-primary">Filter</div> <div className="hidden text-primary md:block">Filter</div>
</Button> </Button>
); );
const content = ( const content = (
@ -33,7 +33,7 @@ export function LogLevelFilterButton({
return ( return (
<Drawer> <Drawer>
<DrawerTrigger asChild>{trigger}</DrawerTrigger> <DrawerTrigger asChild>{trigger}</DrawerTrigger>
<DrawerContent className="max-h-[75dvh] p-3 mx-1 overflow-hidden"> <DrawerContent className="mx-1 max-h-[75dvh] overflow-hidden p-3">
{content} {content}
</DrawerContent> </DrawerContent>
</Drawer> </Drawer>
@ -59,9 +59,9 @@ export function GeneralFilterContent({
return ( return (
<> <>
<div className="h-auto overflow-y-auto overflow-x-hidden"> <div className="h-auto overflow-y-auto overflow-x-hidden">
<div className="flex justify-between items-center my-2.5"> <div className="my-2.5 flex items-center justify-between">
<Label <Label
className="mx-2 text-primary cursor-pointer" className="mx-2 cursor-pointer text-primary"
htmlFor="allLabels" htmlFor="allLabels"
> >
All Logs All Logs
@ -80,9 +80,9 @@ export function GeneralFilterContent({
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<div className="my-2.5 flex flex-col gap-2.5"> <div className="my-2.5 flex flex-col gap-2.5">
{["debug", "info", "warning", "error"].map((item) => ( {["debug", "info", "warning", "error"].map((item) => (
<div className="flex justify-between items-center"> <div className="flex items-center justify-between">
<Label <Label
className="w-full mx-2 text-primary capitalize cursor-pointer" className="mx-2 w-full cursor-pointer capitalize text-primary"
htmlFor={item} htmlFor={item}
> >
{item.replaceAll("_", " ")} {item.replaceAll("_", " ")}

View File

@ -35,12 +35,12 @@ export default function ReviewActionGroup({
}, [selectedReviews, setSelectedReviews, pullLatestData]); }, [selectedReviews, setSelectedReviews, pullLatestData]);
return ( return (
<div className="absolute inset-x-2 inset-y-0 md:left-auto py-2 flex gap-2 justify-between items-center bg-background"> <div className="absolute inset-x-2 inset-y-0 flex items-center justify-between gap-2 bg-background py-2 md:left-auto">
<div className="mx-1 flex justify-center items-center text-sm text-muted-foreground"> <div className="mx-1 flex items-center justify-center text-sm text-muted-foreground">
<div className="p-1">{`${selectedReviews.length} selected`}</div> <div className="p-1">{`${selectedReviews.length} selected`}</div>
<div className="p-1">{"|"}</div> <div className="p-1">{"|"}</div>
<div <div
className="p-2 text-primary cursor-pointer hover:bg-secondary hover:rounded-lg" className="cursor-pointer p-2 text-primary hover:rounded-lg hover:bg-secondary"
onClick={onClearSelected} onClick={onClearSelected}
> >
Unselect Unselect
@ -49,7 +49,7 @@ export default function ReviewActionGroup({
<div className="flex items-center gap-1 md:gap-2"> <div className="flex items-center gap-1 md:gap-2">
{selectedReviews.length == 1 && ( {selectedReviews.length == 1 && (
<Button <Button
className="p-2 flex items-center gap-2" className="flex items-center gap-2 p-2"
size="sm" size="sm"
onClick={() => { onClick={() => {
onExport(selectedReviews[0]); onExport(selectedReviews[0]);
@ -61,7 +61,7 @@ export default function ReviewActionGroup({
</Button> </Button>
)} )}
<Button <Button
className="p-2 flex items-center gap-2" className="flex items-center gap-2 p-2"
size="sm" size="sm"
onClick={onMarkAsReviewed} onClick={onMarkAsReviewed}
> >
@ -69,7 +69,7 @@ export default function ReviewActionGroup({
{isDesktop && <div className="text-primary">Mark as reviewed</div>} {isDesktop && <div className="text-primary">Mark as reviewed</div>}
</Button> </Button>
<Button <Button
className="p-2 flex items-center gap-2" className="flex items-center gap-2 p-2"
size="sm" size="sm"
onClick={onDelete} onClick={onDelete}
> >

View File

@ -263,7 +263,7 @@ export function CamerasFilterButton({
<DropdownMenuSeparator /> <DropdownMenuSeparator />
</> </>
)} )}
<div className="h-auto max-h-[80dvh] p-4 overflow-y-auto overflow-x-hidden"> <div className="h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden p-4">
<FilterSwitch <FilterSwitch
isChecked={currentCameras == undefined} isChecked={currentCameras == undefined}
label="All Cameras" label="All Cameras"
@ -280,7 +280,7 @@ export function CamerasFilterButton({
return ( return (
<div <div
key={name} key={name}
className="w-full px-2 py-1.5 text-sm text-primary capitalize cursor-pointer rounded-lg hover:bg-muted" className="w-full cursor-pointer rounded-lg px-2 py-1.5 text-sm capitalize text-primary hover:bg-muted"
onClick={() => setCurrentCameras([...conf.cameras])} onClick={() => setCurrentCameras([...conf.cameras])}
> >
{name} {name}
@ -321,7 +321,7 @@ export function CamerasFilterButton({
</div> </div>
</div> </div>
<DropdownMenuSeparator className="my-2" /> <DropdownMenuSeparator className="my-2" />
<div className="p-2 flex justify-evenly items-center"> <div className="flex items-center justify-evenly p-2">
<Button <Button
variant="select" variant="select"
onClick={() => { onClick={() => {
@ -394,7 +394,7 @@ function ShowReviewFilter({
); );
return ( return (
<> <>
<div className="hidden h-9 md:flex p-2 justify-start items-center text-sm bg-secondary hover:bg-secondary/80 rounded-md cursor-pointer"> <div className="hidden h-9 cursor-pointer items-center justify-start rounded-md bg-secondary p-2 text-sm hover:bg-secondary/80 md:flex">
<Switch <Switch
id="reviewed" id="reviewed"
checked={showReviewedSwitch == 1} checked={showReviewedSwitch == 1}
@ -408,7 +408,7 @@ function ShowReviewFilter({
</div> </div>
<Button <Button
className="block md:hidden duration-0" className="block duration-0 md:hidden"
variant={showReviewedSwitch == 1 ? "select" : "default"} variant={showReviewedSwitch == 1 ? "select" : "default"}
size="sm" size="sm"
onClick={() => setShowReviewedSwitch(showReviewedSwitch == 0 ? 1 : 0)} onClick={() => setShowReviewedSwitch(showReviewedSwitch == 0 ? 1 : 0)}
@ -460,7 +460,7 @@ function CalendarFilterButton({
onSelect={updateSelectedDay} onSelect={updateSelectedDay}
/> />
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<div className="p-2 flex justify-center items-center"> <div className="flex items-center justify-center p-2">
<Button <Button
onClick={() => { onClick={() => {
updateSelectedDay(undefined); updateSelectedDay(undefined);
@ -621,9 +621,9 @@ export function GeneralFilterContent({
<DropdownMenuSeparator /> <DropdownMenuSeparator />
</div> </div>
)} )}
<div className="flex justify-between items-center my-2.5"> <div className="my-2.5 flex items-center justify-between">
<Label <Label
className="mx-2 text-primary cursor-pointer" className="mx-2 cursor-pointer text-primary"
htmlFor="allLabels" htmlFor="allLabels"
> >
All Labels All Labels
@ -666,7 +666,7 @@ export function GeneralFilterContent({
</div> </div>
</div> </div>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<div className="p-2 flex justify-evenly items-center"> <div className="flex items-center justify-evenly p-2">
<Button <Button
variant="select" variant="select"
onClick={() => { onClick={() => {
@ -707,7 +707,7 @@ function ShowMotionOnlyButton({
return ( return (
<> <>
<div className="hidden md:inline-flex items-center justify-center whitespace-nowrap text-sm bg-secondary hover:bg-secondary/80 text-primary h-9 rounded-md px-3 mx-1 cursor-pointer"> <div className="mx-1 hidden h-9 cursor-pointer items-center justify-center whitespace-nowrap rounded-md bg-secondary px-3 text-sm text-primary hover:bg-secondary/80 md:inline-flex">
<Switch <Switch
className="ml-1" className="ml-1"
id="collapse-motion" id="collapse-motion"
@ -715,7 +715,7 @@ function ShowMotionOnlyButton({
onCheckedChange={setMotionOnlyButton} onCheckedChange={setMotionOnlyButton}
/> />
<Label <Label
className="mx-2 text-primary cursor-pointer" className="mx-2 cursor-pointer text-primary"
htmlFor="collapse-motion" htmlFor="collapse-motion"
> >
Motion only Motion only

View File

@ -43,7 +43,7 @@ export function ZoneMaskFilterButton({
return ( return (
<Drawer> <Drawer>
<DrawerTrigger asChild>{trigger}</DrawerTrigger> <DrawerTrigger asChild>{trigger}</DrawerTrigger>
<DrawerContent className="max-h-[75dvh] p-3 mx-1 overflow-hidden"> <DrawerContent className="mx-1 max-h-[75dvh] overflow-hidden p-3">
{content} {content}
</DrawerContent> </DrawerContent>
</Drawer> </Drawer>
@ -69,9 +69,9 @@ export function GeneralFilterContent({
return ( return (
<> <>
<div className="h-auto overflow-y-auto overflow-x-hidden"> <div className="h-auto overflow-y-auto overflow-x-hidden">
<div className="flex justify-between items-center my-2.5"> <div className="my-2.5 flex items-center justify-between">
<Label <Label
className="mx-2 text-primary cursor-pointer" className="mx-2 cursor-pointer text-primary"
htmlFor="allLabels" htmlFor="allLabels"
> >
All Masks and Zones All Masks and Zones
@ -90,9 +90,9 @@ export function GeneralFilterContent({
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<div className="my-2.5 flex flex-col gap-2.5"> <div className="my-2.5 flex flex-col gap-2.5">
{["zone", "motion_mask", "object_mask"].map((item) => ( {["zone", "motion_mask", "object_mask"].map((item) => (
<div key={item} className="flex justify-between items-center"> <div key={item} className="flex items-center justify-between">
<Label <Label
className="w-full mx-2 text-primary capitalize cursor-pointer" className="mx-2 w-full cursor-pointer capitalize text-primary"
htmlFor={item} htmlFor={item}
> >
{item {item

View File

@ -131,7 +131,7 @@ export function ThresholdBarGraph({
}, [graphId, options]); }, [graphId, options]);
return ( return (
<div className="w-full flex flex-col"> <div className="flex w-full flex-col">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<div className="text-xs text-muted-foreground">{name}</div> <div className="text-xs text-muted-foreground">{name}</div>
<div className="text-xs text-primary"> <div className="text-xs text-primary">
@ -234,8 +234,8 @@ export function StorageGraph({ graphId, used, total }: StorageGraphProps) {
}, [graphId, options]); }, [graphId, options]);
return ( return (
<div className="w-full flex flex-col gap-2.5"> <div className="flex w-full flex-col gap-2.5">
<div className="w-full flex justify-between items-center gap-1"> <div className="flex w-full items-center justify-between gap-1">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<div className="text-xs text-primary">{getUnitSize(used)}</div> <div className="text-xs text-primary">{getUnitSize(used)}</div>
<div className="text-xs text-primary">/</div> <div className="text-xs text-primary">/</div>
@ -247,7 +247,7 @@ export function StorageGraph({ graphId, used, total }: StorageGraphProps) {
{Math.round((used / total) * 100)}% {Math.round((used / total) * 100)}%
</div> </div>
</div> </div>
<div className="h-5 rounded-md overflow-hidden"> <div className="h-5 overflow-hidden rounded-md">
<Chart <Chart
type="bar" type="bar"
options={options} options={options}
@ -369,7 +369,7 @@ export function CameraLineGraph({
}, [graphId, options]); }, [graphId, options]);
return ( return (
<div className="w-full flex flex-col"> <div className="flex w-full flex-col">
{lastValues && ( {lastValues && (
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-2.5">
{dataLabels.map((label, labelIdx) => ( {dataLabels.map((label, labelIdx) => (

View File

@ -66,12 +66,12 @@ export default function IconPicker({
> >
<PopoverTrigger asChild> <PopoverTrigger asChild>
{!selectedIcon?.name || !selectedIcon?.Icon ? ( {!selectedIcon?.name || !selectedIcon?.Icon ? (
<Button className="text-muted-foreground w-full mt-2"> <Button className="mt-2 w-full text-muted-foreground">
Select an icon Select an icon
</Button> </Button>
) : ( ) : (
<div className="hover:cursor-pointer"> <div className="hover:cursor-pointer">
<div className="flex flex-row w-full justify-between items-center gap-2 my-3"> <div className="my-3 flex w-full flex-row items-center justify-between gap-2">
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<selectedIcon.Icon size={15} /> <selectedIcon.Icon size={15} />
<div className="text-sm"> <div className="text-sm">
@ -95,9 +95,9 @@ export default function IconPicker({
align="start" align="start"
side="top" side="top"
container={containerRef.current} container={containerRef.current}
className="flex flex-col max-h-[50dvh] md:max-h-[30dvh] overflow-y-hidden" className="flex max-h-[50dvh] flex-col overflow-y-hidden md:max-h-[30dvh]"
> >
<div className="flex flex-row justify-between items-center mb-3"> <div className="mb-3 flex flex-row items-center justify-between">
<Heading as="h4">Select an icon</Heading> <Heading as="h4">Select an icon</Heading>
<span tabIndex={0} className="sr-only" /> <span tabIndex={0} className="sr-only" />
<IoClose <IoClose
@ -111,17 +111,17 @@ export default function IconPicker({
<Input <Input
type="text" type="text"
placeholder="Search for an icon..." placeholder="Search for an icon..."
className="mb-3 text-md md:text-sm" className="text-md mb-3 md:text-sm"
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
/> />
<div className="flex flex-col h-full overflow-y-auto"> <div className="flex h-full flex-col overflow-y-auto">
<div className="grid grid-cols-6 gap-2 pr-1"> <div className="grid grid-cols-6 gap-2 pr-1">
{icons.map(([name, Icon]) => ( {icons.map(([name, Icon]) => (
<div <div
key={name} key={name}
className={cn( className={cn(
"flex flex-row justify-center items-center hover:cursor-pointer p-1 rounded-lg", "flex flex-row items-center justify-center rounded-lg p-1 hover:cursor-pointer",
selectedIcon?.name === name selectedIcon?.name === name
? "bg-selected text-white" ? "bg-selected text-white"
: "hover:bg-secondary-foreground", : "hover:bg-secondary-foreground",

View File

@ -4,11 +4,11 @@ type LiveIconProps = {
export function LiveGridIcon({ layout }: LiveIconProps) { export function LiveGridIcon({ layout }: LiveIconProps) {
return ( return (
<div className="size-full flex flex-col gap-0.5 rounded-md overflow-hidden"> <div className="flex size-full flex-col gap-0.5 overflow-hidden rounded-md">
<div <div
className={`h-1 w-full ${layout == "grid" ? "bg-selected" : "bg-muted-foreground"}`} className={`h-1 w-full ${layout == "grid" ? "bg-selected" : "bg-muted-foreground"}`}
/> />
<div className="h-1 w-full flex gap-0.5"> <div className="flex h-1 w-full gap-0.5">
<div <div
className={`w-full ${layout == "grid" ? "bg-selected" : "bg-muted-foreground"}`} className={`w-full ${layout == "grid" ? "bg-selected" : "bg-muted-foreground"}`}
/> />
@ -16,7 +16,7 @@ export function LiveGridIcon({ layout }: LiveIconProps) {
className={`w-full ${layout == "grid" ? "bg-selected" : "bg-muted-foreground"}`} className={`w-full ${layout == "grid" ? "bg-selected" : "bg-muted-foreground"}`}
/> />
</div> </div>
<div className="h-1 w-full flex gap-0.5"> <div className="flex h-1 w-full gap-0.5">
<div <div
className={`w-full ${layout == "grid" ? "bg-selected" : "bg-muted-foreground"}`} className={`w-full ${layout == "grid" ? "bg-selected" : "bg-muted-foreground"}`}
/> />
@ -30,7 +30,7 @@ export function LiveGridIcon({ layout }: LiveIconProps) {
export function LiveListIcon({ layout }: LiveIconProps) { export function LiveListIcon({ layout }: LiveIconProps) {
return ( return (
<div className="size-full flex flex-col gap-0.5 rounded-md overflow-hidden"> <div className="flex size-full flex-col gap-0.5 overflow-hidden rounded-md">
<div <div
className={`size-full ${layout == "list" ? "bg-selected" : "bg-secondary-foreground"}`} className={`size-full ${layout == "list" ? "bg-selected" : "bg-secondary-foreground"}`}
/> />

View File

@ -1,12 +1,12 @@
function CameraActivityIndicator() { function CameraActivityIndicator() {
return ( return (
<div className="flex items-center justify-center relative z-20"> <div className="relative z-20 flex items-center justify-center">
<div className="flex"> <div className="flex">
<div className="absolute size-[5px] inset-0 bg-severity_alert-dimmed rounded-full shadow-[0px_0px_10px_0px_#00000024,0px_0px_15px_0px_#00000024] z-20 animate-move"></div> <div className="absolute inset-0 z-20 size-[5px] animate-move rounded-full bg-severity_alert-dimmed shadow-[0px_0px_10px_0px_#00000024,0px_0px_15px_0px_#00000024]"></div>
<div className="flex-1 size-[5px] mr-[2px] bg-severity_alert rounded-full animate-scale1"></div> <div className="mr-[2px] size-[5px] flex-1 animate-scale1 rounded-full bg-severity_alert"></div>
<div className="flex-1 size-[5px] mr-[2px] bg-severity_alert rounded-full animate-scale2"></div> <div className="mr-[2px] size-[5px] flex-1 animate-scale2 rounded-full bg-severity_alert"></div>
<div className="flex-1 size-[5px] mr-[2px] bg-severity_alert rounded-full animate-scale3"></div> <div className="mr-[2px] size-[5px] flex-1 animate-scale3 rounded-full bg-severity_alert"></div>
<div className="flex-1 size-[5px] mr-[2px] bg-severity_alert rounded-full animate-scale4"></div> <div className="mr-[2px] size-[5px] flex-1 animate-scale4 rounded-full bg-severity_alert"></div>
</div> </div>
<svg className="hidden" xmlns="http://www.w3.org/2000/svg" version="1.1"> <svg className="hidden" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs> <defs>

View File

@ -34,7 +34,7 @@ export default function Chip({
<div <div
ref={nodeRef} ref={nodeRef}
className={cn( className={cn(
"flex px-2 py-1.5 rounded-2xl items-center z-10", "z-10 flex items-center rounded-2xl px-2 py-1.5",
className, className,
)} )}
onClick={onClick} onClick={onClick}
@ -63,7 +63,7 @@ export function LogChip({ severity, onClickSeverity }: LogChipProps) {
return ( return (
<div <div
className={`py-[1px] px-1 capitalize text-xs rounded-md ${onClickSeverity ? "cursor-pointer" : ""} ${severityClassName}`} className={`rounded-md px-1 py-[1px] text-xs capitalize ${onClickSeverity ? "cursor-pointer" : ""} ${severityClassName}`}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();

View File

@ -14,7 +14,7 @@ export default function ImageLoadingIndicator({
} }
return isSafari ? ( return isSafari ? (
<div className={cn("bg-gray-300 pointer-events-none", className)} /> <div className={cn("pointer-events-none bg-gray-300", className)} />
) : ( ) : (
<Skeleton className={cn("pointer-events-none", className)} /> <Skeleton className={cn("pointer-events-none", className)} />
); );

View File

@ -17,9 +17,9 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div <div
className={cn( className={cn(
"flex flex-col justify-center items-center", "flex flex-col items-center justify-center",
isDesktop isDesktop
? "rounded-lg text-secondary-foreground bg-secondary hover:bg-muted cursor-pointer" ? "cursor-pointer rounded-lg bg-secondary text-secondary-foreground hover:bg-muted"
: "text-secondary-foreground", : "text-secondary-foreground",
className, className,
)} )}

View File

@ -120,7 +120,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div <div
className={`flex flex-col justify-center items-center ${isDesktop ? "rounded-lg text-secondary-foreground bg-secondary hover:bg-muted cursor-pointer" : "text-secondary-foreground"}`} className={`flex flex-col items-center justify-center ${isDesktop ? "cursor-pointer rounded-lg bg-secondary text-secondary-foreground hover:bg-muted" : "text-secondary-foreground"}`}
> >
<LuSettings className="size-5 md:m-[6px]" /> <LuSettings className="size-5 md:m-[6px]" />
</div> </div>
@ -135,7 +135,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
</Trigger> </Trigger>
<Content <Content
className={ className={
isDesktop ? "w-72 mr-5" : "max-h-[75dvh] p-2 overflow-hidden" isDesktop ? "mr-5 w-72" : "max-h-[75dvh] overflow-hidden p-2"
} }
> >
<div className="w-full flex-col overflow-y-auto overflow-x-hidden"> <div className="w-full flex-col overflow-y-auto overflow-x-hidden">
@ -147,7 +147,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
className={ className={
isDesktop isDesktop
? "cursor-pointer" ? "cursor-pointer"
: "w-full p-2 flex items-center text-sm" : "flex w-full items-center p-2 text-sm"
} }
> >
<LuActivity className="mr-2 size-4" /> <LuActivity className="mr-2 size-4" />
@ -159,7 +159,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
className={ className={
isDesktop isDesktop
? "cursor-pointer" ? "cursor-pointer"
: "w-full p-2 flex items-center text-sm" : "flex w-full items-center p-2 text-sm"
} }
> >
<LuList className="mr-2 size-4" /> <LuList className="mr-2 size-4" />
@ -177,7 +177,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
className={ className={
isDesktop isDesktop
? "cursor-pointer" ? "cursor-pointer"
: "w-full p-2 flex items-center text-sm" : "flex w-full items-center p-2 text-sm"
} }
> >
<LuSettings className="mr-2 size-4" /> <LuSettings className="mr-2 size-4" />
@ -189,7 +189,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
className={ className={
isDesktop isDesktop
? "cursor-pointer" ? "cursor-pointer"
: "w-full p-2 flex items-center text-sm" : "flex w-full items-center p-2 text-sm"
} }
> >
<LuPenSquare className="mr-2 size-4" /> <LuPenSquare className="mr-2 size-4" />
@ -205,7 +205,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
className={ className={
isDesktop isDesktop
? "cursor-pointer" ? "cursor-pointer"
: "p-2 flex items-center text-sm" : "flex items-center p-2 text-sm"
} }
> >
<LuSunMoon className="mr-2 size-4" /> <LuSunMoon className="mr-2 size-4" />
@ -222,7 +222,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
className={ className={
isDesktop isDesktop
? "cursor-pointer" ? "cursor-pointer"
: "p-2 flex items-center text-sm" : "flex items-center p-2 text-sm"
} }
onClick={() => setTheme("light")} onClick={() => setTheme("light")}
> >
@ -232,14 +232,14 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
Light Light
</> </>
) : ( ) : (
<span className="mr-2 ml-6">Light</span> <span className="ml-6 mr-2">Light</span>
)} )}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
className={ className={
isDesktop isDesktop
? "cursor-pointer" ? "cursor-pointer"
: "p-2 flex items-center text-sm" : "flex items-center p-2 text-sm"
} }
onClick={() => setTheme("dark")} onClick={() => setTheme("dark")}
> >
@ -249,14 +249,14 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
Dark Dark
</> </>
) : ( ) : (
<span className="mr-2 ml-6">Dark</span> <span className="ml-6 mr-2">Dark</span>
)} )}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
className={ className={
isDesktop isDesktop
? "cursor-pointer" ? "cursor-pointer"
: "p-2 flex items-center text-sm" : "flex items-center p-2 text-sm"
} }
onClick={() => setTheme("system")} onClick={() => setTheme("system")}
> >
@ -266,7 +266,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
System System
</> </>
) : ( ) : (
<span className="mr-2 ml-6">System</span> <span className="ml-6 mr-2">System</span>
)} )}
</MenuItem> </MenuItem>
</SubItemContent> </SubItemContent>
@ -277,7 +277,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
className={ className={
isDesktop isDesktop
? "cursor-pointer" ? "cursor-pointer"
: "p-2 flex items-center text-sm" : "flex items-center p-2 text-sm"
} }
> >
<LuSunMoon className="mr-2 size-4" /> <LuSunMoon className="mr-2 size-4" />
@ -296,7 +296,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
className={ className={
isDesktop isDesktop
? "cursor-pointer" ? "cursor-pointer"
: "p-2 flex items-center text-sm" : "flex items-center p-2 text-sm"
} }
onClick={() => setColorScheme(scheme)} onClick={() => setColorScheme(scheme)}
> >
@ -306,7 +306,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
{friendlyColorSchemeName(scheme)} {friendlyColorSchemeName(scheme)}
</> </>
) : ( ) : (
<span className="mr-2 ml-6"> <span className="ml-6 mr-2">
{friendlyColorSchemeName(scheme)} {friendlyColorSchemeName(scheme)}
</span> </span>
)} )}
@ -325,7 +325,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
className={ className={
isDesktop isDesktop
? "cursor-pointer" ? "cursor-pointer"
: "p-2 flex items-center text-sm" : "flex items-center p-2 text-sm"
} }
> >
<LuLifeBuoy className="mr-2 size-4" /> <LuLifeBuoy className="mr-2 size-4" />
@ -337,7 +337,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
className={ className={
isDesktop isDesktop
? "cursor-pointer" ? "cursor-pointer"
: "p-2 flex items-center text-sm" : "flex items-center p-2 text-sm"
} }
> >
<LuGithub className="mr-2 size-4" /> <LuGithub className="mr-2 size-4" />
@ -347,7 +347,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
<DropdownMenuSeparator className={isDesktop ? "mt-3" : "mt-1"} /> <DropdownMenuSeparator className={isDesktop ? "mt-3" : "mt-1"} />
<MenuItem <MenuItem
className={ className={
isDesktop ? "cursor-pointer" : "p-2 flex items-center text-sm" isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
} }
onClick={() => setRestartDialogOpen(true)} onClick={() => setRestartDialogOpen(true)}
> >

View File

@ -20,7 +20,7 @@ function Bottombar() {
const navItems = useNavigation("secondary"); const navItems = useNavigation("secondary");
return ( return (
<div className="absolute h-16 inset-x-4 bottom-0 flex flex-row items-center justify-between"> <div className="absolute inset-x-4 bottom-0 flex h-16 flex-row items-center justify-between">
{navItems.map((item) => ( {navItems.map((item) => (
<NavItem key={item.id} className="p-2" item={item} Icon={item.icon} /> <NavItem key={item.id} className="p-2" item={item} Icon={item.icon} />
))} ))}
@ -77,16 +77,16 @@ function StatusAlertNav({ className }: StatusAlertNavProps) {
</DrawerTrigger> </DrawerTrigger>
<DrawerContent <DrawerContent
className={cn( className={cn(
"max-h-[75dvh] px-2 mx-1 rounded-t-2xl overflow-hidden", "mx-1 max-h-[75dvh] overflow-hidden rounded-t-2xl px-2",
className, className,
)} )}
> >
<div className="w-full h-auto py-4 overflow-y-auto overflow-x-hidden flex flex-col items-center gap-2"> <div className="flex h-auto w-full flex-col items-center gap-2 overflow-y-auto overflow-x-hidden py-4">
{Object.entries(messages).map(([key, messageArray]) => ( {Object.entries(messages).map(([key, messageArray]) => (
<div key={key} className="w-full flex items-center gap-2"> <div key={key} className="flex w-full items-center gap-2">
{messageArray.map(({ id, text, color, link }: StatusMessage) => { {messageArray.map(({ id, text, color, link }: StatusMessage) => {
const message = ( const message = (
<div key={id} className="flex items-center text-xs gap-2"> <div key={id} className="flex items-center gap-2 text-xs">
<IoIosWarning <IoIosWarning
className={`size-5 ${color || "text-danger"}`} className={`size-5 ${color || "text-danger"}`}
/> />

View File

@ -44,7 +44,7 @@ export default function NavItem({
onClick={onClick} onClick={onClick}
className={({ isActive }) => className={({ isActive }) =>
cn( cn(
"flex flex-col justify-center items-center rounded-lg", "flex flex-col items-center justify-center rounded-lg",
className, className,
variants[item.variant ?? "primary"][isActive ? "active" : "inactive"], variants[item.variant ?? "primary"][isActive ? "active" : "inactive"],
) )

View File

@ -12,10 +12,10 @@ function Sidebar() {
const navbarLinks = useNavigation(); const navbarLinks = useNavigation();
return ( return (
<aside className="absolute w-[52px] z-10 left-o inset-y-0 overflow-y-auto scrollbar-hidden py-4 flex flex-col justify-between bg-background_alt border-r border-secondary-highlight"> <aside className="left-o scrollbar-hidden absolute inset-y-0 z-10 flex w-[52px] flex-col justify-between overflow-y-auto border-r border-secondary-highlight bg-background_alt py-4">
<span tabIndex={0} className="sr-only" /> <span tabIndex={0} className="sr-only" />
<div className="w-full flex flex-col gap-0 items-center"> <div className="flex w-full flex-col items-center gap-0">
<Logo className="w-8 h-8 mb-6" /> <Logo className="mb-6 h-8 w-8" />
{navbarLinks.map((item) => { {navbarLinks.map((item) => {
const showCameraGroups = const showCameraGroups =
item.id == 1 && item.url == location.pathname; item.id == 1 && item.url == location.pathname;
@ -32,7 +32,7 @@ function Sidebar() {
); );
})} })}
</div> </div>
<div className="flex flex-col items-center gap-4 mb-8"> <div className="mb-8 flex flex-col items-center gap-4">
<GeneralSettings /> <GeneralSettings />
<AccountSettings /> <AccountSettings />
</div> </div>

View File

@ -103,7 +103,7 @@ export default function ExportDialog({
return ( return (
<> <>
<SaveExportOverlay <SaveExportOverlay
className="absolute top-8 left-1/2 -translate-x-1/2 z-50 pointer-events-none" className="pointer-events-none absolute left-1/2 top-8 z-50 -translate-x-1/2"
show={mode == "timeline"} show={mode == "timeline"}
onSave={() => onStartExport()} onSave={() => onStartExport()}
onCancel={() => setMode("none")} onCancel={() => setMode("none")}
@ -132,7 +132,7 @@ export default function ExportDialog({
setMode("select"); setMode("select");
}} }}
> >
<FaArrowDown className="p-1 fill-secondary bg-secondary-foreground rounded-md" /> <FaArrowDown className="rounded-md bg-secondary-foreground fill-secondary p-1" />
{isDesktop && <div className="text-primary">Export</div>} {isDesktop && <div className="text-primary">Export</div>}
</Button> </Button>
</Trigger> </Trigger>
@ -140,7 +140,7 @@ export default function ExportDialog({
className={ className={
isDesktop isDesktop
? "sm:rounded-lg md:rounded-2xl" ? "sm:rounded-lg md:rounded-2xl"
: "px-4 pb-4 mx-4 rounded-lg md:rounded-2xl" : "mx-4 rounded-lg px-4 pb-4 md:rounded-2xl"
} }
> >
<ExportContent <ExportContent
@ -241,8 +241,8 @@ export function ExportContent({
<RadioGroupItem <RadioGroupItem
className={ className={
opt == selectedOption opt == selectedOption
? "from-selected/50 to-selected/90 text-selected bg-selected" ? "bg-selected from-selected/50 to-selected/90 text-selected"
: "from-secondary/50 to-secondary/90 text-secondary bg-secondary" : "bg-secondary from-secondary/50 to-secondary/90 text-secondary"
} }
id={opt} id={opt}
value={opt} value={opt}
@ -277,7 +277,7 @@ export function ExportContent({
className={isDesktop ? "" : "mt-3 flex flex-col-reverse gap-4"} className={isDesktop ? "" : "mt-3 flex flex-col-reverse gap-4"}
> >
<div <div
className={`p-2 cursor-pointer text-center ${isDesktop ? "" : "w-full"}`} className={`cursor-pointer p-2 text-center ${isDesktop ? "" : "w-full"}`}
onClick={onCancel} onClick={onCancel}
> >
Cancel Cancel
@ -383,7 +383,7 @@ function CustomTimeSelector({
return ( return (
<div <div
className={`mt-3 flex items-center bg-secondary text-secondary-foreground rounded-lg ${isDesktop ? "mx-8 px-2 gap-2" : "pl-2"}`} className={`mt-3 flex items-center rounded-lg bg-secondary text-secondary-foreground ${isDesktop ? "mx-8 gap-2 px-2" : "pl-2"}`}
> >
<FaCalendarAlt /> <FaCalendarAlt />
<Popover <Popover
@ -424,7 +424,7 @@ function CustomTimeSelector({
/> />
<SelectSeparator className="bg-secondary" /> <SelectSeparator className="bg-secondary" />
<input <input
className="w-full mx-4 p-1 border border-input bg-background text-secondary-foreground hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="mx-4 w-full border border-input bg-background p-1 text-secondary-foreground hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
id="startTime" id="startTime"
type="time" type="time"
value={startClock} value={startClock}
@ -486,7 +486,7 @@ function CustomTimeSelector({
/> />
<SelectSeparator className="bg-secondary" /> <SelectSeparator className="bg-secondary" />
<input <input
className="w-full mx-4 p-1 border border-input bg-background text-secondary-foreground hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="mx-4 w-full border border-input bg-background p-1 text-secondary-foreground hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
id="startTime" id="startTime"
type="time" type="time"
value={endClock} value={endClock}

View File

@ -29,11 +29,11 @@ export default function LogInfoDialog({
}} }}
> >
<Content <Content
className={isDesktop ? "" : "max-h-[75dvh] p-2 pb-4 overflow-hidden"} className={isDesktop ? "" : "max-h-[75dvh] overflow-hidden p-2 pb-4"}
> >
{logLine && ( {logLine && (
<div className="size-full flex flex-col gap-5"> <div className="flex size-full flex-col gap-5">
<div className="w-min flex flex-col gap-1.5"> <div className="flex w-min flex-col gap-1.5">
<div className="text-sm text-primary/40">Type</div> <div className="text-sm text-primary/40">Type</div>
<LogChip severity={logLine.severity} /> <LogChip severity={logLine.severity} />
</div> </div>

View File

@ -27,12 +27,12 @@ export default function MobileCameraDrawer({
<FaVideo className="text-secondary-foreground" /> <FaVideo className="text-secondary-foreground" />
</Button> </Button>
</DrawerTrigger> </DrawerTrigger>
<DrawerContent className="max-h-[75dvh] px-4 mx-1 rounded-t-2xl overflow-hidden"> <DrawerContent className="mx-1 max-h-[75dvh] overflow-hidden rounded-t-2xl px-4">
<div className="w-full h-auto py-4 overflow-y-auto overflow-x-hidden flex flex-col items-center gap-2"> <div className="flex h-auto w-full flex-col items-center gap-2 overflow-y-auto overflow-x-hidden py-4">
{allCameras.map((cam) => ( {allCameras.map((cam) => (
<div <div
key={cam} key={cam}
className={`w-full mx-4 py-2 text-center capitalize ${cam == selected ? "bg-secondary rounded-lg" : ""}`} className={`mx-4 w-full py-2 text-center capitalize ${cam == selected ? "rounded-lg bg-secondary" : ""}`}
onClick={() => { onClick={() => {
onSelectCamera(cam); onSelectCamera(cam);
setCameraDrawer(false); setCameraDrawer(false);

View File

@ -112,22 +112,22 @@ export default function MobileReviewSettingsDrawer({
let content; let content;
if (drawerMode == "select") { if (drawerMode == "select") {
content = ( content = (
<div className="w-full p-4 flex flex-col gap-2"> <div className="flex w-full flex-col gap-2 p-4">
{features.includes("export") && ( {features.includes("export") && (
<Button <Button
className="w-full flex justify-center items-center gap-2" className="flex w-full items-center justify-center gap-2"
onClick={() => { onClick={() => {
setDrawerMode("export"); setDrawerMode("export");
setMode("select"); setMode("select");
}} }}
> >
<FaArrowDown className="p-1 fill-secondary bg-secondary-foreground rounded-md" /> <FaArrowDown className="rounded-md bg-secondary-foreground fill-secondary p-1" />
Export Export
</Button> </Button>
)} )}
{features.includes("calendar") && ( {features.includes("calendar") && (
<Button <Button
className="w-full flex justify-center items-center gap-2" className="flex w-full items-center justify-center gap-2"
variant={filter?.after ? "select" : "default"} variant={filter?.after ? "select" : "default"}
onClick={() => setDrawerMode("calendar")} onClick={() => setDrawerMode("calendar")}
> >
@ -139,7 +139,7 @@ export default function MobileReviewSettingsDrawer({
)} )}
{features.includes("filter") && ( {features.includes("filter") && (
<Button <Button
className="w-full flex justify-center items-center gap-2" className="flex w-full items-center justify-center gap-2"
variant={filter?.labels ? "select" : "default"} variant={filter?.labels ? "select" : "default"}
onClick={() => setDrawerMode("filter")} onClick={() => setDrawerMode("filter")}
> >
@ -177,8 +177,8 @@ export default function MobileReviewSettingsDrawer({
); );
} else if (drawerMode == "calendar") { } else if (drawerMode == "calendar") {
content = ( content = (
<div className="w-full flex flex-col"> <div className="flex w-full flex-col">
<div className="w-full h-8 relative"> <div className="relative h-8 w-full">
<div <div
className="absolute left-0 text-selected" className="absolute left-0 text-selected"
onClick={() => setDrawerMode("select")} onClick={() => setDrawerMode("select")}
@ -205,7 +205,7 @@ export default function MobileReviewSettingsDrawer({
}} }}
/> />
<SelectSeparator /> <SelectSeparator />
<div className="p-2 flex justify-center items-center"> <div className="flex items-center justify-center p-2">
<Button <Button
onClick={() => { onClick={() => {
onUpdateFilter({ onUpdateFilter({
@ -222,8 +222,8 @@ export default function MobileReviewSettingsDrawer({
); );
} else if (drawerMode == "filter") { } else if (drawerMode == "filter") {
content = ( content = (
<div className="w-full h-auto overflow-y-auto flex flex-col"> <div className="flex h-auto w-full flex-col overflow-y-auto">
<div className="w-full h-8 mb-2 relative"> <div className="relative mb-2 h-8 w-full">
<div <div
className="absolute left-0 text-selected" className="absolute left-0 text-selected"
onClick={() => setDrawerMode("select")} onClick={() => setDrawerMode("select")}
@ -256,7 +256,7 @@ export default function MobileReviewSettingsDrawer({
return ( return (
<> <>
<SaveExportOverlay <SaveExportOverlay
className="absolute top-8 left-1/2 -translate-x-1/2 z-50 pointer-events-none" className="pointer-events-none absolute left-1/2 top-8 z-50 -translate-x-1/2"
show={mode == "timeline"} show={mode == "timeline"}
onSave={() => onStartExport()} onSave={() => onStartExport()}
onCancel={() => setMode("none")} onCancel={() => setMode("none")}
@ -281,7 +281,7 @@ export default function MobileReviewSettingsDrawer({
/> />
</Button> </Button>
</DrawerTrigger> </DrawerTrigger>
<DrawerContent className="max-h-[80dvh] overflow-hidden flex flex-col items-center gap-2 px-4 pb-4 mx-1 rounded-t-2xl"> <DrawerContent className="mx-1 flex max-h-[80dvh] flex-col items-center gap-2 overflow-hidden rounded-t-2xl px-4 pb-4">
{content} {content}
</DrawerContent> </DrawerContent>
</Drawer> </Drawer>

View File

@ -26,9 +26,9 @@ export default function MobileTimelineDrawer({
<FaFlag className="text-secondary-foreground" /> <FaFlag className="text-secondary-foreground" />
</Button> </Button>
</DrawerTrigger> </DrawerTrigger>
<DrawerContent className="max-h-[75dvh] overflow-hidden flex flex-col items-center gap-2 px-4 pb-4 mx-1 rounded-t-2xl"> <DrawerContent className="mx-1 flex max-h-[75dvh] flex-col items-center gap-2 overflow-hidden rounded-t-2xl px-4 pb-4">
<div <div
className={`w-full mx-4 py-2 text-center capitalize ${selected == "timeline" ? "bg-secondary rounded-lg" : ""}`} className={`mx-4 w-full py-2 text-center capitalize ${selected == "timeline" ? "rounded-lg bg-secondary" : ""}`}
onClick={() => { onClick={() => {
onSelect("timeline"); onSelect("timeline");
setDrawer(false); setDrawer(false);
@ -37,7 +37,7 @@ export default function MobileTimelineDrawer({
Timeline Timeline
</div> </div>
<div <div
className={`w-full mx-4 py-2 text-center capitalize ${selected == "events" ? "bg-secondary rounded-lg" : ""}`} className={`mx-4 w-full py-2 text-center capitalize ${selected == "events" ? "rounded-lg bg-secondary" : ""}`}
onClick={() => { onClick={() => {
onSelect("events"); onSelect("events");
setDrawer(false); setDrawer(false);

View File

@ -67,7 +67,7 @@ function ReviewActivityDay({ reviewSummary, day }: ReviewActivityDayProps) {
}, [reviewSummary, day]); }, [reviewSummary, day]);
return ( return (
<div className="flex flex-col justify-center items-center"> <div className="flex flex-col items-center justify-center">
{day.getDate()} {day.getDate()}
{dayActivity != "none" && ( {dayActivity != "none" && (
<FaCircle <FaCircle

View File

@ -17,9 +17,9 @@ export default function SaveExportOverlay({
return ( return (
<div className={className}> <div className={className}>
<div <div
className={`flex justify-center px-2 gap-2 items-center pointer-events-auto rounded-lg ${ className={`pointer-events-auto flex items-center justify-center gap-2 rounded-lg px-2 ${
show ? "animate-in slide-in-from-top duration-500" : "invisible" show ? "duration-500 animate-in slide-in-from-top" : "invisible"
} text-center mt-5 mx-auto`} } mx-auto mt-5 text-center`}
> >
<Button <Button
className="flex items-center gap-1" className="flex items-center gap-1"

View File

@ -84,12 +84,12 @@ export default function TimelineEventOverlay({
}} }}
> >
{timeline.class_type == "entered_zone" ? ( {timeline.class_type == "entered_zone" ? (
<div className="absolute w-2 h-2 bg-yellow-500 left-[50%] -translate-x-1/2 translate-y-3/4 bottom-0" /> <div className="absolute bottom-0 left-[50%] h-2 w-2 -translate-x-1/2 translate-y-3/4 bg-yellow-500" />
) : null} ) : null}
</div> </div>
{isHovering && ( {isHovering && (
<div <div
className="absolute bg-white dark:bg-slate-800 p-4 block text-black dark:text-white text-lg" className="absolute block bg-white p-4 text-lg text-black dark:bg-slate-800 dark:text-white"
style={getHoverStyle()} style={getHoverStyle()}
> >
<div>{`Area: ${getObjectArea()} px`}</div> <div>{`Area: ${getObjectArea()} px`}</div>

View File

@ -33,7 +33,7 @@ export default function VainfoDialog({
<DialogTitle>Vainfo Output</DialogTitle> <DialogTitle>Vainfo Output</DialogTitle>
</DialogHeader> </DialogHeader>
{vainfo ? ( {vainfo ? (
<div className="mb-2 max-h-96 whitespace-pre-line overflow-y-scroll"> <div className="mb-2 max-h-96 overflow-y-scroll whitespace-pre-line">
<div>Return Code: {vainfo.return_code}</div> <div>Return Code: {vainfo.return_code}</div>
<br /> <br />
<div>Process {vainfo.return_code == 0 ? "Output" : "Error"}:</div> <div>Process {vainfo.return_code == 0 ? "Output" : "Error"}:</div>

View File

@ -23,7 +23,7 @@ export default function BirdseyeLivePlayer({
if (liveMode == "webrtc") { if (liveMode == "webrtc") {
player = ( player = (
<WebRtcPlayer <WebRtcPlayer
className={`rounded-lg md:rounded-2xl size-full`} className={`size-full rounded-lg md:rounded-2xl`}
camera="birdseye" camera="birdseye"
/> />
); );
@ -31,7 +31,7 @@ export default function BirdseyeLivePlayer({
if ("MediaSource" in window || "ManagedMediaSource" in window) { if ("MediaSource" in window || "ManagedMediaSource" in window) {
player = ( player = (
<MSEPlayer <MSEPlayer
className={`rounded-lg md:rounded-2xl size-full`} className={`size-full rounded-lg md:rounded-2xl`}
camera="birdseye" camera="birdseye"
/> />
); );
@ -46,7 +46,7 @@ export default function BirdseyeLivePlayer({
} else if (liveMode == "jsmpeg") { } else if (liveMode == "jsmpeg") {
player = ( player = (
<JSMpegPlayer <JSMpegPlayer
className="size-full flex justify-center rounded-lg md:rounded-2xl overflow-hidden" className="flex size-full justify-center overflow-hidden rounded-lg md:rounded-2xl"
camera="birdseye" camera="birdseye"
width={birdseyeConfig.width} width={birdseyeConfig.width}
height={birdseyeConfig.height} height={birdseyeConfig.height}
@ -59,13 +59,13 @@ export default function BirdseyeLivePlayer({
return ( return (
<div <div
className={cn( className={cn(
"relative flex justify-center w-full cursor-pointer", "relative flex w-full cursor-pointer justify-center",
className, className,
)} )}
onClick={onClick} onClick={onClick}
> >
<div className="absolute top-0 inset-x-0 rounded-lg md:rounded-2xl z-10 w-full h-[30%] bg-gradient-to-b from-black/20 to-transparent pointer-events-none"></div> <div className="pointer-events-none absolute inset-x-0 top-0 z-10 h-[30%] w-full rounded-lg bg-gradient-to-b from-black/20 to-transparent md:rounded-2xl"></div>
<div className="absolute bottom-0 inset-x-0 rounded-lg md:rounded-2xl z-10 w-full h-[10%] bg-gradient-to-t from-black/20 to-transparent pointer-events-none"></div> <div className="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-[10%] w-full rounded-lg bg-gradient-to-t from-black/20 to-transparent md:rounded-2xl"></div>
<div className="size-full">{player}</div> <div className="size-full">{player}</div>
</div> </div>
); );

View File

@ -153,7 +153,7 @@ export default function HlsVideoPlayer({
return ( return (
<TransformWrapper minScale={1.0}> <TransformWrapper minScale={1.0}>
<VideoControls <VideoControls
className="absolute bottom-5 left-1/2 -translate-x-1/2 z-50" className="absolute bottom-5 left-1/2 z-50 -translate-x-1/2"
video={videoRef.current} video={videoRef.current}
isPlaying={isPlaying} isPlaying={isPlaying}
show={visible && (controls || controlsOpen)} show={visible && (controls || controlsOpen)}
@ -231,7 +231,7 @@ export default function HlsVideoPlayer({
> >
<video <video
ref={videoRef} ref={videoRef}
className={`size-full bg-black rounded-lg md:rounded-2xl ${loadedMetadata ? "" : "invisible"}`} className={`size-full rounded-lg bg-black md:rounded-2xl ${loadedMetadata ? "" : "invisible"}`}
preload="auto" preload="auto"
autoPlay autoPlay
controls={false} controls={false}

View File

@ -103,7 +103,7 @@ export default function LivePlayer({
if (liveMode == "webrtc") { if (liveMode == "webrtc") {
player = ( player = (
<WebRtcPlayer <WebRtcPlayer
className={`rounded-lg md:rounded-2xl size-full ${liveReady ? "" : "hidden"}`} className={`size-full rounded-lg md:rounded-2xl ${liveReady ? "" : "hidden"}`}
camera={cameraConfig.live.stream_name} camera={cameraConfig.live.stream_name}
playbackEnabled={cameraActive} playbackEnabled={cameraActive}
audioEnabled={playAudio} audioEnabled={playAudio}
@ -117,7 +117,7 @@ export default function LivePlayer({
if ("MediaSource" in window || "ManagedMediaSource" in window) { if ("MediaSource" in window || "ManagedMediaSource" in window) {
player = ( player = (
<MSEPlayer <MSEPlayer
className={`rounded-lg md:rounded-2xl size-full ${liveReady ? "" : "hidden"}`} className={`size-full rounded-lg md:rounded-2xl ${liveReady ? "" : "hidden"}`}
camera={cameraConfig.live.stream_name} camera={cameraConfig.live.stream_name}
playbackEnabled={cameraActive} playbackEnabled={cameraActive}
audioEnabled={playAudio} audioEnabled={playAudio}
@ -137,7 +137,7 @@ export default function LivePlayer({
} else if (liveMode == "jsmpeg") { } else if (liveMode == "jsmpeg") {
player = ( player = (
<JSMpegPlayer <JSMpegPlayer
className="size-full flex justify-center rounded-lg md:rounded-2xl overflow-hidden" className="flex size-full justify-center overflow-hidden rounded-lg md:rounded-2xl"
camera={cameraConfig.live.stream_name} camera={cameraConfig.live.stream_name}
width={cameraConfig.detect.width} width={cameraConfig.detect.width}
height={cameraConfig.detect.height} height={cameraConfig.detect.height}
@ -154,17 +154,17 @@ export default function LivePlayer({
className={cn( className={cn(
"relative flex justify-center", "relative flex justify-center",
liveMode === "jsmpeg" ? "size-full" : "w-full", liveMode === "jsmpeg" ? "size-full" : "w-full",
"outline cursor-pointer", "cursor-pointer outline",
activeTracking activeTracking
? "outline-severity_alert outline-3 rounded-lg md:rounded-2xl shadow-severity_alert" ? "outline-3 rounded-lg shadow-severity_alert outline-severity_alert md:rounded-2xl"
: "outline-0 outline-background", : "outline-0 outline-background",
"transition-all duration-500", "transition-all duration-500",
className, className,
)} )}
onClick={onClick} onClick={onClick}
> >
<div className="absolute top-0 inset-x-0 rounded-lg md:rounded-2xl z-10 w-full h-[30%] bg-gradient-to-b from-black/20 to-transparent pointer-events-none"></div> <div className="pointer-events-none absolute inset-x-0 top-0 z-10 h-[30%] w-full rounded-lg bg-gradient-to-b from-black/20 to-transparent md:rounded-2xl"></div>
<div className="absolute bottom-0 inset-x-0 rounded-lg md:rounded-2xl z-10 w-full h-[10%] bg-gradient-to-t from-black/20 to-transparent pointer-events-none"></div> <div className="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-[10%] w-full rounded-lg bg-gradient-to-t from-black/20 to-transparent md:rounded-2xl"></div>
{player} {player}
{objects.length > 0 && ( {objects.length > 0 && (
@ -172,9 +172,9 @@ export default function LivePlayer({
<Tooltip> <Tooltip>
<div className="flex"> <div className="flex">
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div className="mx-3 pb-1 text-white text-sm"> <div className="mx-3 pb-1 text-sm text-white">
<Chip <Chip
className={`flex items-start justify-between space-x-1 bg-gradient-to-br from-gray-400 to-gray-500 bg-gray-500 z-0`} className={`z-0 flex items-start justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500`}
> >
{[ {[
...new Set([ ...new Set([
@ -226,7 +226,7 @@ export default function LivePlayer({
<div className="absolute right-2 top-2 size-4"> <div className="absolute right-2 top-2 size-4">
{activeMotion && ( {activeMotion && (
<MdCircle className="size-2 drop-shadow-md shadow-danger text-danger animate-pulse" /> <MdCircle className="size-2 animate-pulse text-danger shadow-danger drop-shadow-md" />
)} )}
</div> </div>
</div> </div>

View File

@ -240,7 +240,7 @@ function PreviewVideoPlayer({
return ( return (
<div <div
className={cn( className={cn(
"relative rounded-lg md:rounded-2xl w-full flex justify-center bg-black overflow-hidden", "relative flex w-full justify-center overflow-hidden rounded-lg bg-black md:rounded-2xl",
onClick && "cursor-pointer", onClick && "cursor-pointer",
className, className,
)} )}
@ -288,11 +288,11 @@ function PreviewVideoPlayer({
)} )}
</video> </video>
{cameraPreviews && !currentPreview && ( {cameraPreviews && !currentPreview && (
<div className="absolute inset-0 text-white rounded-lg md:rounded-2xl flex justify-center items-center"> <div className="absolute inset-0 flex items-center justify-center rounded-lg text-white md:rounded-2xl">
No Preview Found No Preview Found
</div> </div>
)} )}
{firstLoad && <Skeleton className="absolute size-full aspect-video" />} {firstLoad && <Skeleton className="absolute aspect-video size-full" />}
</div> </div>
); );
} }
@ -482,7 +482,7 @@ function PreviewFramesPlayer({
return ( return (
<div <div
className={cn( className={cn(
"relative w-full flex justify-center", "relative flex w-full justify-center",
className, className,
onClick && "cursor-pointer", onClick && "cursor-pointer",
)} )}
@ -490,15 +490,15 @@ function PreviewFramesPlayer({
> >
<img <img
ref={imgRef} ref={imgRef}
className={`size-full object-contain rounded-lg md:rounded-2xl bg-black`} className={`size-full rounded-lg bg-black object-contain md:rounded-2xl`}
onLoad={onImageLoaded} onLoad={onImageLoaded}
/> />
{previewFrames?.length === 0 && ( {previewFrames?.length === 0 && (
<div className="absolute inset-x-0 top-1/2 -y-translate-1/2 bg-black text-white rounded-lg md:rounded-2xl align-center text-center"> <div className="-y-translate-1/2 align-center absolute inset-x-0 top-1/2 rounded-lg bg-black text-center text-white md:rounded-2xl">
No Preview Found No Preview Found
</div> </div>
)} )}
{firstLoad && <Skeleton className="absolute size-full aspect-video" />} {firstLoad && <Skeleton className="absolute aspect-video size-full" />}
</div> </div>
); );
} }

View File

@ -207,7 +207,7 @@ export default function PreviewThumbnailPlayer({
<div className={`${imgLoaded ? "visible" : "invisible"}`}> <div className={`${imgLoaded ? "visible" : "invisible"}`}>
<img <img
ref={imgRef} ref={imgRef}
className={`size-full transition-opacity select-none ${ className={`size-full select-none transition-opacity ${
playingBack ? "opacity-0" : "opacity-100" playingBack ? "opacity-0" : "opacity-100"
}`} }`}
style={ style={
@ -234,12 +234,12 @@ export default function PreviewThumbnailPlayer({
onMouseLeave={() => setTooltipHovering(false)} onMouseLeave={() => setTooltipHovering(false)}
> >
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div className="mx-3 pb-1 text-white text-sm"> <div className="mx-3 pb-1 text-sm text-white">
{(review.severity == "alert" || {(review.severity == "alert" ||
review.severity == "detection") && ( review.severity == "detection") && (
<> <>
<Chip <Chip
className={`flex items-start justify-between space-x-1 ${playingBack ? "hidden" : ""} bg-gradient-to-br ${review.has_been_reviewed ? "from-green-600 to-green-700 bg-green-600" : "from-gray-400 to-gray-500 bg-gray-500"} z-0`} className={`flex items-start justify-between space-x-1 ${playingBack ? "hidden" : ""} bg-gradient-to-br ${review.has_been_reviewed ? "bg-green-600 from-green-600 to-green-700" : "bg-gray-500 from-gray-400 to-gray-500"} z-0`}
> >
{review.data.objects.sort().map((object) => { {review.data.objects.sort().map((object) => {
return getIconForLabel(object, "size-3 text-white"); return getIconForLabel(object, "size-3 text-white");
@ -273,9 +273,9 @@ export default function PreviewThumbnailPlayer({
</div> </div>
{!playingBack && ( {!playingBack && (
<> <>
<div className="absolute top-0 inset-x-0 rounded-t-l z-10 w-full h-[30%] bg-gradient-to-b from-black/60 to-transparent pointer-events-none"></div> <div className="rounded-t-l pointer-events-none absolute inset-x-0 top-0 z-10 h-[30%] w-full bg-gradient-to-b from-black/60 to-transparent"></div>
<div className="absolute bottom-0 inset-x-0 rounded-b-l z-10 w-full h-[20%] bg-gradient-to-t from-black/60 to-transparent pointer-events-none"> <div className="rounded-b-l pointer-events-none absolute inset-x-0 bottom-0 z-10 h-[20%] w-full bg-gradient-to-t from-black/60 to-transparent">
<div className="flex h-full justify-between items-end mx-3 pb-1 text-white text-sm"> <div className="mx-3 flex h-full items-end justify-between pb-1 text-sm text-white">
{review.end_time ? ( {review.end_time ? (
<TimeAgo time={review.start_time * 1000} dense /> <TimeAgo time={review.start_time * 1000} dense />
) : ( ) : (
@ -557,10 +557,10 @@ export function VideoPreview({
); );
return ( return (
<div className="relative size-full aspect-video bg-black"> <div className="relative aspect-video size-full bg-black">
<video <video
ref={playerRef} ref={playerRef}
className="size-full aspect-video bg-black pointer-events-none" className="pointer-events-none aspect-video size-full bg-black"
autoPlay autoPlay
playsInline playsInline
preload="auto" preload="auto"
@ -738,9 +738,9 @@ export function InProgressPreview({
} }
return ( return (
<div className="relative size-full flex items-center bg-black"> <div className="relative flex size-full items-center bg-black">
<img <img
className="size-full object-contain pointer-events-none" className="pointer-events-none size-full object-contain"
src={`${apiHost}api/preview/${previewFrames[key]}/thumbnail.webp`} src={`${apiHost}api/preview/${previewFrames[key]}/thumbnail.webp`}
onLoad={handleLoad} onLoad={handleLoad}
/> />

View File

@ -171,16 +171,16 @@ export default function VideoControls({
return ( return (
<div <div
className={cn( className={cn(
"w-auto px-4 py-2 flex sm:flex-nowrap justify-between items-center gap-4 sm:gap-8 text-primary z-50 bg-background/60 rounded-lg", "z-50 flex w-auto items-center justify-between gap-4 rounded-lg bg-background/60 px-4 py-2 text-primary sm:flex-nowrap sm:gap-8",
className, className,
isMobileOnly && isMobileOnly &&
Object.values(features).filter((feat) => feat).length > Object.values(features).filter((feat) => feat).length >
MIN_ITEMS_WRAP && MIN_ITEMS_WRAP &&
"flex-wrap min-w-[75%]", "min-w-[75%] flex-wrap",
)} )}
> >
{video && features.volume && ( {video && features.volume && (
<div className="flex justify-normal items-center gap-2 cursor-pointer"> <div className="flex cursor-pointer items-center justify-normal gap-2">
<VolumeIcon <VolumeIcon
className="size-5" className="size-5"
onClick={(e: React.MouseEvent) => { onClick={(e: React.MouseEvent) => {
@ -208,9 +208,9 @@ export default function VideoControls({
)} )}
<div className="cursor-pointer" onClick={onTogglePlay}> <div className="cursor-pointer" onClick={onTogglePlay}>
{isPlaying ? ( {isPlaying ? (
<LuPause className="size-5 text-primary fill-primary" /> <LuPause className="size-5 fill-primary text-primary" />
) : ( ) : (
<LuPlay className="size-5 text-primary fill-primary" /> <LuPlay className="size-5 fill-primary text-primary" />
)} )}
</div> </div>
{features.seek && ( {features.seek && (

View File

@ -53,18 +53,18 @@ export default function General() {
return ( return (
<> <>
<div className="flex flex-col md:flex-row size-full"> <div className="flex size-full flex-col md:flex-row">
<Toaster position="top-center" closeButton={true} /> <Toaster position="top-center" closeButton={true} />
<div className="flex flex-col h-full w-full overflow-y-auto mt-2 md:mt-0 mb-10 md:mb-0 order-last md:order-none md:mr-2 rounded-lg border-secondary-foreground border-[1px] p-2 bg-background_alt"> <div className="order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0">
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
General Settings General Settings
</Heading> </Heading>
<div className="flex flex-col w-full space-y-6"> <div className="flex w-full flex-col space-y-6">
<div className="mt-2 space-y-6"> <div className="mt-2 space-y-6">
<div className="space-y-0.5"> <div className="space-y-0.5">
<div className="text-md">Stored Layouts</div> <div className="text-md">Stored Layouts</div>
<div className="text-sm text-muted-foreground my-2"> <div className="my-2 text-sm text-muted-foreground">
<p> <p>
The layout of cameras in a camera group can be The layout of cameras in a camera group can be
dragged/resized. The positions are stored in your browser's dragged/resized. The positions are stored in your browser's
@ -72,11 +72,11 @@ export default function General() {
</p> </p>
</div> </div>
</div> </div>
<div className="flex flex-row justify-start items-center gap-2"> <div className="flex flex-row items-center justify-start gap-2">
<Button onClick={clearStoredLayouts}>Clear All Layouts</Button> <Button onClick={clearStoredLayouts}>Clear All Layouts</Button>
</div> </div>
</div> </div>
<Separator className="flex my-2 bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<div className="mt-2 space-y-6"> <div className="mt-2 space-y-6">
<div className="space-y-0.5"> <div className="space-y-0.5">
<div className="text-md">Default Playback Rate</div> <div className="text-md">Default Playback Rate</div>
@ -110,13 +110,13 @@ export default function General() {
<div className="mt-2 space-y-6"> <div className="mt-2 space-y-6">
<div className="space-y-0.5"> <div className="space-y-0.5">
<div className="text-md">Low Data Mode</div> <div className="text-md">Low Data Mode</div>
<div className="text-sm text-muted-foreground my-2"> <div className="my-2 text-sm text-muted-foreground">
<p> <p>
Not yet implemented. <em>Default: disabled</em> Not yet implemented. <em>Default: disabled</em>
</p> </p>
</div> </div>
</div> </div>
<div className="flex flex-row justify-start items-center gap-2"> <div className="flex flex-row items-center justify-start gap-2">
<Switch <Switch
id="lowdata" id="lowdata"
checked={false} checked={false}

View File

@ -370,9 +370,9 @@ export default function MasksAndZones({
return ( return (
<> <>
{cameraConfig && editingPolygons && ( {cameraConfig && editingPolygons && (
<div className="flex flex-col md:flex-row size-full"> <div className="flex size-full flex-col md:flex-row">
<Toaster position="top-center" closeButton={true} /> <Toaster position="top-center" closeButton={true} />
<div className="flex flex-col h-full w-full overflow-y-auto mt-2 md:mt-0 mb-10 md:mb-0 md:w-3/12 order-last md:order-none md:mr-2 rounded-lg border-secondary-foreground border-[1px] p-2 bg-background_alt"> <div className="order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0 md:w-3/12">
{editPane == "zone" && ( {editPane == "zone" && (
<ZoneEditPane <ZoneEditPane
polygons={editingPolygons} polygons={editingPolygons}
@ -417,17 +417,17 @@ export default function MasksAndZones({
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
Masks / Zones Masks / Zones
</Heading> </Heading>
<div className="flex flex-col w-full"> <div className="flex w-full flex-col">
{(selectedZoneMask === undefined || {(selectedZoneMask === undefined ||
selectedZoneMask.includes("zone" as PolygonType)) && ( selectedZoneMask.includes("zone" as PolygonType)) && (
<div className="mt-0 pt-0 last:pb-3 last:border-b-[1px] last:border-secondary"> <div className="mt-0 pt-0 last:border-b-[1px] last:border-secondary last:pb-3">
<div className="flex flex-row justify-between items-center my-3"> <div className="my-3 flex flex-row items-center justify-between">
<HoverCard> <HoverCard>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<div className="text-md cursor-default">Zones</div> <div className="text-md cursor-default">Zones</div>
</HoverCardTrigger> </HoverCardTrigger>
<HoverCardContent> <HoverCardContent>
<div className="flex flex-col gap-2 text-sm text-primary-variant my-2"> <div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
<p> <p>
Zones allow you to define a specific area of the Zones allow you to define a specific area of the
frame so you can determine whether or not an frame so you can determine whether or not an
@ -441,7 +441,7 @@ export default function MasksAndZones({
className="inline" className="inline"
> >
Documentation{" "} Documentation{" "}
<LuExternalLink className="size-3 ml-2 inline-flex" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
</div> </div>
@ -451,7 +451,7 @@ export default function MasksAndZones({
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
className="size-6 p-1 rounded-md text-background bg-secondary-foreground" className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
onClick={() => { onClick={() => {
setEditPane("zone"); setEditPane("zone");
handleNewPolygon("zone"); handleNewPolygon("zone");
@ -485,8 +485,8 @@ export default function MasksAndZones({
selectedZoneMask.includes( selectedZoneMask.includes(
"motion_mask" as PolygonType, "motion_mask" as PolygonType,
)) && ( )) && (
<div className="first:mt-0 mt-3 first:pt-0 pt-3 last:pb-3 border-t-[1px] last:border-b-[1px] first:border-transparent border-secondary"> <div className="mt-3 border-t-[1px] border-secondary pt-3 first:mt-0 first:border-transparent first:pt-0 last:border-b-[1px] last:pb-3">
<div className="flex flex-row justify-between items-center my-3"> <div className="my-3 flex flex-row items-center justify-between">
<HoverCard> <HoverCard>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<div className="text-md cursor-default"> <div className="text-md cursor-default">
@ -494,7 +494,7 @@ export default function MasksAndZones({
</div> </div>
</HoverCardTrigger> </HoverCardTrigger>
<HoverCardContent> <HoverCardContent>
<div className="flex flex-col gap-2 text-sm text-primary-variant my-2"> <div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
<p> <p>
Motion masks are used to prevent unwanted types Motion masks are used to prevent unwanted types
of motion from triggering detection. Over of motion from triggering detection. Over
@ -509,7 +509,7 @@ export default function MasksAndZones({
className="inline" className="inline"
> >
Documentation{" "} Documentation{" "}
<LuExternalLink className="size-3 ml-2 inline-flex" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
</div> </div>
@ -519,7 +519,7 @@ export default function MasksAndZones({
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
className="size-6 p-1 rounded-md text-background bg-secondary-foreground" className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
onClick={() => { onClick={() => {
setEditPane("motion_mask"); setEditPane("motion_mask");
handleNewPolygon("motion_mask"); handleNewPolygon("motion_mask");
@ -555,8 +555,8 @@ export default function MasksAndZones({
selectedZoneMask.includes( selectedZoneMask.includes(
"object_mask" as PolygonType, "object_mask" as PolygonType,
)) && ( )) && (
<div className="first:mt-0 mt-3 first:pt-0 pt-3 last:pb-3 border-t-[1px] last:border-b-[1px] first:border-transparent border-secondary"> <div className="mt-3 border-t-[1px] border-secondary pt-3 first:mt-0 first:border-transparent first:pt-0 last:border-b-[1px] last:pb-3">
<div className="flex flex-row justify-between items-center my-3"> <div className="my-3 flex flex-row items-center justify-between">
<HoverCard> <HoverCard>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<div className="text-md cursor-default"> <div className="text-md cursor-default">
@ -564,7 +564,7 @@ export default function MasksAndZones({
</div> </div>
</HoverCardTrigger> </HoverCardTrigger>
<HoverCardContent> <HoverCardContent>
<div className="flex flex-col gap-2 text-sm text-primary-variant my-2"> <div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
<p> <p>
Object filter masks are used to filter out false Object filter masks are used to filter out false
positives for a given object type based on positives for a given object type based on
@ -578,7 +578,7 @@ export default function MasksAndZones({
className="inline" className="inline"
> >
Documentation{" "} Documentation{" "}
<LuExternalLink className="size-3 ml-2 inline-flex" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
</div> </div>
@ -588,7 +588,7 @@ export default function MasksAndZones({
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
variant="secondary" variant="secondary"
className="size-6 p-1 rounded-md text-background bg-secondary-foreground" className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
onClick={() => { onClick={() => {
setEditPane("object_mask"); setEditPane("object_mask");
handleNewPolygon("object_mask"); handleNewPolygon("object_mask");
@ -626,9 +626,9 @@ export default function MasksAndZones({
</div> </div>
<div <div
ref={containerRef} ref={containerRef}
className="flex md:w-7/12 md:grow md:h-dvh max-h-[50%] md:max-h-full" className="flex max-h-[50%] md:h-dvh md:max-h-full md:w-7/12 md:grow"
> >
<div className="flex flex-row justify-center mx-auto size-full"> <div className="mx-auto flex size-full flex-row justify-center">
{cameraConfig && {cameraConfig &&
scaledWidth && scaledWidth &&
scaledHeight && scaledHeight &&

View File

@ -187,7 +187,7 @@ export default function MotionMaskEditPane({
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
{polygon.name.length ? "Edit" : "New"} Motion Mask {polygon.name.length ? "Edit" : "New"} Motion Mask
</Heading> </Heading>
<div className="text-sm text-muted-foreground my-2"> <div className="my-2 text-sm text-muted-foreground">
<p> <p>
Motion masks are used to prevent unwanted types of motion from Motion masks are used to prevent unwanted types of motion from
triggering detection. Over masking will make it more difficult for triggering detection. Over masking will make it more difficult for
@ -196,7 +196,7 @@ export default function MotionMaskEditPane({
</div> </div>
<Separator className="my-3 bg-secondary" /> <Separator className="my-3 bg-secondary" />
{polygons && activePolygonIndex !== undefined && ( {polygons && activePolygonIndex !== undefined && (
<div className="flex flex-row my-2 text-sm w-full justify-between"> <div className="my-2 flex w-full flex-row justify-between text-sm">
<div className="my-1 inline-flex"> <div className="my-1 inline-flex">
{polygons[activePolygonIndex].points.length}{" "} {polygons[activePolygonIndex].points.length}{" "}
{polygons[activePolygonIndex].points.length > 1 || {polygons[activePolygonIndex].points.length > 1 ||
@ -223,7 +223,7 @@ export default function MotionMaskEditPane({
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6 flex flex-col flex-1" className="flex flex-1 flex-col space-y-6"
> >
<FormField <FormField
control={form.control} control={form.control}
@ -243,7 +243,7 @@ export default function MotionMaskEditPane({
</FormItem> </FormItem>
)} )}
/> />
<div className="flex flex-col flex-1 justify-end"> <div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5"> <div className="flex flex-row gap-2 pt-5">
<Button className="flex flex-1" onClick={onCancel}> <Button className="flex flex-1" onClick={onCancel}>
Cancel Cancel

View File

@ -173,13 +173,13 @@ export default function MotionTuner({
} }
return ( return (
<div className="flex flex-col md:flex-row size-full"> <div className="flex size-full flex-col md:flex-row">
<Toaster position="top-center" closeButton={true} /> <Toaster position="top-center" closeButton={true} />
<div className="flex flex-col h-full w-full overflow-y-auto mt-2 md:mt-0 mb-10 md:mb-0 md:w-3/12 order-last md:order-none md:mr-2 rounded-lg border-secondary-foreground border-[1px] p-2 bg-background_alt"> <div className="order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0 md:w-3/12">
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
Motion Detection Tuner Motion Detection Tuner
</Heading> </Heading>
<div className="text-sm text-muted-foreground my-3 space-y-3"> <div className="my-3 space-y-3 text-sm text-muted-foreground">
<p> <p>
Frigate uses motion detection as a first line check to see if there Frigate uses motion detection as a first line check to see if there
is anything happening in the frame worth checking with object is anything happening in the frame worth checking with object
@ -194,18 +194,18 @@ export default function MotionTuner({
className="inline" className="inline"
> >
Read the Motion Tuning Guide{" "} Read the Motion Tuning Guide{" "}
<LuExternalLink className="size-3 ml-2 inline-flex" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
</div> </div>
<Separator className="flex my-2 bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<div className="flex flex-col w-full space-y-6"> <div className="flex w-full flex-col space-y-6">
<div className="mt-2 space-y-6"> <div className="mt-2 space-y-6">
<div className="space-y-0.5"> <div className="space-y-0.5">
<Label htmlFor="motion-threshold" className="text-md"> <Label htmlFor="motion-threshold" className="text-md">
Threshold Threshold
</Label> </Label>
<div className="text-sm text-muted-foreground my-2"> <div className="my-2 text-sm text-muted-foreground">
<p> <p>
The threshold value dictates how much of a change in a pixel's The threshold value dictates how much of a change in a pixel's
luminance is required to be considered motion.{" "} luminance is required to be considered motion.{" "}
@ -226,7 +226,7 @@ export default function MotionTuner({
handleMotionConfigChange({ threshold: value[0] }); handleMotionConfigChange({ threshold: value[0] });
}} }}
/> />
<div className="text-lg ml-6 mr-2 flex align-center"> <div className="align-center ml-6 mr-2 flex text-lg">
{motionSettings.threshold} {motionSettings.threshold}
</div> </div>
</div> </div>
@ -236,7 +236,7 @@ export default function MotionTuner({
<Label htmlFor="motion-threshold" className="text-md"> <Label htmlFor="motion-threshold" className="text-md">
Contour Area Contour Area
</Label> </Label>
<div className="text-sm text-muted-foreground my-2"> <div className="my-2 text-sm text-muted-foreground">
<p> <p>
The contour area value is used to decide which groups of The contour area value is used to decide which groups of
changed pixels qualify as motion. <em>Default: 10</em> changed pixels qualify as motion. <em>Default: 10</em>
@ -256,12 +256,12 @@ export default function MotionTuner({
handleMotionConfigChange({ contour_area: value[0] }); handleMotionConfigChange({ contour_area: value[0] });
}} }}
/> />
<div className="text-lg ml-6 mr-2 flex align-center"> <div className="align-center ml-6 mr-2 flex text-lg">
{motionSettings.contour_area} {motionSettings.contour_area}
</div> </div>
</div> </div>
</div> </div>
<Separator className="flex my-2 bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<div className="flex flex-row items-center justify-between"> <div className="flex flex-row items-center justify-between">
<div className="space-y-0.5"> <div className="space-y-0.5">
<Label htmlFor="improve-contrast">Improve Contrast</Label> <Label htmlFor="improve-contrast">Improve Contrast</Label>
@ -280,7 +280,7 @@ export default function MotionTuner({
/> />
</div> </div>
</div> </div>
<div className="flex flex-col flex-1 justify-end"> <div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5"> <div className="flex flex-row gap-2 pt-5">
<Button className="flex flex-1" onClick={onCancel}> <Button className="flex flex-1" onClick={onCancel}>
Reset Reset
@ -305,7 +305,7 @@ export default function MotionTuner({
</div> </div>
{cameraConfig ? ( {cameraConfig ? (
<div className="flex md:w-7/12 md:grow md:h-dvh md:max-h-full"> <div className="flex md:h-dvh md:max-h-full md:w-7/12 md:grow">
<div className="size-full min-h-10"> <div className="size-full min-h-10">
<AutoUpdatingCameraImage <AutoUpdatingCameraImage
camera={cameraConfig.name} camera={cameraConfig.name}

View File

@ -249,7 +249,7 @@ export default function ObjectMaskEditPane({
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
{polygon.name.length ? "Edit" : "New"} Object Mask {polygon.name.length ? "Edit" : "New"} Object Mask
</Heading> </Heading>
<div className="text-sm text-muted-foreground my-2"> <div className="my-2 text-sm text-muted-foreground">
<p> <p>
Object filter masks are used to filter out false positives for a given Object filter masks are used to filter out false positives for a given
object type based on location. object type based on location.
@ -257,7 +257,7 @@ export default function ObjectMaskEditPane({
</div> </div>
<Separator className="my-3 bg-secondary" /> <Separator className="my-3 bg-secondary" />
{polygons && activePolygonIndex !== undefined && ( {polygons && activePolygonIndex !== undefined && (
<div className="flex flex-row my-2 text-sm w-full justify-between"> <div className="my-2 flex w-full flex-row justify-between text-sm">
<div className="my-1 inline-flex"> <div className="my-1 inline-flex">
{polygons[activePolygonIndex].points.length}{" "} {polygons[activePolygonIndex].points.length}{" "}
{polygons[activePolygonIndex].points.length > 1 || {polygons[activePolygonIndex].points.length > 1 ||
@ -284,7 +284,7 @@ export default function ObjectMaskEditPane({
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6 flex flex-col flex-1" className="flex flex-1 flex-col space-y-6"
> >
<div> <div>
<FormField <FormField
@ -332,7 +332,7 @@ export default function ObjectMaskEditPane({
)} )}
/> />
</div> </div>
<div className="flex flex-col flex-1 justify-end"> <div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5"> <div className="flex flex-row gap-2 pt-5">
<Button className="flex flex-1" onClick={onCancel}> <Button className="flex flex-1" onClick={onCancel}>
Cancel Cancel

View File

@ -111,13 +111,13 @@ export default function ObjectSettings({
} }
return ( return (
<div className="flex flex-col md:flex-row size-full"> <div className="flex size-full flex-col md:flex-row">
<Toaster position="top-center" closeButton={true} /> <Toaster position="top-center" closeButton={true} />
<div className="flex flex-col h-full w-full overflow-y-auto mt-2 md:mt-0 mb-10 md:mb-0 md:w-3/12 order-last md:order-none md:mr-2 rounded-lg border-secondary-foreground border-[1px] p-2 bg-background_alt"> <div className="order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0 md:w-3/12">
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
Debug Debug
</Heading> </Heading>
<div className="text-sm text-muted-foreground mb-5 space-y-3"> <div className="mb-5 space-y-3 text-sm text-muted-foreground">
<p> <p>
Frigate uses your detectors{" "} Frigate uses your detectors{" "}
{config {config
@ -142,17 +142,17 @@ export default function ObjectSettings({
<TabsTrigger value="objectlist">Object List</TabsTrigger> <TabsTrigger value="objectlist">Object List</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="debug"> <TabsContent value="debug">
<div className="flex flex-col w-full space-y-6"> <div className="flex w-full flex-col space-y-6">
<div className="mt-2 space-y-6"> <div className="mt-2 space-y-6">
<div className="my-2.5 flex flex-col gap-2.5"> <div className="my-2.5 flex flex-col gap-2.5">
{DEBUG_OPTIONS.map(({ param, title, description }) => ( {DEBUG_OPTIONS.map(({ param, title, description }) => (
<div <div
key={param} key={param}
className="flex flex-row w-full justify-between items-center" className="flex w-full flex-row items-center justify-between"
> >
<div className="flex flex-col mb-2"> <div className="mb-2 flex flex-col">
<Label <Label
className="w-full text-primary capitalize cursor-pointer mb-2" className="mb-2 w-full cursor-pointer capitalize text-primary"
htmlFor={param} htmlFor={param}
> >
{title} {title}
@ -183,7 +183,7 @@ export default function ObjectSettings({
</div> </div>
{cameraConfig ? ( {cameraConfig ? (
<div className="flex md:w-7/12 md:grow md:h-dvh md:max-h-full"> <div className="flex md:h-dvh md:max-h-full md:w-7/12 md:grow">
<div className="size-full min-h-10"> <div className="size-full min-h-10">
<AutoUpdatingCameraImage <AutoUpdatingCameraImage
camera={cameraConfig.name} camera={cameraConfig.name}
@ -222,15 +222,15 @@ function ObjectList(objects?: ObjectType[]) {
); );
return ( return (
<div className="flex flex-col w-full overflow-y-auto"> <div className="flex w-full flex-col overflow-y-auto">
{objects && objects.length > 0 ? ( {objects && objects.length > 0 ? (
objects.map((obj) => { objects.map((obj) => {
return ( return (
<Card className="text-sm p-2 mb-1" key={obj.id}> <Card className="mb-1 p-2 text-sm" key={obj.id}>
<div className="flex flex-row items-center gap-3 pb-1"> <div className="flex flex-row items-center gap-3 pb-1">
<div className="flex flex-row flex-1 items-center justify-start p-3 pl-1"> <div className="flex flex-1 flex-row items-center justify-start p-3 pl-1">
<div <div
className="p-2 rounded-lg" className="rounded-lg p-2"
style={{ style={{
backgroundColor: obj.stationary backgroundColor: obj.stationary
? "rgb(110,110,110)" ? "rgb(110,110,110)"
@ -243,10 +243,10 @@ function ObjectList(objects?: ObjectType[]) {
{capitalizeFirstLetter(obj.label)} {capitalizeFirstLetter(obj.label)}
</div> </div>
</div> </div>
<div className="flex flex-row w-8/12 items-end justify-end"> <div className="flex w-8/12 flex-row items-end justify-end">
<div className="mr-2 text-md w-1/3"> <div className="text-md mr-2 w-1/3">
<div className="flex flex-col items-end justify-end"> <div className="flex flex-col items-end justify-end">
<p className="text-sm mb-1.5 text-primary-variant"> <p className="mb-1.5 text-sm text-primary-variant">
Score Score
</p> </p>
{obj.score {obj.score
@ -255,17 +255,17 @@ function ObjectList(objects?: ObjectType[]) {
% %
</div> </div>
</div> </div>
<div className="mr-2 text-md w-1/3"> <div className="text-md mr-2 w-1/3">
<div className="flex flex-col items-end justify-end"> <div className="flex flex-col items-end justify-end">
<p className="text-sm mb-1.5 text-primary-variant"> <p className="mb-1.5 text-sm text-primary-variant">
Ratio Ratio
</p> </p>
{obj.ratio ? obj.ratio.toFixed(2).toString() : "-"} {obj.ratio ? obj.ratio.toFixed(2).toString() : "-"}
</div> </div>
</div> </div>
<div className="mr-2 text-md w-1/3"> <div className="text-md mr-2 w-1/3">
<div className="flex flex-col items-end justify-end"> <div className="flex flex-col items-end justify-end">
<p className="text-sm mb-1.5 text-primary-variant"> <p className="mb-1.5 text-sm text-primary-variant">
Area Area
</p> </p>
{obj.area ? obj.area.toString() : "-"} {obj.area ? obj.area.toString() : "-"}

View File

@ -73,7 +73,7 @@ export default function PolygonEditControls({
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
variant="default" variant="default"
className="size-6 p-1 rounded-md" className="size-6 rounded-md p-1"
disabled={!polygons[activePolygonIndex].points.length} disabled={!polygons[activePolygonIndex].points.length}
onClick={undo} onClick={undo}
> >
@ -86,7 +86,7 @@ export default function PolygonEditControls({
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
variant="default" variant="default"
className="size-6 p-1 rounded-md" className="size-6 rounded-md p-1"
disabled={!polygons[activePolygonIndex].points.length} disabled={!polygons[activePolygonIndex].points.length}
onClick={reset} onClick={reset}
> >

View File

@ -206,7 +206,7 @@ export default function PolygonItem({
<div <div
key={index} key={index}
className="flex p-1 rounded-lg flex-row items-center justify-between my-1.5 transition-background duration-100" className="transition-background my-1.5 flex flex-row items-center justify-between rounded-lg p-1 duration-100"
data-index={index} data-index={index}
onMouseEnter={() => setHoveredPolygonIndex(index)} onMouseEnter={() => setHoveredPolygonIndex(index)}
onMouseLeave={() => setHoveredPolygonIndex(null)} onMouseLeave={() => setHoveredPolygonIndex(null)}
@ -226,7 +226,7 @@ export default function PolygonItem({
> >
{PolygonItemIcon && ( {PolygonItemIcon && (
<PolygonItemIcon <PolygonItemIcon
className="size-5 mr-2" className="mr-2 size-5"
style={{ style={{
fill: toRGBColorString(polygon.color, true), fill: toRGBColorString(polygon.color, true),
color: toRGBColorString(polygon.color, true), color: toRGBColorString(polygon.color, true),
@ -285,7 +285,7 @@ export default function PolygonItem({
</> </>
)} )}
{!isMobile && hoveredPolygonIndex === index && ( {!isMobile && hoveredPolygonIndex === index && (
<div className="flex flex-row gap-2 items-center"> <div className="flex flex-row items-center gap-2">
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<IconWrapper <IconWrapper
@ -319,7 +319,7 @@ export default function PolygonItem({
icon={HiTrash} icon={HiTrash}
className={`size-[15px] cursor-pointer ${ className={`size-[15px] cursor-pointer ${
hoveredPolygonIndex === index && hoveredPolygonIndex === index &&
"text-primary-variant fill-primary-variant" "fill-primary-variant text-primary-variant"
}`} }`}
onClick={() => !isLoading && setDeleteDialogOpen(true)} onClick={() => !isLoading && setDeleteDialogOpen(true)}
/> />

View File

@ -322,7 +322,7 @@ export default function ZoneEditPane({
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
{polygon.name.length ? "Edit" : "New"} Zone {polygon.name.length ? "Edit" : "New"} Zone
</Heading> </Heading>
<div className="text-sm text-muted-foreground my-2"> <div className="my-2 text-sm text-muted-foreground">
<p> <p>
Zones allow you to define a specific area of the frame so you can Zones allow you to define a specific area of the frame so you can
determine whether or not an object is within a particular area. determine whether or not an object is within a particular area.
@ -330,7 +330,7 @@ export default function ZoneEditPane({
</div> </div>
<Separator className="my-3 bg-secondary" /> <Separator className="my-3 bg-secondary" />
{polygons && activePolygonIndex !== undefined && ( {polygons && activePolygonIndex !== undefined && (
<div className="flex flex-row my-2 text-sm w-full justify-between"> <div className="my-2 flex w-full flex-row justify-between text-sm">
<div className="my-1 inline-flex"> <div className="my-1 inline-flex">
{polygons[activePolygonIndex].points.length}{" "} {polygons[activePolygonIndex].points.length}{" "}
{polygons[activePolygonIndex].points.length > 1 || {polygons[activePolygonIndex].points.length > 1 ||
@ -364,7 +364,7 @@ export default function ZoneEditPane({
<FormLabel>Name</FormLabel> <FormLabel>Name</FormLabel>
<FormControl> <FormControl>
<Input <Input
className="w-full p-2 border border-input bg-background hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
placeholder="Enter a name..." placeholder="Enter a name..."
{...field} {...field}
/> />
@ -377,7 +377,7 @@ export default function ZoneEditPane({
</FormItem> </FormItem>
)} )}
/> />
<Separator className="flex my-2 bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<FormField <FormField
control={form.control} control={form.control}
name="inertia" name="inertia"
@ -386,7 +386,7 @@ export default function ZoneEditPane({
<FormLabel>Inertia</FormLabel> <FormLabel>Inertia</FormLabel>
<FormControl> <FormControl>
<Input <Input
className="w-full p-2 border border-input bg-background hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
placeholder="3" placeholder="3"
{...field} {...field}
/> />
@ -399,7 +399,7 @@ export default function ZoneEditPane({
</FormItem> </FormItem>
)} )}
/> />
<Separator className="flex my-2 bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<FormField <FormField
control={form.control} control={form.control}
name="loitering_time" name="loitering_time"
@ -408,7 +408,7 @@ export default function ZoneEditPane({
<FormLabel>Loitering Time</FormLabel> <FormLabel>Loitering Time</FormLabel>
<FormControl> <FormControl>
<Input <Input
className="w-full p-2 border border-input bg-background hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
placeholder="0" placeholder="0"
{...field} {...field}
/> />
@ -421,7 +421,7 @@ export default function ZoneEditPane({
</FormItem> </FormItem>
)} )}
/> />
<Separator className="flex my-2 bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<FormItem> <FormItem>
<FormLabel>Objects</FormLabel> <FormLabel>Objects</FormLabel>
<FormDescription> <FormDescription>
@ -446,7 +446,7 @@ export default function ZoneEditPane({
/> />
</FormItem> </FormItem>
<Separator className="flex my-2 bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<FormField <FormField
control={form.control} control={form.control}
@ -585,8 +585,8 @@ export function ZoneObjectSelector({
return ( return (
<> <>
<div className="h-auto overflow-y-auto overflow-x-hidden"> <div className="h-auto overflow-y-auto overflow-x-hidden">
<div className="flex justify-between items-center my-2.5"> <div className="my-2.5 flex items-center justify-between">
<Label className="text-primary cursor-pointer" htmlFor="allLabels"> <Label className="cursor-pointer text-primary" htmlFor="allLabels">
All Objects All Objects
</Label> </Label>
<Switch <Switch
@ -603,9 +603,9 @@ export function ZoneObjectSelector({
<Separator /> <Separator />
<div className="my-2.5 flex flex-col gap-2.5"> <div className="my-2.5 flex flex-col gap-2.5">
{allLabels.map((item) => ( {allLabels.map((item) => (
<div key={item} className="flex justify-between items-center"> <div key={item} className="flex items-center justify-between">
<Label <Label
className="w-full text-primary capitalize cursor-pointer" className="w-full cursor-pointer capitalize text-primary"
htmlFor={item} htmlFor={item}
> >
{item.replaceAll("_", " ")} {item.replaceAll("_", " ")}

View File

@ -229,16 +229,16 @@ export function EventSegment({
{severityValue === displaySeverityType && ( {severityValue === displaySeverityType && (
<HoverCard openDelay={200} closeDelay={100}> <HoverCard openDelay={200} closeDelay={100}>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<div className="absolute left-1/2 transform -translate-x-1/2 w-[20px] md:w-[40px] h-[8px] z-10 cursor-pointer"> <div className="absolute left-1/2 z-10 h-[8px] w-[20px] -translate-x-1/2 transform cursor-pointer md:w-[40px]">
<div className="flex flex-row justify-center w-[20px] md:w-[40px]"> <div className="flex w-[20px] flex-row justify-center md:w-[40px]">
<div className="flex justify-center"> <div className="flex justify-center">
<div <div
className="absolute left-1/2 transform -translate-x-1/2 w-[8px] h-[8px] ml-[2px] z-10 cursor-pointer" className="absolute left-1/2 z-10 ml-[2px] h-[8px] w-[8px] -translate-x-1/2 transform cursor-pointer"
data-severity={severityValue} data-severity={severityValue}
> >
<div <div
key={`${segmentKey}_${index}_primary_data`} key={`${segmentKey}_${index}_primary_data`}
className={`w-full h-[8px] bg-gradient-to-r ${roundBottomPrimary ? "rounded-bl-full rounded-br-full" : ""} ${roundTopPrimary ? "rounded-tl-full rounded-tr-full" : ""} ${severityColors[severityValue]}`} className={`h-[8px] w-full bg-gradient-to-r ${roundBottomPrimary ? "rounded-bl-full rounded-br-full" : ""} ${roundTopPrimary ? "rounded-tl-full rounded-tr-full" : ""} ${severityColors[severityValue]}`}
></div> ></div>
</div> </div>
</div> </div>
@ -247,7 +247,7 @@ export function EventSegment({
</HoverCardTrigger> </HoverCardTrigger>
<HoverCardPortal> <HoverCardPortal>
<HoverCardContent <HoverCardContent
className="rounded-lg md:rounded-2xl w-[250px] p-2" className="w-[250px] rounded-lg p-2 md:rounded-2xl"
side="left" side="left"
> >
<img <img

View File

@ -204,9 +204,9 @@ export function MotionSegment({
</> </>
)} )}
<div className="absolute left-1/2 transform -translate-x-1/2 w-[20px] md:w-[40px] h-[8px] z-10 cursor-pointer"> <div className="absolute left-1/2 z-10 h-[8px] w-[20px] -translate-x-1/2 transform cursor-pointer md:w-[40px]">
<div className="flex flex-row justify-center w-[20px] md:w-[40px] pt-[1px] mb-[1px]"> <div className="mb-[1px] flex w-[20px] flex-row justify-center pt-[1px] md:w-[40px]">
<div className="flex justify-center mb-[1px]"> <div className="mb-[1px] flex justify-center">
<div <div
key={`${segmentKey}_motion_data_1`} key={`${segmentKey}_motion_data_1`}
data-motion-value={secondHalfSegmentWidth} data-motion-value={secondHalfSegmentWidth}
@ -218,7 +218,7 @@ export function MotionSegment({
</div> </div>
</div> </div>
<div className="flex flex-row justify-center pb-[1px] w-[20px] md:w-[40px]"> <div className="flex w-[20px] flex-row justify-center pb-[1px] md:w-[40px]">
<div className="flex justify-center"> <div className="flex justify-center">
<div <div
key={`${segmentKey}_motion_data_2`} key={`${segmentKey}_motion_data_2`}

View File

@ -316,15 +316,15 @@ export function ReviewTimeline({
return ( return (
<div <div
ref={timelineRef} ref={timelineRef}
className={`relative h-full overflow-y-auto no-scrollbar select-none bg-secondary ${ className={`no-scrollbar relative h-full select-none overflow-y-auto bg-secondary ${
isDragging && (showHandlebar || showExportHandles) isDragging && (showHandlebar || showExportHandles)
? "cursor-grabbing" ? "cursor-grabbing"
: "cursor-auto" : "cursor-auto"
}`} }`}
> >
<div ref={segmentsRef} className="flex flex-col relative"> <div ref={segmentsRef} className="relative flex flex-col">
<div className="absolute top-0 inset-x-0 z-20 w-full h-[30px] bg-gradient-to-b from-secondary to-transparent pointer-events-none"></div> <div className="pointer-events-none absolute inset-x-0 top-0 z-20 h-[30px] w-full bg-gradient-to-b from-secondary to-transparent"></div>
<div className="absolute bottom-0 inset-x-0 z-20 w-full h-[30px] bg-gradient-to-t from-secondary to-transparent pointer-events-none"></div> <div className="pointer-events-none absolute inset-x-0 bottom-0 z-20 h-[30px] w-full bg-gradient-to-t from-secondary to-transparent"></div>
{children} {children}
</div> </div>
{children.length > 0 && ( {children.length > 0 && (
@ -336,7 +336,7 @@ export function ReviewTimeline({
ref={handlebarRef} ref={handlebarRef}
> >
<div <div
className="flex items-center justify-center touch-none select-none" className="flex touch-none select-none items-center justify-center"
onMouseDown={handleHandlebar} onMouseDown={handleHandlebar}
onTouchStart={handleHandlebar} onTouchStart={handleHandlebar}
> >
@ -346,21 +346,21 @@ export function ReviewTimeline({
}`} }`}
> >
<div <div
className={`bg-destructive rounded-full mx-auto ${ className={`mx-auto rounded-full bg-destructive ${
dense dense
? "w-12 md:w-20" ? "w-12 md:w-20"
: segmentDuration < 60 : segmentDuration < 60
? "w-24" ? "w-24"
: "w-20" : "w-20"
} h-5 ${isDraggingHandlebar && isMobile ? "fixed top-[18px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-destructive/80" : "static"} flex items-center justify-center`} } h-5 ${isDraggingHandlebar && isMobile ? "fixed left-1/2 top-[18px] z-20 h-[30px] w-32 -translate-x-1/2 transform bg-destructive/80" : "static"} flex items-center justify-center`}
> >
<div <div
ref={handlebarTimeRef} ref={handlebarTimeRef}
className={`text-white pointer-events-none ${textSizeClasses("handlebar")} z-10`} className={`pointer-events-none text-white ${textSizeClasses("handlebar")} z-10`}
></div> ></div>
</div> </div>
<div <div
className={`absolute h-[4px] w-full bg-destructive ${isDraggingHandlebar && isMobile ? "top-1" : "top-1/2 transform -translate-y-1/2"}`} className={`absolute h-[4px] w-full bg-destructive ${isDraggingHandlebar && isMobile ? "top-1" : "top-1/2 -translate-y-1/2 transform"}`}
></div> ></div>
</div> </div>
</div> </div>
@ -374,7 +374,7 @@ export function ReviewTimeline({
ref={exportEndRef} ref={exportEndRef}
> >
<div <div
className="flex items-center justify-center touch-none select-none" className="flex touch-none select-none items-center justify-center"
onMouseDown={handleExportEnd} onMouseDown={handleExportEnd}
onTouchStart={handleExportEnd} onTouchStart={handleExportEnd}
> >
@ -384,28 +384,28 @@ export function ReviewTimeline({
}`} }`}
> >
<div <div
className={`bg-selected -mt-4 mx-auto ${ className={`mx-auto -mt-4 bg-selected ${
dense dense
? "w-12 md:w-20" ? "w-12 md:w-20"
: segmentDuration < 60 : segmentDuration < 60
? "w-24" ? "w-24"
: "w-20" : "w-20"
} h-5 ${isDraggingExportEnd && isMobile ? "fixed mt-0 rounded-full top-[18px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-selected/80" : "rounded-tr-lg rounded-tl-lg static"} flex items-center justify-center`} } h-5 ${isDraggingExportEnd && isMobile ? "fixed left-1/2 top-[18px] z-20 mt-0 h-[30px] w-32 -translate-x-1/2 transform rounded-full bg-selected/80" : "static rounded-tl-lg rounded-tr-lg"} flex items-center justify-center`}
> >
<div <div
ref={exportEndTimeRef} ref={exportEndTimeRef}
className={`text-white pointer-events-none ${isDraggingExportEnd && isMobile ? "mt-0" : ""} ${textSizeClasses("export_end")} z-10`} className={`pointer-events-none text-white ${isDraggingExportEnd && isMobile ? "mt-0" : ""} ${textSizeClasses("export_end")} z-10`}
></div> ></div>
</div> </div>
<div <div
className={`absolute h-[4px] w-full bg-selected ${isDraggingExportEnd && isMobile ? "top-0" : "top-1/2 transform -translate-y-1/2"}`} className={`absolute h-[4px] w-full bg-selected ${isDraggingExportEnd && isMobile ? "top-0" : "top-1/2 -translate-y-1/2 transform"}`}
></div> ></div>
</div> </div>
</div> </div>
</div> </div>
<div <div
ref={exportSectionRef} ref={exportSectionRef}
className="bg-selected/50 absolute w-full" className="absolute w-full bg-selected/50"
></div> ></div>
<div <div
className={`export-start absolute left-0 top-0 ${isDraggingExportStart && isIOS ? "" : "z-20"} w-full`} className={`export-start absolute left-0 top-0 ${isDraggingExportStart && isIOS ? "" : "z-20"} w-full`}
@ -413,7 +413,7 @@ export function ReviewTimeline({
ref={exportStartRef} ref={exportStartRef}
> >
<div <div
className="flex items-center justify-center touch-none select-none" className="flex touch-none select-none items-center justify-center"
onMouseDown={handleExportStart} onMouseDown={handleExportStart}
onTouchStart={handleExportStart} onTouchStart={handleExportStart}
> >
@ -423,20 +423,20 @@ export function ReviewTimeline({
}`} }`}
> >
<div <div
className={`absolute h-[4px] w-full bg-selected ${isDraggingExportStart && isMobile ? "top-[12px]" : "top-1/2 transform -translate-y-1/2"}`} className={`absolute h-[4px] w-full bg-selected ${isDraggingExportStart && isMobile ? "top-[12px]" : "top-1/2 -translate-y-1/2 transform"}`}
></div> ></div>
<div <div
className={`bg-selected mt-4 mx-auto ${ className={`mx-auto mt-4 bg-selected ${
dense dense
? "w-12 md:w-20" ? "w-12 md:w-20"
: segmentDuration < 60 : segmentDuration < 60
? "w-24" ? "w-24"
: "w-20" : "w-20"
} h-5 ${isDraggingExportStart && isMobile ? "fixed mt-0 rounded-full top-[4px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-selected/80" : "rounded-br-lg rounded-bl-lg static"} flex items-center justify-center`} } h-5 ${isDraggingExportStart && isMobile ? "fixed left-1/2 top-[4px] z-20 mt-0 h-[30px] w-32 -translate-x-1/2 transform rounded-full bg-selected/80" : "static rounded-bl-lg rounded-br-lg"} flex items-center justify-center`}
> >
<div <div
ref={exportStartTimeRef} ref={exportStartTimeRef}
className={`text-white pointer-events-none ${isDraggingExportStart && isMobile ? "mt-0" : ""} ${textSizeClasses("export_start")} z-10`} className={`pointer-events-none text-white ${isDraggingExportStart && isMobile ? "mt-0" : ""} ${textSizeClasses("export_start")} z-10`}
></div> ></div>
</div> </div>
</div> </div>

View File

@ -51,7 +51,7 @@ export function SummarySegment({
<React.Fragment key={index}> <React.Fragment key={index}>
{severityValue === displaySeverityType && ( {severityValue === displaySeverityType && (
<div <div
className="flex justify-end cursor-pointer" className="flex cursor-pointer justify-end"
style={{ height: segmentHeight }} style={{ height: segmentHeight }}
> >
<div <div

View File

@ -339,12 +339,12 @@ export function SummaryTimeline({
return ( return (
<div <div
className={`relative h-full overflow-hidden no-scrollbar select-none bg-secondary border-l-[1px] border-neutral-700`} className={`no-scrollbar relative h-full select-none overflow-hidden border-l-[1px] border-neutral-700 bg-secondary`}
role="scrollbar" role="scrollbar"
> >
<div <div
ref={summaryTimelineRef} ref={summaryTimelineRef}
className="h-full flex flex-col relative z-10" className="relative z-10 flex h-full flex-col"
onClick={timelineClick} onClick={timelineClick}
onTouchEnd={timelineClick} onTouchEnd={timelineClick}
> >
@ -354,7 +354,7 @@ export function SummaryTimeline({
ref={visibleSectionRef} ref={visibleSectionRef}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onTouchStart={handleMouseDown} onTouchStart={handleMouseDown}
className={`bg-primary/30 z-20 absolute w-full touch-none ${ className={`absolute z-20 w-full touch-none bg-primary/30 ${
isDragging ? "cursor-grabbing" : "cursor-grab" isDragging ? "cursor-grabbing" : "cursor-grab"
}`} }`}
></div> ></div>

View File

@ -32,7 +32,7 @@ export function MinimapBounds({
<> <>
{isFirstSegmentInMinimap && ( {isFirstSegmentInMinimap && (
<div <div
className="absolute inset-0 -bottom-7 w-full flex items-center justify-center text-primary font-medium z-20 text-center text-[10px] scroll-mt-8 pointer-events-none select-none" className="pointer-events-none absolute inset-0 -bottom-7 z-20 flex w-full select-none scroll-mt-8 items-center justify-center text-center text-[10px] font-medium text-primary"
ref={firstMinimapSegmentRef} ref={firstMinimapSegmentRef}
> >
{new Date(alignedMinimapStartTime * 1000).toLocaleTimeString([], { {new Date(alignedMinimapStartTime * 1000).toLocaleTimeString([], {
@ -44,7 +44,7 @@ export function MinimapBounds({
)} )}
{isLastSegmentInMinimap && ( {isLastSegmentInMinimap && (
<div className="absolute inset-0 -top-3 w-full flex items-center justify-center text-primary font-medium z-20 text-center text-[10px] pointer-events-none select-none"> <div className="pointer-events-none absolute inset-0 -top-3 z-20 flex w-full select-none items-center justify-center text-center text-[10px] font-medium text-primary">
{new Date(alignedMinimapEndTime * 1000).toLocaleTimeString([], { {new Date(alignedMinimapEndTime * 1000).toLocaleTimeString([], {
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
@ -59,9 +59,9 @@ export function MinimapBounds({
export function Tick({ timestamp, timestampSpread }: TickSegmentProps) { export function Tick({ timestamp, timestampSpread }: TickSegmentProps) {
return ( return (
<div className="absolute"> <div className="absolute">
<div className="flex items-end content-end w-[12px] h-[8px]"> <div className="flex h-[8px] w-[12px] content-end items-end">
<div <div
className={`pointer-events-none select-none h-0.5 ${ className={`pointer-events-none h-0.5 select-none ${
timestamp.getMinutes() % timestampSpread === 0 && timestamp.getMinutes() % timestampSpread === 0 &&
timestamp.getSeconds() === 0 timestamp.getSeconds() === 0
? "w-[12px] bg-neutral-600 dark:bg-neutral-500" ? "w-[12px] bg-neutral-600 dark:bg-neutral-500"
@ -84,7 +84,7 @@ export function Timestamp({
segmentKey, segmentKey,
}: TimestampSegmentProps) { }: TimestampSegmentProps) {
return ( return (
<div className="absolute left-[15px] h-[8px] z-10"> <div className="absolute left-[15px] z-10 h-[8px]">
{!isFirstSegmentInMinimap && !isLastSegmentInMinimap && ( {!isFirstSegmentInMinimap && !isLastSegmentInMinimap && (
<div <div
key={`${segmentKey}_timestamp`} key={`${segmentKey}_timestamp`}

View File

@ -127,8 +127,8 @@ function ConfigEditor() {
} }
return ( return (
<div className="absolute top-2 bottom-16 right-0 left-0 md:left-2"> <div className="absolute bottom-16 left-0 right-0 top-2 md:left-2">
<div className="lg:flex justify-between mr-1"> <div className="mr-1 justify-between lg:flex">
<Heading as="h2">Config</Heading> <Heading as="h2">Config</Heading>
<div> <div>
<Button size="sm" className="mx-1" onClick={() => handleCopyConfig()}> <Button size="sm" className="mx-1" onClick={() => handleCopyConfig()}>
@ -152,12 +152,12 @@ function ConfigEditor() {
</div> </div>
{error && ( {error && (
<div className="p-4 overflow-scroll text-danger whitespace-pre-wrap"> <div className="overflow-scroll whitespace-pre-wrap p-4 text-danger">
{error} {error}
</div> </div>
)} )}
<div ref={configRef} className="h-full mt-2" /> <div ref={configRef} className="mt-2 h-full" />
<Toaster closeButton={true} /> <Toaster closeButton={true} />
</div> </div>
); );

View File

@ -77,7 +77,7 @@ function Exports() {
const [selected, setSelected] = useState<Export>(); const [selected, setSelected] = useState<Export>();
return ( return (
<div className="size-full p-2 overflow-hidden flex flex-col gap-2"> <div className="flex size-full flex-col gap-2 overflow-hidden p-2">
<AlertDialog <AlertDialog
open={deleteClip != undefined} open={deleteClip != undefined}
onOpenChange={() => setDeleteClip(undefined)} onOpenChange={() => setDeleteClip(undefined)}
@ -128,9 +128,9 @@ function Exports() {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<div className="w-full p-2 flex items-center justify-center"> <div className="flex w-full items-center justify-center p-2">
<Input <Input
className="w-full md:w-1/3 bg-muted" className="w-full bg-muted md:w-1/3"
placeholder="Search" placeholder="Search"
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
@ -139,7 +139,7 @@ function Exports() {
<div className="w-full overflow-hidden"> <div className="w-full overflow-hidden">
{exports && filteredExports && ( {exports && filteredExports && (
<div className="size-full grid gap-2 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 overflow-y-auto"> <div className="grid size-full gap-2 overflow-y-auto sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{Object.values(exports).map((item) => ( {Object.values(exports).map((item) => (
<ExportCard <ExportCard
key={item.name} key={item.name}

View File

@ -332,13 +332,13 @@ function Logs() {
const [selectedLog, setSelectedLog] = useState<LogLine>(); const [selectedLog, setSelectedLog] = useState<LogLine>();
return ( return (
<div className="size-full p-2 flex flex-col"> <div className="flex size-full flex-col p-2">
<Toaster position="top-center" closeButton={true} /> <Toaster position="top-center" closeButton={true} />
<LogInfoDialog logLine={selectedLog} setLogLine={setSelectedLog} /> <LogInfoDialog logLine={selectedLog} setLogLine={setSelectedLog} />
<div className="flex justify-between items-center"> <div className="flex items-center justify-between">
<ToggleGroup <ToggleGroup
className="*:px-3 *:py-4 *:rounded-md" className="*:rounded-md *:px-3 *:py-4"
type="single" type="single"
size="sm" size="sm"
value={logService} value={logService}
@ -363,12 +363,12 @@ function Logs() {
</ToggleGroup> </ToggleGroup>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button
className="flex justify-between items-center gap-2" className="flex items-center justify-between gap-2"
size="sm" size="sm"
onClick={handleCopyLogs} onClick={handleCopyLogs}
> >
<FaCopy className="text-secondary-foreground" /> <FaCopy className="text-secondary-foreground" />
<div className="hidden md:block text-primary"> <div className="hidden text-primary md:block">
Copy to Clipboard Copy to Clipboard
</div> </div>
</Button> </Button>
@ -381,7 +381,7 @@ function Logs() {
{initialScroll && !endVisible && ( {initialScroll && !endVisible && (
<Button <Button
className="absolute bottom-8 left-[50%] -translate-x-[50%] rounded-md text-primary bg-secondary-foreground z-20 p-2" className="absolute bottom-8 left-[50%] z-20 -translate-x-[50%] rounded-md bg-secondary-foreground p-2 text-primary"
onClick={() => onClick={() =>
contentRef.current?.scrollTo({ contentRef.current?.scrollTo({
top: contentRef.current?.scrollHeight, top: contentRef.current?.scrollHeight,
@ -393,20 +393,20 @@ function Logs() {
</Button> </Button>
)} )}
<div className="relative size-full flex flex-col my-2 font-mono text-sm sm:p-2 whitespace-pre-wrap bg-background_alt border border-secondary rounded-md overflow-hidden"> <div className="font-mono relative my-2 flex size-full flex-col overflow-hidden whitespace-pre-wrap rounded-md border border-secondary bg-background_alt text-sm sm:p-2">
<div className="grid grid-cols-5 sm:grid-cols-8 md:grid-cols-12 *:px-2 *:py-3 *:text-sm *:text-primary/40"> <div className="grid grid-cols-5 *:px-2 *:py-3 *:text-sm *:text-primary/40 sm:grid-cols-8 md:grid-cols-12">
<div className="p-1 flex items-center capitalize">Type</div> <div className="flex items-center p-1 capitalize">Type</div>
<div className="col-span-2 sm:col-span-1 flex items-center"> <div className="col-span-2 flex items-center sm:col-span-1">
Timestamp Timestamp
</div> </div>
<div className="col-span-2 flex items-center">Tag</div> <div className="col-span-2 flex items-center">Tag</div>
<div className="col-span-5 sm:col-span-4 md:col-span-8 flex items-center"> <div className="col-span-5 flex items-center sm:col-span-4 md:col-span-8">
Message Message
</div> </div>
</div> </div>
<div <div
ref={contentRef} ref={contentRef}
className="w-full flex flex-col overflow-y-auto no-scrollbar overscroll-contain" className="no-scrollbar flex w-full flex-col overflow-y-auto overscroll-contain"
> >
{logLines.length > 0 && {logLines.length > 0 &&
[...Array(logRange.end).keys()].map((idx) => { [...Array(logRange.end).keys()].map((idx) => {
@ -449,7 +449,7 @@ function Logs() {
{logLines.length > 0 && <div id="page-bottom" ref={endLogRef} />} {logLines.length > 0 && <div id="page-bottom" ref={endLogRef} />}
</div> </div>
{logLines.length == 0 && ( {logLines.length == 0 && (
<ActivityIndicator className="absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2" /> <ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
)} )}
</div> </div>
</div> </div>
@ -474,25 +474,25 @@ function LogLineData({
<div <div
ref={startRef} ref={startRef}
className={cn( className={cn(
"w-full py-2 grid grid-cols-5 sm:grid-cols-8 md:grid-cols-12 gap-2 border-secondary border-t cursor-pointer hover:bg-muted", "grid w-full cursor-pointer grid-cols-5 gap-2 border-t border-secondary py-2 hover:bg-muted sm:grid-cols-8 md:grid-cols-12",
className, className,
"*:text-sm", "*:text-sm",
)} )}
onClick={onSelect} onClick={onSelect}
> >
<div className="h-full p-1 flex items-center gap-2"> <div className="flex h-full items-center gap-2 p-1">
<LogChip severity={line.severity} onClickSeverity={onClickSeverity} /> <LogChip severity={line.severity} onClickSeverity={onClickSeverity} />
</div> </div>
<div className="h-full col-span-2 sm:col-span-1 flex items-center"> <div className="col-span-2 flex h-full items-center sm:col-span-1">
{line.dateStamp} {line.dateStamp}
</div> </div>
<div className="size-full pr-2 col-span-2 flex items-center"> <div className="col-span-2 flex size-full items-center pr-2">
<div className="w-full overflow-hidden whitespace-nowrap text-ellipsis"> <div className="w-full overflow-hidden text-ellipsis whitespace-nowrap">
{line.section} {line.section}
</div> </div>
</div> </div>
<div className="size-full pl-2 sm:pl-0 pr-2 col-span-5 sm:col-span-4 md:col-span-8 flex justify-between items-center"> <div className="col-span-5 flex size-full items-center justify-between pl-2 pr-2 sm:col-span-4 sm:pl-0 md:col-span-8">
<div className="w-full overflow-hidden whitespace-nowrap text-ellipsis"> <div className="w-full overflow-hidden text-ellipsis whitespace-nowrap">
{line.content} {line.content}
</div> </div>
</div> </div>

View File

@ -105,12 +105,12 @@ export default function Settings() {
}, []); }, []);
return ( return (
<div className="size-full p-2 flex flex-col"> <div className="flex size-full flex-col p-2">
<div className="w-full h-11 relative flex justify-between items-center"> <div className="relative flex h-11 w-full items-center justify-between">
<ScrollArea className="w-full whitespace-nowrap"> <ScrollArea className="w-full whitespace-nowrap">
<div ref={tabsRef} className="flex flex-row"> <div ref={tabsRef} className="flex flex-row">
<ToggleGroup <ToggleGroup
className="*:px-3 *:py-4 *:rounded-md" className="*:rounded-md *:px-3 *:py-4"
type="single" type="single"
size="sm" size="sm"
value={pageToggle} value={pageToggle}
@ -123,7 +123,7 @@ export default function Settings() {
{Object.values(settingsViews).map((item) => ( {Object.values(settingsViews).map((item) => (
<ToggleGroupItem <ToggleGroupItem
key={item} key={item}
className={`flex items-center justify-between gap-2 scroll-mx-10 ${page == "general" ? "last:mr-20" : ""} ${pageToggle == item ? "" : "*:text-muted-foreground"}`} className={`flex scroll-mx-10 items-center justify-between gap-2 ${page == "general" ? "last:mr-20" : ""} ${pageToggle == item ? "" : "*:text-muted-foreground"}`}
value={item} value={item}
data-nav-item={item} data-nav-item={item}
aria-label={`Select ${item}`} aria-label={`Select ${item}`}
@ -138,7 +138,7 @@ export default function Settings() {
{(page == "debug" || {(page == "debug" ||
page == "masks / zones" || page == "masks / zones" ||
page == "motion tuner") && ( page == "motion tuner") && (
<div className="flex items-center gap-2 ml-2 flex-shrink-0"> <div className="ml-2 flex flex-shrink-0 items-center gap-2">
{page == "masks / zones" && ( {page == "masks / zones" && (
<ZoneMaskFilterButton <ZoneMaskFilterButton
selectedZoneMask={filterZoneMask} selectedZoneMask={filterZoneMask}
@ -153,7 +153,7 @@ export default function Settings() {
</div> </div>
)} )}
</div> </div>
<div className="mt-2 flex flex-col items-start w-full h-full md:h-dvh md:pb-24"> <div className="mt-2 flex h-full w-full flex-col items-start md:h-dvh md:pb-24">
{page == "general" && <General />} {page == "general" && <General />}
{page == "debug" && <ObjectSettings selectedCamera={selectedCamera} />} {page == "debug" && <ObjectSettings selectedCamera={selectedCamera} />}
{page == "masks / zones" && ( {page == "masks / zones" && (
@ -216,11 +216,11 @@ function CameraSelectButton({
const trigger = ( const trigger = (
<Button <Button
className="flex items-center gap-2 capitalize bg-selected hover:bg-selected" className="flex items-center gap-2 bg-selected capitalize hover:bg-selected"
size="sm" size="sm"
> >
<FaVideo className="text-background dark:text-primary" /> <FaVideo className="text-background dark:text-primary" />
<div className="hidden md:block text-background dark:text-primary"> <div className="hidden text-background dark:text-primary md:block">
{selectedCamera == undefined {selectedCamera == undefined
? "No Camera" ? "No Camera"
: selectedCamera.replaceAll("_", " ")} : selectedCamera.replaceAll("_", " ")}
@ -237,7 +237,7 @@ function CameraSelectButton({
<DropdownMenuSeparator /> <DropdownMenuSeparator />
</> </>
)} )}
<div className="h-auto max-h-[80dvh] p-4 mb-5 md:mb-1 overflow-y-auto overflow-x-hidden"> <div className="mb-5 h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden p-4 md:mb-1">
<div className="flex flex-col gap-2.5"> <div className="flex flex-col gap-2.5">
{allCameras.map((item) => ( {allCameras.map((item) => (
<FilterSwitch <FilterSwitch

View File

@ -224,8 +224,8 @@ export default function SubmitPlus() {
); );
return ( return (
<div className="size-full flex flex-col"> <div className="flex size-full flex-col">
<div className="w-full h-16 px-2 flex items-center justify-between overflow-x-auto"> <div className="flex h-16 w-full items-center justify-between overflow-x-auto px-2">
<PlusFilterGroup <PlusFilterGroup
selectedCameras={selectedCameras} selectedCameras={selectedCameras}
selectedLabels={selectedLabels} selectedLabels={selectedLabels}
@ -236,8 +236,8 @@ export default function SubmitPlus() {
/> />
<PlusSortSelector selectedSort={sort} setSelectedSort={setSort} /> <PlusSortSelector selectedSort={sort} setSelectedSort={setSort} />
</div> </div>
<div className="size-full flex flex-1 flex-wrap content-start gap-2 md:gap-4 overflow-y-auto no-scrollbar"> <div className="no-scrollbar flex size-full flex-1 flex-wrap content-start gap-2 overflow-y-auto md:gap-4">
<div className="w-full p-2 grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-2"> <div className="grid w-full gap-2 p-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
<Dialog <Dialog
open={upload != undefined} open={upload != undefined}
onOpenChange={(open) => (!open ? setUpload(undefined) : null)} onOpenChange={(open) => (!open ? setUpload(undefined) : null)}
@ -286,16 +286,16 @@ export default function SubmitPlus() {
<div <div
key={event.id} key={event.id}
ref={lastRow ? lastEventRef : null} ref={lastRow ? lastEventRef : null}
className="w-full relative rounded-lg md:rounded-2xl aspect-video flex justify-center items-center bg-black cursor-pointer" className="relative flex aspect-video w-full cursor-pointer items-center justify-center rounded-lg bg-black md:rounded-2xl"
onClick={() => setUpload(event)} onClick={() => setUpload(event)}
> >
<div className="absolute left-0 top-2 z-40"> <div className="absolute left-0 top-2 z-40">
<Tooltip> <Tooltip>
<div className="flex"> <div className="flex">
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div className="mx-3 pb-1 text-white text-sm"> <div className="mx-3 pb-1 text-sm text-white">
<Chip <Chip
className={`flex items-start justify-between space-x-1 bg-gradient-to-br from-gray-400 to-gray-500 bg-gray-500 z-0`} className={`z-0 flex items-start justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500`}
> >
{[event.label].map((object) => { {[event.label].map((object) => {
return getIconForLabel( return getIconForLabel(
@ -317,7 +317,7 @@ export default function SubmitPlus() {
</Tooltip> </Tooltip>
</div> </div>
<img <img
className="aspect-video h-full object-contain rounded-lg md:rounded-2xl" className="aspect-video h-full rounded-lg object-contain md:rounded-2xl"
src={`${baseUrl}api/events/${event.id}/snapshot.jpg`} src={`${baseUrl}api/events/${event.id}/snapshot.jpg`}
loading="lazy" loading="lazy"
/> />
@ -392,7 +392,7 @@ function PlusFilterGroup({
const Content = isMobile ? DrawerContent : DropdownMenuContent; const Content = isMobile ? DrawerContent : DropdownMenuContent;
return ( return (
<div className="h-full flex justify-start gap-2 items-center"> <div className="flex h-full items-center justify-start gap-2">
<CamerasFilterButton <CamerasFilterButton
allCameras={allCameras} allCameras={allCameras}
groups={[]} groups={[]}
@ -417,7 +417,7 @@ function PlusFilterGroup({
<FaList <FaList
className={`${selectedLabels == undefined ? "text-secondary-foreground" : "text-selected-foreground"}`} className={`${selectedLabels == undefined ? "text-secondary-foreground" : "text-selected-foreground"}`}
/> />
<div className="hidden md:block text-primary"> <div className="hidden text-primary md:block">
{selectedLabels == undefined {selectedLabels == undefined
? "All Labels" ? "All Labels"
: `${selectedLabels.length} Labels`} : `${selectedLabels.length} Labels`}
@ -450,7 +450,7 @@ function PlusFilterGroup({
<PiSlidersHorizontalFill <PiSlidersHorizontalFill
className={`${selectedScoreRange == undefined ? "text-secondary-foreground" : "text-selected-foreground"}`} className={`${selectedScoreRange == undefined ? "text-secondary-foreground" : "text-selected-foreground"}`}
/> />
<div className="hidden md:block text-primary"> <div className="hidden text-primary md:block">
{selectedScoreRange == undefined {selectedScoreRange == undefined
? "Score Range" ? "Score Range"
: `${selectedScoreRange[0] * 100}% - ${selectedScoreRange[1] * 100}%`} : `${selectedScoreRange[0] * 100}% - ${selectedScoreRange[1] * 100}%`}
@ -458,7 +458,7 @@ function PlusFilterGroup({
</Button> </Button>
</Trigger> </Trigger>
<Content <Content
className={`min-w-80 p-2 flex flex-col justify-center ${isMobile ? "gap-2 *:max-h-[75dvh]" : ""}`} className={`flex min-w-80 flex-col justify-center p-2 ${isMobile ? "gap-2 *:max-h-[75dvh]" : ""}`}
> >
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Input <Input
@ -493,7 +493,7 @@ function PlusFilterGroup({
/> />
</div> </div>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<div className="p-2 flex justify-evenly items-center"> <div className="flex items-center justify-evenly p-2">
<Button <Button
variant="select" variant="select"
onClick={() => { onClick={() => {
@ -547,7 +547,7 @@ function PlusSortSelector({
const Content = isMobile ? DrawerContent : DropdownMenuContent; const Content = isMobile ? DrawerContent : DropdownMenuContent;
return ( return (
<div className="h-full flex justify-start gap-2 items-center"> <div className="flex h-full items-center justify-start gap-2">
<Menu <Menu
open={open} open={open}
onOpenChange={(open) => { onOpenChange={(open) => {
@ -572,24 +572,24 @@ function PlusSortSelector({
<Sort <Sort
className={`${selectedSort == undefined ? "text-secondary-foreground" : "text-selected-foreground"}`} className={`${selectedSort == undefined ? "text-secondary-foreground" : "text-selected-foreground"}`}
/> />
<div className="hidden md:block text-primary"> <div className="hidden text-primary md:block">
{selectedSort == undefined ? "Sort" : selectedSort.split("_")[0]} {selectedSort == undefined ? "Sort" : selectedSort.split("_")[0]}
</div> </div>
</Button> </Button>
</Trigger> </Trigger>
<Content <Content
className={`p-2 flex flex-col justify-center gap-2 ${isMobile ? "max-h-[75dvh]" : ""}`} className={`flex flex-col justify-center gap-2 p-2 ${isMobile ? "max-h-[75dvh]" : ""}`}
> >
<RadioGroup <RadioGroup
className={`flex flex-col gap-4 ${isMobile ? "mt-4" : ""}`} className={`flex flex-col gap-4 ${isMobile ? "mt-4" : ""}`}
onValueChange={(value) => setCurrentSort(value)} onValueChange={(value) => setCurrentSort(value)}
> >
<div className="w-full flex items-center gap-2"> <div className="flex w-full items-center gap-2">
<RadioGroupItem <RadioGroupItem
className={ className={
currentSort == "date" currentSort == "date"
? "from-selected/50 to-selected/90 text-selected bg-selected" ? "bg-selected from-selected/50 to-selected/90 text-selected"
: "from-secondary/50 to-secondary/90 text-secondary bg-secondary" : "bg-secondary from-secondary/50 to-secondary/90 text-secondary"
} }
id="date" id="date"
value="date" value="date"
@ -616,12 +616,12 @@ function PlusSortSelector({
<div className="size-5" /> <div className="size-5" />
)} )}
</div> </div>
<div className="w-full flex items-center gap-2"> <div className="flex w-full items-center gap-2">
<RadioGroupItem <RadioGroupItem
className={ className={
currentSort == "score" currentSort == "score"
? "from-selected/50 to-selected/90 text-selected bg-selected" ? "bg-selected from-selected/50 to-selected/90 text-selected"
: "from-secondary/50 to-secondary/90 text-secondary bg-secondary" : "bg-secondary from-secondary/50 to-secondary/90 text-secondary"
} }
id="score" id="score"
value="score" value="score"
@ -650,7 +650,7 @@ function PlusSortSelector({
</div> </div>
</RadioGroup> </RadioGroup>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<div className="p-2 flex justify-evenly items-center"> <div className="flex items-center justify-evenly p-2">
<Button <Button
variant="select" variant="select"
onClick={() => { onClick={() => {

View File

@ -41,13 +41,13 @@ function System() {
}); });
return ( return (
<div className="size-full p-2 flex flex-col"> <div className="flex size-full flex-col p-2">
<div className="w-full h-11 relative flex justify-between items-center"> <div className="relative flex h-11 w-full items-center justify-between">
{isMobile && ( {isMobile && (
<Logo className="absolute inset-x-1/2 -translate-x-1/2 h-8" /> <Logo className="absolute inset-x-1/2 h-8 -translate-x-1/2" />
)} )}
<ToggleGroup <ToggleGroup
className="*:px-3 *:py-4 *:rounded-md" className="*:rounded-md *:px-3 *:py-4"
type="single" type="single"
size="sm" size="sm"
value={pageToggle} value={pageToggle}
@ -72,18 +72,18 @@ function System() {
))} ))}
</ToggleGroup> </ToggleGroup>
<div className="h-full flex items-center"> <div className="flex h-full items-center">
{lastUpdated && ( {lastUpdated && (
<div className="h-full text-muted-foreground text-sm content-center"> <div className="h-full content-center text-sm text-muted-foreground">
Last refreshed: <TimeAgo time={lastUpdated * 1000} dense /> Last refreshed: <TimeAgo time={lastUpdated * 1000} dense />
</div> </div>
)} )}
</div> </div>
</div> </div>
<div className="mt-2 flex items-end gap-2"> <div className="mt-2 flex items-end gap-2">
<div className="h-full font-medium content-center">System</div> <div className="h-full content-center font-medium">System</div>
{statsSnapshot && ( {statsSnapshot && (
<div className="h-full text-muted-foreground text-sm content-center"> <div className="h-full content-center text-sm text-muted-foreground">
{statsSnapshot.service.version} {statsSnapshot.service.version}
</div> </div>
)} )}

View File

@ -55,9 +55,9 @@ const colors = [
function ColorSwatch({ name, value }: { name: string; value: string }) { function ColorSwatch({ name, value }: { name: string; value: string }) {
return ( return (
<div className="flex items-center mb-2"> <div className="mb-2 flex items-center">
<div <div
className="w-10 h-10 mr-2 border border-gray-300" className="mr-2 h-10 w-10 border border-gray-300"
style={{ backgroundColor: value }} style={{ backgroundColor: value }}
></div> ></div>
<span>{name}</span> <span>{name}</span>
@ -212,9 +212,9 @@ function UIPlayground() {
return ( return (
<> <>
<div className="w-full h-full"> <div className="h-full w-full">
<div className="flex h-full"> <div className="flex h-full">
<div className="flex-1 content-start gap-2 overflow-y-auto no-scrollbar mt-4 mr-5"> <div className="no-scrollbar mr-5 mt-4 flex-1 content-start gap-2 overflow-y-auto">
<Heading as="h2">UI Playground</Heading> <Heading as="h2">UI Playground</Heading>
<IconPicker <IconPicker
@ -294,7 +294,7 @@ function UIPlayground() {
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="flex p-2 justify-start items-center"> <div className="flex items-center justify-start p-2">
<Switch <Switch
id="exporthandles" id="exporthandles"
checked={showExportHandles} checked={showExportHandles}
@ -326,7 +326,7 @@ function UIPlayground() {
Export Export
</Button> </Button>
</div> </div>
<div className="w-[40px] my-4"> <div className="my-4 w-[40px]">
<CameraActivityIndicator /> <CameraActivityIndicator />
</div> </div>
<p> <p>
@ -378,7 +378,7 @@ function UIPlayground() {
</div> </div>
</div> </div>
<div className="w-[55px] md:w-[100px] overflow-y-auto no-scrollbar"> <div className="no-scrollbar w-[55px] overflow-y-auto md:w-[100px]">
{!isEventsReviewTimeline && ( {!isEventsReviewTimeline && (
<MotionReviewTimeline <MotionReviewTimeline
segmentDuration={zoomSettings.segmentDuration} // seconds per segment segmentDuration={zoomSettings.segmentDuration} // seconds per segment

View File

@ -26,28 +26,28 @@ import { TimeRange, Timeline } from "@/types/timeline";
export function getTimelineIcon(timelineItem: Timeline) { export function getTimelineIcon(timelineItem: Timeline) {
switch (timelineItem.class_type) { switch (timelineItem.class_type) {
case "visible": case "visible":
return <LuPlay className="w-4 mr-1" />; return <LuPlay className="mr-1 w-4" />;
case "gone": case "gone":
return <IoMdExit className="w-4 mr-1" />; return <IoMdExit className="mr-1 w-4" />;
case "active": case "active":
return <LuPlayCircle className="w-4 mr-1" />; return <LuPlayCircle className="mr-1 w-4" />;
case "stationary": case "stationary":
return <LuCircle className="w-4 mr-1" />; return <LuCircle className="mr-1 w-4" />;
case "entered_zone": case "entered_zone":
return <MdOutlineLocationOn className="w-4 mr-1" />; return <MdOutlineLocationOn className="mr-1 w-4" />;
case "attribute": case "attribute":
switch (timelineItem.data.attribute) { switch (timelineItem.data.attribute) {
case "face": case "face":
return <MdFaceUnlock className="w-4 mr-1" />; return <MdFaceUnlock className="mr-1 w-4" />;
case "license_plate": case "license_plate":
return <MdOutlinePictureInPictureAlt className="w-4 mr-1" />; return <MdOutlinePictureInPictureAlt className="mr-1 w-4" />;
default: default:
return <LuTruck className="w-4 mr-1" />; return <LuTruck className="mr-1 w-4" />;
} }
case "heard": case "heard":
return <LuEar className="w-4 mr-1" />; return <LuEar className="mr-1 w-4" />;
case "external": case "external":
return <LuCircleDot className="w-4 mr-1" />; return <LuCircleDot className="mr-1 w-4" />;
} }
} }
@ -59,21 +59,21 @@ export function getTimelineIcon(timelineItem: Timeline) {
export function getTimelineDetectionIcon(timelineItem: Timeline) { export function getTimelineDetectionIcon(timelineItem: Timeline) {
switch (timelineItem.data.label) { switch (timelineItem.data.label) {
case "bicycle": case "bicycle":
return <FaBicycle className="w-4 mr-1" />; return <FaBicycle className="mr-1 w-4" />;
case "car": case "car":
return <LuCar className="w-4 mr-1" />; return <LuCar className="mr-1 w-4" />;
case "cat": case "cat":
return <LuCat className="w-4 mr-1" />; return <LuCat className="mr-1 w-4" />;
case "deer": case "deer":
return <GiDeer className="w-4 mr-1" />; return <GiDeer className="mr-1 w-4" />;
case "dog": case "dog":
return <LuDog className="w-4 mr-1" />; return <LuDog className="mr-1 w-4" />;
case "package": case "package":
return <LuPackage className="w-4 mr-1" />; return <LuPackage className="mr-1 w-4" />;
case "person": case "person":
return <LuPersonStanding className="w-4 mr-1" />; return <LuPersonStanding className="mr-1 w-4" />;
default: default:
return <LuCamera className="w-4 mr-1" />; return <LuCamera className="mr-1 w-4" />;
} }
} }

View File

@ -252,14 +252,14 @@ export default function EventView({
} }
return ( return (
<div className="py-2 flex flex-col size-full"> <div className="flex size-full flex-col py-2">
<Toaster closeButton={true} /> <Toaster closeButton={true} />
<div className="h-11 mb-2 pl-3 pr-2 relative flex justify-between items-center"> <div className="relative mb-2 flex h-11 items-center justify-between pl-3 pr-2">
{isMobile && ( {isMobile && (
<Logo className="absolute inset-x-1/2 -translate-x-1/2 h-8" /> <Logo className="absolute inset-x-1/2 h-8 -translate-x-1/2" />
)} )}
<ToggleGroup <ToggleGroup
className="*:px-3 *:py-4 *:rounded-md" className="*:rounded-md *:px-3 *:py-4"
type="single" type="single"
size="sm" size="sm"
value={severityToggle} value={severityToggle}
@ -272,7 +272,7 @@ export default function EventView({
value="alert" value="alert"
aria-label="Select alerts" aria-label="Select alerts"
> >
<MdCircle className="size-2 md:mr-[10px] text-severity_alert" /> <MdCircle className="size-2 text-severity_alert md:mr-[10px]" />
<div className="hidden md:block"> <div className="hidden md:block">
Alerts{`${reviewCounts.alert > -1 ? reviewCounts.alert : ""}`} Alerts{`${reviewCounts.alert > -1 ? reviewCounts.alert : ""}`}
</div> </div>
@ -282,14 +282,14 @@ export default function EventView({
value="detection" value="detection"
aria-label="Select detections" aria-label="Select detections"
> >
<MdCircle className="size-2 md:mr-[10px] text-severity_detection" /> <MdCircle className="size-2 text-severity_detection md:mr-[10px]" />
<div className="hidden md:block"> <div className="hidden md:block">
Detections Detections
{`${reviewCounts.detection > -1 ? reviewCounts.detection : ""}`} {`${reviewCounts.detection > -1 ? reviewCounts.detection : ""}`}
</div> </div>
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem <ToggleGroupItem
className={`px-3 py-4 rounded-lg ${ className={`rounded-lg px-3 py-4 ${
severityToggle == "significant_motion" severityToggle == "significant_motion"
? "" ? ""
: "text-muted-foreground" : "text-muted-foreground"
@ -297,7 +297,7 @@ export default function EventView({
value="significant_motion" value="significant_motion"
aria-label="Select motion" aria-label="Select motion"
> >
<MdCircle className="size-2 md:mr-[10px] text-severity_significant_motion" /> <MdCircle className="size-2 text-severity_significant_motion md:mr-[10px]" />
<div className="hidden md:block">Motion</div> <div className="hidden md:block">Motion</div>
</ToggleGroupItem> </ToggleGroupItem>
</ToggleGroup> </ToggleGroup>
@ -576,11 +576,11 @@ function DetectionReview({
<> <>
<div <div
ref={contentRef} ref={contentRef}
className="flex flex-1 flex-wrap content-start gap-2 md:gap-4 overflow-y-auto no-scrollbar" className="no-scrollbar flex flex-1 flex-wrap content-start gap-2 overflow-y-auto md:gap-4"
> >
{filter?.before == undefined && ( {filter?.before == undefined && (
<NewReviewData <NewReviewData
className="absolute left-1/2 -translate-x-1/2 z-50 pointer-events-none" className="pointer-events-none absolute left-1/2 z-50 -translate-x-1/2"
contentRef={contentRef} contentRef={contentRef}
reviewItems={currentItems} reviewItems={currentItems}
itemsToReview={loading ? 0 : itemsToReview} itemsToReview={loading ? 0 : itemsToReview}
@ -589,20 +589,20 @@ function DetectionReview({
)} )}
{!currentItems && ( {!currentItems && (
<div className="absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2"> <div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
<ActivityIndicator /> <ActivityIndicator />
</div> </div>
)} )}
{!loading && currentItems?.length === 0 && ( {!loading && currentItems?.length === 0 && (
<div className="absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2 flex flex-col justify-center items-center text-center"> <div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center">
<LuFolderCheck className="size-16" /> <LuFolderCheck className="size-16" />
There are no {severity.replace(/_/g, " ")}s to review There are no {severity.replace(/_/g, " ")}s to review
</div> </div>
)} )}
<div <div
className="w-full mx-2 px-1 grid sm:grid-cols-2 md:grid-cols-3 3xl:grid-cols-4 gap-2 md:gap-4" className="mx-2 grid w-full gap-2 px-1 sm:grid-cols-2 md:grid-cols-3 md:gap-4 3xl:grid-cols-4"
ref={contentRef} ref={contentRef}
> >
{!loading && currentItems {!loading && currentItems
@ -620,7 +620,7 @@ function DetectionReview({
} }
className="review-item relative rounded-lg" className="review-item relative rounded-lg"
> >
<div className="aspect-video rounded-lg overflow-hidden"> <div className="aspect-video overflow-hidden rounded-lg">
<PreviewThumbnailPlayer <PreviewThumbnailPlayer
review={value} review={value}
allPreviews={relevantPreviews} allPreviews={relevantPreviews}
@ -632,7 +632,7 @@ function DetectionReview({
/> />
</div> </div>
<div <div
className={`review-item-ring pointer-events-none z-10 absolute rounded-lg inset-0 size-full -outline-offset-[2.8px] outline outline-[3px] ${selected ? `outline-severity_${value.severity} shadow-severity_${value.severity}` : "outline-transparent duration-500"}`} className={`review-item-ring pointer-events-none absolute inset-0 z-10 size-full rounded-lg outline outline-[3px] -outline-offset-[2.8px] ${selected ? `outline-severity_${value.severity} shadow-severity_${value.severity}` : "outline-transparent duration-500"}`}
/> />
</div> </div>
); );
@ -641,12 +641,12 @@ function DetectionReview({
Array(itemsToReview) Array(itemsToReview)
.fill(0) .fill(0)
.map((_, idx) => ( .map((_, idx) => (
<Skeleton key={idx} className="size-full aspect-video" /> <Skeleton key={idx} className="aspect-video size-full" />
))} ))}
{!loading && {!loading &&
(currentItems?.length ?? 0) > 0 && (currentItems?.length ?? 0) > 0 &&
(itemsToReview ?? 0) > 0 && ( (itemsToReview ?? 0) > 0 && (
<div className="col-span-full flex justify-center items-center"> <div className="col-span-full flex items-center justify-center">
<Button <Button
className="text-white" className="text-white"
variant="select" variant="select"
@ -660,8 +660,8 @@ function DetectionReview({
)} )}
</div> </div>
</div> </div>
<div className="w-[65px] md:w-[110px] flex flex-row"> <div className="flex w-[65px] flex-row md:w-[110px]">
<div className="w-[55px] md:w-[100px] overflow-y-auto no-scrollbar"> <div className="no-scrollbar w-[55px] overflow-y-auto md:w-[100px]">
{loading ? ( {loading ? (
<Skeleton className="size-full" /> <Skeleton className="size-full" />
) : ( ) : (
@ -919,10 +919,10 @@ function MotionReview({
return ( return (
<> <>
<div className="flex flex-1 flex-wrap content-start gap-2 md:gap-4 overflow-y-auto no-scrollbar"> <div className="no-scrollbar flex flex-1 flex-wrap content-start gap-2 overflow-y-auto md:gap-4">
<div <div
ref={contentRef} ref={contentRef}
className="w-full mx-2 px-1 grid sm:grid-cols-2 xl:grid-cols-3 3xl:grid-cols-4 gap-2 md:gap-4 overflow-auto no-scrollbar" className="no-scrollbar mx-2 grid w-full gap-2 overflow-auto px-1 sm:grid-cols-2 md:gap-4 xl:grid-cols-3 3xl:grid-cols-4"
> >
{reviewCameras.map((camera) => { {reviewCameras.map((camera) => {
let grow; let grow;
@ -965,12 +965,12 @@ function MotionReview({
} }
/> />
<div <div
className={`review-item-ring pointer-events-none z-20 absolute rounded-lg inset-0 size-full -outline-offset-[2.8px] outline outline-[3px] ${detectionType ? `outline-severity_${detectionType} shadow-severity_${detectionType}` : "outline-transparent duration-500"}`} className={`review-item-ring pointer-events-none absolute inset-0 z-20 size-full rounded-lg outline outline-[3px] -outline-offset-[2.8px] ${detectionType ? `outline-severity_${detectionType} shadow-severity_${detectionType}` : "outline-transparent duration-500"}`}
/> />
</> </>
) : ( ) : (
<Skeleton <Skeleton
className={`rounded-lg md:rounded-2xl size-full ${spans} ${grow}`} className={`size-full rounded-lg md:rounded-2xl ${spans} ${grow}`}
/> />
)} )}
</div> </div>
@ -978,7 +978,7 @@ function MotionReview({
})} })}
</div> </div>
</div> </div>
<div className="w-[55px] md:w-[100px] overflow-y-auto no-scrollbar"> <div className="no-scrollbar w-[55px] overflow-y-auto md:w-[100px]">
{motionData ? ( {motionData ? (
<MotionReviewTimeline <MotionReviewTimeline
segmentDuration={segmentDuration} segmentDuration={segmentDuration}

View File

@ -357,11 +357,11 @@ export function RecordingView({
}, [previewRowRef.current?.scrollWidth, previewRowRef.current?.scrollHeight]); }, [previewRowRef.current?.scrollWidth, previewRowRef.current?.scrollHeight]);
return ( return (
<div ref={contentRef} className="size-full pt-2 flex flex-col"> <div ref={contentRef} className="flex size-full flex-col pt-2">
<Toaster closeButton={true} /> <Toaster closeButton={true} />
<div className="w-full h-11 mb-2 px-2 relative flex items-center justify-between"> <div className="relative mb-2 flex h-11 w-full items-center justify-between px-2">
{isMobile && ( {isMobile && (
<Logo className="absolute inset-x-1/2 -translate-x-1/2 h-8" /> <Logo className="absolute inset-x-1/2 h-8 -translate-x-1/2" />
)} )}
<div className={cn("flex items-center gap-2")}> <div className={cn("flex items-center gap-2")}>
<Button <Button
@ -422,7 +422,7 @@ export function RecordingView({
)} )}
{isDesktop ? ( {isDesktop ? (
<ToggleGroup <ToggleGroup
className="*:px-3 *:py-4 *:rounded-md" className="*:rounded-md *:px-3 *:py-4"
type="single" type="single"
size="sm" size="sm"
value={timelineType} value={timelineType}
@ -469,8 +469,8 @@ export function RecordingView({
<div <div
ref={mainLayoutRef} ref={mainLayoutRef}
className={cn( className={cn(
"h-full flex justify-center overflow-hidden", "flex h-full justify-center overflow-hidden",
isDesktop ? "" : "flex-col landscape:flex-row gap-2", isDesktop ? "" : "flex-col gap-2 landscape:flex-row",
)} )}
> >
<div <div
@ -479,7 +479,7 @@ export function RecordingView({
> >
<div <div
className={cn( className={cn(
"size-full flex items-center", "flex size-full items-center",
mainCameraAspect == "tall" mainCameraAspect == "tall"
? "flex-row justify-evenly" ? "flex-row justify-evenly"
: "flex-col justify-center gap-2", : "flex-col justify-center gap-2",
@ -491,7 +491,7 @@ export function RecordingView({
"relative", "relative",
isDesktop isDesktop
? cn( ? cn(
"px-4 flex justify-center", "flex justify-center px-4",
mainCameraAspect == "tall" mainCameraAspect == "tall"
? "h-[50%] md:h-[60%] lg:h-[75%] xl:h-[90%]" ? "h-[50%] md:h-[60%] lg:h-[75%] xl:h-[90%]"
: mainCameraAspect == "wide" : mainCameraAspect == "wide"
@ -499,10 +499,10 @@ export function RecordingView({
: "", : "",
) )
: cn( : cn(
"portrait:w-full pt-2", "pt-2 portrait:w-full",
mainCameraAspect == "wide" mainCameraAspect == "wide"
? "landscape:w-full aspect-wide" ? "aspect-wide landscape:w-full"
: "landscape:h-[94%] aspect-video", : "aspect-video landscape:h-[94%]",
), ),
)} )}
style={{ style={{
@ -545,8 +545,8 @@ export function RecordingView({
"flex gap-2 overflow-auto", "flex gap-2 overflow-auto",
mainCameraAspect == "tall" mainCameraAspect == "tall"
? "h-full w-48 flex-col" ? "h-full w-48 flex-col"
: `w-full h-28`, : `h-28 w-full`,
previewRowOverflows ? "" : "justify-center items-center", previewRowOverflows ? "" : "items-center justify-center",
)} )}
> >
<div className="w-2" /> <div className="w-2" />
@ -660,12 +660,12 @@ function Timeline({
<div <div
className={`${ className={`${
isDesktop isDesktop
? `${timelineType == "timeline" ? "w-[100px]" : "w-60"} overflow-y-auto no-scrollbar` ? `${timelineType == "timeline" ? "w-[100px]" : "w-60"} no-scrollbar overflow-y-auto`
: "portrait:flex-grow landscape:w-[20%] overflow-hidden" : "overflow-hidden portrait:flex-grow landscape:w-[20%]"
} relative`} } relative`}
> >
<div className="absolute top-0 inset-x-0 z-20 w-full h-[30px] bg-gradient-to-b from-secondary to-transparent pointer-events-none"></div> <div className="pointer-events-none absolute inset-x-0 top-0 z-20 h-[30px] w-full bg-gradient-to-b from-secondary to-transparent"></div>
<div className="absolute bottom-0 inset-x-0 z-20 w-full h-[30px] bg-gradient-to-t from-secondary to-transparent pointer-events-none"></div> <div className="pointer-events-none absolute inset-x-0 bottom-0 z-20 h-[30px] w-full bg-gradient-to-t from-secondary to-transparent"></div>
{timelineType == "timeline" ? ( {timelineType == "timeline" ? (
motionData ? ( motionData ? (
<MotionReviewTimeline <MotionReviewTimeline
@ -693,7 +693,7 @@ function Timeline({
) )
) : ( ) : (
<div <div
className={`h-full grid grid-cols-1 gap-4 overflow-auto p-4 bg-secondary ${isDesktop ? "" : "sm:grid-cols-2"}`} className={`grid h-full grid-cols-1 gap-4 overflow-auto bg-secondary p-4 ${isDesktop ? "" : "sm:grid-cols-2"}`}
> >
{mainCameraReviewItems.map((review) => { {mainCameraReviewItems.map((review) => {
if (review.severity == "significant_motion") { if (review.severity == "significant_motion") {

View File

@ -325,7 +325,7 @@ export default function DraggableGridLayout({
<> <>
<Toaster position="top-center" closeButton={true} /> <Toaster position="top-center" closeButton={true} />
{!isGridLayoutLoaded || !currentGridLayout ? ( {!isGridLayoutLoaded || !currentGridLayout ? (
<div className="mt-2 px-2 grid grid-cols-2 xl:grid-cols-3 3xl:grid-cols-4 gap-2 md:gap-4"> <div className="mt-2 grid grid-cols-2 gap-2 px-2 md:gap-4 xl:grid-cols-3 3xl:grid-cols-4">
{includeBirdseye && birdseyeConfig?.enabled && ( {includeBirdseye && birdseyeConfig?.enabled && (
<Skeleton className="size-full rounded-lg md:rounded-2xl" /> <Skeleton className="size-full rounded-lg md:rounded-2xl" />
)} )}
@ -340,7 +340,7 @@ export default function DraggableGridLayout({
</div> </div>
) : ( ) : (
<div <div
className="my-2 px-2 pb-8 no-scrollbar overflow-x-hidden" className="no-scrollbar my-2 overflow-x-hidden px-2 pb-8"
ref={gridContainerRef} ref={gridContainerRef}
> >
<EditGroupDialog <EditGroupDialog
@ -373,7 +373,7 @@ export default function DraggableGridLayout({
key="birdseye" key="birdseye"
className={cn( className={cn(
isEditMode && isEditMode &&
"outline outline-2 hover:outline-4 outline-muted-foreground hover:cursor-grab active:cursor-grabbing", "outline outline-2 outline-muted-foreground hover:cursor-grab hover:outline-4 active:cursor-grabbing",
)} )}
birdseyeConfig={birdseyeConfig} birdseyeConfig={birdseyeConfig}
liveMode={birdseyeConfig.restream ? "mse" : "jsmpeg"} liveMode={birdseyeConfig.restream ? "mse" : "jsmpeg"}
@ -397,10 +397,10 @@ export default function DraggableGridLayout({
key={camera.name} key={camera.name}
cameraRef={cameraRef} cameraRef={cameraRef}
className={cn( className={cn(
"rounded-lg md:rounded-2xl bg-black", "rounded-lg bg-black md:rounded-2xl",
grow, grow,
isEditMode && isEditMode &&
"outline-2 hover:outline-4 outline-muted-foreground hover:cursor-grab active:cursor-grabbing", "outline-2 outline-muted-foreground hover:cursor-grab hover:outline-4 active:cursor-grabbing",
)} )}
windowVisible={ windowVisible={
windowVisible && visibleCameras.includes(camera.name) windowVisible && visibleCameras.includes(camera.name)
@ -429,7 +429,7 @@ export default function DraggableGridLayout({
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div <div
className="rounded-lg text-secondary-foreground bg-secondary hover:bg-muted cursor-pointer opacity-60 hover:opacity-100 transition-all duration-300" className="cursor-pointer rounded-lg bg-secondary text-secondary-foreground opacity-60 transition-all duration-300 hover:bg-muted hover:opacity-100"
onClick={() => onClick={() =>
setIsEditMode((prevIsEditMode) => !prevIsEditMode) setIsEditMode((prevIsEditMode) => !prevIsEditMode)
} }
@ -450,7 +450,7 @@ export default function DraggableGridLayout({
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div <div
className="rounded-lg text-secondary-foreground bg-secondary hover:bg-muted cursor-pointer opacity-60 hover:opacity-100 transition-all duration-300" className="cursor-pointer rounded-lg bg-secondary text-secondary-foreground opacity-60 transition-all duration-300 hover:bg-muted hover:opacity-100"
onClick={() => onClick={() =>
setEditGroup((prevEditGroup) => !prevEditGroup) setEditGroup((prevEditGroup) => !prevEditGroup)
} }
@ -465,7 +465,7 @@ export default function DraggableGridLayout({
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div <div
className="rounded-lg text-secondary-foreground bg-secondary hover:bg-muted cursor-pointer opacity-60 hover:opacity-100 transition-all duration-300" className="cursor-pointer rounded-lg bg-secondary text-secondary-foreground opacity-60 transition-all duration-300 hover:bg-muted hover:opacity-100"
onClick={toggleFullscreen} onClick={toggleFullscreen}
> >
{fullscreen ? ( {fullscreen ? (
@ -492,10 +492,10 @@ export default function DraggableGridLayout({
function CornerCircles() { function CornerCircles() {
return ( return (
<> <>
<div className="absolute top-[-4px] left-[-4px] z-50 size-3 p-2 rounded-full bg-primary-variant outline-2 outline-muted text-background pointer-events-none" /> <div className="pointer-events-none absolute left-[-4px] top-[-4px] z-50 size-3 rounded-full bg-primary-variant p-2 text-background outline-2 outline-muted" />
<div className="absolute top-[-4px] right-[-4px] z-50 size-3 p-2 rounded-full bg-primary-variant outline-2 outline-muted text-background pointer-events-none" /> <div className="pointer-events-none absolute right-[-4px] top-[-4px] z-50 size-3 rounded-full bg-primary-variant p-2 text-background outline-2 outline-muted" />
<div className="absolute bottom-[-4px] right-[-4px] z-50 size-3 p-2 rounded-full bg-primary-variant outline-2 outline-muted text-background pointer-events-none" /> <div className="pointer-events-none absolute bottom-[-4px] right-[-4px] z-50 size-3 rounded-full bg-primary-variant p-2 text-background outline-2 outline-muted" />
<div className="absolute bottom-[-4px] left-[-4px] z-50 size-3 p-2 rounded-full bg-primary-variant outline-2 outline-muted text-background pointer-events-none" /> <div className="pointer-events-none absolute bottom-[-4px] left-[-4px] z-50 size-3 rounded-full bg-primary-variant p-2 text-background outline-2 outline-muted" />
</> </>
); );
} }

View File

@ -115,20 +115,20 @@ export default function LiveBirdseyeView() {
ref={mainRef} ref={mainRef}
className={ className={
fullscreen fullscreen
? `fixed inset-0 bg-black z-30` ? `fixed inset-0 z-30 bg-black`
: `size-full p-2 flex flex-col ${isMobile ? "landscape:flex-row" : ""}` : `flex size-full flex-col p-2 ${isMobile ? "landscape:flex-row" : ""}`
} }
> >
<div <div
className={ className={
fullscreen fullscreen
? `absolute right-32 top-1 z-40 ${isMobile ? "landscape:left-2 landscape:right-auto landscape:bottom-1 landscape:top-auto" : ""}` ? `absolute right-32 top-1 z-40 ${isMobile ? "landscape:bottom-1 landscape:left-2 landscape:right-auto landscape:top-auto" : ""}`
: `w-full h-12 flex flex-row items-center justify-between ${isMobile ? "landscape:w-min landscape:h-full landscape:flex-col" : ""}` : `flex h-12 w-full flex-row items-center justify-between ${isMobile ? "landscape:h-full landscape:w-min landscape:flex-col" : ""}`
} }
> >
{!fullscreen ? ( {!fullscreen ? (
<Button <Button
className={`rounded-lg flex items-center gap-2 ${isMobile ? "ml-2" : "ml-0"}`} className={`flex items-center gap-2 rounded-lg ${isMobile ? "ml-2" : "ml-0"}`}
size={isMobile ? "icon" : "sm"} size={isMobile ? "icon" : "sm"}
onClick={() => navigate(-1)} onClick={() => navigate(-1)}
> >
@ -140,7 +140,7 @@ export default function LiveBirdseyeView() {
)} )}
<TooltipProvider> <TooltipProvider>
<div <div
className={`flex flex-row items-center gap-2 mr-1 *:rounded-lg ${isMobile ? "landscape:flex-col" : ""}`} className={`mr-1 flex flex-row items-center gap-2 *:rounded-lg ${isMobile ? "landscape:flex-col" : ""}`}
> >
<CameraFeatureToggle <CameraFeatureToggle
className="p-2 md:p-0" className="p-2 md:p-0"

View File

@ -222,15 +222,15 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
ref={mainRef} ref={mainRef}
className={ className={
fullscreen fullscreen
? `fixed inset-0 bg-black z-30` ? `fixed inset-0 z-30 bg-black`
: `size-full p-2 flex flex-col ${isMobile ? "landscape:flex-row landscape:gap-1" : ""}` : `flex size-full flex-col p-2 ${isMobile ? "landscape:flex-row landscape:gap-1" : ""}`
} }
> >
<div <div
className={ className={
fullscreen fullscreen
? `absolute right-32 top-1 z-40 ${isMobile ? "landscape:left-2 landscape:right-auto landscape:bottom-1 landscape:top-auto" : ""}` ? `absolute right-32 top-1 z-40 ${isMobile ? "landscape:bottom-1 landscape:left-2 landscape:right-auto landscape:top-auto" : ""}`
: `w-full h-12 flex flex-row items-center justify-between ${isMobile ? "landscape:w-12 landscape:h-full landscape:flex-col" : ""}` : `flex h-12 w-full flex-row items-center justify-between ${isMobile ? "landscape:h-full landscape:w-12 landscape:flex-col" : ""}`
} }
> >
{!fullscreen ? ( {!fullscreen ? (
@ -344,7 +344,7 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
}} }}
> >
<div <div
className={`flex flex-col justify-center items-center ${growClassName}`} className={`flex flex-col items-center justify-center ${growClassName}`}
ref={clickOverlayRef} ref={clickOverlayRef}
onClick={handleOverlayClick} onClick={handleOverlayClick}
style={{ style={{
@ -435,7 +435,7 @@ function PtzControlPanel({
); );
return ( return (
<div className="absolute inset-x-2 md:left-[50%] md:-translate-x-[50%] bottom-[10%] flex flex-wrap md:flex-nowrap justify-center items-center gap-1"> <div className="absolute inset-x-2 bottom-[10%] flex flex-wrap items-center justify-center gap-1 md:left-[50%] md:-translate-x-[50%] md:flex-nowrap">
{ptz?.features?.includes("pt") && ( {ptz?.features?.includes("pt") && (
<> <>
<Button <Button
@ -637,7 +637,7 @@ function FrigateCameraFeatures({
title={`${camera} Settings`} title={`${camera} Settings`}
/> />
</DrawerTrigger> </DrawerTrigger>
<DrawerContent className="px-2 py-4 flex flex-col gap-3 rounded-2xl"> <DrawerContent className="flex flex-col gap-3 rounded-2xl px-2 py-4">
<FilterSwitch <FilterSwitch
label="Object Detection" label="Object Detection"
isChecked={detectState == "ON"} isChecked={detectState == "ON"}

View File

@ -155,10 +155,10 @@ export default function LiveDashboardView({
const birdseyeConfig = useMemo(() => config?.birdseye, [config]); const birdseyeConfig = useMemo(() => config?.birdseye, [config]);
return ( return (
<div className="size-full p-2 overflow-y-auto" ref={containerRef}> <div className="size-full overflow-y-auto p-2" ref={containerRef}>
{isMobile && ( {isMobile && (
<div className="h-11 relative flex items-center justify-between"> <div className="relative flex h-11 items-center justify-between">
<Logo className="absolute inset-x-1/2 -translate-x-1/2 h-8" /> <Logo className="absolute inset-x-1/2 h-8 -translate-x-1/2" />
<div className="max-w-[45%]"> <div className="max-w-[45%]">
<CameraGroupSelector /> <CameraGroupSelector />
</div> </div>
@ -167,7 +167,7 @@ export default function LiveDashboardView({
<Button <Button
className={`p-1 ${ className={`p-1 ${
mobileLayout == "grid" mobileLayout == "grid"
? "bg-blue-900 focus:bg-blue-900 bg-opacity-60 focus:bg-opacity-60" ? "bg-blue-900 bg-opacity-60 focus:bg-blue-900 focus:bg-opacity-60"
: "bg-secondary" : "bg-secondary"
}`} }`}
size="xs" size="xs"
@ -178,7 +178,7 @@ export default function LiveDashboardView({
<Button <Button
className={`p-1 ${ className={`p-1 ${
mobileLayout == "list" mobileLayout == "list"
? "bg-blue-900 focus:bg-blue-900 bg-opacity-60 focus:bg-opacity-60" ? "bg-blue-900 bg-opacity-60 focus:bg-blue-900 focus:bg-opacity-60"
: "bg-secondary" : "bg-secondary"
}`} }`}
size="xs" size="xs"
@ -194,8 +194,8 @@ export default function LiveDashboardView({
className={cn( className={cn(
"p-1", "p-1",
isEditMode isEditMode
? "text-primary bg-selected" ? "bg-selected text-primary"
: "text-secondary-foreground bg-secondary", : "bg-secondary text-secondary-foreground",
)} )}
size="xs" size="xs"
onClick={() => onClick={() =>
@ -212,7 +212,7 @@ export default function LiveDashboardView({
{events && events.length > 0 && ( {events && events.length > 0 && (
<ScrollArea> <ScrollArea>
<TooltipProvider> <TooltipProvider>
<div className="px-1 flex gap-2 items-center"> <div className="flex items-center gap-2 px-1">
{events.map((event) => { {events.map((event) => {
return <AnimatedEventCard key={event.id} event={event} />; return <AnimatedEventCard key={event.id} event={event} />;
})} })}
@ -224,7 +224,7 @@ export default function LiveDashboardView({
{!cameraGroup || cameraGroup == "default" || isMobileOnly ? ( {!cameraGroup || cameraGroup == "default" || isMobileOnly ? (
<div <div
className={`mt-2 px-2 grid ${mobileLayout == "grid" ? "grid-cols-2 xl:grid-cols-3 3xl:grid-cols-4" : ""} gap-2 md:gap-4`} className={`mt-2 grid px-2 ${mobileLayout == "grid" ? "grid-cols-2 xl:grid-cols-3 3xl:grid-cols-4" : ""} gap-2 md:gap-4`}
> >
{includeBirdseye && birdseyeConfig?.enabled && ( {includeBirdseye && birdseyeConfig?.enabled && (
<BirdseyeLivePlayer <BirdseyeLivePlayer
@ -247,7 +247,7 @@ export default function LiveDashboardView({
<LivePlayer <LivePlayer
cameraRef={cameraRef} cameraRef={cameraRef}
key={camera.name} key={camera.name}
className={`${grow} rounded-lg md:rounded-2xl bg-black`} className={`${grow} rounded-lg bg-black md:rounded-2xl`}
windowVisible={ windowVisible={
windowVisible && visibleCameras.includes(camera.name) windowVisible && visibleCameras.includes(camera.name)
} }

View File

@ -204,11 +204,11 @@ export default function CameraMetrics({
}, [statsHistory]); }, [statsHistory]);
return ( return (
<div className="size-full mt-4 flex flex-col gap-3 overflow-y-auto"> <div className="mt-4 flex size-full flex-col gap-3 overflow-y-auto">
<div className="text-muted-foreground text-sm font-medium">Overview</div> <div className="text-sm font-medium text-muted-foreground">Overview</div>
<div className="grid grid-cols-1 md:grid-cols-3"> <div className="grid grid-cols-1 md:grid-cols-3">
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">Frames / Detections</div> <div className="mb-5">Frames / Detections</div>
<CameraLineGraph <CameraLineGraph
graphId="overall-stats" graphId="overall-stats"
@ -219,21 +219,21 @@ export default function CameraMetrics({
/> />
</div> </div>
) : ( ) : (
<Skeleton className="w-full rounded-lg md:rounded-2xl h-32" /> <Skeleton className="h-32 w-full rounded-lg md:rounded-2xl" />
)} )}
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 gap-6 md:grid-cols-2">
{config && {config &&
Object.values(config.cameras).map((camera) => { Object.values(config.cameras).map((camera) => {
if (camera.enabled) { if (camera.enabled) {
return ( return (
<div className="w-full flex flex-col gap-3"> <div className="flex w-full flex-col gap-3">
<div className="capitalize text-muted-foreground text-sm font-medium"> <div className="text-sm font-medium capitalize text-muted-foreground">
{camera.name.replaceAll("_", " ")} {camera.name.replaceAll("_", " ")}
</div> </div>
<div key={camera.name} className="grid sm:grid-cols-2 gap-2"> <div key={camera.name} className="grid gap-2 sm:grid-cols-2">
{Object.keys(cameraCpuSeries).includes(camera.name) ? ( {Object.keys(cameraCpuSeries).includes(camera.name) ? (
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">CPU</div> <div className="mb-5">CPU</div>
<CameraLineGraph <CameraLineGraph
graphId={`${camera.name}-cpu`} graphId={`${camera.name}-cpu`}
@ -246,10 +246,10 @@ export default function CameraMetrics({
/> />
</div> </div>
) : ( ) : (
<Skeleton className="size-full aspect-video" /> <Skeleton className="aspect-video size-full" />
)} )}
{Object.keys(cameraFpsSeries).includes(camera.name) ? ( {Object.keys(cameraFpsSeries).includes(camera.name) ? (
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">Frames / Detections</div> <div className="mb-5">Frames / Detections</div>
<CameraLineGraph <CameraLineGraph
graphId={`${camera.name}-dps`} graphId={`${camera.name}-dps`}
@ -262,7 +262,7 @@ export default function CameraMetrics({
/> />
</div> </div>
) : ( ) : (
<Skeleton className="size-full aspect-video" /> <Skeleton className="aspect-video size-full" />
)} )}
</div> </div>
</div> </div>

View File

@ -344,15 +344,15 @@ export default function GeneralMetrics({
<> <>
<VainfoDialog showVainfo={showVainfo} setShowVainfo={setShowVainfo} /> <VainfoDialog showVainfo={showVainfo} setShowVainfo={setShowVainfo} />
<div className="size-full mt-4 flex flex-col overflow-y-auto"> <div className="mt-4 flex size-full flex-col overflow-y-auto">
<div className="text-muted-foreground text-sm font-medium"> <div className="text-sm font-medium text-muted-foreground">
Detectors Detectors
</div> </div>
<div <div
className={`w-full mt-4 grid grid-cols-1 gap-2 ${detTempSeries == undefined ? "sm:grid-cols-3" : "sm:grid-cols-4"}`} className={`mt-4 grid w-full grid-cols-1 gap-2 ${detTempSeries == undefined ? "sm:grid-cols-3" : "sm:grid-cols-4"}`}
> >
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">Detector Inference Speed</div> <div className="mb-5">Detector Inference Speed</div>
{detInferenceTimeSeries.map((series) => ( {detInferenceTimeSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
@ -367,12 +367,12 @@ export default function GeneralMetrics({
))} ))}
</div> </div>
) : ( ) : (
<Skeleton className="w-full rounded-lg md:rounded-2xl aspect-video" /> <Skeleton className="aspect-video w-full rounded-lg md:rounded-2xl" />
)} )}
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<> <>
{detTempSeries && ( {detTempSeries && (
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">Detector Temperature</div> <div className="mb-5">Detector Temperature</div>
{detTempSeries.map((series) => ( {detTempSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
@ -389,10 +389,10 @@ export default function GeneralMetrics({
)} )}
</> </>
) : ( ) : (
<Skeleton className="w-full aspect-video" /> <Skeleton className="aspect-video w-full" />
)} )}
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">Detector CPU Usage</div> <div className="mb-5">Detector CPU Usage</div>
{detCpuSeries.map((series) => ( {detCpuSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
@ -407,10 +407,10 @@ export default function GeneralMetrics({
))} ))}
</div> </div>
) : ( ) : (
<Skeleton className="w-full aspect-video" /> <Skeleton className="aspect-video w-full" />
)} )}
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">Detector Memory Usage</div> <div className="mb-5">Detector Memory Usage</div>
{detMemSeries.map((series) => ( {detMemSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
@ -425,14 +425,14 @@ export default function GeneralMetrics({
))} ))}
</div> </div>
) : ( ) : (
<Skeleton className="w-full aspect-video" /> <Skeleton className="aspect-video w-full" />
)} )}
</div> </div>
{(statsHistory.length == 0 || statsHistory[0].gpu_usages) && ( {(statsHistory.length == 0 || statsHistory[0].gpu_usages) && (
<> <>
<div className="mt-4 flex items-center justify-between"> <div className="mt-4 flex items-center justify-between">
<div className="text-muted-foreground text-sm font-medium"> <div className="text-sm font-medium text-muted-foreground">
GPUs GPUs
</div> </div>
{canGetGpuInfo && ( {canGetGpuInfo && (
@ -445,9 +445,9 @@ export default function GeneralMetrics({
</Button> </Button>
)} )}
</div> </div>
<div className=" mt-4 grid grid-cols-1 sm:grid-cols-2 gap-2"> <div className=" mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2">
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">GPU Usage</div> <div className="mb-5">GPU Usage</div>
{gpuSeries.map((series) => ( {gpuSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
@ -462,12 +462,12 @@ export default function GeneralMetrics({
))} ))}
</div> </div>
) : ( ) : (
<Skeleton className="w-full aspect-video" /> <Skeleton className="aspect-video w-full" />
)} )}
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<> <>
{gpuMemSeries && ( {gpuMemSeries && (
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">GPU Memory</div> <div className="mb-5">GPU Memory</div>
{gpuMemSeries.map((series) => ( {gpuMemSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
@ -484,18 +484,18 @@ export default function GeneralMetrics({
)} )}
</> </>
) : ( ) : (
<Skeleton className="w-full aspect-video" /> <Skeleton className="aspect-video w-full" />
)} )}
</div> </div>
</> </>
)} )}
<div className="mt-4 text-muted-foreground text-sm font-medium"> <div className="mt-4 text-sm font-medium text-muted-foreground">
Other Processes Other Processes
</div> </div>
<div className="mt-4 grid grid-cols-1 sm:grid-cols-2 gap-2"> <div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2">
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">Process CPU Usage</div> <div className="mb-5">Process CPU Usage</div>
{otherProcessCpuSeries.map((series) => ( {otherProcessCpuSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
@ -510,10 +510,10 @@ export default function GeneralMetrics({
))} ))}
</div> </div>
) : ( ) : (
<Skeleton className="w-full aspect-tall" /> <Skeleton className="aspect-tall w-full" />
)} )}
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">Process Memory Usage</div> <div className="mb-5">Process Memory Usage</div>
{otherProcessMemSeries.map((series) => ( {otherProcessMemSeries.map((series) => (
<ThresholdBarGraph <ThresholdBarGraph
@ -528,7 +528,7 @@ export default function GeneralMetrics({
))} ))}
</div> </div>
) : ( ) : (
<Skeleton className="w-full aspect-tall" /> <Skeleton className="aspect-tall w-full" />
)} )}
</div> </div>
</div> </div>

View File

@ -42,10 +42,10 @@ export default function StorageMetrics({
} }
return ( return (
<div className="size-full mt-4 flex flex-col overflow-y-auto"> <div className="mt-4 flex size-full flex-col overflow-y-auto">
<div className="text-muted-foreground text-sm font-medium">Overview</div> <div className="text-sm font-medium text-muted-foreground">Overview</div>
<div className="mt-4 grid grid-cols-1 sm:grid-cols-3 gap-2"> <div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-3">
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl flex-col"> <div className="flex-col rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">Recordings</div> <div className="mb-5">Recordings</div>
<StorageGraph <StorageGraph
graphId="general-recordings" graphId="general-recordings"
@ -53,7 +53,7 @@ export default function StorageMetrics({
total={totalStorage.total} total={totalStorage.total}
/> />
</div> </div>
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl flex-col"> <div className="flex-col rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">/tmp/cache</div> <div className="mb-5">/tmp/cache</div>
<StorageGraph <StorageGraph
graphId="general-cache" graphId="general-cache"
@ -61,7 +61,7 @@ export default function StorageMetrics({
total={stats.service.storage["/tmp/cache"]["total"]} total={stats.service.storage["/tmp/cache"]["total"]}
/> />
</div> </div>
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl flex-col"> <div className="flex-col rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">/dev/shm</div> <div className="mb-5">/dev/shm</div>
<StorageGraph <StorageGraph
graphId="general-shared-memory" graphId="general-shared-memory"
@ -70,12 +70,12 @@ export default function StorageMetrics({
/> />
</div> </div>
</div> </div>
<div className="mt-4 text-muted-foreground text-sm font-medium"> <div className="mt-4 text-sm font-medium text-muted-foreground">
Camera Storage Camera Storage
</div> </div>
<div className="mt-4 grid grid-cols-1 sm:grid-cols-3 gap-2"> <div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-3">
{Object.keys(cameraStorage).map((camera) => ( {Object.keys(cameraStorage).map((camera) => (
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl flex-col"> <div className="flex-col rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5 capitalize">{camera.replaceAll("_", " ")}</div> <div className="mb-5 capitalize">{camera.replaceAll("_", " ")}</div>
<StorageGraph <StorageGraph
graphId={`${camera}-storage`} graphId={`${camera}-storage`}