mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 08:34:21 -06:00
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:
parent
b10ae68c1f
commit
1757f4cb04
@ -44,6 +44,12 @@ module.exports = {
|
||||
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
|
||||
],
|
||||
"no-console": "error",
|
||||
"prettier/prettier": [
|
||||
"warn",
|
||||
{
|
||||
plugins: ["prettier-plugin-tailwindcss"],
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
|
3
web/.prettierrc
Normal file
3
web/.prettierrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
91
web/package-lock.json
generated
91
web/package-lock.json
generated
@ -96,9 +96,10 @@
|
||||
"fake-indexeddb": "^5.0.2",
|
||||
"jest-websocket-mock": "^2.5.0",
|
||||
"jsdom": "^24.0.0",
|
||||
"msw": "^2.2.14",
|
||||
"msw": "^2.3.0",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.14",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.11",
|
||||
@ -848,9 +849,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mswjs/interceptors": {
|
||||
"version": "0.26.15",
|
||||
"resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.26.15.tgz",
|
||||
"integrity": "sha512-HM47Lu1YFmnYHKMBynFfjCp0U/yRskHj/8QEJW0CBEPOlw8Gkmjfll+S9b8M7V5CNDw2/ciRxjjnWeaCiblSIQ==",
|
||||
"version": "0.29.1",
|
||||
"resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.29.1.tgz",
|
||||
"integrity": "sha512-3rDakgJZ77+RiQUuSK69t1F0m8BQKA8Vh5DCS5V0DWvNY67zob2JhhQrhCO0AKLGINTRSFd1tBaHcJTkhefoSw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@open-draft/deferred-promise": "^2.2.0",
|
||||
@ -5595,9 +5596,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/msw": {
|
||||
"version": "2.2.14",
|
||||
"resolved": "https://registry.npmjs.org/msw/-/msw-2.2.14.tgz",
|
||||
"integrity": "sha512-64i8rNCa1xzDK8ZYsTrVMli05D687jty8+Th+PU5VTbJ2/4P7fkQFVyDQ6ZFT5FrNR8z2BHhbY47fKNvfHrumA==",
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/msw/-/msw-2.3.0.tgz",
|
||||
"integrity": "sha512-cDr1q/QTMzaWhY8n9lpGhceY209k29UZtdTgJ3P8Bzne3TSMchX2EM/ldvn4ATLOktpCefCU2gcEgzHc31GTPw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
@ -5605,7 +5606,7 @@
|
||||
"@bundled-es-modules/statuses": "^1.0.1",
|
||||
"@inquirer/confirm": "^3.0.0",
|
||||
"@mswjs/cookies": "^1.1.0",
|
||||
"@mswjs/interceptors": "^0.26.14",
|
||||
"@mswjs/interceptors": "^0.29.0",
|
||||
"@open-draft/until": "^2.1.0",
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/statuses": "^2.0.4",
|
||||
@ -6181,6 +6182,80 @@
|
||||
"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": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
|
||||
|
@ -104,6 +104,7 @@
|
||||
"msw": "^2.3.0",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.14",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.11",
|
||||
|
@ -31,7 +31,7 @@ function App() {
|
||||
{isMobile && <Bottombar />}
|
||||
<div
|
||||
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>
|
||||
<Routes>
|
||||
|
@ -55,11 +55,11 @@ export default function Statusbar() {
|
||||
}, [potentialProblems, addMessage, clearMessages]);
|
||||
|
||||
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="h-full flex items-center gap-2">
|
||||
<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="flex h-full items-center gap-2">
|
||||
{cpuPercent && (
|
||||
<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
|
||||
className={`size-2 ${
|
||||
cpuPercent < 50
|
||||
@ -99,7 +99,7 @@ export default function Statusbar() {
|
||||
{" "}
|
||||
<div
|
||||
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
|
||||
className={`size-2 ${
|
||||
@ -116,20 +116,20 @@ export default function Statusbar() {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="h-full flex items-center gap-2">
|
||||
<div className="flex h-full items-center gap-2">
|
||||
{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" />
|
||||
System is healthy
|
||||
</div>
|
||||
) : (
|
||||
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) => {
|
||||
const message = (
|
||||
<div
|
||||
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
|
||||
className={`size-5 ${color || "text-danger"}`}
|
||||
|
@ -5,7 +5,7 @@ type 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;
|
||||
|
@ -27,11 +27,11 @@ export default function TimelineBar({
|
||||
|
||||
return (
|
||||
<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}
|
||||
>
|
||||
{graphData != undefined && (
|
||||
<div className="relative w-full h-8 flex">
|
||||
<div className="relative flex h-8 w-full">
|
||||
{getHourBlocks().map((idx) => {
|
||||
return (
|
||||
<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 ml-1 bottom-0 text-sm text-gray-500">
|
||||
<div className="absolute bottom-0 left-0 top-0 border-l border-gray-500 align-bottom">
|
||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
||||
{formatUnixTimestampToDateTime(startTime, {
|
||||
strftime_fmt:
|
||||
config?.ui.time_format == "24hour" ? "%H:00" : "%I:00%P",
|
||||
@ -56,8 +56,8 @@ export default function TimelineBar({
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute left-[8.3%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||
<div className="absolute bottom-0 left-[8.3%] top-0 border-l border-gray-500 align-bottom">
|
||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
||||
{formatUnixTimestampToDateTime(startTime, {
|
||||
strftime_fmt:
|
||||
config?.ui.time_format == "24hour" ? "%H:05" : "%I:05%P",
|
||||
@ -66,8 +66,8 @@ export default function TimelineBar({
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute left-[16.7%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||
<div className="absolute bottom-0 left-[16.7%] top-0 border-l border-gray-500 align-bottom">
|
||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
||||
{formatUnixTimestampToDateTime(startTime, {
|
||||
strftime_fmt:
|
||||
config?.ui.time_format == "24hour" ? "%H:10" : "%I:10%P",
|
||||
@ -76,8 +76,8 @@ export default function TimelineBar({
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute left-[25%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||
<div className="absolute bottom-0 left-[25%] top-0 border-l border-gray-500 align-bottom">
|
||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
||||
{formatUnixTimestampToDateTime(startTime, {
|
||||
strftime_fmt:
|
||||
config?.ui.time_format == "24hour" ? "%H:15" : "%I:15%P",
|
||||
@ -86,8 +86,8 @@ export default function TimelineBar({
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute left-[33.3%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||
<div className="absolute bottom-0 left-[33.3%] top-0 border-l border-gray-500 align-bottom">
|
||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
||||
{formatUnixTimestampToDateTime(startTime, {
|
||||
strftime_fmt:
|
||||
config?.ui.time_format == "24hour" ? "%H:20" : "%I:20%P",
|
||||
@ -96,8 +96,8 @@ export default function TimelineBar({
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute left-[41.7%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||
<div className="absolute bottom-0 left-[41.7%] top-0 border-l border-gray-500 align-bottom">
|
||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
||||
{formatUnixTimestampToDateTime(startTime, {
|
||||
strftime_fmt:
|
||||
config?.ui.time_format == "24hour" ? "%H:25" : "%I:25%P",
|
||||
@ -106,8 +106,8 @@ export default function TimelineBar({
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute left-[50%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||
<div className="absolute bottom-0 left-[50%] top-0 border-l border-gray-500 align-bottom">
|
||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
||||
{formatUnixTimestampToDateTime(startTime, {
|
||||
strftime_fmt:
|
||||
config?.ui.time_format == "24hour" ? "%H:30" : "%I:30%P",
|
||||
@ -116,8 +116,8 @@ export default function TimelineBar({
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute left-[58.3%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||
<div className="absolute bottom-0 left-[58.3%] top-0 border-l border-gray-500 align-bottom">
|
||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
||||
{formatUnixTimestampToDateTime(startTime, {
|
||||
strftime_fmt:
|
||||
config?.ui.time_format == "24hour" ? "%H:35" : "%I:35%P",
|
||||
@ -126,8 +126,8 @@ export default function TimelineBar({
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute left-[66.7%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||
<div className="absolute bottom-0 left-[66.7%] top-0 border-l border-gray-500 align-bottom">
|
||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
||||
{formatUnixTimestampToDateTime(startTime, {
|
||||
strftime_fmt:
|
||||
config?.ui.time_format == "24hour" ? "%H:40" : "%I:40%P",
|
||||
@ -136,8 +136,8 @@ export default function TimelineBar({
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute left-[75%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||
<div className="absolute bottom-0 left-[75%] top-0 border-l border-gray-500 align-bottom">
|
||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
||||
{formatUnixTimestampToDateTime(startTime, {
|
||||
strftime_fmt:
|
||||
config?.ui.time_format == "24hour" ? "%H:45" : "%I:45%P",
|
||||
@ -146,8 +146,8 @@ export default function TimelineBar({
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute left-[83.3%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||
<div className="absolute bottom-0 left-[83.3%] top-0 border-l border-gray-500 align-bottom">
|
||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
||||
{formatUnixTimestampToDateTime(startTime, {
|
||||
strftime_fmt:
|
||||
config?.ui.time_format == "24hour" ? "%H:50" : "%I:50%P",
|
||||
@ -156,8 +156,8 @@ export default function TimelineBar({
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute left-[91.7%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||
<div className="absolute bottom-0 left-[91.7%] top-0 border-l border-gray-500 align-bottom">
|
||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
||||
{formatUnixTimestampToDateTime(startTime, {
|
||||
strftime_fmt:
|
||||
config?.ui.time_format == "24hour" ? "%H:55" : "%I:55%P",
|
||||
|
@ -45,7 +45,7 @@ export default function CameraImage({
|
||||
{enabled ? (
|
||||
<img
|
||||
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={() => {
|
||||
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!
|
||||
</div>
|
||||
)}
|
||||
{!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 />
|
||||
</div>
|
||||
) : null}
|
||||
|
@ -56,7 +56,7 @@ export default function DebugCameraImage({
|
||||
cameraClasses="relative w-full h-full flex justify-center"
|
||||
/>
|
||||
<Button onClick={handleToggleSettings} variant="link" size="sm">
|
||||
<span className="w-5 h-5">
|
||||
<span className="h-5 w-5">
|
||||
<LuSettings />
|
||||
</span>{" "}
|
||||
<span>{showSettings ? "Hide" : "Show"} Options</span>
|
||||
@ -85,7 +85,7 @@ type DebugSettingsProps = {
|
||||
|
||||
function DebugSettings({ handleSetOption, options }: DebugSettingsProps) {
|
||||
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">
|
||||
<Switch
|
||||
id="bbox"
|
||||
|
@ -96,7 +96,7 @@ export default function CameraImage({
|
||||
|
||||
return (
|
||||
<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}
|
||||
>
|
||||
{enabled ? (
|
||||
@ -108,7 +108,7 @@ export default function CameraImage({
|
||||
width={scaledWidth}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-center pt-6">
|
||||
<div className="pt-6 text-center">
|
||||
Camera is disabled in config, no stream or snapshot available!
|
||||
</div>
|
||||
)}
|
||||
|
@ -67,13 +67,13 @@ export function AnimatedEventCard({ event }: AnimatedEventCardProps) {
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className="h-24 4k:h-32 relative"
|
||||
className="relative h-24 4k:h-32"
|
||||
style={{
|
||||
aspectRatio: aspectRatio,
|
||||
}}
|
||||
>
|
||||
<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}
|
||||
>
|
||||
{previews ? (
|
||||
@ -102,8 +102,8 @@ export function AnimatedEventCard({ event }: AnimatedEventCardProps) {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute bottom-0 inset-x-0 h-6 bg-gradient-to-t from-slate-900/50 to-transparent rounded">
|
||||
<div className="w-full absolute left-1 bottom-0 text-xs text-white">
|
||||
<div className="absolute inset-x-0 bottom-0 h-6 rounded bg-gradient-to-t from-slate-900/50 to-transparent">
|
||||
<div className="absolute bottom-0 left-1 w-full text-xs text-white">
|
||||
<TimeAgo time={event.start_time * 1000} dense />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -106,7 +106,7 @@ export default function ExportCard({
|
||||
|
||||
<div
|
||||
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,
|
||||
)}
|
||||
onMouseEnter={
|
||||
@ -127,19 +127,19 @@ export default function ExportCard({
|
||||
>
|
||||
{hovered && (
|
||||
<>
|
||||
<div className="absolute inset-0 z-10 bg-black bg-opacity-60 rounded-lg md:rounded-2xl" />
|
||||
<div className="absolute top-1 right-1 flex items-center gap-2">
|
||||
<div className="absolute inset-0 z-10 rounded-lg bg-black bg-opacity-60 md:rounded-2xl" />
|
||||
<div className="absolute right-1 top-1 flex items-center gap-2">
|
||||
<a
|
||||
className="z-20"
|
||||
download
|
||||
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" />
|
||||
</Chip>
|
||||
</a>
|
||||
<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={() =>
|
||||
setEditName({ original: exportedRecording.name, update: "" })
|
||||
}
|
||||
@ -147,7 +147,7 @@ export default function ExportCard({
|
||||
<MdEditSquare className="size-4 text-white" />
|
||||
</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={() =>
|
||||
onDelete({
|
||||
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>
|
||||
</div>
|
||||
|
||||
<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"
|
||||
onClick={() => {
|
||||
onSelect(exportedRecording);
|
||||
@ -176,20 +176,20 @@ export default function ExportCard({
|
||||
<>
|
||||
{exportedRecording.thumb_path.length > 0 ? (
|
||||
<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", "")}
|
||||
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 && (
|
||||
<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="flex h-full justify-between items-end mx-3 pb-1 text-white text-sm capitalize">
|
||||
<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="mx-3 flex h-full items-end justify-between pb-1 text-sm capitalize text-white">
|
||||
{exportedRecording.name.replaceAll("_", " ")}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -35,7 +35,7 @@ export default function ReviewCard({
|
||||
|
||||
return (
|
||||
<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}
|
||||
>
|
||||
<ImageLoadingIndicator
|
||||
@ -51,8 +51,8 @@ export default function ReviewCard({
|
||||
onImgLoad();
|
||||
}}
|
||||
/>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex justify-evenly items-center gap-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-evenly gap-1">
|
||||
{event.data.objects.map((object) => {
|
||||
return getIconForLabel(object, "size-3 text-white");
|
||||
})}
|
||||
|
@ -41,7 +41,7 @@ export default function CameraFeatureToggle({
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
className,
|
||||
"flex flex-col justify-center items-center",
|
||||
"flex flex-col items-center justify-center",
|
||||
variants[variant][isActive ? "active" : "inactive"],
|
||||
)}
|
||||
>
|
||||
|
@ -27,13 +27,13 @@ export default function NewReviewData({
|
||||
|
||||
return (
|
||||
<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
|
||||
className={`${
|
||||
hasUpdate
|
||||
? "animate-in slide-in-from-top duration-500"
|
||||
? "duration-500 animate-in slide-in-from-top"
|
||||
: "invisible"
|
||||
} text-center mt-5 mx-auto bg-gray-400 text-white`}
|
||||
} mx-auto mt-5 bg-gray-400 text-center text-white`}
|
||||
onClick={() => {
|
||||
pullLatestData();
|
||||
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
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -125,8 +125,8 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
||||
<Button
|
||||
className={
|
||||
group == "default"
|
||||
? "text-selected bg-blue-900 focus:bg-blue-900 bg-opacity-60 focus:bg-opacity-60"
|
||||
: "text-secondary-foreground bg-secondary focus:text-secondary-foreground focus:bg-secondary"
|
||||
? "bg-blue-900 bg-opacity-60 text-selected focus:bg-blue-900 focus:bg-opacity-60"
|
||||
: "bg-secondary text-secondary-foreground focus:bg-secondary focus:text-secondary-foreground"
|
||||
}
|
||||
size="xs"
|
||||
onClick={() => (group ? setGroup("default", true) : null)}
|
||||
@ -149,8 +149,8 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
||||
<Button
|
||||
className={
|
||||
group == name
|
||||
? "text-selected bg-blue-900 focus:bg-blue-900 bg-opacity-60 focus:bg-opacity-60"
|
||||
: "text-secondary-foreground bg-secondary"
|
||||
? "bg-blue-900 bg-opacity-60 text-selected focus:bg-blue-900 focus:bg-opacity-60"
|
||||
: "bg-secondary text-secondary-foreground"
|
||||
}
|
||||
size="xs"
|
||||
onClick={() => setGroup(name, group != "default")}
|
||||
@ -177,7 +177,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
||||
})}
|
||||
|
||||
<Button
|
||||
className="text-muted-foreground bg-secondary"
|
||||
className="bg-secondary text-muted-foreground"
|
||||
size="xs"
|
||||
onClick={() => setAddGroup(true)}
|
||||
>
|
||||
@ -308,16 +308,16 @@ function NewGroupDialog({
|
||||
}}
|
||||
>
|
||||
<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" && (
|
||||
<>
|
||||
<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>
|
||||
<Button
|
||||
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={() => {
|
||||
setEditState("add");
|
||||
}}
|
||||
@ -338,7 +338,7 @@ function NewGroupDialog({
|
||||
|
||||
{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>
|
||||
{editState == "add" ? "Add" : "Edit"} Camera Group
|
||||
</DialogTitle>
|
||||
@ -398,10 +398,10 @@ export function EditGroupDialog({
|
||||
}}
|
||||
>
|
||||
<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="flex flex-row justify-between items-center mb-3">
|
||||
<div className="my-4 flex flex-col overflow-y-auto">
|
||||
<div className="mb-3 flex flex-row items-center justify-between">
|
||||
<DialogTitle>Edit Camera Group</DialogTitle>
|
||||
</div>
|
||||
<CameraGroupEdit
|
||||
@ -440,7 +440,7 @@ export function CameraGroupRow({
|
||||
<>
|
||||
<div
|
||||
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`}>
|
||||
<p className="cursor-default">{group[0]}</p>
|
||||
@ -482,7 +482,7 @@ export function CameraGroupRow({
|
||||
</>
|
||||
)}
|
||||
{!isMobile && (
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<IconWrapper
|
||||
@ -641,7 +641,7 @@ export function CameraGroupEdit({
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<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..."
|
||||
disabled={editingGroup !== undefined}
|
||||
{...field}
|
||||
@ -652,8 +652,8 @@ export function CameraGroupEdit({
|
||||
)}
|
||||
/>
|
||||
|
||||
<Separator className="flex my-2 bg-secondary" />
|
||||
<div className="max-h-[25dvh] md:max-h-[40dvh] overflow-y-auto">
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
<div className="max-h-[25dvh] overflow-y-auto md:max-h-[40dvh]">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cameras"
|
||||
@ -686,7 +686,7 @@ export function CameraGroupEdit({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Separator className="flex my-2 bg-secondary" />
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
<FormField
|
||||
control={form.control}
|
||||
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">
|
||||
<Button type="button" className="flex flex-1" onClick={onCancel}>
|
||||
|
@ -14,9 +14,9 @@ export default function FilterSwitch({
|
||||
onCheckedChange,
|
||||
}: FilterSwitchProps) {
|
||||
return (
|
||||
<div className="flex justify-between items-center gap-1">
|
||||
<div className="flex items-center justify-between gap-1">
|
||||
<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}
|
||||
>
|
||||
{label}
|
||||
|
@ -19,7 +19,7 @@ export function LogLevelFilterButton({
|
||||
const trigger = (
|
||||
<Button size="sm" className="flex items-center gap-2">
|
||||
<FaFilter className="text-secondary-foreground" />
|
||||
<div className="hidden md:block text-primary">Filter</div>
|
||||
<div className="hidden text-primary md:block">Filter</div>
|
||||
</Button>
|
||||
);
|
||||
const content = (
|
||||
@ -33,7 +33,7 @@ export function LogLevelFilterButton({
|
||||
return (
|
||||
<Drawer>
|
||||
<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}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
@ -59,9 +59,9 @@ export function GeneralFilterContent({
|
||||
return (
|
||||
<>
|
||||
<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="mx-2 text-primary cursor-pointer"
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allLabels"
|
||||
>
|
||||
All Logs
|
||||
@ -80,9 +80,9 @@ export function GeneralFilterContent({
|
||||
<DropdownMenuSeparator />
|
||||
<div className="my-2.5 flex flex-col gap-2.5">
|
||||
{["debug", "info", "warning", "error"].map((item) => (
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label
|
||||
className="w-full mx-2 text-primary capitalize cursor-pointer"
|
||||
className="mx-2 w-full cursor-pointer capitalize text-primary"
|
||||
htmlFor={item}
|
||||
>
|
||||
{item.replaceAll("_", " ")}
|
||||
|
@ -35,12 +35,12 @@ export default function ReviewActionGroup({
|
||||
}, [selectedReviews, setSelectedReviews, pullLatestData]);
|
||||
|
||||
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="mx-1 flex justify-center items-center text-sm text-muted-foreground">
|
||||
<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 items-center justify-center text-sm text-muted-foreground">
|
||||
<div className="p-1">{`${selectedReviews.length} selected`}</div>
|
||||
<div className="p-1">{"|"}</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}
|
||||
>
|
||||
Unselect
|
||||
@ -49,7 +49,7 @@ export default function ReviewActionGroup({
|
||||
<div className="flex items-center gap-1 md:gap-2">
|
||||
{selectedReviews.length == 1 && (
|
||||
<Button
|
||||
className="p-2 flex items-center gap-2"
|
||||
className="flex items-center gap-2 p-2"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
onExport(selectedReviews[0]);
|
||||
@ -61,7 +61,7 @@ export default function ReviewActionGroup({
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className="p-2 flex items-center gap-2"
|
||||
className="flex items-center gap-2 p-2"
|
||||
size="sm"
|
||||
onClick={onMarkAsReviewed}
|
||||
>
|
||||
@ -69,7 +69,7 @@ export default function ReviewActionGroup({
|
||||
{isDesktop && <div className="text-primary">Mark as reviewed</div>}
|
||||
</Button>
|
||||
<Button
|
||||
className="p-2 flex items-center gap-2"
|
||||
className="flex items-center gap-2 p-2"
|
||||
size="sm"
|
||||
onClick={onDelete}
|
||||
>
|
||||
|
@ -263,7 +263,7 @@ export function CamerasFilterButton({
|
||||
<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
|
||||
isChecked={currentCameras == undefined}
|
||||
label="All Cameras"
|
||||
@ -280,7 +280,7 @@ export function CamerasFilterButton({
|
||||
return (
|
||||
<div
|
||||
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])}
|
||||
>
|
||||
{name}
|
||||
@ -321,7 +321,7 @@ export function CamerasFilterButton({
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenuSeparator className="my-2" />
|
||||
<div className="p-2 flex justify-evenly items-center">
|
||||
<div className="flex items-center justify-evenly p-2">
|
||||
<Button
|
||||
variant="select"
|
||||
onClick={() => {
|
||||
@ -394,7 +394,7 @@ function ShowReviewFilter({
|
||||
);
|
||||
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
|
||||
id="reviewed"
|
||||
checked={showReviewedSwitch == 1}
|
||||
@ -408,7 +408,7 @@ function ShowReviewFilter({
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="block md:hidden duration-0"
|
||||
className="block duration-0 md:hidden"
|
||||
variant={showReviewedSwitch == 1 ? "select" : "default"}
|
||||
size="sm"
|
||||
onClick={() => setShowReviewedSwitch(showReviewedSwitch == 0 ? 1 : 0)}
|
||||
@ -460,7 +460,7 @@ function CalendarFilterButton({
|
||||
onSelect={updateSelectedDay}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="p-2 flex justify-center items-center">
|
||||
<div className="flex items-center justify-center p-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
updateSelectedDay(undefined);
|
||||
@ -621,9 +621,9 @@ export function GeneralFilterContent({
|
||||
<DropdownMenuSeparator />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between items-center my-2.5">
|
||||
<div className="my-2.5 flex items-center justify-between">
|
||||
<Label
|
||||
className="mx-2 text-primary cursor-pointer"
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allLabels"
|
||||
>
|
||||
All Labels
|
||||
@ -666,7 +666,7 @@ export function GeneralFilterContent({
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="p-2 flex justify-evenly items-center">
|
||||
<div className="flex items-center justify-evenly p-2">
|
||||
<Button
|
||||
variant="select"
|
||||
onClick={() => {
|
||||
@ -707,7 +707,7 @@ function ShowMotionOnlyButton({
|
||||
|
||||
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
|
||||
className="ml-1"
|
||||
id="collapse-motion"
|
||||
@ -715,7 +715,7 @@ function ShowMotionOnlyButton({
|
||||
onCheckedChange={setMotionOnlyButton}
|
||||
/>
|
||||
<Label
|
||||
className="mx-2 text-primary cursor-pointer"
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="collapse-motion"
|
||||
>
|
||||
Motion only
|
||||
|
@ -43,7 +43,7 @@ export function ZoneMaskFilterButton({
|
||||
return (
|
||||
<Drawer>
|
||||
<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}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
@ -69,9 +69,9 @@ export function GeneralFilterContent({
|
||||
return (
|
||||
<>
|
||||
<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="mx-2 text-primary cursor-pointer"
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allLabels"
|
||||
>
|
||||
All Masks and Zones
|
||||
@ -90,9 +90,9 @@ export function GeneralFilterContent({
|
||||
<DropdownMenuSeparator />
|
||||
<div className="my-2.5 flex flex-col gap-2.5">
|
||||
{["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
|
||||
className="w-full mx-2 text-primary capitalize cursor-pointer"
|
||||
className="mx-2 w-full cursor-pointer capitalize text-primary"
|
||||
htmlFor={item}
|
||||
>
|
||||
{item
|
||||
|
@ -131,7 +131,7 @@ export function ThresholdBarGraph({
|
||||
}, [graphId, options]);
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col">
|
||||
<div className="flex w-full flex-col">
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="text-xs text-muted-foreground">{name}</div>
|
||||
<div className="text-xs text-primary">
|
||||
@ -234,8 +234,8 @@ export function StorageGraph({ graphId, used, total }: StorageGraphProps) {
|
||||
}, [graphId, options]);
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-2.5">
|
||||
<div className="w-full flex justify-between items-center gap-1">
|
||||
<div className="flex w-full flex-col gap-2.5">
|
||||
<div className="flex w-full items-center justify-between gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="text-xs text-primary">{getUnitSize(used)}</div>
|
||||
<div className="text-xs text-primary">/</div>
|
||||
@ -247,7 +247,7 @@ export function StorageGraph({ graphId, used, total }: StorageGraphProps) {
|
||||
{Math.round((used / total) * 100)}%
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-5 rounded-md overflow-hidden">
|
||||
<div className="h-5 overflow-hidden rounded-md">
|
||||
<Chart
|
||||
type="bar"
|
||||
options={options}
|
||||
@ -369,7 +369,7 @@ export function CameraLineGraph({
|
||||
}, [graphId, options]);
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col">
|
||||
<div className="flex w-full flex-col">
|
||||
{lastValues && (
|
||||
<div className="flex items-center gap-2.5">
|
||||
{dataLabels.map((label, labelIdx) => (
|
||||
|
@ -66,12 +66,12 @@ export default function IconPicker({
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
{!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
|
||||
</Button>
|
||||
) : (
|
||||
<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">
|
||||
<selectedIcon.Icon size={15} />
|
||||
<div className="text-sm">
|
||||
@ -95,9 +95,9 @@ export default function IconPicker({
|
||||
align="start"
|
||||
side="top"
|
||||
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>
|
||||
<span tabIndex={0} className="sr-only" />
|
||||
<IoClose
|
||||
@ -111,17 +111,17 @@ export default function IconPicker({
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search for an icon..."
|
||||
className="mb-3 text-md md:text-sm"
|
||||
className="text-md mb-3 md:text-sm"
|
||||
value={searchTerm}
|
||||
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">
|
||||
{icons.map(([name, Icon]) => (
|
||||
<div
|
||||
key={name}
|
||||
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
|
||||
? "bg-selected text-white"
|
||||
: "hover:bg-secondary-foreground",
|
||||
|
@ -4,11 +4,11 @@ type LiveIconProps = {
|
||||
|
||||
export function LiveGridIcon({ layout }: LiveIconProps) {
|
||||
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
|
||||
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
|
||||
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"}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="h-1 w-full flex gap-0.5">
|
||||
<div className="flex h-1 w-full gap-0.5">
|
||||
<div
|
||||
className={`w-full ${layout == "grid" ? "bg-selected" : "bg-muted-foreground"}`}
|
||||
/>
|
||||
@ -30,7 +30,7 @@ export function LiveGridIcon({ layout }: LiveIconProps) {
|
||||
|
||||
export function LiveListIcon({ layout }: LiveIconProps) {
|
||||
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
|
||||
className={`size-full ${layout == "list" ? "bg-selected" : "bg-secondary-foreground"}`}
|
||||
/>
|
||||
|
@ -1,12 +1,12 @@
|
||||
function CameraActivityIndicator() {
|
||||
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="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="flex-1 size-[5px] mr-[2px] bg-severity_alert rounded-full animate-scale1"></div>
|
||||
<div className="flex-1 size-[5px] mr-[2px] bg-severity_alert rounded-full animate-scale2"></div>
|
||||
<div className="flex-1 size-[5px] mr-[2px] bg-severity_alert rounded-full animate-scale3"></div>
|
||||
<div className="flex-1 size-[5px] mr-[2px] bg-severity_alert rounded-full animate-scale4"></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="mr-[2px] size-[5px] flex-1 animate-scale1 rounded-full bg-severity_alert"></div>
|
||||
<div className="mr-[2px] size-[5px] flex-1 animate-scale2 rounded-full bg-severity_alert"></div>
|
||||
<div className="mr-[2px] size-[5px] flex-1 animate-scale3 rounded-full bg-severity_alert"></div>
|
||||
<div className="mr-[2px] size-[5px] flex-1 animate-scale4 rounded-full bg-severity_alert"></div>
|
||||
</div>
|
||||
<svg className="hidden" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<defs>
|
||||
|
@ -34,7 +34,7 @@ export default function Chip({
|
||||
<div
|
||||
ref={nodeRef}
|
||||
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,
|
||||
)}
|
||||
onClick={onClick}
|
||||
@ -63,7 +63,7 @@ export function LogChip({ severity, onClickSeverity }: LogChipProps) {
|
||||
|
||||
return (
|
||||
<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) => {
|
||||
e.stopPropagation();
|
||||
|
||||
|
@ -14,7 +14,7 @@ export default function ImageLoadingIndicator({
|
||||
}
|
||||
|
||||
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)} />
|
||||
);
|
||||
|
@ -17,9 +17,9 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col justify-center items-center",
|
||||
"flex flex-col items-center justify-center",
|
||||
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",
|
||||
className,
|
||||
)}
|
||||
|
@ -120,7 +120,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<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]" />
|
||||
</div>
|
||||
@ -135,7 +135,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
</Trigger>
|
||||
<Content
|
||||
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">
|
||||
@ -147,7 +147,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
className={
|
||||
isDesktop
|
||||
? "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" />
|
||||
@ -159,7 +159,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
className={
|
||||
isDesktop
|
||||
? "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" />
|
||||
@ -177,7 +177,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
className={
|
||||
isDesktop
|
||||
? "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" />
|
||||
@ -189,7 +189,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
className={
|
||||
isDesktop
|
||||
? "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" />
|
||||
@ -205,7 +205,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
className={
|
||||
isDesktop
|
||||
? "cursor-pointer"
|
||||
: "p-2 flex items-center text-sm"
|
||||
: "flex items-center p-2 text-sm"
|
||||
}
|
||||
>
|
||||
<LuSunMoon className="mr-2 size-4" />
|
||||
@ -222,7 +222,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
className={
|
||||
isDesktop
|
||||
? "cursor-pointer"
|
||||
: "p-2 flex items-center text-sm"
|
||||
: "flex items-center p-2 text-sm"
|
||||
}
|
||||
onClick={() => setTheme("light")}
|
||||
>
|
||||
@ -232,14 +232,14 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
Light
|
||||
</>
|
||||
) : (
|
||||
<span className="mr-2 ml-6">Light</span>
|
||||
<span className="ml-6 mr-2">Light</span>
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={
|
||||
isDesktop
|
||||
? "cursor-pointer"
|
||||
: "p-2 flex items-center text-sm"
|
||||
: "flex items-center p-2 text-sm"
|
||||
}
|
||||
onClick={() => setTheme("dark")}
|
||||
>
|
||||
@ -249,14 +249,14 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
Dark
|
||||
</>
|
||||
) : (
|
||||
<span className="mr-2 ml-6">Dark</span>
|
||||
<span className="ml-6 mr-2">Dark</span>
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={
|
||||
isDesktop
|
||||
? "cursor-pointer"
|
||||
: "p-2 flex items-center text-sm"
|
||||
: "flex items-center p-2 text-sm"
|
||||
}
|
||||
onClick={() => setTheme("system")}
|
||||
>
|
||||
@ -266,7 +266,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
System
|
||||
</>
|
||||
) : (
|
||||
<span className="mr-2 ml-6">System</span>
|
||||
<span className="ml-6 mr-2">System</span>
|
||||
)}
|
||||
</MenuItem>
|
||||
</SubItemContent>
|
||||
@ -277,7 +277,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
className={
|
||||
isDesktop
|
||||
? "cursor-pointer"
|
||||
: "p-2 flex items-center text-sm"
|
||||
: "flex items-center p-2 text-sm"
|
||||
}
|
||||
>
|
||||
<LuSunMoon className="mr-2 size-4" />
|
||||
@ -296,7 +296,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
className={
|
||||
isDesktop
|
||||
? "cursor-pointer"
|
||||
: "p-2 flex items-center text-sm"
|
||||
: "flex items-center p-2 text-sm"
|
||||
}
|
||||
onClick={() => setColorScheme(scheme)}
|
||||
>
|
||||
@ -306,7 +306,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
{friendlyColorSchemeName(scheme)}
|
||||
</>
|
||||
) : (
|
||||
<span className="mr-2 ml-6">
|
||||
<span className="ml-6 mr-2">
|
||||
{friendlyColorSchemeName(scheme)}
|
||||
</span>
|
||||
)}
|
||||
@ -325,7 +325,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
className={
|
||||
isDesktop
|
||||
? "cursor-pointer"
|
||||
: "p-2 flex items-center text-sm"
|
||||
: "flex items-center p-2 text-sm"
|
||||
}
|
||||
>
|
||||
<LuLifeBuoy className="mr-2 size-4" />
|
||||
@ -337,7 +337,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
className={
|
||||
isDesktop
|
||||
? "cursor-pointer"
|
||||
: "p-2 flex items-center text-sm"
|
||||
: "flex items-center p-2 text-sm"
|
||||
}
|
||||
>
|
||||
<LuGithub className="mr-2 size-4" />
|
||||
@ -347,7 +347,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
<DropdownMenuSeparator className={isDesktop ? "mt-3" : "mt-1"} />
|
||||
<MenuItem
|
||||
className={
|
||||
isDesktop ? "cursor-pointer" : "p-2 flex items-center text-sm"
|
||||
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
|
||||
}
|
||||
onClick={() => setRestartDialogOpen(true)}
|
||||
>
|
||||
|
@ -20,7 +20,7 @@ function Bottombar() {
|
||||
const navItems = useNavigation("secondary");
|
||||
|
||||
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) => (
|
||||
<NavItem key={item.id} className="p-2" item={item} Icon={item.icon} />
|
||||
))}
|
||||
@ -77,16 +77,16 @@ function StatusAlertNav({ className }: StatusAlertNavProps) {
|
||||
</DrawerTrigger>
|
||||
<DrawerContent
|
||||
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,
|
||||
)}
|
||||
>
|
||||
<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]) => (
|
||||
<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) => {
|
||||
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
|
||||
className={`size-5 ${color || "text-danger"}`}
|
||||
/>
|
||||
|
@ -44,7 +44,7 @@ export default function NavItem({
|
||||
onClick={onClick}
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
"flex flex-col justify-center items-center rounded-lg",
|
||||
"flex flex-col items-center justify-center rounded-lg",
|
||||
className,
|
||||
variants[item.variant ?? "primary"][isActive ? "active" : "inactive"],
|
||||
)
|
||||
|
@ -12,10 +12,10 @@ function Sidebar() {
|
||||
const navbarLinks = useNavigation();
|
||||
|
||||
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" />
|
||||
<div className="w-full flex flex-col gap-0 items-center">
|
||||
<Logo className="w-8 h-8 mb-6" />
|
||||
<div className="flex w-full flex-col items-center gap-0">
|
||||
<Logo className="mb-6 h-8 w-8" />
|
||||
{navbarLinks.map((item) => {
|
||||
const showCameraGroups =
|
||||
item.id == 1 && item.url == location.pathname;
|
||||
@ -32,7 +32,7 @@ function Sidebar() {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-4 mb-8">
|
||||
<div className="mb-8 flex flex-col items-center gap-4">
|
||||
<GeneralSettings />
|
||||
<AccountSettings />
|
||||
</div>
|
||||
|
@ -103,7 +103,7 @@ export default function ExportDialog({
|
||||
return (
|
||||
<>
|
||||
<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"}
|
||||
onSave={() => onStartExport()}
|
||||
onCancel={() => setMode("none")}
|
||||
@ -132,7 +132,7 @@ export default function ExportDialog({
|
||||
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>}
|
||||
</Button>
|
||||
</Trigger>
|
||||
@ -140,7 +140,7 @@ export default function ExportDialog({
|
||||
className={
|
||||
isDesktop
|
||||
? "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
|
||||
@ -241,8 +241,8 @@ export function ExportContent({
|
||||
<RadioGroupItem
|
||||
className={
|
||||
opt == selectedOption
|
||||
? "from-selected/50 to-selected/90 text-selected bg-selected"
|
||||
: "from-secondary/50 to-secondary/90 text-secondary bg-secondary"
|
||||
? "bg-selected from-selected/50 to-selected/90 text-selected"
|
||||
: "bg-secondary from-secondary/50 to-secondary/90 text-secondary"
|
||||
}
|
||||
id={opt}
|
||||
value={opt}
|
||||
@ -277,7 +277,7 @@ export function ExportContent({
|
||||
className={isDesktop ? "" : "mt-3 flex flex-col-reverse gap-4"}
|
||||
>
|
||||
<div
|
||||
className={`p-2 cursor-pointer text-center ${isDesktop ? "" : "w-full"}`}
|
||||
className={`cursor-pointer p-2 text-center ${isDesktop ? "" : "w-full"}`}
|
||||
onClick={onCancel}
|
||||
>
|
||||
Cancel
|
||||
@ -383,7 +383,7 @@ function CustomTimeSelector({
|
||||
|
||||
return (
|
||||
<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 />
|
||||
<Popover
|
||||
@ -424,7 +424,7 @@ function CustomTimeSelector({
|
||||
/>
|
||||
<SelectSeparator className="bg-secondary" />
|
||||
<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"
|
||||
type="time"
|
||||
value={startClock}
|
||||
@ -486,7 +486,7 @@ function CustomTimeSelector({
|
||||
/>
|
||||
<SelectSeparator className="bg-secondary" />
|
||||
<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"
|
||||
type="time"
|
||||
value={endClock}
|
||||
|
@ -29,11 +29,11 @@ export default function LogInfoDialog({
|
||||
}}
|
||||
>
|
||||
<Content
|
||||
className={isDesktop ? "" : "max-h-[75dvh] p-2 pb-4 overflow-hidden"}
|
||||
className={isDesktop ? "" : "max-h-[75dvh] overflow-hidden p-2 pb-4"}
|
||||
>
|
||||
{logLine && (
|
||||
<div className="size-full flex flex-col gap-5">
|
||||
<div className="w-min flex flex-col gap-1.5">
|
||||
<div className="flex size-full flex-col gap-5">
|
||||
<div className="flex w-min flex-col gap-1.5">
|
||||
<div className="text-sm text-primary/40">Type</div>
|
||||
<LogChip severity={logLine.severity} />
|
||||
</div>
|
||||
|
@ -27,12 +27,12 @@ export default function MobileCameraDrawer({
|
||||
<FaVideo className="text-secondary-foreground" />
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent className="max-h-[75dvh] px-4 mx-1 rounded-t-2xl overflow-hidden">
|
||||
<div className="w-full h-auto py-4 overflow-y-auto overflow-x-hidden flex flex-col items-center gap-2">
|
||||
<DrawerContent className="mx-1 max-h-[75dvh] overflow-hidden rounded-t-2xl px-4">
|
||||
<div className="flex h-auto w-full flex-col items-center gap-2 overflow-y-auto overflow-x-hidden py-4">
|
||||
{allCameras.map((cam) => (
|
||||
<div
|
||||
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={() => {
|
||||
onSelectCamera(cam);
|
||||
setCameraDrawer(false);
|
||||
|
@ -112,22 +112,22 @@ export default function MobileReviewSettingsDrawer({
|
||||
let content;
|
||||
if (drawerMode == "select") {
|
||||
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") && (
|
||||
<Button
|
||||
className="w-full flex justify-center items-center gap-2"
|
||||
className="flex w-full items-center justify-center gap-2"
|
||||
onClick={() => {
|
||||
setDrawerMode("export");
|
||||
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
|
||||
</Button>
|
||||
)}
|
||||
{features.includes("calendar") && (
|
||||
<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"}
|
||||
onClick={() => setDrawerMode("calendar")}
|
||||
>
|
||||
@ -139,7 +139,7 @@ export default function MobileReviewSettingsDrawer({
|
||||
)}
|
||||
{features.includes("filter") && (
|
||||
<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"}
|
||||
onClick={() => setDrawerMode("filter")}
|
||||
>
|
||||
@ -177,8 +177,8 @@ export default function MobileReviewSettingsDrawer({
|
||||
);
|
||||
} else if (drawerMode == "calendar") {
|
||||
content = (
|
||||
<div className="w-full flex flex-col">
|
||||
<div className="w-full h-8 relative">
|
||||
<div className="flex w-full flex-col">
|
||||
<div className="relative h-8 w-full">
|
||||
<div
|
||||
className="absolute left-0 text-selected"
|
||||
onClick={() => setDrawerMode("select")}
|
||||
@ -205,7 +205,7 @@ export default function MobileReviewSettingsDrawer({
|
||||
}}
|
||||
/>
|
||||
<SelectSeparator />
|
||||
<div className="p-2 flex justify-center items-center">
|
||||
<div className="flex items-center justify-center p-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
onUpdateFilter({
|
||||
@ -222,8 +222,8 @@ export default function MobileReviewSettingsDrawer({
|
||||
);
|
||||
} else if (drawerMode == "filter") {
|
||||
content = (
|
||||
<div className="w-full h-auto overflow-y-auto flex flex-col">
|
||||
<div className="w-full h-8 mb-2 relative">
|
||||
<div className="flex h-auto w-full flex-col overflow-y-auto">
|
||||
<div className="relative mb-2 h-8 w-full">
|
||||
<div
|
||||
className="absolute left-0 text-selected"
|
||||
onClick={() => setDrawerMode("select")}
|
||||
@ -256,7 +256,7 @@ export default function MobileReviewSettingsDrawer({
|
||||
return (
|
||||
<>
|
||||
<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"}
|
||||
onSave={() => onStartExport()}
|
||||
onCancel={() => setMode("none")}
|
||||
@ -281,7 +281,7 @@ export default function MobileReviewSettingsDrawer({
|
||||
/>
|
||||
</Button>
|
||||
</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}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
@ -26,9 +26,9 @@ export default function MobileTimelineDrawer({
|
||||
<FaFlag className="text-secondary-foreground" />
|
||||
</Button>
|
||||
</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
|
||||
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={() => {
|
||||
onSelect("timeline");
|
||||
setDrawer(false);
|
||||
@ -37,7 +37,7 @@ export default function MobileTimelineDrawer({
|
||||
Timeline
|
||||
</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={() => {
|
||||
onSelect("events");
|
||||
setDrawer(false);
|
||||
|
@ -67,7 +67,7 @@ function ReviewActivityDay({ reviewSummary, day }: ReviewActivityDayProps) {
|
||||
}, [reviewSummary, day]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
{day.getDate()}
|
||||
{dayActivity != "none" && (
|
||||
<FaCircle
|
||||
|
@ -17,9 +17,9 @@ export default function SaveExportOverlay({
|
||||
return (
|
||||
<div className={className}>
|
||||
<div
|
||||
className={`flex justify-center px-2 gap-2 items-center pointer-events-auto rounded-lg ${
|
||||
show ? "animate-in slide-in-from-top duration-500" : "invisible"
|
||||
} text-center mt-5 mx-auto`}
|
||||
className={`pointer-events-auto flex items-center justify-center gap-2 rounded-lg px-2 ${
|
||||
show ? "duration-500 animate-in slide-in-from-top" : "invisible"
|
||||
} mx-auto mt-5 text-center`}
|
||||
>
|
||||
<Button
|
||||
className="flex items-center gap-1"
|
||||
|
@ -84,12 +84,12 @@ export default function TimelineEventOverlay({
|
||||
}}
|
||||
>
|
||||
{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}
|
||||
</div>
|
||||
{isHovering && (
|
||||
<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()}
|
||||
>
|
||||
<div>{`Area: ${getObjectArea()} px`}</div>
|
||||
|
@ -33,7 +33,7 @@ export default function VainfoDialog({
|
||||
<DialogTitle>Vainfo Output</DialogTitle>
|
||||
</DialogHeader>
|
||||
{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>
|
||||
<br />
|
||||
<div>Process {vainfo.return_code == 0 ? "Output" : "Error"}:</div>
|
||||
|
@ -23,7 +23,7 @@ export default function BirdseyeLivePlayer({
|
||||
if (liveMode == "webrtc") {
|
||||
player = (
|
||||
<WebRtcPlayer
|
||||
className={`rounded-lg md:rounded-2xl size-full`}
|
||||
className={`size-full rounded-lg md:rounded-2xl`}
|
||||
camera="birdseye"
|
||||
/>
|
||||
);
|
||||
@ -31,7 +31,7 @@ export default function BirdseyeLivePlayer({
|
||||
if ("MediaSource" in window || "ManagedMediaSource" in window) {
|
||||
player = (
|
||||
<MSEPlayer
|
||||
className={`rounded-lg md:rounded-2xl size-full`}
|
||||
className={`size-full rounded-lg md:rounded-2xl`}
|
||||
camera="birdseye"
|
||||
/>
|
||||
);
|
||||
@ -46,7 +46,7 @@ export default function BirdseyeLivePlayer({
|
||||
} else if (liveMode == "jsmpeg") {
|
||||
player = (
|
||||
<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"
|
||||
width={birdseyeConfig.width}
|
||||
height={birdseyeConfig.height}
|
||||
@ -59,13 +59,13 @@ export default function BirdseyeLivePlayer({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative flex justify-center w-full cursor-pointer",
|
||||
"relative flex w-full cursor-pointer justify-center",
|
||||
className,
|
||||
)}
|
||||
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="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 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="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>
|
||||
);
|
||||
|
@ -153,7 +153,7 @@ export default function HlsVideoPlayer({
|
||||
return (
|
||||
<TransformWrapper minScale={1.0}>
|
||||
<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}
|
||||
isPlaying={isPlaying}
|
||||
show={visible && (controls || controlsOpen)}
|
||||
@ -231,7 +231,7 @@ export default function HlsVideoPlayer({
|
||||
>
|
||||
<video
|
||||
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"
|
||||
autoPlay
|
||||
controls={false}
|
||||
|
@ -103,7 +103,7 @@ export default function LivePlayer({
|
||||
if (liveMode == "webrtc") {
|
||||
player = (
|
||||
<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}
|
||||
playbackEnabled={cameraActive}
|
||||
audioEnabled={playAudio}
|
||||
@ -117,7 +117,7 @@ export default function LivePlayer({
|
||||
if ("MediaSource" in window || "ManagedMediaSource" in window) {
|
||||
player = (
|
||||
<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}
|
||||
playbackEnabled={cameraActive}
|
||||
audioEnabled={playAudio}
|
||||
@ -137,7 +137,7 @@ export default function LivePlayer({
|
||||
} else if (liveMode == "jsmpeg") {
|
||||
player = (
|
||||
<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}
|
||||
width={cameraConfig.detect.width}
|
||||
height={cameraConfig.detect.height}
|
||||
@ -154,17 +154,17 @@ export default function LivePlayer({
|
||||
className={cn(
|
||||
"relative flex justify-center",
|
||||
liveMode === "jsmpeg" ? "size-full" : "w-full",
|
||||
"outline cursor-pointer",
|
||||
"cursor-pointer outline",
|
||||
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",
|
||||
"transition-all duration-500",
|
||||
className,
|
||||
)}
|
||||
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="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 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="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}
|
||||
|
||||
{objects.length > 0 && (
|
||||
@ -172,9 +172,9 @@ export default function LivePlayer({
|
||||
<Tooltip>
|
||||
<div className="flex">
|
||||
<TooltipTrigger asChild>
|
||||
<div className="mx-3 pb-1 text-white text-sm">
|
||||
<div className="mx-3 pb-1 text-sm text-white">
|
||||
<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([
|
||||
@ -226,7 +226,7 @@ export default function LivePlayer({
|
||||
|
||||
<div className="absolute right-2 top-2 size-4">
|
||||
{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>
|
||||
|
@ -240,7 +240,7 @@ function PreviewVideoPlayer({
|
||||
return (
|
||||
<div
|
||||
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",
|
||||
className,
|
||||
)}
|
||||
@ -288,11 +288,11 @@ function PreviewVideoPlayer({
|
||||
)}
|
||||
</video>
|
||||
{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
|
||||
</div>
|
||||
)}
|
||||
{firstLoad && <Skeleton className="absolute size-full aspect-video" />}
|
||||
{firstLoad && <Skeleton className="absolute aspect-video size-full" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -482,7 +482,7 @@ function PreviewFramesPlayer({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative w-full flex justify-center",
|
||||
"relative flex w-full justify-center",
|
||||
className,
|
||||
onClick && "cursor-pointer",
|
||||
)}
|
||||
@ -490,15 +490,15 @@ function PreviewFramesPlayer({
|
||||
>
|
||||
<img
|
||||
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}
|
||||
/>
|
||||
{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
|
||||
</div>
|
||||
)}
|
||||
{firstLoad && <Skeleton className="absolute size-full aspect-video" />}
|
||||
{firstLoad && <Skeleton className="absolute aspect-video size-full" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ export default function PreviewThumbnailPlayer({
|
||||
<div className={`${imgLoaded ? "visible" : "invisible"}`}>
|
||||
<img
|
||||
ref={imgRef}
|
||||
className={`size-full transition-opacity select-none ${
|
||||
className={`size-full select-none transition-opacity ${
|
||||
playingBack ? "opacity-0" : "opacity-100"
|
||||
}`}
|
||||
style={
|
||||
@ -234,12 +234,12 @@ export default function PreviewThumbnailPlayer({
|
||||
onMouseLeave={() => setTooltipHovering(false)}
|
||||
>
|
||||
<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 == "detection") && (
|
||||
<>
|
||||
<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) => {
|
||||
return getIconForLabel(object, "size-3 text-white");
|
||||
@ -273,9 +273,9 @@ export default function PreviewThumbnailPlayer({
|
||||
</div>
|
||||
{!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="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="flex h-full justify-between items-end mx-3 pb-1 text-white text-sm">
|
||||
<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="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="mx-3 flex h-full items-end justify-between pb-1 text-sm text-white">
|
||||
{review.end_time ? (
|
||||
<TimeAgo time={review.start_time * 1000} dense />
|
||||
) : (
|
||||
@ -557,10 +557,10 @@ export function VideoPreview({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative size-full aspect-video bg-black">
|
||||
<div className="relative aspect-video size-full bg-black">
|
||||
<video
|
||||
ref={playerRef}
|
||||
className="size-full aspect-video bg-black pointer-events-none"
|
||||
className="pointer-events-none aspect-video size-full bg-black"
|
||||
autoPlay
|
||||
playsInline
|
||||
preload="auto"
|
||||
@ -738,9 +738,9 @@ export function InProgressPreview({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative size-full flex items-center bg-black">
|
||||
<div className="relative flex size-full items-center bg-black">
|
||||
<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`}
|
||||
onLoad={handleLoad}
|
||||
/>
|
||||
|
@ -171,16 +171,16 @@ export default function VideoControls({
|
||||
return (
|
||||
<div
|
||||
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,
|
||||
isMobileOnly &&
|
||||
Object.values(features).filter((feat) => feat).length >
|
||||
MIN_ITEMS_WRAP &&
|
||||
"flex-wrap min-w-[75%]",
|
||||
"min-w-[75%] flex-wrap",
|
||||
)}
|
||||
>
|
||||
{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
|
||||
className="size-5"
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
@ -208,9 +208,9 @@ export default function VideoControls({
|
||||
)}
|
||||
<div className="cursor-pointer" onClick={onTogglePlay}>
|
||||
{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>
|
||||
{features.seek && (
|
||||
|
@ -53,18 +53,18 @@ export default function General() {
|
||||
|
||||
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} />
|
||||
<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">
|
||||
General Settings
|
||||
</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="space-y-0.5">
|
||||
<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>
|
||||
The layout of cameras in a camera group can be
|
||||
dragged/resized. The positions are stored in your browser's
|
||||
@ -72,11 +72,11 @@ export default function General() {
|
||||
</p>
|
||||
</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>
|
||||
</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="space-y-0.5">
|
||||
<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="space-y-0.5">
|
||||
<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>
|
||||
Not yet implemented. <em>Default: disabled</em>
|
||||
</p>
|
||||
</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
|
||||
id="lowdata"
|
||||
checked={false}
|
||||
|
@ -370,9 +370,9 @@ export default function MasksAndZones({
|
||||
return (
|
||||
<>
|
||||
{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} />
|
||||
<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" && (
|
||||
<ZoneEditPane
|
||||
polygons={editingPolygons}
|
||||
@ -417,17 +417,17 @@ export default function MasksAndZones({
|
||||
<Heading as="h3" className="my-2">
|
||||
Masks / Zones
|
||||
</Heading>
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex w-full flex-col">
|
||||
{(selectedZoneMask === undefined ||
|
||||
selectedZoneMask.includes("zone" as PolygonType)) && (
|
||||
<div className="mt-0 pt-0 last:pb-3 last:border-b-[1px] last:border-secondary">
|
||||
<div className="flex flex-row justify-between items-center my-3">
|
||||
<div className="mt-0 pt-0 last:border-b-[1px] last:border-secondary last:pb-3">
|
||||
<div className="my-3 flex flex-row items-center justify-between">
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<div className="text-md cursor-default">Zones</div>
|
||||
</HoverCardTrigger>
|
||||
<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>
|
||||
Zones allow you to define a specific area of the
|
||||
frame so you can determine whether or not an
|
||||
@ -441,7 +441,7 @@ export default function MasksAndZones({
|
||||
className="inline"
|
||||
>
|
||||
Documentation{" "}
|
||||
<LuExternalLink className="size-3 ml-2 inline-flex" />
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@ -451,7 +451,7 @@ export default function MasksAndZones({
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
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={() => {
|
||||
setEditPane("zone");
|
||||
handleNewPolygon("zone");
|
||||
@ -485,8 +485,8 @@ export default function MasksAndZones({
|
||||
selectedZoneMask.includes(
|
||||
"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="flex flex-row justify-between items-center my-3">
|
||||
<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="my-3 flex flex-row items-center justify-between">
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<div className="text-md cursor-default">
|
||||
@ -494,7 +494,7 @@ export default function MasksAndZones({
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<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>
|
||||
Motion masks are used to prevent unwanted types
|
||||
of motion from triggering detection. Over
|
||||
@ -509,7 +509,7 @@ export default function MasksAndZones({
|
||||
className="inline"
|
||||
>
|
||||
Documentation{" "}
|
||||
<LuExternalLink className="size-3 ml-2 inline-flex" />
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@ -519,7 +519,7 @@ export default function MasksAndZones({
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
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={() => {
|
||||
setEditPane("motion_mask");
|
||||
handleNewPolygon("motion_mask");
|
||||
@ -555,8 +555,8 @@ export default function MasksAndZones({
|
||||
selectedZoneMask.includes(
|
||||
"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="flex flex-row justify-between items-center my-3">
|
||||
<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="my-3 flex flex-row items-center justify-between">
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<div className="text-md cursor-default">
|
||||
@ -564,7 +564,7 @@ export default function MasksAndZones({
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<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>
|
||||
Object filter masks are used to filter out false
|
||||
positives for a given object type based on
|
||||
@ -578,7 +578,7 @@ export default function MasksAndZones({
|
||||
className="inline"
|
||||
>
|
||||
Documentation{" "}
|
||||
<LuExternalLink className="size-3 ml-2 inline-flex" />
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@ -588,7 +588,7 @@ export default function MasksAndZones({
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
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={() => {
|
||||
setEditPane("object_mask");
|
||||
handleNewPolygon("object_mask");
|
||||
@ -626,9 +626,9 @@ export default function MasksAndZones({
|
||||
</div>
|
||||
<div
|
||||
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 &&
|
||||
scaledWidth &&
|
||||
scaledHeight &&
|
||||
|
@ -187,7 +187,7 @@ export default function MotionMaskEditPane({
|
||||
<Heading as="h3" className="my-2">
|
||||
{polygon.name.length ? "Edit" : "New"} Motion Mask
|
||||
</Heading>
|
||||
<div className="text-sm text-muted-foreground my-2">
|
||||
<div className="my-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
Motion masks are used to prevent unwanted types of motion from
|
||||
triggering detection. Over masking will make it more difficult for
|
||||
@ -196,7 +196,7 @@ export default function MotionMaskEditPane({
|
||||
</div>
|
||||
<Separator className="my-3 bg-secondary" />
|
||||
{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">
|
||||
{polygons[activePolygonIndex].points.length}{" "}
|
||||
{polygons[activePolygonIndex].points.length > 1 ||
|
||||
@ -223,7 +223,7 @@ export default function MotionMaskEditPane({
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6 flex flex-col flex-1"
|
||||
className="flex flex-1 flex-col space-y-6"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
@ -243,7 +243,7 @@ export default function MotionMaskEditPane({
|
||||
</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">
|
||||
<Button className="flex flex-1" onClick={onCancel}>
|
||||
Cancel
|
||||
|
@ -173,13 +173,13 @@ export default function MotionTuner({
|
||||
}
|
||||
|
||||
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} />
|
||||
<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">
|
||||
Motion Detection Tuner
|
||||
</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>
|
||||
Frigate uses motion detection as a first line check to see if there
|
||||
is anything happening in the frame worth checking with object
|
||||
@ -194,18 +194,18 @@ export default function MotionTuner({
|
||||
className="inline"
|
||||
>
|
||||
Read the Motion Tuning Guide{" "}
|
||||
<LuExternalLink className="size-3 ml-2 inline-flex" />
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="flex my-2 bg-secondary" />
|
||||
<div className="flex flex-col w-full space-y-6">
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
<div className="flex w-full flex-col space-y-6">
|
||||
<div className="mt-2 space-y-6">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="motion-threshold" className="text-md">
|
||||
Threshold
|
||||
</Label>
|
||||
<div className="text-sm text-muted-foreground my-2">
|
||||
<div className="my-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
The threshold value dictates how much of a change in a pixel's
|
||||
luminance is required to be considered motion.{" "}
|
||||
@ -226,7 +226,7 @@ export default function MotionTuner({
|
||||
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}
|
||||
</div>
|
||||
</div>
|
||||
@ -236,7 +236,7 @@ export default function MotionTuner({
|
||||
<Label htmlFor="motion-threshold" className="text-md">
|
||||
Contour Area
|
||||
</Label>
|
||||
<div className="text-sm text-muted-foreground my-2">
|
||||
<div className="my-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
The contour area value is used to decide which groups of
|
||||
changed pixels qualify as motion. <em>Default: 10</em>
|
||||
@ -256,12 +256,12 @@ export default function MotionTuner({
|
||||
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}
|
||||
</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="space-y-0.5">
|
||||
<Label htmlFor="improve-contrast">Improve Contrast</Label>
|
||||
@ -280,7 +280,7 @@ export default function MotionTuner({
|
||||
/>
|
||||
</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">
|
||||
<Button className="flex flex-1" onClick={onCancel}>
|
||||
Reset
|
||||
@ -305,7 +305,7 @@ export default function MotionTuner({
|
||||
</div>
|
||||
|
||||
{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">
|
||||
<AutoUpdatingCameraImage
|
||||
camera={cameraConfig.name}
|
||||
|
@ -249,7 +249,7 @@ export default function ObjectMaskEditPane({
|
||||
<Heading as="h3" className="my-2">
|
||||
{polygon.name.length ? "Edit" : "New"} Object Mask
|
||||
</Heading>
|
||||
<div className="text-sm text-muted-foreground my-2">
|
||||
<div className="my-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
Object filter masks are used to filter out false positives for a given
|
||||
object type based on location.
|
||||
@ -257,7 +257,7 @@ export default function ObjectMaskEditPane({
|
||||
</div>
|
||||
<Separator className="my-3 bg-secondary" />
|
||||
{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">
|
||||
{polygons[activePolygonIndex].points.length}{" "}
|
||||
{polygons[activePolygonIndex].points.length > 1 ||
|
||||
@ -284,7 +284,7 @@ export default function ObjectMaskEditPane({
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6 flex flex-col flex-1"
|
||||
className="flex flex-1 flex-col space-y-6"
|
||||
>
|
||||
<div>
|
||||
<FormField
|
||||
@ -332,7 +332,7 @@ export default function ObjectMaskEditPane({
|
||||
)}
|
||||
/>
|
||||
</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">
|
||||
<Button className="flex flex-1" onClick={onCancel}>
|
||||
Cancel
|
||||
|
@ -111,13 +111,13 @@ export default function ObjectSettings({
|
||||
}
|
||||
|
||||
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} />
|
||||
<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">
|
||||
Debug
|
||||
</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>
|
||||
Frigate uses your detectors{" "}
|
||||
{config
|
||||
@ -142,17 +142,17 @@ export default function ObjectSettings({
|
||||
<TabsTrigger value="objectlist">Object List</TabsTrigger>
|
||||
</TabsList>
|
||||
<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="my-2.5 flex flex-col gap-2.5">
|
||||
{DEBUG_OPTIONS.map(({ param, title, description }) => (
|
||||
<div
|
||||
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
|
||||
className="w-full text-primary capitalize cursor-pointer mb-2"
|
||||
className="mb-2 w-full cursor-pointer capitalize text-primary"
|
||||
htmlFor={param}
|
||||
>
|
||||
{title}
|
||||
@ -183,7 +183,7 @@ export default function ObjectSettings({
|
||||
</div>
|
||||
|
||||
{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">
|
||||
<AutoUpdatingCameraImage
|
||||
camera={cameraConfig.name}
|
||||
@ -222,15 +222,15 @@ function ObjectList(objects?: ObjectType[]) {
|
||||
);
|
||||
|
||||
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.map((obj) => {
|
||||
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 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
|
||||
className="p-2 rounded-lg"
|
||||
className="rounded-lg p-2"
|
||||
style={{
|
||||
backgroundColor: obj.stationary
|
||||
? "rgb(110,110,110)"
|
||||
@ -243,10 +243,10 @@ function ObjectList(objects?: ObjectType[]) {
|
||||
{capitalizeFirstLetter(obj.label)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row w-8/12 items-end justify-end">
|
||||
<div className="mr-2 text-md w-1/3">
|
||||
<div className="flex w-8/12 flex-row items-end justify-end">
|
||||
<div className="text-md mr-2 w-1/3">
|
||||
<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
|
||||
</p>
|
||||
{obj.score
|
||||
@ -255,17 +255,17 @@ function ObjectList(objects?: ObjectType[]) {
|
||||
%
|
||||
</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">
|
||||
<p className="text-sm mb-1.5 text-primary-variant">
|
||||
<p className="mb-1.5 text-sm text-primary-variant">
|
||||
Ratio
|
||||
</p>
|
||||
{obj.ratio ? obj.ratio.toFixed(2).toString() : "-"}
|
||||
</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">
|
||||
<p className="text-sm mb-1.5 text-primary-variant">
|
||||
<p className="mb-1.5 text-sm text-primary-variant">
|
||||
Area
|
||||
</p>
|
||||
{obj.area ? obj.area.toString() : "-"}
|
||||
|
@ -73,7 +73,7 @@ export default function PolygonEditControls({
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
className="size-6 p-1 rounded-md"
|
||||
className="size-6 rounded-md p-1"
|
||||
disabled={!polygons[activePolygonIndex].points.length}
|
||||
onClick={undo}
|
||||
>
|
||||
@ -86,7 +86,7 @@ export default function PolygonEditControls({
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
className="size-6 p-1 rounded-md"
|
||||
className="size-6 rounded-md p-1"
|
||||
disabled={!polygons[activePolygonIndex].points.length}
|
||||
onClick={reset}
|
||||
>
|
||||
|
@ -206,7 +206,7 @@ export default function PolygonItem({
|
||||
|
||||
<div
|
||||
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}
|
||||
onMouseEnter={() => setHoveredPolygonIndex(index)}
|
||||
onMouseLeave={() => setHoveredPolygonIndex(null)}
|
||||
@ -226,7 +226,7 @@ export default function PolygonItem({
|
||||
>
|
||||
{PolygonItemIcon && (
|
||||
<PolygonItemIcon
|
||||
className="size-5 mr-2"
|
||||
className="mr-2 size-5"
|
||||
style={{
|
||||
fill: toRGBColorString(polygon.color, true),
|
||||
color: toRGBColorString(polygon.color, true),
|
||||
@ -285,7 +285,7 @@ export default function PolygonItem({
|
||||
</>
|
||||
)}
|
||||
{!isMobile && hoveredPolygonIndex === index && (
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<IconWrapper
|
||||
@ -319,7 +319,7 @@ export default function PolygonItem({
|
||||
icon={HiTrash}
|
||||
className={`size-[15px] cursor-pointer ${
|
||||
hoveredPolygonIndex === index &&
|
||||
"text-primary-variant fill-primary-variant"
|
||||
"fill-primary-variant text-primary-variant"
|
||||
}`}
|
||||
onClick={() => !isLoading && setDeleteDialogOpen(true)}
|
||||
/>
|
||||
|
@ -322,7 +322,7 @@ export default function ZoneEditPane({
|
||||
<Heading as="h3" className="my-2">
|
||||
{polygon.name.length ? "Edit" : "New"} Zone
|
||||
</Heading>
|
||||
<div className="text-sm text-muted-foreground my-2">
|
||||
<div className="my-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
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.
|
||||
@ -330,7 +330,7 @@ export default function ZoneEditPane({
|
||||
</div>
|
||||
<Separator className="my-3 bg-secondary" />
|
||||
{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">
|
||||
{polygons[activePolygonIndex].points.length}{" "}
|
||||
{polygons[activePolygonIndex].points.length > 1 ||
|
||||
@ -364,7 +364,7 @@ export default function ZoneEditPane({
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<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..."
|
||||
{...field}
|
||||
/>
|
||||
@ -377,7 +377,7 @@ export default function ZoneEditPane({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Separator className="flex my-2 bg-secondary" />
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="inertia"
|
||||
@ -386,7 +386,7 @@ export default function ZoneEditPane({
|
||||
<FormLabel>Inertia</FormLabel>
|
||||
<FormControl>
|
||||
<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"
|
||||
{...field}
|
||||
/>
|
||||
@ -399,7 +399,7 @@ export default function ZoneEditPane({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Separator className="flex my-2 bg-secondary" />
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="loitering_time"
|
||||
@ -408,7 +408,7 @@ export default function ZoneEditPane({
|
||||
<FormLabel>Loitering Time</FormLabel>
|
||||
<FormControl>
|
||||
<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"
|
||||
{...field}
|
||||
/>
|
||||
@ -421,7 +421,7 @@ export default function ZoneEditPane({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Separator className="flex my-2 bg-secondary" />
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
<FormItem>
|
||||
<FormLabel>Objects</FormLabel>
|
||||
<FormDescription>
|
||||
@ -446,7 +446,7 @@ export default function ZoneEditPane({
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<Separator className="flex my-2 bg-secondary" />
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
@ -585,8 +585,8 @@ export function ZoneObjectSelector({
|
||||
return (
|
||||
<>
|
||||
<div className="h-auto overflow-y-auto overflow-x-hidden">
|
||||
<div className="flex justify-between items-center my-2.5">
|
||||
<Label className="text-primary cursor-pointer" htmlFor="allLabels">
|
||||
<div className="my-2.5 flex items-center justify-between">
|
||||
<Label className="cursor-pointer text-primary" htmlFor="allLabels">
|
||||
All Objects
|
||||
</Label>
|
||||
<Switch
|
||||
@ -603,9 +603,9 @@ export function ZoneObjectSelector({
|
||||
<Separator />
|
||||
<div className="my-2.5 flex flex-col gap-2.5">
|
||||
{allLabels.map((item) => (
|
||||
<div key={item} className="flex justify-between items-center">
|
||||
<div key={item} className="flex items-center justify-between">
|
||||
<Label
|
||||
className="w-full text-primary capitalize cursor-pointer"
|
||||
className="w-full cursor-pointer capitalize text-primary"
|
||||
htmlFor={item}
|
||||
>
|
||||
{item.replaceAll("_", " ")}
|
||||
|
@ -229,16 +229,16 @@ export function EventSegment({
|
||||
{severityValue === displaySeverityType && (
|
||||
<HoverCard openDelay={200} closeDelay={100}>
|
||||
<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="flex flex-row justify-center w-[20px] md:w-[40px]">
|
||||
<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 w-[20px] flex-row justify-center md:w-[40px]">
|
||||
<div className="flex justify-center">
|
||||
<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}
|
||||
>
|
||||
<div
|
||||
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>
|
||||
@ -247,7 +247,7 @@ export function EventSegment({
|
||||
</HoverCardTrigger>
|
||||
<HoverCardPortal>
|
||||
<HoverCardContent
|
||||
className="rounded-lg md:rounded-2xl w-[250px] p-2"
|
||||
className="w-[250px] rounded-lg p-2 md:rounded-2xl"
|
||||
side="left"
|
||||
>
|
||||
<img
|
||||
|
@ -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="flex flex-row justify-center w-[20px] md:w-[40px] pt-[1px] mb-[1px]">
|
||||
<div className="flex justify-center mb-[1px]">
|
||||
<div className="absolute left-1/2 z-10 h-[8px] w-[20px] -translate-x-1/2 transform cursor-pointer md:w-[40px]">
|
||||
<div className="mb-[1px] flex w-[20px] flex-row justify-center pt-[1px] md:w-[40px]">
|
||||
<div className="mb-[1px] flex justify-center">
|
||||
<div
|
||||
key={`${segmentKey}_motion_data_1`}
|
||||
data-motion-value={secondHalfSegmentWidth}
|
||||
@ -218,7 +218,7 @@ export function MotionSegment({
|
||||
</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
|
||||
key={`${segmentKey}_motion_data_2`}
|
||||
|
@ -316,15 +316,15 @@ export function ReviewTimeline({
|
||||
return (
|
||||
<div
|
||||
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)
|
||||
? "cursor-grabbing"
|
||||
: "cursor-auto"
|
||||
}`}
|
||||
>
|
||||
<div ref={segmentsRef} className="flex flex-col 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="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 ref={segmentsRef} className="relative flex flex-col">
|
||||
<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="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}
|
||||
</div>
|
||||
{children.length > 0 && (
|
||||
@ -336,7 +336,7 @@ export function ReviewTimeline({
|
||||
ref={handlebarRef}
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-center touch-none select-none"
|
||||
className="flex touch-none select-none items-center justify-center"
|
||||
onMouseDown={handleHandlebar}
|
||||
onTouchStart={handleHandlebar}
|
||||
>
|
||||
@ -346,21 +346,21 @@ export function ReviewTimeline({
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`bg-destructive rounded-full mx-auto ${
|
||||
className={`mx-auto rounded-full bg-destructive ${
|
||||
dense
|
||||
? "w-12 md:w-20"
|
||||
: segmentDuration < 60
|
||||
? "w-24"
|
||||
: "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
|
||||
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
|
||||
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>
|
||||
@ -374,7 +374,7 @@ export function ReviewTimeline({
|
||||
ref={exportEndRef}
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-center touch-none select-none"
|
||||
className="flex touch-none select-none items-center justify-center"
|
||||
onMouseDown={handleExportEnd}
|
||||
onTouchStart={handleExportEnd}
|
||||
>
|
||||
@ -384,28 +384,28 @@ export function ReviewTimeline({
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`bg-selected -mt-4 mx-auto ${
|
||||
className={`mx-auto -mt-4 bg-selected ${
|
||||
dense
|
||||
? "w-12 md:w-20"
|
||||
: segmentDuration < 60
|
||||
? "w-24"
|
||||
: "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
|
||||
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
|
||||
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
|
||||
ref={exportSectionRef}
|
||||
className="bg-selected/50 absolute w-full"
|
||||
className="absolute w-full bg-selected/50"
|
||||
></div>
|
||||
<div
|
||||
className={`export-start absolute left-0 top-0 ${isDraggingExportStart && isIOS ? "" : "z-20"} w-full`}
|
||||
@ -413,7 +413,7 @@ export function ReviewTimeline({
|
||||
ref={exportStartRef}
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-center touch-none select-none"
|
||||
className="flex touch-none select-none items-center justify-center"
|
||||
onMouseDown={handleExportStart}
|
||||
onTouchStart={handleExportStart}
|
||||
>
|
||||
@ -423,20 +423,20 @@ export function ReviewTimeline({
|
||||
}`}
|
||||
>
|
||||
<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
|
||||
className={`bg-selected mt-4 mx-auto ${
|
||||
className={`mx-auto mt-4 bg-selected ${
|
||||
dense
|
||||
? "w-12 md:w-20"
|
||||
: segmentDuration < 60
|
||||
? "w-24"
|
||||
: "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
|
||||
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>
|
||||
|
@ -51,7 +51,7 @@ export function SummarySegment({
|
||||
<React.Fragment key={index}>
|
||||
{severityValue === displaySeverityType && (
|
||||
<div
|
||||
className="flex justify-end cursor-pointer"
|
||||
className="flex cursor-pointer justify-end"
|
||||
style={{ height: segmentHeight }}
|
||||
>
|
||||
<div
|
||||
|
@ -339,12 +339,12 @@ export function SummaryTimeline({
|
||||
|
||||
return (
|
||||
<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"
|
||||
>
|
||||
<div
|
||||
ref={summaryTimelineRef}
|
||||
className="h-full flex flex-col relative z-10"
|
||||
className="relative z-10 flex h-full flex-col"
|
||||
onClick={timelineClick}
|
||||
onTouchEnd={timelineClick}
|
||||
>
|
||||
@ -354,7 +354,7 @@ export function SummaryTimeline({
|
||||
ref={visibleSectionRef}
|
||||
onMouseDown={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"
|
||||
}`}
|
||||
></div>
|
||||
|
@ -32,7 +32,7 @@ export function MinimapBounds({
|
||||
<>
|
||||
{isFirstSegmentInMinimap && (
|
||||
<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}
|
||||
>
|
||||
{new Date(alignedMinimapStartTime * 1000).toLocaleTimeString([], {
|
||||
@ -44,7 +44,7 @@ export function MinimapBounds({
|
||||
)}
|
||||
|
||||
{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([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
@ -59,9 +59,9 @@ export function MinimapBounds({
|
||||
export function Tick({ timestamp, timestampSpread }: TickSegmentProps) {
|
||||
return (
|
||||
<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
|
||||
className={`pointer-events-none select-none h-0.5 ${
|
||||
className={`pointer-events-none h-0.5 select-none ${
|
||||
timestamp.getMinutes() % timestampSpread === 0 &&
|
||||
timestamp.getSeconds() === 0
|
||||
? "w-[12px] bg-neutral-600 dark:bg-neutral-500"
|
||||
@ -84,7 +84,7 @@ export function Timestamp({
|
||||
segmentKey,
|
||||
}: TimestampSegmentProps) {
|
||||
return (
|
||||
<div className="absolute left-[15px] h-[8px] z-10">
|
||||
<div className="absolute left-[15px] z-10 h-[8px]">
|
||||
{!isFirstSegmentInMinimap && !isLastSegmentInMinimap && (
|
||||
<div
|
||||
key={`${segmentKey}_timestamp`}
|
||||
|
@ -127,8 +127,8 @@ function ConfigEditor() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="absolute top-2 bottom-16 right-0 left-0 md:left-2">
|
||||
<div className="lg:flex justify-between mr-1">
|
||||
<div className="absolute bottom-16 left-0 right-0 top-2 md:left-2">
|
||||
<div className="mr-1 justify-between lg:flex">
|
||||
<Heading as="h2">Config</Heading>
|
||||
<div>
|
||||
<Button size="sm" className="mx-1" onClick={() => handleCopyConfig()}>
|
||||
@ -152,12 +152,12 @@ function ConfigEditor() {
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="p-4 overflow-scroll text-danger whitespace-pre-wrap">
|
||||
<div className="overflow-scroll whitespace-pre-wrap p-4 text-danger">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={configRef} className="h-full mt-2" />
|
||||
<div ref={configRef} className="mt-2 h-full" />
|
||||
<Toaster closeButton={true} />
|
||||
</div>
|
||||
);
|
||||
|
@ -77,7 +77,7 @@ function Exports() {
|
||||
const [selected, setSelected] = useState<Export>();
|
||||
|
||||
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
|
||||
open={deleteClip != undefined}
|
||||
onOpenChange={() => setDeleteClip(undefined)}
|
||||
@ -128,9 +128,9 @@ function Exports() {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<div className="w-full p-2 flex items-center justify-center">
|
||||
<div className="flex w-full items-center justify-center p-2">
|
||||
<Input
|
||||
className="w-full md:w-1/3 bg-muted"
|
||||
className="w-full bg-muted md:w-1/3"
|
||||
placeholder="Search"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
@ -139,7 +139,7 @@ function Exports() {
|
||||
|
||||
<div className="w-full overflow-hidden">
|
||||
{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) => (
|
||||
<ExportCard
|
||||
key={item.name}
|
||||
|
@ -332,13 +332,13 @@ function Logs() {
|
||||
const [selectedLog, setSelectedLog] = useState<LogLine>();
|
||||
|
||||
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} />
|
||||
<LogInfoDialog logLine={selectedLog} setLogLine={setSelectedLog} />
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center justify-between">
|
||||
<ToggleGroup
|
||||
className="*:px-3 *:py-4 *:rounded-md"
|
||||
className="*:rounded-md *:px-3 *:py-4"
|
||||
type="single"
|
||||
size="sm"
|
||||
value={logService}
|
||||
@ -363,12 +363,12 @@ function Logs() {
|
||||
</ToggleGroup>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
className="flex justify-between items-center gap-2"
|
||||
className="flex items-center justify-between gap-2"
|
||||
size="sm"
|
||||
onClick={handleCopyLogs}
|
||||
>
|
||||
<FaCopy className="text-secondary-foreground" />
|
||||
<div className="hidden md:block text-primary">
|
||||
<div className="hidden text-primary md:block">
|
||||
Copy to Clipboard
|
||||
</div>
|
||||
</Button>
|
||||
@ -381,7 +381,7 @@ function Logs() {
|
||||
|
||||
{initialScroll && !endVisible && (
|
||||
<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={() =>
|
||||
contentRef.current?.scrollTo({
|
||||
top: contentRef.current?.scrollHeight,
|
||||
@ -393,20 +393,20 @@ function Logs() {
|
||||
</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="grid grid-cols-5 sm:grid-cols-8 md:grid-cols-12 *:px-2 *:py-3 *:text-sm *:text-primary/40">
|
||||
<div className="p-1 flex items-center capitalize">Type</div>
|
||||
<div className="col-span-2 sm:col-span-1 flex items-center">
|
||||
<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 *:px-2 *:py-3 *:text-sm *:text-primary/40 sm:grid-cols-8 md:grid-cols-12">
|
||||
<div className="flex items-center p-1 capitalize">Type</div>
|
||||
<div className="col-span-2 flex items-center sm:col-span-1">
|
||||
Timestamp
|
||||
</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
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
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 &&
|
||||
[...Array(logRange.end).keys()].map((idx) => {
|
||||
@ -449,7 +449,7 @@ function Logs() {
|
||||
{logLines.length > 0 && <div id="page-bottom" ref={endLogRef} />}
|
||||
</div>
|
||||
{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>
|
||||
@ -474,25 +474,25 @@ function LogLineData({
|
||||
<div
|
||||
ref={startRef}
|
||||
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,
|
||||
"*:text-sm",
|
||||
)}
|
||||
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} />
|
||||
</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}
|
||||
</div>
|
||||
<div className="size-full pr-2 col-span-2 flex items-center">
|
||||
<div className="w-full overflow-hidden whitespace-nowrap text-ellipsis">
|
||||
<div className="col-span-2 flex size-full items-center pr-2">
|
||||
<div className="w-full overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{line.section}
|
||||
</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="w-full overflow-hidden whitespace-nowrap text-ellipsis">
|
||||
<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 text-ellipsis whitespace-nowrap">
|
||||
{line.content}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -105,12 +105,12 @@ export default function Settings() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="size-full p-2 flex flex-col">
|
||||
<div className="w-full h-11 relative flex justify-between items-center">
|
||||
<div className="flex size-full flex-col p-2">
|
||||
<div className="relative flex h-11 w-full items-center justify-between">
|
||||
<ScrollArea className="w-full whitespace-nowrap">
|
||||
<div ref={tabsRef} className="flex flex-row">
|
||||
<ToggleGroup
|
||||
className="*:px-3 *:py-4 *:rounded-md"
|
||||
className="*:rounded-md *:px-3 *:py-4"
|
||||
type="single"
|
||||
size="sm"
|
||||
value={pageToggle}
|
||||
@ -123,7 +123,7 @@ export default function Settings() {
|
||||
{Object.values(settingsViews).map((item) => (
|
||||
<ToggleGroupItem
|
||||
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}
|
||||
data-nav-item={item}
|
||||
aria-label={`Select ${item}`}
|
||||
@ -138,7 +138,7 @@ export default function Settings() {
|
||||
{(page == "debug" ||
|
||||
page == "masks / zones" ||
|
||||
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" && (
|
||||
<ZoneMaskFilterButton
|
||||
selectedZoneMask={filterZoneMask}
|
||||
@ -153,7 +153,7 @@ export default function Settings() {
|
||||
</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 == "debug" && <ObjectSettings selectedCamera={selectedCamera} />}
|
||||
{page == "masks / zones" && (
|
||||
@ -216,11 +216,11 @@ function CameraSelectButton({
|
||||
|
||||
const trigger = (
|
||||
<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"
|
||||
>
|
||||
<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
|
||||
? "No Camera"
|
||||
: selectedCamera.replaceAll("_", " ")}
|
||||
@ -237,7 +237,7 @@ function CameraSelectButton({
|
||||
<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">
|
||||
{allCameras.map((item) => (
|
||||
<FilterSwitch
|
||||
|
@ -224,8 +224,8 @@ export default function SubmitPlus() {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="size-full flex flex-col">
|
||||
<div className="w-full h-16 px-2 flex items-center justify-between overflow-x-auto">
|
||||
<div className="flex size-full flex-col">
|
||||
<div className="flex h-16 w-full items-center justify-between overflow-x-auto px-2">
|
||||
<PlusFilterGroup
|
||||
selectedCameras={selectedCameras}
|
||||
selectedLabels={selectedLabels}
|
||||
@ -236,8 +236,8 @@ export default function SubmitPlus() {
|
||||
/>
|
||||
<PlusSortSelector selectedSort={sort} setSelectedSort={setSort} />
|
||||
</div>
|
||||
<div className="size-full flex flex-1 flex-wrap content-start gap-2 md:gap-4 overflow-y-auto no-scrollbar">
|
||||
<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="no-scrollbar flex size-full flex-1 flex-wrap content-start gap-2 overflow-y-auto md:gap-4">
|
||||
<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
|
||||
open={upload != undefined}
|
||||
onOpenChange={(open) => (!open ? setUpload(undefined) : null)}
|
||||
@ -286,16 +286,16 @@ export default function SubmitPlus() {
|
||||
<div
|
||||
key={event.id}
|
||||
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)}
|
||||
>
|
||||
<div className="absolute left-0 top-2 z-40">
|
||||
<Tooltip>
|
||||
<div className="flex">
|
||||
<TooltipTrigger asChild>
|
||||
<div className="mx-3 pb-1 text-white text-sm">
|
||||
<div className="mx-3 pb-1 text-sm text-white">
|
||||
<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) => {
|
||||
return getIconForLabel(
|
||||
@ -317,7 +317,7 @@ export default function SubmitPlus() {
|
||||
</Tooltip>
|
||||
</div>
|
||||
<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`}
|
||||
loading="lazy"
|
||||
/>
|
||||
@ -392,7 +392,7 @@ function PlusFilterGroup({
|
||||
const Content = isMobile ? DrawerContent : DropdownMenuContent;
|
||||
|
||||
return (
|
||||
<div className="h-full flex justify-start gap-2 items-center">
|
||||
<div className="flex h-full items-center justify-start gap-2">
|
||||
<CamerasFilterButton
|
||||
allCameras={allCameras}
|
||||
groups={[]}
|
||||
@ -417,7 +417,7 @@ function PlusFilterGroup({
|
||||
<FaList
|
||||
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
|
||||
? "All Labels"
|
||||
: `${selectedLabels.length} Labels`}
|
||||
@ -450,7 +450,7 @@ function PlusFilterGroup({
|
||||
<PiSlidersHorizontalFill
|
||||
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
|
||||
? "Score Range"
|
||||
: `${selectedScoreRange[0] * 100}% - ${selectedScoreRange[1] * 100}%`}
|
||||
@ -458,7 +458,7 @@ function PlusFilterGroup({
|
||||
</Button>
|
||||
</Trigger>
|
||||
<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">
|
||||
<Input
|
||||
@ -493,7 +493,7 @@ function PlusFilterGroup({
|
||||
/>
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="p-2 flex justify-evenly items-center">
|
||||
<div className="flex items-center justify-evenly p-2">
|
||||
<Button
|
||||
variant="select"
|
||||
onClick={() => {
|
||||
@ -547,7 +547,7 @@ function PlusSortSelector({
|
||||
const Content = isMobile ? DrawerContent : DropdownMenuContent;
|
||||
|
||||
return (
|
||||
<div className="h-full flex justify-start gap-2 items-center">
|
||||
<div className="flex h-full items-center justify-start gap-2">
|
||||
<Menu
|
||||
open={open}
|
||||
onOpenChange={(open) => {
|
||||
@ -572,24 +572,24 @@ function PlusSortSelector({
|
||||
<Sort
|
||||
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]}
|
||||
</div>
|
||||
</Button>
|
||||
</Trigger>
|
||||
<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
|
||||
className={`flex flex-col gap-4 ${isMobile ? "mt-4" : ""}`}
|
||||
onValueChange={(value) => setCurrentSort(value)}
|
||||
>
|
||||
<div className="w-full flex items-center gap-2">
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<RadioGroupItem
|
||||
className={
|
||||
currentSort == "date"
|
||||
? "from-selected/50 to-selected/90 text-selected bg-selected"
|
||||
: "from-secondary/50 to-secondary/90 text-secondary bg-secondary"
|
||||
? "bg-selected from-selected/50 to-selected/90 text-selected"
|
||||
: "bg-secondary from-secondary/50 to-secondary/90 text-secondary"
|
||||
}
|
||||
id="date"
|
||||
value="date"
|
||||
@ -616,12 +616,12 @@ function PlusSortSelector({
|
||||
<div className="size-5" />
|
||||
)}
|
||||
</div>
|
||||
<div className="w-full flex items-center gap-2">
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<RadioGroupItem
|
||||
className={
|
||||
currentSort == "score"
|
||||
? "from-selected/50 to-selected/90 text-selected bg-selected"
|
||||
: "from-secondary/50 to-secondary/90 text-secondary bg-secondary"
|
||||
? "bg-selected from-selected/50 to-selected/90 text-selected"
|
||||
: "bg-secondary from-secondary/50 to-secondary/90 text-secondary"
|
||||
}
|
||||
id="score"
|
||||
value="score"
|
||||
@ -650,7 +650,7 @@ function PlusSortSelector({
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="p-2 flex justify-evenly items-center">
|
||||
<div className="flex items-center justify-evenly p-2">
|
||||
<Button
|
||||
variant="select"
|
||||
onClick={() => {
|
||||
|
@ -41,13 +41,13 @@ function System() {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="size-full p-2 flex flex-col">
|
||||
<div className="w-full h-11 relative flex justify-between items-center">
|
||||
<div className="flex size-full flex-col p-2">
|
||||
<div className="relative flex h-11 w-full items-center justify-between">
|
||||
{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
|
||||
className="*:px-3 *:py-4 *:rounded-md"
|
||||
className="*:rounded-md *:px-3 *:py-4"
|
||||
type="single"
|
||||
size="sm"
|
||||
value={pageToggle}
|
||||
@ -72,18 +72,18 @@ function System() {
|
||||
))}
|
||||
</ToggleGroup>
|
||||
|
||||
<div className="h-full flex items-center">
|
||||
<div className="flex h-full items-center">
|
||||
{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 />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<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 && (
|
||||
<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}
|
||||
</div>
|
||||
)}
|
||||
|
@ -55,9 +55,9 @@ const colors = [
|
||||
|
||||
function ColorSwatch({ name, value }: { name: string; value: string }) {
|
||||
return (
|
||||
<div className="flex items-center mb-2">
|
||||
<div className="mb-2 flex items-center">
|
||||
<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 }}
|
||||
></div>
|
||||
<span>{name}</span>
|
||||
@ -212,9 +212,9 @@ function UIPlayground() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full h-full">
|
||||
<div className="h-full w-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>
|
||||
|
||||
<IconPicker
|
||||
@ -294,7 +294,7 @@ function UIPlayground() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex p-2 justify-start items-center">
|
||||
<div className="flex items-center justify-start p-2">
|
||||
<Switch
|
||||
id="exporthandles"
|
||||
checked={showExportHandles}
|
||||
@ -326,7 +326,7 @@ function UIPlayground() {
|
||||
Export
|
||||
</Button>
|
||||
</div>
|
||||
<div className="w-[40px] my-4">
|
||||
<div className="my-4 w-[40px]">
|
||||
<CameraActivityIndicator />
|
||||
</div>
|
||||
<p>
|
||||
@ -378,7 +378,7 @@ function UIPlayground() {
|
||||
</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 && (
|
||||
<MotionReviewTimeline
|
||||
segmentDuration={zoomSettings.segmentDuration} // seconds per segment
|
||||
|
@ -26,28 +26,28 @@ import { TimeRange, Timeline } from "@/types/timeline";
|
||||
export function getTimelineIcon(timelineItem: Timeline) {
|
||||
switch (timelineItem.class_type) {
|
||||
case "visible":
|
||||
return <LuPlay className="w-4 mr-1" />;
|
||||
return <LuPlay className="mr-1 w-4" />;
|
||||
case "gone":
|
||||
return <IoMdExit className="w-4 mr-1" />;
|
||||
return <IoMdExit className="mr-1 w-4" />;
|
||||
case "active":
|
||||
return <LuPlayCircle className="w-4 mr-1" />;
|
||||
return <LuPlayCircle className="mr-1 w-4" />;
|
||||
case "stationary":
|
||||
return <LuCircle className="w-4 mr-1" />;
|
||||
return <LuCircle className="mr-1 w-4" />;
|
||||
case "entered_zone":
|
||||
return <MdOutlineLocationOn className="w-4 mr-1" />;
|
||||
return <MdOutlineLocationOn className="mr-1 w-4" />;
|
||||
case "attribute":
|
||||
switch (timelineItem.data.attribute) {
|
||||
case "face":
|
||||
return <MdFaceUnlock className="w-4 mr-1" />;
|
||||
return <MdFaceUnlock className="mr-1 w-4" />;
|
||||
case "license_plate":
|
||||
return <MdOutlinePictureInPictureAlt className="w-4 mr-1" />;
|
||||
return <MdOutlinePictureInPictureAlt className="mr-1 w-4" />;
|
||||
default:
|
||||
return <LuTruck className="w-4 mr-1" />;
|
||||
return <LuTruck className="mr-1 w-4" />;
|
||||
}
|
||||
case "heard":
|
||||
return <LuEar className="w-4 mr-1" />;
|
||||
return <LuEar className="mr-1 w-4" />;
|
||||
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) {
|
||||
switch (timelineItem.data.label) {
|
||||
case "bicycle":
|
||||
return <FaBicycle className="w-4 mr-1" />;
|
||||
return <FaBicycle className="mr-1 w-4" />;
|
||||
case "car":
|
||||
return <LuCar className="w-4 mr-1" />;
|
||||
return <LuCar className="mr-1 w-4" />;
|
||||
case "cat":
|
||||
return <LuCat className="w-4 mr-1" />;
|
||||
return <LuCat className="mr-1 w-4" />;
|
||||
case "deer":
|
||||
return <GiDeer className="w-4 mr-1" />;
|
||||
return <GiDeer className="mr-1 w-4" />;
|
||||
case "dog":
|
||||
return <LuDog className="w-4 mr-1" />;
|
||||
return <LuDog className="mr-1 w-4" />;
|
||||
case "package":
|
||||
return <LuPackage className="w-4 mr-1" />;
|
||||
return <LuPackage className="mr-1 w-4" />;
|
||||
case "person":
|
||||
return <LuPersonStanding className="w-4 mr-1" />;
|
||||
return <LuPersonStanding className="mr-1 w-4" />;
|
||||
default:
|
||||
return <LuCamera className="w-4 mr-1" />;
|
||||
return <LuCamera className="mr-1 w-4" />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,14 +252,14 @@ export default function EventView({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="py-2 flex flex-col size-full">
|
||||
<div className="flex size-full flex-col py-2">
|
||||
<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 && (
|
||||
<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
|
||||
className="*:px-3 *:py-4 *:rounded-md"
|
||||
className="*:rounded-md *:px-3 *:py-4"
|
||||
type="single"
|
||||
size="sm"
|
||||
value={severityToggle}
|
||||
@ -272,7 +272,7 @@ export default function EventView({
|
||||
value="alert"
|
||||
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">
|
||||
Alerts{` ∙ ${reviewCounts.alert > -1 ? reviewCounts.alert : ""}`}
|
||||
</div>
|
||||
@ -282,14 +282,14 @@ export default function EventView({
|
||||
value="detection"
|
||||
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">
|
||||
Detections
|
||||
{` ∙ ${reviewCounts.detection > -1 ? reviewCounts.detection : ""}`}
|
||||
</div>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
className={`px-3 py-4 rounded-lg ${
|
||||
className={`rounded-lg px-3 py-4 ${
|
||||
severityToggle == "significant_motion"
|
||||
? ""
|
||||
: "text-muted-foreground"
|
||||
@ -297,7 +297,7 @@ export default function EventView({
|
||||
value="significant_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>
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
@ -576,11 +576,11 @@ function DetectionReview({
|
||||
<>
|
||||
<div
|
||||
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 && (
|
||||
<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}
|
||||
reviewItems={currentItems}
|
||||
itemsToReview={loading ? 0 : itemsToReview}
|
||||
@ -589,20 +589,20 @@ function DetectionReview({
|
||||
)}
|
||||
|
||||
{!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 />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!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" />
|
||||
There are no {severity.replace(/_/g, " ")}s to review
|
||||
</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}
|
||||
>
|
||||
{!loading && currentItems
|
||||
@ -620,7 +620,7 @@ function DetectionReview({
|
||||
}
|
||||
className="review-item relative rounded-lg"
|
||||
>
|
||||
<div className="aspect-video rounded-lg overflow-hidden">
|
||||
<div className="aspect-video overflow-hidden rounded-lg">
|
||||
<PreviewThumbnailPlayer
|
||||
review={value}
|
||||
allPreviews={relevantPreviews}
|
||||
@ -632,7 +632,7 @@ function DetectionReview({
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
@ -641,12 +641,12 @@ function DetectionReview({
|
||||
Array(itemsToReview)
|
||||
.fill(0)
|
||||
.map((_, idx) => (
|
||||
<Skeleton key={idx} className="size-full aspect-video" />
|
||||
<Skeleton key={idx} className="aspect-video size-full" />
|
||||
))}
|
||||
{!loading &&
|
||||
(currentItems?.length ?? 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
|
||||
className="text-white"
|
||||
variant="select"
|
||||
@ -660,8 +660,8 @@ function DetectionReview({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[65px] md:w-[110px] flex flex-row">
|
||||
<div className="w-[55px] md:w-[100px] overflow-y-auto no-scrollbar">
|
||||
<div className="flex w-[65px] flex-row md:w-[110px]">
|
||||
<div className="no-scrollbar w-[55px] overflow-y-auto md:w-[100px]">
|
||||
{loading ? (
|
||||
<Skeleton className="size-full" />
|
||||
) : (
|
||||
@ -919,10 +919,10 @@ function MotionReview({
|
||||
|
||||
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
|
||||
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) => {
|
||||
let grow;
|
||||
@ -965,12 +965,12 @@ function MotionReview({
|
||||
}
|
||||
/>
|
||||
<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
|
||||
className={`rounded-lg md:rounded-2xl size-full ${spans} ${grow}`}
|
||||
className={`size-full rounded-lg md:rounded-2xl ${spans} ${grow}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@ -978,7 +978,7 @@ function MotionReview({
|
||||
})}
|
||||
</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 ? (
|
||||
<MotionReviewTimeline
|
||||
segmentDuration={segmentDuration}
|
||||
|
@ -357,11 +357,11 @@ export function RecordingView({
|
||||
}, [previewRowRef.current?.scrollWidth, previewRowRef.current?.scrollHeight]);
|
||||
|
||||
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} />
|
||||
<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 && (
|
||||
<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")}>
|
||||
<Button
|
||||
@ -422,7 +422,7 @@ export function RecordingView({
|
||||
)}
|
||||
{isDesktop ? (
|
||||
<ToggleGroup
|
||||
className="*:px-3 *:py-4 *:rounded-md"
|
||||
className="*:rounded-md *:px-3 *:py-4"
|
||||
type="single"
|
||||
size="sm"
|
||||
value={timelineType}
|
||||
@ -469,8 +469,8 @@ export function RecordingView({
|
||||
<div
|
||||
ref={mainLayoutRef}
|
||||
className={cn(
|
||||
"h-full flex justify-center overflow-hidden",
|
||||
isDesktop ? "" : "flex-col landscape:flex-row gap-2",
|
||||
"flex h-full justify-center overflow-hidden",
|
||||
isDesktop ? "" : "flex-col gap-2 landscape:flex-row",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
@ -479,7 +479,7 @@ export function RecordingView({
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"size-full flex items-center",
|
||||
"flex size-full items-center",
|
||||
mainCameraAspect == "tall"
|
||||
? "flex-row justify-evenly"
|
||||
: "flex-col justify-center gap-2",
|
||||
@ -491,7 +491,7 @@ export function RecordingView({
|
||||
"relative",
|
||||
isDesktop
|
||||
? cn(
|
||||
"px-4 flex justify-center",
|
||||
"flex justify-center px-4",
|
||||
mainCameraAspect == "tall"
|
||||
? "h-[50%] md:h-[60%] lg:h-[75%] xl:h-[90%]"
|
||||
: mainCameraAspect == "wide"
|
||||
@ -499,10 +499,10 @@ export function RecordingView({
|
||||
: "",
|
||||
)
|
||||
: cn(
|
||||
"portrait:w-full pt-2",
|
||||
"pt-2 portrait:w-full",
|
||||
mainCameraAspect == "wide"
|
||||
? "landscape:w-full aspect-wide"
|
||||
: "landscape:h-[94%] aspect-video",
|
||||
? "aspect-wide landscape:w-full"
|
||||
: "aspect-video landscape:h-[94%]",
|
||||
),
|
||||
)}
|
||||
style={{
|
||||
@ -545,8 +545,8 @@ export function RecordingView({
|
||||
"flex gap-2 overflow-auto",
|
||||
mainCameraAspect == "tall"
|
||||
? "h-full w-48 flex-col"
|
||||
: `w-full h-28`,
|
||||
previewRowOverflows ? "" : "justify-center items-center",
|
||||
: `h-28 w-full`,
|
||||
previewRowOverflows ? "" : "items-center justify-center",
|
||||
)}
|
||||
>
|
||||
<div className="w-2" />
|
||||
@ -660,12 +660,12 @@ function Timeline({
|
||||
<div
|
||||
className={`${
|
||||
isDesktop
|
||||
? `${timelineType == "timeline" ? "w-[100px]" : "w-60"} overflow-y-auto no-scrollbar`
|
||||
: "portrait:flex-grow landscape:w-[20%] overflow-hidden"
|
||||
? `${timelineType == "timeline" ? "w-[100px]" : "w-60"} no-scrollbar overflow-y-auto`
|
||||
: "overflow-hidden portrait:flex-grow landscape:w-[20%]"
|
||||
} 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="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 top-0 z-20 h-[30px] w-full bg-gradient-to-b from-secondary to-transparent"></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" ? (
|
||||
motionData ? (
|
||||
<MotionReviewTimeline
|
||||
@ -693,7 +693,7 @@ function Timeline({
|
||||
)
|
||||
) : (
|
||||
<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) => {
|
||||
if (review.severity == "significant_motion") {
|
||||
|
@ -325,7 +325,7 @@ export default function DraggableGridLayout({
|
||||
<>
|
||||
<Toaster position="top-center" closeButton={true} />
|
||||
{!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 && (
|
||||
<Skeleton className="size-full rounded-lg md:rounded-2xl" />
|
||||
)}
|
||||
@ -340,7 +340,7 @@ export default function DraggableGridLayout({
|
||||
</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}
|
||||
>
|
||||
<EditGroupDialog
|
||||
@ -373,7 +373,7 @@ export default function DraggableGridLayout({
|
||||
key="birdseye"
|
||||
className={cn(
|
||||
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}
|
||||
liveMode={birdseyeConfig.restream ? "mse" : "jsmpeg"}
|
||||
@ -397,10 +397,10 @@ export default function DraggableGridLayout({
|
||||
key={camera.name}
|
||||
cameraRef={cameraRef}
|
||||
className={cn(
|
||||
"rounded-lg md:rounded-2xl bg-black",
|
||||
"rounded-lg bg-black md:rounded-2xl",
|
||||
grow,
|
||||
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 && visibleCameras.includes(camera.name)
|
||||
@ -429,7 +429,7 @@ export default function DraggableGridLayout({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<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={() =>
|
||||
setIsEditMode((prevIsEditMode) => !prevIsEditMode)
|
||||
}
|
||||
@ -450,7 +450,7 @@ export default function DraggableGridLayout({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<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={() =>
|
||||
setEditGroup((prevEditGroup) => !prevEditGroup)
|
||||
}
|
||||
@ -465,7 +465,7 @@ export default function DraggableGridLayout({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<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}
|
||||
>
|
||||
{fullscreen ? (
|
||||
@ -492,10 +492,10 @@ export default function DraggableGridLayout({
|
||||
function CornerCircles() {
|
||||
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="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="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="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 left-[-4px] top-[-4px] z-50 size-3 rounded-full bg-primary-variant p-2 text-background outline-2 outline-muted" />
|
||||
<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="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="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" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -115,20 +115,20 @@ export default function LiveBirdseyeView() {
|
||||
ref={mainRef}
|
||||
className={
|
||||
fullscreen
|
||||
? `fixed inset-0 bg-black z-30`
|
||||
: `size-full p-2 flex flex-col ${isMobile ? "landscape:flex-row" : ""}`
|
||||
? `fixed inset-0 z-30 bg-black`
|
||||
: `flex size-full flex-col p-2 ${isMobile ? "landscape:flex-row" : ""}`
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
fullscreen
|
||||
? `absolute right-32 top-1 z-40 ${isMobile ? "landscape:left-2 landscape:right-auto landscape:bottom-1 landscape:top-auto" : ""}`
|
||||
: `w-full h-12 flex flex-row items-center justify-between ${isMobile ? "landscape:w-min landscape:h-full landscape:flex-col" : ""}`
|
||||
? `absolute right-32 top-1 z-40 ${isMobile ? "landscape:bottom-1 landscape:left-2 landscape:right-auto landscape:top-auto" : ""}`
|
||||
: `flex h-12 w-full flex-row items-center justify-between ${isMobile ? "landscape:h-full landscape:w-min landscape:flex-col" : ""}`
|
||||
}
|
||||
>
|
||||
{!fullscreen ? (
|
||||
<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"}
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
@ -140,7 +140,7 @@ export default function LiveBirdseyeView() {
|
||||
)}
|
||||
<TooltipProvider>
|
||||
<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
|
||||
className="p-2 md:p-0"
|
||||
|
@ -222,15 +222,15 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
||||
ref={mainRef}
|
||||
className={
|
||||
fullscreen
|
||||
? `fixed inset-0 bg-black z-30`
|
||||
: `size-full p-2 flex flex-col ${isMobile ? "landscape:flex-row landscape:gap-1" : ""}`
|
||||
? `fixed inset-0 z-30 bg-black`
|
||||
: `flex size-full flex-col p-2 ${isMobile ? "landscape:flex-row landscape:gap-1" : ""}`
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
fullscreen
|
||||
? `absolute right-32 top-1 z-40 ${isMobile ? "landscape:left-2 landscape:right-auto landscape:bottom-1 landscape:top-auto" : ""}`
|
||||
: `w-full h-12 flex flex-row items-center justify-between ${isMobile ? "landscape:w-12 landscape:h-full landscape:flex-col" : ""}`
|
||||
? `absolute right-32 top-1 z-40 ${isMobile ? "landscape:bottom-1 landscape:left-2 landscape:right-auto landscape:top-auto" : ""}`
|
||||
: `flex h-12 w-full flex-row items-center justify-between ${isMobile ? "landscape:h-full landscape:w-12 landscape:flex-col" : ""}`
|
||||
}
|
||||
>
|
||||
{!fullscreen ? (
|
||||
@ -344,7 +344,7 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`flex flex-col justify-center items-center ${growClassName}`}
|
||||
className={`flex flex-col items-center justify-center ${growClassName}`}
|
||||
ref={clickOverlayRef}
|
||||
onClick={handleOverlayClick}
|
||||
style={{
|
||||
@ -435,7 +435,7 @@ function PtzControlPanel({
|
||||
);
|
||||
|
||||
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") && (
|
||||
<>
|
||||
<Button
|
||||
@ -637,7 +637,7 @@ function FrigateCameraFeatures({
|
||||
title={`${camera} Settings`}
|
||||
/>
|
||||
</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
|
||||
label="Object Detection"
|
||||
isChecked={detectState == "ON"}
|
||||
|
@ -155,10 +155,10 @@ export default function LiveDashboardView({
|
||||
const birdseyeConfig = useMemo(() => config?.birdseye, [config]);
|
||||
|
||||
return (
|
||||
<div className="size-full p-2 overflow-y-auto" ref={containerRef}>
|
||||
<div className="size-full overflow-y-auto p-2" ref={containerRef}>
|
||||
{isMobile && (
|
||||
<div className="h-11 relative flex items-center justify-between">
|
||||
<Logo className="absolute inset-x-1/2 -translate-x-1/2 h-8" />
|
||||
<div className="relative flex h-11 items-center justify-between">
|
||||
<Logo className="absolute inset-x-1/2 h-8 -translate-x-1/2" />
|
||||
<div className="max-w-[45%]">
|
||||
<CameraGroupSelector />
|
||||
</div>
|
||||
@ -167,7 +167,7 @@ export default function LiveDashboardView({
|
||||
<Button
|
||||
className={`p-1 ${
|
||||
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"
|
||||
}`}
|
||||
size="xs"
|
||||
@ -178,7 +178,7 @@ export default function LiveDashboardView({
|
||||
<Button
|
||||
className={`p-1 ${
|
||||
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"
|
||||
}`}
|
||||
size="xs"
|
||||
@ -194,8 +194,8 @@ export default function LiveDashboardView({
|
||||
className={cn(
|
||||
"p-1",
|
||||
isEditMode
|
||||
? "text-primary bg-selected"
|
||||
: "text-secondary-foreground bg-secondary",
|
||||
? "bg-selected text-primary"
|
||||
: "bg-secondary text-secondary-foreground",
|
||||
)}
|
||||
size="xs"
|
||||
onClick={() =>
|
||||
@ -212,7 +212,7 @@ export default function LiveDashboardView({
|
||||
{events && events.length > 0 && (
|
||||
<ScrollArea>
|
||||
<TooltipProvider>
|
||||
<div className="px-1 flex gap-2 items-center">
|
||||
<div className="flex items-center gap-2 px-1">
|
||||
{events.map((event) => {
|
||||
return <AnimatedEventCard key={event.id} event={event} />;
|
||||
})}
|
||||
@ -224,7 +224,7 @@ export default function LiveDashboardView({
|
||||
|
||||
{!cameraGroup || cameraGroup == "default" || isMobileOnly ? (
|
||||
<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 && (
|
||||
<BirdseyeLivePlayer
|
||||
@ -247,7 +247,7 @@ export default function LiveDashboardView({
|
||||
<LivePlayer
|
||||
cameraRef={cameraRef}
|
||||
key={camera.name}
|
||||
className={`${grow} rounded-lg md:rounded-2xl bg-black`}
|
||||
className={`${grow} rounded-lg bg-black md:rounded-2xl`}
|
||||
windowVisible={
|
||||
windowVisible && visibleCameras.includes(camera.name)
|
||||
}
|
||||
|
@ -204,11 +204,11 @@ export default function CameraMetrics({
|
||||
}, [statsHistory]);
|
||||
|
||||
return (
|
||||
<div className="size-full mt-4 flex flex-col gap-3 overflow-y-auto">
|
||||
<div className="text-muted-foreground text-sm font-medium">Overview</div>
|
||||
<div className="mt-4 flex size-full flex-col gap-3 overflow-y-auto">
|
||||
<div className="text-sm font-medium text-muted-foreground">Overview</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3">
|
||||
{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>
|
||||
<CameraLineGraph
|
||||
graphId="overall-stats"
|
||||
@ -219,21 +219,21 @@ export default function CameraMetrics({
|
||||
/>
|
||||
</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 className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
{config &&
|
||||
Object.values(config.cameras).map((camera) => {
|
||||
if (camera.enabled) {
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-3">
|
||||
<div className="capitalize text-muted-foreground text-sm font-medium">
|
||||
<div className="flex w-full flex-col gap-3">
|
||||
<div className="text-sm font-medium capitalize text-muted-foreground">
|
||||
{camera.name.replaceAll("_", " ")}
|
||||
</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) ? (
|
||||
<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>
|
||||
<CameraLineGraph
|
||||
graphId={`${camera.name}-cpu`}
|
||||
@ -246,10 +246,10 @@ export default function CameraMetrics({
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Skeleton className="size-full aspect-video" />
|
||||
<Skeleton className="aspect-video size-full" />
|
||||
)}
|
||||
{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>
|
||||
<CameraLineGraph
|
||||
graphId={`${camera.name}-dps`}
|
||||
@ -262,7 +262,7 @@ export default function CameraMetrics({
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Skeleton className="size-full aspect-video" />
|
||||
<Skeleton className="aspect-video size-full" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -344,15 +344,15 @@ export default function GeneralMetrics({
|
||||
<>
|
||||
<VainfoDialog showVainfo={showVainfo} setShowVainfo={setShowVainfo} />
|
||||
|
||||
<div className="size-full mt-4 flex flex-col overflow-y-auto">
|
||||
<div className="text-muted-foreground text-sm font-medium">
|
||||
<div className="mt-4 flex size-full flex-col overflow-y-auto">
|
||||
<div className="text-sm font-medium text-muted-foreground">
|
||||
Detectors
|
||||
</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 ? (
|
||||
<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>
|
||||
{detInferenceTimeSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -367,12 +367,12 @@ export default function GeneralMetrics({
|
||||
))}
|
||||
</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 ? (
|
||||
<>
|
||||
{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>
|
||||
{detTempSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -389,10 +389,10 @@ export default function GeneralMetrics({
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Skeleton className="w-full aspect-video" />
|
||||
<Skeleton className="aspect-video w-full" />
|
||||
)}
|
||||
{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>
|
||||
{detCpuSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -407,10 +407,10 @@ export default function GeneralMetrics({
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Skeleton className="w-full aspect-video" />
|
||||
<Skeleton className="aspect-video w-full" />
|
||||
)}
|
||||
{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>
|
||||
{detMemSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -425,14 +425,14 @@ export default function GeneralMetrics({
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Skeleton className="w-full aspect-video" />
|
||||
<Skeleton className="aspect-video w-full" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(statsHistory.length == 0 || statsHistory[0].gpu_usages) && (
|
||||
<>
|
||||
<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
|
||||
</div>
|
||||
{canGetGpuInfo && (
|
||||
@ -445,9 +445,9 @@ export default function GeneralMetrics({
|
||||
</Button>
|
||||
)}
|
||||
</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 ? (
|
||||
<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>
|
||||
{gpuSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -462,12 +462,12 @@ export default function GeneralMetrics({
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Skeleton className="w-full aspect-video" />
|
||||
<Skeleton className="aspect-video w-full" />
|
||||
)}
|
||||
{statsHistory.length != 0 ? (
|
||||
<>
|
||||
{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>
|
||||
{gpuMemSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -484,18 +484,18 @@ export default function GeneralMetrics({
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Skeleton className="w-full aspect-video" />
|
||||
<Skeleton className="aspect-video w-full" />
|
||||
)}
|
||||
</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
|
||||
</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 ? (
|
||||
<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>
|
||||
{otherProcessCpuSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -510,10 +510,10 @@ export default function GeneralMetrics({
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Skeleton className="w-full aspect-tall" />
|
||||
<Skeleton className="aspect-tall w-full" />
|
||||
)}
|
||||
{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>
|
||||
{otherProcessMemSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -528,7 +528,7 @@ export default function GeneralMetrics({
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Skeleton className="w-full aspect-tall" />
|
||||
<Skeleton className="aspect-tall w-full" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -42,10 +42,10 @@ export default function StorageMetrics({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="size-full mt-4 flex flex-col overflow-y-auto">
|
||||
<div className="text-muted-foreground text-sm font-medium">Overview</div>
|
||||
<div className="mt-4 grid grid-cols-1 sm:grid-cols-3 gap-2">
|
||||
<div className="p-2.5 bg-background_alt rounded-lg md:rounded-2xl flex-col">
|
||||
<div className="mt-4 flex size-full flex-col overflow-y-auto">
|
||||
<div className="text-sm font-medium text-muted-foreground">Overview</div>
|
||||
<div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-3">
|
||||
<div className="flex-col rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<div className="mb-5">Recordings</div>
|
||||
<StorageGraph
|
||||
graphId="general-recordings"
|
||||
@ -53,7 +53,7 @@ export default function StorageMetrics({
|
||||
total={totalStorage.total}
|
||||
/>
|
||||
</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>
|
||||
<StorageGraph
|
||||
graphId="general-cache"
|
||||
@ -61,7 +61,7 @@ export default function StorageMetrics({
|
||||
total={stats.service.storage["/tmp/cache"]["total"]}
|
||||
/>
|
||||
</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>
|
||||
<StorageGraph
|
||||
graphId="general-shared-memory"
|
||||
@ -70,12 +70,12 @@ export default function StorageMetrics({
|
||||
/>
|
||||
</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
|
||||
</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) => (
|
||||
<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>
|
||||
<StorageGraph
|
||||
graphId={`${camera}-storage`}
|
||||
|
Loading…
Reference in New Issue
Block a user