forked from Github/frigate
Explore UI changes (#14393)
* Add time ago to explore summary view on desktop * add search settings for columns and default view selection * add descriptions * clarify wording * padding tweak * padding tweaks for mobile * fix size of activity indicator * smaller
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { isIOS, isMobileOnly, isSafari } from "react-device-detect";
|
||||
import { isDesktop, isIOS, isMobileOnly, isSafari } from "react-device-detect";
|
||||
import useSWR from "swr";
|
||||
import { useApiHost } from "@/api";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -17,6 +17,7 @@ import useImageLoaded from "@/hooks/use-image-loaded";
|
||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||
import { useEventUpdate } from "@/api/ws";
|
||||
import { isEqual } from "lodash";
|
||||
import TimeAgo from "@/components/dynamic/TimeAgo";
|
||||
|
||||
type ExploreViewProps = {
|
||||
searchDetail: SearchResult | undefined;
|
||||
@@ -197,6 +198,7 @@ function ExploreThumbnailImage({
|
||||
className="absolute inset-0"
|
||||
imgLoaded={imgLoaded}
|
||||
/>
|
||||
|
||||
<img
|
||||
ref={imgRef}
|
||||
className={cn(
|
||||
@@ -218,6 +220,17 @@ function ExploreThumbnailImage({
|
||||
onImgLoad();
|
||||
}}
|
||||
/>
|
||||
{isDesktop && (
|
||||
<div className="absolute bottom-1 right-1 z-10 rounded-lg bg-black/50 px-2 py-1 text-xs text-white">
|
||||
{event.end_time ? (
|
||||
<TimeAgo time={event.start_time * 1000} dense />
|
||||
) : (
|
||||
<div>
|
||||
<ActivityIndicator size={10} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,17 +5,12 @@ import SearchDetailDialog, {
|
||||
SearchTab,
|
||||
} from "@/components/overlay/detail/SearchDetailDialog";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { SearchFilter, SearchResult, SearchSource } from "@/types/search";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { isDesktop, isMobileOnly } from "react-device-detect";
|
||||
import { LuColumns, LuSearchX } from "react-icons/lu";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
import { LuSearchX } from "react-icons/lu";
|
||||
import useSWR from "swr";
|
||||
import ExploreView from "../explore/ExploreView";
|
||||
import useKeyboardListener, {
|
||||
@@ -26,14 +21,8 @@ import InputWithTags from "@/components/input/InputWithTags";
|
||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||
import { isEqual } from "lodash";
|
||||
import { formatDateToLocaleString } from "@/utils/dateUtil";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { usePersistence } from "@/hooks/use-persistence";
|
||||
import SearchThumbnailFooter from "@/components/card/SearchThumbnailFooter";
|
||||
import SearchSettings from "@/components/settings/SearchSettings";
|
||||
|
||||
type SearchViewProps = {
|
||||
search: string;
|
||||
@@ -42,12 +31,16 @@ type SearchViewProps = {
|
||||
searchResults?: SearchResult[];
|
||||
isLoading: boolean;
|
||||
hasMore: boolean;
|
||||
columns: number;
|
||||
defaultView?: string;
|
||||
setSearch: (search: string) => void;
|
||||
setSimilaritySearch: (search: SearchResult) => void;
|
||||
setSearchFilter: (filter: SearchFilter) => void;
|
||||
onUpdateFilter: (filter: SearchFilter) => void;
|
||||
loadMore: () => void;
|
||||
refresh: () => void;
|
||||
setColumns: (columns: number) => void;
|
||||
setDefaultView: (name: string) => void;
|
||||
};
|
||||
export default function SearchView({
|
||||
search,
|
||||
@@ -56,12 +49,16 @@ export default function SearchView({
|
||||
searchResults,
|
||||
isLoading,
|
||||
hasMore,
|
||||
columns,
|
||||
defaultView = "summary",
|
||||
setSearch,
|
||||
setSimilaritySearch,
|
||||
setSearchFilter,
|
||||
onUpdateFilter,
|
||||
loadMore,
|
||||
refresh,
|
||||
setColumns,
|
||||
setDefaultView,
|
||||
}: SearchViewProps) {
|
||||
const contentRef = useRef<HTMLDivElement | null>(null);
|
||||
const { data: config } = useSWR<FrigateConfig>("config", {
|
||||
@@ -70,18 +67,15 @@ export default function SearchView({
|
||||
|
||||
// grid
|
||||
|
||||
const [columnCount, setColumnCount] = usePersistence("exploreGridColumns", 4);
|
||||
const effectiveColumnCount = useMemo(() => columnCount ?? 4, [columnCount]);
|
||||
|
||||
const gridClassName = cn(
|
||||
"grid w-full gap-2 px-1 gap-2 lg:gap-4 md:mx-2",
|
||||
isMobileOnly && "grid-cols-2",
|
||||
{
|
||||
"sm:grid-cols-2": effectiveColumnCount <= 2,
|
||||
"sm:grid-cols-3": effectiveColumnCount === 3,
|
||||
"sm:grid-cols-4": effectiveColumnCount === 4,
|
||||
"sm:grid-cols-5": effectiveColumnCount === 5,
|
||||
"sm:grid-cols-6": effectiveColumnCount === 6,
|
||||
"sm:grid-cols-2": columns <= 2,
|
||||
"sm:grid-cols-3": columns === 3,
|
||||
"sm:grid-cols-4": columns === 4,
|
||||
"sm:grid-cols-5": columns === 5,
|
||||
"sm:grid-cols-6": columns === 6,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -342,7 +336,7 @@ export default function SearchView({
|
||||
|
||||
{hasExistingSearch && (
|
||||
<ScrollArea className="w-full whitespace-nowrap lg:ml-[35%]">
|
||||
<div className="flex flex-row">
|
||||
<div className="flex flex-row gap-2">
|
||||
<SearchFilterGroup
|
||||
className={cn(
|
||||
"w-full justify-between md:justify-start lg:justify-end",
|
||||
@@ -350,6 +344,12 @@ export default function SearchView({
|
||||
filter={searchFilter}
|
||||
onUpdateFilter={onUpdateFilter}
|
||||
/>
|
||||
<SearchSettings
|
||||
columns={columns}
|
||||
setColumns={setColumns}
|
||||
defaultView={defaultView}
|
||||
setDefaultView={setDefaultView}
|
||||
/>
|
||||
<ScrollBar orientation="horizontal" className="h-0" />
|
||||
</div>
|
||||
</ScrollArea>
|
||||
@@ -425,53 +425,13 @@ export default function SearchView({
|
||||
<div className="flex h-12 w-full justify-center">
|
||||
{hasMore && isLoading && <ActivityIndicator />}
|
||||
</div>
|
||||
|
||||
{isDesktop && columnCount && (
|
||||
<div
|
||||
className={cn(
|
||||
"fixed bottom-12 right-3 z-50 flex flex-row gap-2 lg:bottom-9",
|
||||
)}
|
||||
>
|
||||
<Popover>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="cursor-pointer rounded-lg bg-secondary text-secondary-foreground opacity-75 transition-all duration-300 hover:bg-muted hover:opacity-100">
|
||||
<LuColumns className="size-5 md:m-[6px]" />
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Adjust Grid Columns</TooltipContent>
|
||||
</Tooltip>
|
||||
<PopoverContent className="mr-2 w-80">
|
||||
<div className="space-y-4">
|
||||
<div className="font-medium leading-none">
|
||||
Grid Columns
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<Slider
|
||||
value={[effectiveColumnCount]}
|
||||
onValueChange={([value]) => setColumnCount(value)}
|
||||
max={6}
|
||||
min={2}
|
||||
step={1}
|
||||
className="flex-grow"
|
||||
/>
|
||||
<span className="w-9 text-center text-sm font-medium">
|
||||
{effectiveColumnCount}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{searchFilter &&
|
||||
Object.keys(searchFilter).length === 0 &&
|
||||
!searchTerm && (
|
||||
!searchTerm &&
|
||||
defaultView == "summary" && (
|
||||
<div className="scrollbar-container flex size-full flex-col overflow-y-auto">
|
||||
<ExploreView
|
||||
searchDetail={searchDetail}
|
||||
|
||||
Reference in New Issue
Block a user