mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Search UI tweaks (#13965)
* Prevent keyboard shortcuts from running when input is focused * fix reset button and update time pickers when using input * simplify css * consistent button order and spacing
This commit is contained in:
parent
fef30bc671
commit
32c7669b28
@ -430,6 +430,13 @@ function TimeRangeFilterButton({
|
|||||||
const formattedSelectedAfter = useFormattedHour(config, selectedAfterHour);
|
const formattedSelectedAfter = useFormattedHour(config, selectedAfterHour);
|
||||||
const formattedSelectedBefore = useFormattedHour(config, selectedBeforeHour);
|
const formattedSelectedBefore = useFormattedHour(config, selectedBeforeHour);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedAfterHour(afterHour);
|
||||||
|
setSelectedBeforeHour(beforeHour);
|
||||||
|
// only refresh when state changes
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [timeRange]);
|
||||||
|
|
||||||
const trigger = (
|
const trigger = (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -447,18 +454,8 @@ function TimeRangeFilterButton({
|
|||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
const content = (
|
const content = (
|
||||||
<div
|
<div className="scrollbar-container h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden">
|
||||||
className={cn(
|
<div className="my-5 flex flex-row items-center justify-center gap-2">
|
||||||
"scrollbar-container flex h-auto max-h-[80dvh] flex-col overflow-y-auto overflow-x-hidden",
|
|
||||||
isDesktop ? "w-64" : "w-full gap-2 pt-2",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"mt-3 flex w-full items-center rounded-lg text-secondary-foreground",
|
|
||||||
isDesktop ? "mx-6 gap-2 px-2" : "justify-center gap-2",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Popover
|
<Popover
|
||||||
open={startOpen}
|
open={startOpen}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
@ -480,7 +477,7 @@ function TimeRangeFilterButton({
|
|||||||
{formattedSelectedAfter}
|
{formattedSelectedAfter}
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="flex flex-col items-center">
|
<PopoverContent className="flex flex-row items-center justify-center">
|
||||||
<input
|
<input
|
||||||
className="text-md 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]"
|
className="text-md mx-4 w-full border border-input bg-background p-1 text-secondary-foreground hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
|
||||||
id="startTime"
|
id="startTime"
|
||||||
@ -534,8 +531,8 @@ function TimeRangeFilterButton({
|
|||||||
/>
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
<DropdownMenuSeparator />
|
|
||||||
</div>
|
</div>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
<div className="flex items-center justify-evenly p-2">
|
<div className="flex items-center justify-evenly p-2">
|
||||||
<Button
|
<Button
|
||||||
variant="select"
|
variant="select"
|
||||||
@ -558,6 +555,7 @@ function TimeRangeFilterButton({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedAfterHour(DEFAULT_TIME_RANGE_AFTER);
|
setSelectedAfterHour(DEFAULT_TIME_RANGE_AFTER);
|
||||||
setSelectedBeforeHour(DEFAULT_TIME_RANGE_BEFORE);
|
setSelectedBeforeHour(DEFAULT_TIME_RANGE_BEFORE);
|
||||||
|
updateTimeRange(undefined);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Reset
|
Reset
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useRef, useEffect, useCallback } from "react";
|
import React, { useState, useRef, useEffect, useCallback } from "react";
|
||||||
import {
|
import {
|
||||||
LuX,
|
LuX,
|
||||||
LuFilter,
|
LuFilter,
|
||||||
@ -45,6 +45,8 @@ import useSWR from "swr";
|
|||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
|
|
||||||
type InputWithTagsProps = {
|
type InputWithTagsProps = {
|
||||||
|
inputFocused: boolean;
|
||||||
|
setInputFocused: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
filters: SearchFilter;
|
filters: SearchFilter;
|
||||||
setFilters: (filter: SearchFilter) => void;
|
setFilters: (filter: SearchFilter) => void;
|
||||||
search: string;
|
search: string;
|
||||||
@ -55,6 +57,8 @@ type InputWithTagsProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function InputWithTags({
|
export default function InputWithTags({
|
||||||
|
inputFocused,
|
||||||
|
setInputFocused,
|
||||||
filters,
|
filters,
|
||||||
setFilters,
|
setFilters,
|
||||||
search,
|
search,
|
||||||
@ -69,7 +73,6 @@ export default function InputWithTags({
|
|||||||
const [currentFilterType, setCurrentFilterType] = useState<FilterType | null>(
|
const [currentFilterType, setCurrentFilterType] = useState<FilterType | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
const [inputFocused, setInputFocused] = useState(false);
|
|
||||||
const [isSimilaritySearch, setIsSimilaritySearch] = useState(false);
|
const [isSimilaritySearch, setIsSimilaritySearch] = useState(false);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const commandRef = useRef<HTMLDivElement>(null);
|
const commandRef = useRef<HTMLDivElement>(null);
|
||||||
@ -381,7 +384,7 @@ export default function InputWithTags({
|
|||||||
|
|
||||||
const handleInputFocus = useCallback(() => {
|
const handleInputFocus = useCallback(() => {
|
||||||
setInputFocused(true);
|
setInputFocused(true);
|
||||||
}, []);
|
}, [setInputFocused]);
|
||||||
|
|
||||||
const handleClearInput = useCallback(() => {
|
const handleClearInput = useCallback(() => {
|
||||||
setInputFocused(false);
|
setInputFocused(false);
|
||||||
@ -392,16 +395,19 @@ export default function InputWithTags({
|
|||||||
setFilters({});
|
setFilters({});
|
||||||
setCurrentFilterType(null);
|
setCurrentFilterType(null);
|
||||||
setIsSimilaritySearch(false);
|
setIsSimilaritySearch(false);
|
||||||
}, [setFilters, resetSuggestions, setSearch]);
|
}, [setFilters, resetSuggestions, setSearch, setInputFocused]);
|
||||||
|
|
||||||
const handleInputBlur = useCallback((e: React.FocusEvent) => {
|
const handleInputBlur = useCallback(
|
||||||
|
(e: React.FocusEvent) => {
|
||||||
if (
|
if (
|
||||||
commandRef.current &&
|
commandRef.current &&
|
||||||
!commandRef.current.contains(e.relatedTarget as Node)
|
!commandRef.current.contains(e.relatedTarget as Node)
|
||||||
) {
|
) {
|
||||||
setInputFocused(false);
|
setInputFocused(false);
|
||||||
}
|
}
|
||||||
}, []);
|
},
|
||||||
|
[setInputFocused],
|
||||||
|
);
|
||||||
|
|
||||||
const handleSuggestionClick = useCallback(
|
const handleSuggestionClick = useCallback(
|
||||||
(suggestion: string) => {
|
(suggestion: string) => {
|
||||||
@ -449,7 +455,7 @@ export default function InputWithTags({
|
|||||||
setInputFocused(false);
|
setInputFocused(false);
|
||||||
inputRef?.current?.blur();
|
inputRef?.current?.blur();
|
||||||
},
|
},
|
||||||
[setSearch],
|
[setSearch, setInputFocused],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleInputKeyDown = useCallback(
|
const handleInputKeyDown = useCallback(
|
||||||
|
@ -414,17 +414,7 @@ export function DateRangePicker({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center gap-2 py-2 pr-4">
|
<div className="mx-auto flex w-64 items-center justify-evenly gap-2 py-2">
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setIsOpen(false);
|
|
||||||
resetValues();
|
|
||||||
onReset?.();
|
|
||||||
}}
|
|
||||||
variant="ghost"
|
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
variant="select"
|
variant="select"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -439,6 +429,16 @@ export function DateRangePicker({
|
|||||||
>
|
>
|
||||||
Apply
|
Apply
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
resetValues();
|
||||||
|
onReset?.();
|
||||||
|
}}
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -10,6 +10,7 @@ export type KeyModifiers = {
|
|||||||
export default function useKeyboardListener(
|
export default function useKeyboardListener(
|
||||||
keys: string[],
|
keys: string[],
|
||||||
listener: (key: string | null, modifiers: KeyModifiers) => void,
|
listener: (key: string | null, modifiers: KeyModifiers) => void,
|
||||||
|
preventDefault: boolean = true,
|
||||||
) {
|
) {
|
||||||
const keyDownListener = useCallback(
|
const keyDownListener = useCallback(
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
@ -25,13 +26,13 @@ export default function useKeyboardListener(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (keys.includes(e.key)) {
|
if (keys.includes(e.key)) {
|
||||||
e.preventDefault();
|
if (preventDefault) e.preventDefault();
|
||||||
listener(e.key, modifiers);
|
listener(e.key, modifiers);
|
||||||
} else if (e.key === "Shift" || e.key === "Control" || e.key === "Meta") {
|
} else if (e.key === "Shift" || e.key === "Control" || e.key === "Meta") {
|
||||||
listener(null, modifiers);
|
listener(null, modifiers);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[keys, listener],
|
[keys, listener, preventDefault],
|
||||||
);
|
);
|
||||||
|
|
||||||
const keyUpListener = useCallback(
|
const keyUpListener = useCallback(
|
||||||
|
@ -209,9 +209,11 @@ export default function SearchView({
|
|||||||
|
|
||||||
// keyboard listener
|
// keyboard listener
|
||||||
|
|
||||||
|
const [inputFocused, setInputFocused] = useState(false);
|
||||||
|
|
||||||
const onKeyboardShortcut = useCallback(
|
const onKeyboardShortcut = useCallback(
|
||||||
(key: string | null, modifiers: KeyModifiers) => {
|
(key: string | null, modifiers: KeyModifiers) => {
|
||||||
if (!modifiers.down || !uniqueResults) {
|
if (!modifiers.down || !uniqueResults || inputFocused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,10 +238,14 @@ export default function SearchView({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[uniqueResults],
|
[uniqueResults, inputFocused],
|
||||||
);
|
);
|
||||||
|
|
||||||
useKeyboardListener(["ArrowLeft", "ArrowRight"], onKeyboardShortcut);
|
useKeyboardListener(
|
||||||
|
["ArrowLeft", "ArrowRight"],
|
||||||
|
onKeyboardShortcut,
|
||||||
|
!inputFocused,
|
||||||
|
);
|
||||||
|
|
||||||
// scroll into view
|
// scroll into view
|
||||||
|
|
||||||
@ -310,6 +316,8 @@ export default function SearchView({
|
|||||||
{config?.semantic_search?.enabled && (
|
{config?.semantic_search?.enabled && (
|
||||||
<div className={cn("z-[41] w-full lg:absolute lg:top-0 lg:w-1/3")}>
|
<div className={cn("z-[41] w-full lg:absolute lg:top-0 lg:w-1/3")}>
|
||||||
<InputWithTags
|
<InputWithTags
|
||||||
|
inputFocused={inputFocused}
|
||||||
|
setInputFocused={setInputFocused}
|
||||||
filters={searchFilter ?? {}}
|
filters={searchFilter ?? {}}
|
||||||
setFilters={setSearchFilter}
|
setFilters={setSearchFilter}
|
||||||
search={search}
|
search={search}
|
||||||
|
Loading…
Reference in New Issue
Block a user