Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI cleanup #10567

Merged
merged 6 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions web/src/components/filter/ReviewActionGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { LuCheckSquare, LuFileUp, LuTrash } from "react-icons/lu";
import { FaCircleCheck } from "react-icons/fa6";
import { useCallback } from "react";
import axios from "axios";
import { Button } from "../ui/button";
import { isDesktop } from "react-device-detect";
import { FaCompactDisc } from "react-icons/fa";
import { HiTrash } from "react-icons/hi";

type ReviewActionGroupProps = {
selectedReviews: string[];
Expand All @@ -21,8 +23,7 @@ export default function ReviewActionGroup({
}, [setSelectedReviews]);

const onMarkAsReviewed = useCallback(async () => {
const idList = selectedReviews.join(",");
await axios.post(`reviews/viewed`, { ids: idList });
await axios.post(`reviews/viewed`, { ids: selectedReviews });
setSelectedReviews([]);
pullLatestData();
}, [selectedReviews, setSelectedReviews, pullLatestData]);
Expand All @@ -36,43 +37,47 @@ export default function ReviewActionGroup({

return (
<div className="absolute inset-x-2 inset-y-0 md:left-auto md:right-2 p-2 flex gap-2 justify-between items-center bg-background">
<div className="flex items-center">
<div className="text-sm text-gray-500 mr-2">{`${selectedReviews.length} selected | `}</div>
<Button size="xs" variant="link" onClick={onClearSelected}>
<div className="mx-1 flex justify-center items-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-foreground cursor-pointer hover:bg-secondary hover:rounded-lg"
onClick={onClearSelected}
>
Unselect
</Button>
</div>
</div>
<div className="flex items-center gap-1 md:gap-2">
{selectedReviews.length == 1 && (
<Button
className="flex items-center"
className="p-2 flex items-center gap-2"
variant="secondary"
size="sm"
onClick={() => {
onExport(selectedReviews[0]);
onClearSelected();
}}
>
<LuFileUp className="mr-1" />
<FaCompactDisc />
{isDesktop && "Export"}
</Button>
)}
<Button
className="flex items-center"
className="p-2 flex items-center gap-2"
variant="secondary"
size="sm"
onClick={onMarkAsReviewed}
>
<LuCheckSquare className="mr-1" />
<FaCircleCheck />
{isDesktop && "Mark as reviewed"}
</Button>
<Button
className="flex items-center"
className="p-2 flex items-center gap-1"
variant="secondary"
size="sm"
onClick={onDelete}
>
<LuTrash className="mr-1" />
<HiTrash />
{isDesktop && "Delete"}
</Button>
</div>
Expand Down
42 changes: 42 additions & 0 deletions web/src/components/icons/LiveIcons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
type LiveIconProps = {
layout?: "list" | "grid";
};

export function LiveGridIcon({ layout }: LiveIconProps) {
return (
<div className="size-full flex flex-col gap-0.5 rounded-md overflow-hidden">
<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={`w-full ${layout == "grid" ? "bg-selected" : "bg-muted-foreground"}`}
/>
<div
className={`w-full ${layout == "grid" ? "bg-selected" : "bg-muted-foreground"}`}
/>
</div>
<div className="h-1 w-full flex gap-0.5">
<div
className={`w-full ${layout == "grid" ? "bg-selected" : "bg-muted-foreground"}`}
/>
<div
className={`w-full ${layout == "grid" ? "bg-selected" : "bg-muted-foreground"}`}
/>
</div>
</div>
);
}

export function LiveListIcon({ layout }: LiveIconProps) {
return (
<div className="size-full flex flex-col gap-0.5 rounded-md overflow-hidden">
<div
className={`size-full ${layout == "list" ? "bg-selected" : "bg-muted-foreground"}`}
/>
<div
className={`size-full ${layout == "list" ? "bg-selected" : "bg-muted-foreground"}`}
/>
</div>
);
}
2 changes: 1 addition & 1 deletion web/src/context/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function providers({ children }: TProvidersProps) {
return (
<RecoilRoot>
<ApiProvider>
<ThemeProvider defaultTheme="light" storageKey="frigate-ui-theme">
<ThemeProvider defaultTheme="system" storageKey="frigate-ui-theme">
<TooltipProvider>
<IconContext.Provider value={{ size: "20" }}>
{children}
Expand Down
24 changes: 16 additions & 8 deletions web/src/context/theme-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext, useContext, useEffect, useState } from "react";
import { createContext, useContext, useEffect, useMemo, useState } from "react";

type Theme = "dark" | "light" | "system";
type ColorScheme =
Expand Down Expand Up @@ -41,13 +41,15 @@ type ThemeProviderProps = {

type ThemeProviderState = {
theme: Theme;
systemTheme?: Theme;
colorScheme: ColorScheme;
setTheme: (theme: Theme) => void;
setColorScheme: (colorScheme: ColorScheme) => void;
};

const initialState: ThemeProviderState = {
theme: "system",
systemTheme: undefined,
colorScheme: "theme-default",
setTheme: () => null,
setColorScheme: () => null,
Expand Down Expand Up @@ -86,6 +88,16 @@ export function ThemeProvider({
}
});

const systemTheme = useMemo<Theme | undefined>(() => {
if (theme != "system") {
return undefined;
}

return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}, [theme]);

useEffect(() => {
//localStorage.removeItem(storageKey);
//console.log(localStorage.getItem(storageKey));
Expand All @@ -95,21 +107,17 @@ export function ThemeProvider({

root.classList.add(theme, colorScheme);

if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light";

if (systemTheme) {
root.classList.add(systemTheme);
return;
}

root.classList.add(theme);
}, [theme, colorScheme]);
}, [theme, colorScheme, systemTheme]);

const value = {
theme,
systemTheme,
colorScheme,
setTheme: (theme: Theme) => {
localStorage.setItem(storageKey, JSON.stringify({ theme, colorScheme }));
Expand Down
4 changes: 2 additions & 2 deletions web/src/pages/ConfigEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function ConfigEditor() {

const { data: config } = useSWR<string>("config/raw");

const { theme } = useTheme();
const { theme, systemTheme } = useTheme();
const [error, setError] = useState<string | undefined>();

const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
Expand Down Expand Up @@ -107,7 +107,7 @@ function ConfigEditor() {
language: "yaml",
model: modelRef.current,
scrollBeyondLastLine: false,
theme: theme == "dark" ? "vs-dark" : "vs-light",
theme: (systemTheme || theme) == "dark" ? "vs-dark" : "vs-light",
});
}

Expand Down
22 changes: 11 additions & 11 deletions web/src/views/live/LiveDashboardView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useFrigateReviews } from "@/api/ws";
import Logo from "@/components/Logo";
import { CameraGroupSelector } from "@/components/filter/CameraGroupSelector";
import { LiveGridIcon, LiveListIcon } from "@/components/icons/LiveIcons";
import { AnimatedEventThumbnail } from "@/components/image/AnimatedEventThumbnail";
import BirdseyeLivePlayer from "@/components/player/BirdseyeLivePlayer";
import LivePlayer from "@/components/player/LivePlayer";
Expand All @@ -12,7 +13,6 @@ import { CameraConfig, FrigateConfig } from "@/types/frigateConfig";
import { ReviewSegment } from "@/types/review";
import { useCallback, useEffect, useMemo, useState } from "react";
import { isDesktop, isMobile, isSafari } from "react-device-detect";
import { CiGrid2H, CiGrid31 } from "react-icons/ci";
import useSWR from "swr";

type LiveDashboardViewProps = {
Expand Down Expand Up @@ -89,26 +89,26 @@ export default function LiveDashboardView({
<CameraGroupSelector />
<div className="flex items-center gap-1">
<Button
className={
className={`p-1 ${
layout == "grid"
? "text-selected bg-blue-900 focus:bg-blue-900 bg-opacity-60 focus:bg-opacity-60"
: "text-muted-foreground bg-muted"
}
? "bg-blue-900 focus:bg-blue-900 bg-opacity-60 focus:bg-opacity-60"
: "bg-muted"
}`}
size="xs"
onClick={() => setLayout("grid")}
>
<CiGrid31 className="m-1" />
<LiveGridIcon layout={layout} />
</Button>
<Button
className={
className={`p-1 ${
layout == "list"
? "text-selected bg-blue-900 focus:bg-blue-900 bg-opacity-60 focus:bg-opacity-60"
: "text-muted-foreground bg-muted"
}
? "bg-blue-900 focus:bg-blue-900 bg-opacity-60 focus:bg-opacity-60"
: "bg-muted"
}`}
size="xs"
onClick={() => setLayout("list")}
>
<CiGrid2H className="m-1" />
<LiveListIcon layout={layout} />
</Button>
</div>
</div>
Expand Down
Loading