mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Accessibility features (#14518)
* Add screen reader aria labels to buttons and menu items * Fix sub_label score in search detail dialog
This commit is contained in:
parent
c7d9f83638
commit
ad308252a1
@ -121,6 +121,7 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
|
|||||||
variant="select"
|
variant="select"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="flex flex-1"
|
className="flex flex-1"
|
||||||
|
aria-label="Login"
|
||||||
>
|
>
|
||||||
{isLoading && <ActivityIndicator className="mr-2 h-4 w-4" />}
|
{isLoading && <ActivityIndicator className="mr-2 h-4 w-4" />}
|
||||||
Login
|
Login
|
||||||
|
@ -46,6 +46,7 @@ export function DownloadVideoButton({
|
|||||||
disabled={isDownloading}
|
disabled={isDownloading}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
aria-label="Download Video"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={source}
|
href={source}
|
||||||
|
@ -55,7 +55,12 @@ export default function DebugCameraImage({
|
|||||||
searchParams={searchParams}
|
searchParams={searchParams}
|
||||||
cameraClasses="relative w-full h-full flex justify-center"
|
cameraClasses="relative w-full h-full flex justify-center"
|
||||||
/>
|
/>
|
||||||
<Button onClick={handleToggleSettings} variant="link" size="sm">
|
<Button
|
||||||
|
onClick={handleToggleSettings}
|
||||||
|
variant="link"
|
||||||
|
size="sm"
|
||||||
|
aria-label="Settings"
|
||||||
|
>
|
||||||
<span className="h-5 w-5">
|
<span className="h-5 w-5">
|
||||||
<LuSettings />
|
<LuSettings />
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
|
@ -121,6 +121,7 @@ export function AnimatedEventCard({
|
|||||||
<Button
|
<Button
|
||||||
className="absolute right-2 top-1 z-40 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"
|
className="absolute right-2 top-1 z-40 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"
|
||||||
size="xs"
|
size="xs"
|
||||||
|
aria-label="Mark as Reviewed"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await axios.post(`reviews/viewed`, { ids: [event.id] });
|
await axios.post(`reviews/viewed`, { ids: [event.id] });
|
||||||
updateEvents();
|
updateEvents();
|
||||||
|
@ -113,6 +113,7 @@ export default function ExportCard({
|
|||||||
/>
|
/>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Save Export"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="select"
|
variant="select"
|
||||||
disabled={(editName?.update?.length ?? 0) == 0}
|
disabled={(editName?.update?.length ?? 0) == 0}
|
||||||
@ -206,6 +207,7 @@ export default function ExportCard({
|
|||||||
{!exportedRecording.in_progress && (
|
{!exportedRecording.in_progress && (
|
||||||
<Button
|
<Button
|
||||||
className="absolute left-1/2 top-1/2 h-20 w-20 -translate-x-1/2 -translate-y-1/2 cursor-pointer text-white hover:bg-transparent hover:text-white"
|
className="absolute left-1/2 top-1/2 h-20 w-20 -translate-x-1/2 -translate-y-1/2 cursor-pointer text-white hover:bg-transparent hover:text-white"
|
||||||
|
aria-label="Play"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSelect(exportedRecording);
|
onSelect(exportedRecording);
|
||||||
|
@ -36,6 +36,7 @@ export default function NewReviewData({
|
|||||||
: "invisible",
|
: "invisible",
|
||||||
"mx-auto mt-5 bg-gray-400 text-center text-white",
|
"mx-auto mt-5 bg-gray-400 text-center text-white",
|
||||||
)}
|
)}
|
||||||
|
aria-label="View new review items"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
pullLatestData();
|
pullLatestData();
|
||||||
if (contentRef.current) {
|
if (contentRef.current) {
|
||||||
|
@ -34,6 +34,7 @@ export default function CalendarFilterButton({
|
|||||||
const trigger = (
|
const trigger = (
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
|
aria-label="Select a date to filter by"
|
||||||
variant={day == undefined ? "default" : "select"}
|
variant={day == undefined ? "default" : "select"}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
@ -57,6 +58,7 @@ export default function CalendarFilterButton({
|
|||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<div className="flex items-center justify-center p-2">
|
<div className="flex items-center justify-center p-2">
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Reset"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateSelectedDay(undefined);
|
updateSelectedDay(undefined);
|
||||||
}}
|
}}
|
||||||
@ -99,6 +101,7 @@ export function CalendarRangeFilterButton({
|
|||||||
const trigger = (
|
const trigger = (
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
|
aria-label="Select a date to filter by"
|
||||||
variant={range == undefined ? "default" : "select"}
|
variant={range == undefined ? "default" : "select"}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
|
@ -141,6 +141,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
|||||||
? "bg-blue-900 bg-opacity-60 text-selected focus:bg-blue-900 focus:bg-opacity-60"
|
? "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"
|
: "bg-secondary text-secondary-foreground focus:bg-secondary focus:text-secondary-foreground"
|
||||||
}
|
}
|
||||||
|
aria-label="All Cameras"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => (group ? setGroup("default", true) : null)}
|
onClick={() => (group ? setGroup("default", true) : null)}
|
||||||
onMouseEnter={() => (isDesktop ? showTooltip("default") : null)}
|
onMouseEnter={() => (isDesktop ? showTooltip("default") : null)}
|
||||||
@ -165,6 +166,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
|||||||
? "bg-blue-900 bg-opacity-60 text-selected focus:bg-blue-900 focus:bg-opacity-60"
|
? "bg-blue-900 bg-opacity-60 text-selected focus:bg-blue-900 focus:bg-opacity-60"
|
||||||
: "bg-secondary text-secondary-foreground"
|
: "bg-secondary text-secondary-foreground"
|
||||||
}
|
}
|
||||||
|
aria-label="Camera Group"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => setGroup(name, group != "default")}
|
onClick={() => setGroup(name, group != "default")}
|
||||||
onMouseEnter={() => (isDesktop ? showTooltip(name) : null)}
|
onMouseEnter={() => (isDesktop ? showTooltip(name) : null)}
|
||||||
@ -191,6 +193,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
className="bg-secondary text-muted-foreground"
|
className="bg-secondary text-muted-foreground"
|
||||||
|
aria-label="Add camera group"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => setAddGroup(true)}
|
onClick={() => setAddGroup(true)}
|
||||||
>
|
>
|
||||||
@ -355,6 +358,7 @@ function NewGroupDialog({
|
|||||||
"size-6 rounded-md bg-secondary-foreground p-1 text-background",
|
"size-6 rounded-md bg-secondary-foreground p-1 text-background",
|
||||||
isMobile && "text-secondary-foreground",
|
isMobile && "text-secondary-foreground",
|
||||||
)}
|
)}
|
||||||
|
aria-label="Add camera group"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditState("add");
|
setEditState("add");
|
||||||
}}
|
}}
|
||||||
@ -536,10 +540,16 @@ export function CameraGroupRow({
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuPortal>
|
<DropdownMenuPortal>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem onClick={onEditGroup}>
|
<DropdownMenuItem
|
||||||
|
aria-label="Edit group"
|
||||||
|
onClick={onEditGroup}
|
||||||
|
>
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => setDeleteDialogOpen(true)}>
|
<DropdownMenuItem
|
||||||
|
aria-label="Delete group"
|
||||||
|
onClick={() => setDeleteDialogOpen(true)}
|
||||||
|
>
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
@ -793,13 +803,19 @@ export function CameraGroupEdit({
|
|||||||
<Separator className="my-2 flex bg-secondary" />
|
<Separator className="my-2 flex bg-secondary" />
|
||||||
|
|
||||||
<div className="flex flex-row gap-2 py-5 md:pb-0">
|
<div className="flex flex-row gap-2 py-5 md:pb-0">
|
||||||
<Button type="button" className="flex flex-1" onClick={onCancel}>
|
<Button
|
||||||
|
type="button"
|
||||||
|
className="flex flex-1"
|
||||||
|
aria-label="Cancel"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="select"
|
variant="select"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="flex flex-1"
|
className="flex flex-1"
|
||||||
|
aria-label="Save"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
@ -55,6 +55,7 @@ export function CamerasFilterButton({
|
|||||||
const trigger = (
|
const trigger = (
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2 capitalize"
|
className="flex items-center gap-2 capitalize"
|
||||||
|
aria-label="Cameras Filter"
|
||||||
variant={selectedCameras?.length == undefined ? "default" : "select"}
|
variant={selectedCameras?.length == undefined ? "default" : "select"}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
@ -202,6 +203,7 @@ export function CamerasFilterContent({
|
|||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<div className="flex items-center justify-evenly p-2">
|
<div className="flex items-center justify-evenly p-2">
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Apply"
|
||||||
variant="select"
|
variant="select"
|
||||||
disabled={currentCameras?.length === 0}
|
disabled={currentCameras?.length === 0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -212,6 +214,7 @@ export function CamerasFilterContent({
|
|||||||
Apply
|
Apply
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Reset"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentCameras(undefined);
|
setCurrentCameras(undefined);
|
||||||
updateCameraFilter(undefined);
|
updateCameraFilter(undefined);
|
||||||
|
@ -17,7 +17,11 @@ export function LogLevelFilterButton({
|
|||||||
updateLabelFilter,
|
updateLabelFilter,
|
||||||
}: LogLevelFilterButtonProps) {
|
}: LogLevelFilterButtonProps) {
|
||||||
const trigger = (
|
const trigger = (
|
||||||
<Button size="sm" className="flex items-center gap-2">
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
aria-label="Filter log level"
|
||||||
|
>
|
||||||
<FaFilter className="text-secondary-foreground" />
|
<FaFilter className="text-secondary-foreground" />
|
||||||
<div className="hidden text-primary md:block">Filter</div>
|
<div className="hidden text-primary md:block">Filter</div>
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -104,6 +104,7 @@ export default function ReviewActionGroup({
|
|||||||
{selectedReviews.length == 1 && (
|
{selectedReviews.length == 1 && (
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2 p-2"
|
className="flex items-center gap-2 p-2"
|
||||||
|
aria-label="Export"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onExport(selectedReviews[0]);
|
onExport(selectedReviews[0]);
|
||||||
@ -116,6 +117,7 @@ export default function ReviewActionGroup({
|
|||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2 p-2"
|
className="flex items-center gap-2 p-2"
|
||||||
|
aria-label="Mark as reviewed"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onMarkAsReviewed}
|
onClick={onMarkAsReviewed}
|
||||||
>
|
>
|
||||||
@ -124,6 +126,7 @@ export default function ReviewActionGroup({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2 p-2"
|
className="flex items-center gap-2 p-2"
|
||||||
|
aria-label="Delete"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
>
|
>
|
||||||
|
@ -278,6 +278,7 @@ function ShowReviewFilter({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
className="block duration-0 md:hidden"
|
className="block duration-0 md:hidden"
|
||||||
|
aria-label="Show reviewed"
|
||||||
variant={showReviewedSwitch ? "select" : "default"}
|
variant={showReviewedSwitch ? "select" : "default"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@ -338,6 +339,7 @@ function GeneralFilterButton({
|
|||||||
selectedLabels?.length || selectedZones?.length ? "select" : "default"
|
selectedLabels?.length || selectedZones?.length ? "select" : "default"
|
||||||
}
|
}
|
||||||
className="flex items-center gap-2 capitalize"
|
className="flex items-center gap-2 capitalize"
|
||||||
|
aria-label="Filter"
|
||||||
>
|
>
|
||||||
<FaFilter
|
<FaFilter
|
||||||
className={`${selectedLabels?.length || selectedZones?.length ? "text-selected-foreground" : "text-secondary-foreground"}`}
|
className={`${selectedLabels?.length || selectedZones?.length ? "text-selected-foreground" : "text-secondary-foreground"}`}
|
||||||
@ -538,6 +540,7 @@ export function GeneralFilterContent({
|
|||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<div className="flex items-center justify-evenly p-2">
|
<div className="flex items-center justify-evenly p-2">
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Apply"
|
||||||
variant="select"
|
variant="select"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (selectedLabels != currentLabels) {
|
if (selectedLabels != currentLabels) {
|
||||||
@ -554,6 +557,7 @@ export function GeneralFilterContent({
|
|||||||
Apply
|
Apply
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Reset"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentLabels(undefined);
|
setCurrentLabels(undefined);
|
||||||
setCurrentZones?.(undefined);
|
setCurrentZones?.(undefined);
|
||||||
@ -601,6 +605,7 @@ function ShowMotionOnlyButton({
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="duration-0"
|
className="duration-0"
|
||||||
|
aria-label="Show Motion Only"
|
||||||
variant={motionOnlyButton ? "select" : "default"}
|
variant={motionOnlyButton ? "select" : "default"}
|
||||||
onClick={() => setMotionOnlyButton(!motionOnlyButton)}
|
onClick={() => setMotionOnlyButton(!motionOnlyButton)}
|
||||||
>
|
>
|
||||||
|
@ -227,6 +227,7 @@ function GeneralFilterButton({
|
|||||||
size="sm"
|
size="sm"
|
||||||
variant={selectedLabels?.length ? "select" : "default"}
|
variant={selectedLabels?.length ? "select" : "default"}
|
||||||
className="flex items-center gap-2 capitalize"
|
className="flex items-center gap-2 capitalize"
|
||||||
|
aria-label="Labels"
|
||||||
>
|
>
|
||||||
<MdLabel
|
<MdLabel
|
||||||
className={`${selectedLabels?.length ? "text-selected-foreground" : "text-secondary-foreground"}`}
|
className={`${selectedLabels?.length ? "text-selected-foreground" : "text-secondary-foreground"}`}
|
||||||
@ -336,6 +337,7 @@ export function GeneralFilterContent({
|
|||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<div className="flex items-center justify-evenly p-2">
|
<div className="flex items-center justify-evenly p-2">
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Apply"
|
||||||
variant="select"
|
variant="select"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (selectedLabels != currentLabels) {
|
if (selectedLabels != currentLabels) {
|
||||||
@ -348,6 +350,7 @@ export function GeneralFilterContent({
|
|||||||
Apply
|
Apply
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Reset"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentLabels(undefined);
|
setCurrentLabels(undefined);
|
||||||
updateLabelFilter(undefined);
|
updateLabelFilter(undefined);
|
||||||
|
@ -21,6 +21,7 @@ export function ZoneMaskFilterButton({
|
|||||||
size="sm"
|
size="sm"
|
||||||
variant={selectedZoneMask?.length ? "select" : "default"}
|
variant={selectedZoneMask?.length ? "select" : "default"}
|
||||||
className="flex items-center gap-2 capitalize"
|
className="flex items-center gap-2 capitalize"
|
||||||
|
aria-label="Filter by zone mask"
|
||||||
>
|
>
|
||||||
<FaFilter
|
<FaFilter
|
||||||
className={`${selectedZoneMask?.length ? "text-selected-foreground" : "text-secondary-foreground"}`}
|
className={`${selectedZoneMask?.length ? "text-selected-foreground" : "text-secondary-foreground"}`}
|
||||||
|
@ -66,7 +66,10 @@ export default function IconPicker({
|
|||||||
>
|
>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
{!selectedIcon?.name || !selectedIcon?.Icon ? (
|
{!selectedIcon?.name || !selectedIcon?.Icon ? (
|
||||||
<Button className="mt-2 w-full text-muted-foreground">
|
<Button
|
||||||
|
className="mt-2 w-full text-muted-foreground"
|
||||||
|
aria-label="Select an icon"
|
||||||
|
>
|
||||||
Select an icon
|
Select an icon
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
|
@ -59,11 +59,14 @@ export function SaveSearchDialog({
|
|||||||
placeholder="Enter a name for your search"
|
placeholder="Enter a name for your search"
|
||||||
/>
|
/>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
<Button aria-label="Cancel" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
variant="select"
|
variant="select"
|
||||||
className="mb-2 md:mb-0"
|
className="mb-2 md:mb-0"
|
||||||
|
aria-label="Save this search"
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -72,6 +72,7 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
|
|||||||
className={
|
className={
|
||||||
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
|
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
|
||||||
}
|
}
|
||||||
|
aria-label="Log out"
|
||||||
>
|
>
|
||||||
<a className="flex" href={logoutUrl}>
|
<a className="flex" href={logoutUrl}>
|
||||||
<LuLogOut className="mr-2 size-4" />
|
<LuLogOut className="mr-2 size-4" />
|
||||||
|
@ -176,6 +176,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
|||||||
? "cursor-pointer"
|
? "cursor-pointer"
|
||||||
: "flex items-center p-2 text-sm"
|
: "flex items-center p-2 text-sm"
|
||||||
}
|
}
|
||||||
|
aria-label="Log out"
|
||||||
>
|
>
|
||||||
<a className="flex" href={logoutUrl}>
|
<a className="flex" href={logoutUrl}>
|
||||||
<LuLogOut className="mr-2 size-4" />
|
<LuLogOut className="mr-2 size-4" />
|
||||||
@ -194,6 +195,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
|||||||
? "cursor-pointer"
|
? "cursor-pointer"
|
||||||
: "flex w-full items-center p-2 text-sm"
|
: "flex w-full items-center p-2 text-sm"
|
||||||
}
|
}
|
||||||
|
aria-label="System metrics"
|
||||||
>
|
>
|
||||||
<LuActivity className="mr-2 size-4" />
|
<LuActivity className="mr-2 size-4" />
|
||||||
<span>System metrics</span>
|
<span>System metrics</span>
|
||||||
@ -206,6 +208,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
|||||||
? "cursor-pointer"
|
? "cursor-pointer"
|
||||||
: "flex w-full items-center p-2 text-sm"
|
: "flex w-full items-center p-2 text-sm"
|
||||||
}
|
}
|
||||||
|
aria-label="System logs"
|
||||||
>
|
>
|
||||||
<LuList className="mr-2 size-4" />
|
<LuList className="mr-2 size-4" />
|
||||||
<span>System logs</span>
|
<span>System logs</span>
|
||||||
@ -224,6 +227,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
|||||||
? "cursor-pointer"
|
? "cursor-pointer"
|
||||||
: "flex w-full items-center p-2 text-sm"
|
: "flex w-full items-center p-2 text-sm"
|
||||||
}
|
}
|
||||||
|
aria-label="Settings"
|
||||||
>
|
>
|
||||||
<LuSettings className="mr-2 size-4" />
|
<LuSettings className="mr-2 size-4" />
|
||||||
<span>Settings</span>
|
<span>Settings</span>
|
||||||
@ -236,6 +240,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
|||||||
? "cursor-pointer"
|
? "cursor-pointer"
|
||||||
: "flex w-full items-center p-2 text-sm"
|
: "flex w-full items-center p-2 text-sm"
|
||||||
}
|
}
|
||||||
|
aria-label="Configuration editor"
|
||||||
>
|
>
|
||||||
<LuPenSquare className="mr-2 size-4" />
|
<LuPenSquare className="mr-2 size-4" />
|
||||||
<span>Configuration editor</span>
|
<span>Configuration editor</span>
|
||||||
@ -269,6 +274,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
|||||||
? "cursor-pointer"
|
? "cursor-pointer"
|
||||||
: "flex items-center p-2 text-sm"
|
: "flex items-center p-2 text-sm"
|
||||||
}
|
}
|
||||||
|
aria-label="Light mode"
|
||||||
onClick={() => setTheme("light")}
|
onClick={() => setTheme("light")}
|
||||||
>
|
>
|
||||||
{theme === "light" ? (
|
{theme === "light" ? (
|
||||||
@ -286,6 +292,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
|||||||
? "cursor-pointer"
|
? "cursor-pointer"
|
||||||
: "flex items-center p-2 text-sm"
|
: "flex items-center p-2 text-sm"
|
||||||
}
|
}
|
||||||
|
aria-label="Dark mode"
|
||||||
onClick={() => setTheme("dark")}
|
onClick={() => setTheme("dark")}
|
||||||
>
|
>
|
||||||
{theme === "dark" ? (
|
{theme === "dark" ? (
|
||||||
@ -303,6 +310,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
|||||||
? "cursor-pointer"
|
? "cursor-pointer"
|
||||||
: "flex items-center p-2 text-sm"
|
: "flex items-center p-2 text-sm"
|
||||||
}
|
}
|
||||||
|
aria-label="Use the system settings for light or dark mode"
|
||||||
onClick={() => setTheme("system")}
|
onClick={() => setTheme("system")}
|
||||||
>
|
>
|
||||||
{theme === "system" ? (
|
{theme === "system" ? (
|
||||||
@ -343,6 +351,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
|||||||
? "cursor-pointer"
|
? "cursor-pointer"
|
||||||
: "flex items-center p-2 text-sm"
|
: "flex items-center p-2 text-sm"
|
||||||
}
|
}
|
||||||
|
aria-label={`Color scheme - ${scheme}`}
|
||||||
onClick={() => setColorScheme(scheme)}
|
onClick={() => setColorScheme(scheme)}
|
||||||
>
|
>
|
||||||
{scheme === colorScheme ? (
|
{scheme === colorScheme ? (
|
||||||
@ -370,6 +379,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
|||||||
className={
|
className={
|
||||||
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
|
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
|
||||||
}
|
}
|
||||||
|
aria-label="Frigate documentation"
|
||||||
>
|
>
|
||||||
<LuLifeBuoy className="mr-2 size-4" />
|
<LuLifeBuoy className="mr-2 size-4" />
|
||||||
<span>Documentation</span>
|
<span>Documentation</span>
|
||||||
@ -383,6 +393,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
|||||||
className={
|
className={
|
||||||
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
|
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
|
||||||
}
|
}
|
||||||
|
aria-label="Frigate Github"
|
||||||
>
|
>
|
||||||
<LuGithub className="mr-2 size-4" />
|
<LuGithub className="mr-2 size-4" />
|
||||||
<span>GitHub</span>
|
<span>GitHub</span>
|
||||||
@ -393,6 +404,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
|||||||
className={
|
className={
|
||||||
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
|
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
|
||||||
}
|
}
|
||||||
|
aria-label="Restart Frigate"
|
||||||
onClick={() => setRestartDialogOpen(true)}
|
onClick={() => setRestartDialogOpen(true)}
|
||||||
>
|
>
|
||||||
<LuRotateCw className="mr-2 size-4" />
|
<LuRotateCw className="mr-2 size-4" />
|
||||||
@ -446,7 +458,12 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
|||||||
<p>This page will reload in {countdown} seconds.</p>
|
<p>This page will reload in {countdown} seconds.</p>
|
||||||
</SheetDescription>
|
</SheetDescription>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<Button size="lg" className="mt-5" onClick={handleForceReload}>
|
<Button
|
||||||
|
size="lg"
|
||||||
|
className="mt-5"
|
||||||
|
aria-label="Force reload now"
|
||||||
|
onClick={handleForceReload}
|
||||||
|
>
|
||||||
Force Reload Now
|
Force Reload Now
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -86,7 +86,7 @@ export default function SearchResultActions({
|
|||||||
const menuItems = (
|
const menuItems = (
|
||||||
<>
|
<>
|
||||||
{searchResult.has_clip && (
|
{searchResult.has_clip && (
|
||||||
<MenuItem>
|
<MenuItem aria-label="Download video">
|
||||||
<a
|
<a
|
||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
href={`${baseUrl}api/events/${searchResult.id}/clip.mp4`}
|
href={`${baseUrl}api/events/${searchResult.id}/clip.mp4`}
|
||||||
@ -98,7 +98,7 @@ export default function SearchResultActions({
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{searchResult.has_snapshot && (
|
{searchResult.has_snapshot && (
|
||||||
<MenuItem>
|
<MenuItem aria-label="Download snapshot">
|
||||||
<a
|
<a
|
||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
href={`${baseUrl}api/events/${searchResult.id}/snapshot.jpg`}
|
href={`${baseUrl}api/events/${searchResult.id}/snapshot.jpg`}
|
||||||
@ -109,12 +109,18 @@ export default function SearchResultActions({
|
|||||||
</a>
|
</a>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
<MenuItem onClick={showObjectLifecycle}>
|
<MenuItem
|
||||||
|
aria-label="Show the object lifecycle"
|
||||||
|
onClick={showObjectLifecycle}
|
||||||
|
>
|
||||||
<FaArrowsRotate className="mr-2 size-4" />
|
<FaArrowsRotate className="mr-2 size-4" />
|
||||||
<span>View object lifecycle</span>
|
<span>View object lifecycle</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{config?.semantic_search?.enabled && isContextMenu && (
|
{config?.semantic_search?.enabled && isContextMenu && (
|
||||||
<MenuItem onClick={findSimilar}>
|
<MenuItem
|
||||||
|
aria-label="Find similar tracked objects"
|
||||||
|
onClick={findSimilar}
|
||||||
|
>
|
||||||
<MdImageSearch className="mr-2 size-4" />
|
<MdImageSearch className="mr-2 size-4" />
|
||||||
<span>Find similar</span>
|
<span>Find similar</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -124,12 +130,18 @@ export default function SearchResultActions({
|
|||||||
searchResult.has_snapshot &&
|
searchResult.has_snapshot &&
|
||||||
searchResult.end_time &&
|
searchResult.end_time &&
|
||||||
!searchResult.plus_id && (
|
!searchResult.plus_id && (
|
||||||
<MenuItem onClick={() => setShowFrigatePlus(true)}>
|
<MenuItem
|
||||||
|
aria-label="Submit to Frigate Plus"
|
||||||
|
onClick={() => setShowFrigatePlus(true)}
|
||||||
|
>
|
||||||
<FrigatePlusIcon className="mr-2 size-4 cursor-pointer text-primary" />
|
<FrigatePlusIcon className="mr-2 size-4 cursor-pointer text-primary" />
|
||||||
<span>Submit to Frigate+</span>
|
<span>Submit to Frigate+</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
<MenuItem onClick={() => setDeleteDialogOpen(true)}>
|
<MenuItem
|
||||||
|
aria-label="Delete this tracked object"
|
||||||
|
onClick={() => setDeleteDialogOpen(true)}
|
||||||
|
>
|
||||||
<LuTrash2 className="mr-2 size-4" />
|
<LuTrash2 className="mr-2 size-4" />
|
||||||
<span>Delete</span>
|
<span>Delete</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -154,6 +154,7 @@ export function MobilePageHeader({
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="absolute left-0 rounded-lg"
|
className="absolute left-0 rounded-lg"
|
||||||
|
aria-label="Go back"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
>
|
>
|
||||||
|
@ -167,7 +167,11 @@ export default function CameraInfoDialog({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="select" onClick={() => onCopyFfprobe()}>
|
<Button
|
||||||
|
variant="select"
|
||||||
|
aria-label="Copy"
|
||||||
|
onClick={() => onCopyFfprobe()}
|
||||||
|
>
|
||||||
Copy
|
Copy
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
@ -98,7 +98,11 @@ export default function CreateUserDialog({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<DialogFooter className="mt-4">
|
<DialogFooter className="mt-4">
|
||||||
<Button variant="select" disabled={isLoading}>
|
<Button
|
||||||
|
variant="select"
|
||||||
|
aria-label="Create user"
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
{isLoading && <ActivityIndicator className="mr-2 h-4 w-4" />}
|
{isLoading && <ActivityIndicator className="mr-2 h-4 w-4" />}
|
||||||
Create User
|
Create User
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -27,6 +27,7 @@ export default function DeleteUserDialog({
|
|||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
|
aria-label="Confirm delete"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
|
@ -142,6 +142,7 @@ export default function ExportDialog({
|
|||||||
<Trigger asChild>
|
<Trigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
|
aria-label="Export"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const now = new Date(latestTime * 1000);
|
const now = new Date(latestTime * 1000);
|
||||||
@ -307,6 +308,7 @@ export function ExportContent({
|
|||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
className={isDesktop ? "" : "w-full"}
|
className={isDesktop ? "" : "w-full"}
|
||||||
|
aria-label="Select or export"
|
||||||
variant="select"
|
variant="select"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -420,6 +422,7 @@ function CustomTimeSelector({
|
|||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className={`text-primary ${isDesktop ? "" : "text-xs"}`}
|
className={`text-primary ${isDesktop ? "" : "text-xs"}`}
|
||||||
|
aria-label="Start time"
|
||||||
variant={startOpen ? "select" : "default"}
|
variant={startOpen ? "select" : "default"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -485,6 +488,7 @@ function CustomTimeSelector({
|
|||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className={`text-primary ${isDesktop ? "" : "text-xs"}`}
|
className={`text-primary ${isDesktop ? "" : "text-xs"}`}
|
||||||
|
aria-label="End time"
|
||||||
variant={endOpen ? "select" : "default"}
|
variant={endOpen ? "select" : "default"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -59,8 +59,17 @@ export default function GPUInfoDialog({
|
|||||||
<ActivityIndicator />
|
<ActivityIndicator />
|
||||||
)}
|
)}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button onClick={() => setShowGpuInfo(false)}>Close</Button>
|
<Button
|
||||||
<Button variant="select" onClick={() => onCopyInfo()}>
|
aria-label="Close GPU info"
|
||||||
|
onClick={() => setShowGpuInfo(false)}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
aria-label="Copy GPU info"
|
||||||
|
variant="select"
|
||||||
|
onClick={() => onCopyInfo()}
|
||||||
|
>
|
||||||
Copy
|
Copy
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
@ -88,8 +97,17 @@ export default function GPUInfoDialog({
|
|||||||
<ActivityIndicator />
|
<ActivityIndicator />
|
||||||
)}
|
)}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button onClick={() => setShowGpuInfo(false)}>Close</Button>
|
<Button
|
||||||
<Button variant="select" onClick={() => onCopyInfo()}>
|
aria-label="Close GPU info"
|
||||||
|
onClick={() => setShowGpuInfo(false)}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
aria-label="Copy GPU info"
|
||||||
|
variant="select"
|
||||||
|
onClick={() => onCopyInfo()}
|
||||||
|
>
|
||||||
Copy
|
Copy
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
@ -23,7 +23,11 @@ export default function MobileCameraDrawer({
|
|||||||
return (
|
return (
|
||||||
<Drawer open={cameraDrawer} onOpenChange={setCameraDrawer}>
|
<Drawer open={cameraDrawer} onOpenChange={setCameraDrawer}>
|
||||||
<DrawerTrigger asChild>
|
<DrawerTrigger asChild>
|
||||||
<Button className="rounded-lg capitalize" size="sm">
|
<Button
|
||||||
|
className="rounded-lg capitalize"
|
||||||
|
aria-label="Cameras"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
<FaVideo className="text-secondary-foreground" />
|
<FaVideo className="text-secondary-foreground" />
|
||||||
</Button>
|
</Button>
|
||||||
</DrawerTrigger>
|
</DrawerTrigger>
|
||||||
|
@ -132,6 +132,7 @@ export default function MobileReviewSettingsDrawer({
|
|||||||
{features.includes("export") && (
|
{features.includes("export") && (
|
||||||
<Button
|
<Button
|
||||||
className="flex w-full items-center justify-center gap-2"
|
className="flex w-full items-center justify-center gap-2"
|
||||||
|
aria-label="Export"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDrawerMode("export");
|
setDrawerMode("export");
|
||||||
setMode("select");
|
setMode("select");
|
||||||
@ -144,6 +145,7 @@ export default function MobileReviewSettingsDrawer({
|
|||||||
{features.includes("calendar") && (
|
{features.includes("calendar") && (
|
||||||
<Button
|
<Button
|
||||||
className="flex w-full items-center justify-center gap-2"
|
className="flex w-full items-center justify-center gap-2"
|
||||||
|
aria-label="Calendar"
|
||||||
variant={filter?.after ? "select" : "default"}
|
variant={filter?.after ? "select" : "default"}
|
||||||
onClick={() => setDrawerMode("calendar")}
|
onClick={() => setDrawerMode("calendar")}
|
||||||
>
|
>
|
||||||
@ -156,6 +158,7 @@ export default function MobileReviewSettingsDrawer({
|
|||||||
{features.includes("filter") && (
|
{features.includes("filter") && (
|
||||||
<Button
|
<Button
|
||||||
className="flex w-full items-center justify-center gap-2"
|
className="flex w-full items-center justify-center gap-2"
|
||||||
|
aria-label="Filter"
|
||||||
variant={filter?.labels || filter?.zones ? "select" : "default"}
|
variant={filter?.labels || filter?.zones ? "select" : "default"}
|
||||||
onClick={() => setDrawerMode("filter")}
|
onClick={() => setDrawerMode("filter")}
|
||||||
>
|
>
|
||||||
@ -226,6 +229,7 @@ export default function MobileReviewSettingsDrawer({
|
|||||||
<SelectSeparator />
|
<SelectSeparator />
|
||||||
<div className="flex items-center justify-center p-2">
|
<div className="flex items-center justify-center p-2">
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Reset"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onUpdateFilter({
|
onUpdateFilter({
|
||||||
...filter,
|
...filter,
|
||||||
@ -306,6 +310,7 @@ export default function MobileReviewSettingsDrawer({
|
|||||||
<DrawerTrigger asChild>
|
<DrawerTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className="rounded-lg capitalize"
|
className="rounded-lg capitalize"
|
||||||
|
aria-label="Filters"
|
||||||
variant={
|
variant={
|
||||||
filter?.labels || filter?.after || filter?.zones
|
filter?.labels || filter?.after || filter?.zones
|
||||||
? "select"
|
? "select"
|
||||||
|
@ -22,7 +22,11 @@ export default function MobileTimelineDrawer({
|
|||||||
return (
|
return (
|
||||||
<Drawer open={drawer} onOpenChange={setDrawer}>
|
<Drawer open={drawer} onOpenChange={setDrawer}>
|
||||||
<DrawerTrigger asChild>
|
<DrawerTrigger asChild>
|
||||||
<Button className="rounded-lg capitalize" size="sm">
|
<Button
|
||||||
|
className="rounded-lg capitalize"
|
||||||
|
aria-label="Select timeline or events list"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
<FaFlag className="text-secondary-foreground" />
|
<FaFlag className="text-secondary-foreground" />
|
||||||
</Button>
|
</Button>
|
||||||
</DrawerTrigger>
|
</DrawerTrigger>
|
||||||
|
@ -28,6 +28,7 @@ export default function SaveExportOverlay({
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-1 text-primary"
|
className="flex items-center gap-1 text-primary"
|
||||||
|
aria-label="Cancel"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
>
|
>
|
||||||
@ -36,6 +37,7 @@ export default function SaveExportOverlay({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
|
aria-label="Preview export"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onPreview}
|
onClick={onPreview}
|
||||||
>
|
>
|
||||||
@ -44,6 +46,7 @@ export default function SaveExportOverlay({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
|
aria-label="Save export"
|
||||||
variant="select"
|
variant="select"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onSave}
|
onClick={onSave}
|
||||||
|
@ -36,6 +36,7 @@ export default function SetPasswordDialog({
|
|||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
|
aria-label="Save Password"
|
||||||
variant="select"
|
variant="select"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -207,12 +207,14 @@ export function AnnotationSettingsPane({
|
|||||||
<div className="flex flex-row gap-2 pt-5">
|
<div className="flex flex-row gap-2 pt-5">
|
||||||
<Button
|
<Button
|
||||||
className="flex flex-1"
|
className="flex flex-1"
|
||||||
|
aria-label="Apply"
|
||||||
onClick={form.handleSubmit(onApply)}
|
onClick={form.handleSubmit(onApply)}
|
||||||
>
|
>
|
||||||
Apply
|
Apply
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="select"
|
variant="select"
|
||||||
|
aria-label="Save"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="flex flex-1"
|
className="flex flex-1"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@ -242,6 +242,7 @@ export default function ObjectLifecycle({
|
|||||||
<div className={cn("flex items-center gap-2")}>
|
<div className={cn("flex items-center gap-2")}>
|
||||||
<Button
|
<Button
|
||||||
className="mb-2 mt-3 flex items-center gap-2.5 rounded-lg md:mt-0"
|
className="mb-2 mt-3 flex items-center gap-2.5 rounded-lg md:mt-0"
|
||||||
|
aria-label="Go back"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setPane("overview")}
|
onClick={() => setPane("overview")}
|
||||||
>
|
>
|
||||||
@ -346,6 +347,7 @@ export default function ObjectLifecycle({
|
|||||||
<Button
|
<Button
|
||||||
variant={showControls ? "select" : "default"}
|
variant={showControls ? "select" : "default"}
|
||||||
className="size-7 p-1.5"
|
className="size-7 p-1.5"
|
||||||
|
aria-label="Adjust annotation settings"
|
||||||
>
|
>
|
||||||
<LuSettings
|
<LuSettings
|
||||||
className="size-5"
|
className="size-5"
|
||||||
|
@ -153,6 +153,7 @@ export default function ReviewDetailDialog({
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Share this review item"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
shareOrCopy(`${baseUrl}review?id=${review.id}`)
|
shareOrCopy(`${baseUrl}review?id=${review.id}`)
|
||||||
|
@ -296,7 +296,7 @@ function ObjectDetailsTab({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (search.sub_label) {
|
if (search.sub_label) {
|
||||||
return Math.round((search.data?.top_score ?? 0) * 100);
|
return Math.round((search.data?.sub_label_score ?? 0) * 100);
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -440,6 +440,7 @@ function ObjectDetailsTab({
|
|||||||
/>
|
/>
|
||||||
{config?.semantic_search.enabled && (
|
{config?.semantic_search.enabled && (
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Find similar tracked objects"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSearch(undefined);
|
setSearch(undefined);
|
||||||
|
|
||||||
@ -466,6 +467,7 @@ function ObjectDetailsTab({
|
|||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Button
|
<Button
|
||||||
className="rounded-r-none border-r-0"
|
className="rounded-r-none border-r-0"
|
||||||
|
aria-label="Regenerate tracked object description"
|
||||||
onClick={() => regenerateDescription("thumbnails")}
|
onClick={() => regenerateDescription("thumbnails")}
|
||||||
>
|
>
|
||||||
Regenerate
|
Regenerate
|
||||||
@ -473,19 +475,24 @@ function ObjectDetailsTab({
|
|||||||
{search.has_snapshot && (
|
{search.has_snapshot && (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button className="rounded-l-none border-l-0 px-2">
|
<Button
|
||||||
|
className="rounded-l-none border-l-0 px-2"
|
||||||
|
aria-label="Expand regeneration menu"
|
||||||
|
>
|
||||||
<FaChevronDown className="size-3" />
|
<FaChevronDown className="size-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
|
aria-label="Regenerate from snapshot"
|
||||||
onClick={() => regenerateDescription("snapshot")}
|
onClick={() => regenerateDescription("snapshot")}
|
||||||
>
|
>
|
||||||
Regenerate from Snapshot
|
Regenerate from Snapshot
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
|
aria-label="Regenerate from thumbnails"
|
||||||
onClick={() => regenerateDescription("thumbnails")}
|
onClick={() => regenerateDescription("thumbnails")}
|
||||||
>
|
>
|
||||||
Regenerate from Thumbnails
|
Regenerate from Thumbnails
|
||||||
@ -495,7 +502,11 @@ function ObjectDetailsTab({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Button variant="select" onClick={updateDescription}>
|
<Button
|
||||||
|
variant="select"
|
||||||
|
aria-label="Save"
|
||||||
|
onClick={updateDescription}
|
||||||
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -601,6 +612,7 @@ function ObjectSnapshotTab({
|
|||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
className="bg-success"
|
className="bg-success"
|
||||||
|
aria-label="Confirm this label for Frigate Plus"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setState("uploading");
|
setState("uploading");
|
||||||
onSubmitToPlus(false);
|
onSubmitToPlus(false);
|
||||||
@ -610,6 +622,7 @@ function ObjectSnapshotTab({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="text-white"
|
className="text-white"
|
||||||
|
aria-label="Do not confirm this label for Frigate Plus"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setState("uploading");
|
setState("uploading");
|
||||||
|
@ -131,9 +131,14 @@ export function FrigatePlusDialog({
|
|||||||
<DialogFooter className="flex flex-row justify-end gap-2">
|
<DialogFooter className="flex flex-row justify-end gap-2">
|
||||||
{state == "reviewing" && (
|
{state == "reviewing" && (
|
||||||
<>
|
<>
|
||||||
{dialog && <Button onClick={onClose}>Cancel</Button>}
|
{dialog && (
|
||||||
|
<Button aria-label="Cancel" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
className="bg-success"
|
className="bg-success"
|
||||||
|
aria-label="Confirm this label for Frigate Plus"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setState("uploading");
|
setState("uploading");
|
||||||
onSubmitToPlus(false);
|
onSubmitToPlus(false);
|
||||||
@ -143,6 +148,7 @@ export function FrigatePlusDialog({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="text-white"
|
className="text-white"
|
||||||
|
aria-label="Do not confirm this label for Frigate Plus"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setState("uploading");
|
setState("uploading");
|
||||||
|
@ -76,6 +76,7 @@ export default function SearchFilterDialog({
|
|||||||
const trigger = (
|
const trigger = (
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
|
aria-label="More Filters"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant={moreFiltersSelected ? "select" : "default"}
|
variant={moreFiltersSelected ? "select" : "default"}
|
||||||
>
|
>
|
||||||
@ -141,6 +142,7 @@ export default function SearchFilterDialog({
|
|||||||
<div className="flex items-center justify-evenly p-2">
|
<div className="flex items-center justify-evenly p-2">
|
||||||
<Button
|
<Button
|
||||||
variant="select"
|
variant="select"
|
||||||
|
aria-label="Apply"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (currentFilter != filter) {
|
if (currentFilter != filter) {
|
||||||
onUpdateFilter(currentFilter);
|
onUpdateFilter(currentFilter);
|
||||||
@ -152,6 +154,7 @@ export default function SearchFilterDialog({
|
|||||||
Apply
|
Apply
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Reset filters to default values"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentFilter((prevFilter) => ({
|
setCurrentFilter((prevFilter) => ({
|
||||||
...prevFilter,
|
...prevFilter,
|
||||||
@ -256,6 +259,7 @@ function TimeRangeFilterContent({
|
|||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className={`text-primary ${isDesktop ? "" : "text-xs"} `}
|
className={`text-primary ${isDesktop ? "" : "text-xs"} `}
|
||||||
|
aria-label="Select Start Time"
|
||||||
variant={startOpen ? "select" : "default"}
|
variant={startOpen ? "select" : "default"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -293,6 +297,7 @@ function TimeRangeFilterContent({
|
|||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className={`text-primary ${isDesktop ? "" : "text-xs"}`}
|
className={`text-primary ${isDesktop ? "" : "text-xs"}`}
|
||||||
|
aria-label="Select End Time"
|
||||||
variant={endOpen ? "select" : "default"}
|
variant={endOpen ? "select" : "default"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -308,11 +308,16 @@ export default function MotionMaskEditPane({
|
|||||||
/>
|
/>
|
||||||
<div className="flex flex-1 flex-col justify-end">
|
<div className="flex flex-1 flex-col justify-end">
|
||||||
<div className="flex flex-row gap-2 pt-5">
|
<div className="flex flex-row gap-2 pt-5">
|
||||||
<Button className="flex flex-1" onClick={onCancel}>
|
<Button
|
||||||
|
className="flex flex-1"
|
||||||
|
aria-label="Cancel"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="select"
|
variant="select"
|
||||||
|
aria-label="Save"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="flex flex-1"
|
className="flex flex-1"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@ -335,13 +335,18 @@ export default function ObjectMaskEditPane({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 flex-col justify-end">
|
<div className="flex flex-1 flex-col justify-end">
|
||||||
<div className="flex flex-row gap-2 pt-5">
|
<div className="flex flex-row gap-2 pt-5">
|
||||||
<Button className="flex flex-1" onClick={onCancel}>
|
<Button
|
||||||
|
className="flex flex-1"
|
||||||
|
aria-label="Cancel"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="select"
|
variant="select"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="flex flex-1"
|
className="flex flex-1"
|
||||||
|
aria-label="Save"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
@ -74,6 +74,7 @@ export default function PolygonEditControls({
|
|||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
className="size-6 rounded-md p-1"
|
className="size-6 rounded-md p-1"
|
||||||
|
aria-label="Remove last point"
|
||||||
disabled={!polygons[activePolygonIndex].points.length}
|
disabled={!polygons[activePolygonIndex].points.length}
|
||||||
onClick={undo}
|
onClick={undo}
|
||||||
>
|
>
|
||||||
@ -87,6 +88,7 @@ export default function PolygonEditControls({
|
|||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
className="size-6 rounded-md p-1"
|
className="size-6 rounded-md p-1"
|
||||||
|
aria-label="Clear all points"
|
||||||
disabled={!polygons[activePolygonIndex].points.length}
|
disabled={!polygons[activePolygonIndex].points.length}
|
||||||
onClick={reset}
|
onClick={reset}
|
||||||
>
|
>
|
||||||
|
@ -276,6 +276,7 @@ export default function PolygonItem({
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
|
aria-label="Edit"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActivePolygonIndex(index);
|
setActivePolygonIndex(index);
|
||||||
setEditPane(polygon.type);
|
setEditPane(polygon.type);
|
||||||
@ -283,10 +284,14 @@ export default function PolygonItem({
|
|||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => handleCopyCoordinates(index)}>
|
<DropdownMenuItem
|
||||||
|
aria-label="Copy"
|
||||||
|
onClick={() => handleCopyCoordinates(index)}
|
||||||
|
>
|
||||||
Copy
|
Copy
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
|
aria-label="Delete"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
onClick={() => setDeleteDialogOpen(true)}
|
onClick={() => setDeleteDialogOpen(true)}
|
||||||
>
|
>
|
||||||
|
@ -44,7 +44,11 @@ export default function SearchSettings({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const trigger = (
|
const trigger = (
|
||||||
<Button className="flex items-center gap-2" size="sm">
|
<Button
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
aria-label="Search Settings"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
<FaCog className="text-secondary-foreground" />
|
<FaCog className="text-secondary-foreground" />
|
||||||
Settings
|
Settings
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -466,13 +466,18 @@ export default function ZoneEditPane({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-row gap-2 pt-5">
|
<div className="flex flex-row gap-2 pt-5">
|
||||||
<Button className="flex flex-1" onClick={onCancel}>
|
<Button
|
||||||
|
className="flex flex-1"
|
||||||
|
aria-label="Cancel"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="select"
|
variant="select"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="flex flex-1"
|
className="flex flex-1"
|
||||||
|
aria-label="Save"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
@ -283,6 +283,7 @@ export function DateRangePicker({
|
|||||||
}): JSX.Element => (
|
}): JSX.Element => (
|
||||||
<Button
|
<Button
|
||||||
className={cn(isSelected && "pointer-events-none text-primary")}
|
className={cn(isSelected && "pointer-events-none text-primary")}
|
||||||
|
aria-label={label}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPreset(preset);
|
setPreset(preset);
|
||||||
@ -417,6 +418,7 @@ export function DateRangePicker({
|
|||||||
<div className="mx-auto flex w-64 items-center justify-evenly gap-2 py-2">
|
<div className="mx-auto flex w-64 items-center justify-evenly gap-2 py-2">
|
||||||
<Button
|
<Button
|
||||||
variant="select"
|
variant="select"
|
||||||
|
aria-label="Apply"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
if (
|
if (
|
||||||
@ -436,6 +438,7 @@ export function DateRangePicker({
|
|||||||
onReset?.();
|
onReset?.();
|
||||||
}}
|
}}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
aria-label="Reset"
|
||||||
>
|
>
|
||||||
Reset
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -1,43 +1,43 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import useEmblaCarousel, {
|
import useEmblaCarousel, {
|
||||||
type UseEmblaCarouselType,
|
type UseEmblaCarouselType,
|
||||||
} from "embla-carousel-react"
|
} from "embla-carousel-react";
|
||||||
import { ArrowLeft, ArrowRight } from "lucide-react"
|
import { ArrowLeft, ArrowRight } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
type CarouselApi = UseEmblaCarouselType[1]
|
type CarouselApi = UseEmblaCarouselType[1];
|
||||||
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
|
||||||
type CarouselOptions = UseCarouselParameters[0]
|
type CarouselOptions = UseCarouselParameters[0];
|
||||||
type CarouselPlugin = UseCarouselParameters[1]
|
type CarouselPlugin = UseCarouselParameters[1];
|
||||||
|
|
||||||
type CarouselProps = {
|
type CarouselProps = {
|
||||||
opts?: CarouselOptions
|
opts?: CarouselOptions;
|
||||||
plugins?: CarouselPlugin
|
plugins?: CarouselPlugin;
|
||||||
orientation?: "horizontal" | "vertical"
|
orientation?: "horizontal" | "vertical";
|
||||||
setApi?: (api: CarouselApi) => void
|
setApi?: (api: CarouselApi) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
type CarouselContextProps = {
|
type CarouselContextProps = {
|
||||||
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
|
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
|
||||||
api: ReturnType<typeof useEmblaCarousel>[1]
|
api: ReturnType<typeof useEmblaCarousel>[1];
|
||||||
scrollPrev: () => void
|
scrollPrev: () => void;
|
||||||
scrollNext: () => void
|
scrollNext: () => void;
|
||||||
canScrollPrev: boolean
|
canScrollPrev: boolean;
|
||||||
canScrollNext: boolean
|
canScrollNext: boolean;
|
||||||
} & CarouselProps
|
} & CarouselProps;
|
||||||
|
|
||||||
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
|
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
|
||||||
|
|
||||||
function useCarousel() {
|
function useCarousel() {
|
||||||
const context = React.useContext(CarouselContext)
|
const context = React.useContext(CarouselContext);
|
||||||
|
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("useCarousel must be used within a <Carousel />")
|
throw new Error("useCarousel must be used within a <Carousel />");
|
||||||
}
|
}
|
||||||
|
|
||||||
return context
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Carousel = React.forwardRef<
|
const Carousel = React.forwardRef<
|
||||||
@ -54,69 +54,69 @@ const Carousel = React.forwardRef<
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const [carouselRef, api] = useEmblaCarousel(
|
const [carouselRef, api] = useEmblaCarousel(
|
||||||
{
|
{
|
||||||
...opts,
|
...opts,
|
||||||
axis: orientation === "horizontal" ? "x" : "y",
|
axis: orientation === "horizontal" ? "x" : "y",
|
||||||
},
|
},
|
||||||
plugins
|
plugins,
|
||||||
)
|
);
|
||||||
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
|
||||||
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
const [canScrollNext, setCanScrollNext] = React.useState(false);
|
||||||
|
|
||||||
const onSelect = React.useCallback((api: CarouselApi) => {
|
const onSelect = React.useCallback((api: CarouselApi) => {
|
||||||
if (!api) {
|
if (!api) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCanScrollPrev(api.canScrollPrev())
|
setCanScrollPrev(api.canScrollPrev());
|
||||||
setCanScrollNext(api.canScrollNext())
|
setCanScrollNext(api.canScrollNext());
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const scrollPrev = React.useCallback(() => {
|
const scrollPrev = React.useCallback(() => {
|
||||||
api?.scrollPrev()
|
api?.scrollPrev();
|
||||||
}, [api])
|
}, [api]);
|
||||||
|
|
||||||
const scrollNext = React.useCallback(() => {
|
const scrollNext = React.useCallback(() => {
|
||||||
api?.scrollNext()
|
api?.scrollNext();
|
||||||
}, [api])
|
}, [api]);
|
||||||
|
|
||||||
const handleKeyDown = React.useCallback(
|
const handleKeyDown = React.useCallback(
|
||||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
if (event.key === "ArrowLeft") {
|
if (event.key === "ArrowLeft") {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
scrollPrev()
|
scrollPrev();
|
||||||
} else if (event.key === "ArrowRight") {
|
} else if (event.key === "ArrowRight") {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
scrollNext()
|
scrollNext();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[scrollPrev, scrollNext]
|
[scrollPrev, scrollNext],
|
||||||
)
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!api || !setApi) {
|
if (!api || !setApi) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setApi(api)
|
setApi(api);
|
||||||
}, [api, setApi])
|
}, [api, setApi]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!api) {
|
if (!api) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelect(api)
|
onSelect(api);
|
||||||
api.on("reInit", onSelect)
|
api.on("reInit", onSelect);
|
||||||
api.on("select", onSelect)
|
api.on("select", onSelect);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
api?.off("select", onSelect)
|
api?.off("select", onSelect);
|
||||||
}
|
};
|
||||||
}, [api, onSelect])
|
}, [api, onSelect]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CarouselContext.Provider
|
<CarouselContext.Provider
|
||||||
@ -143,16 +143,16 @@ const Carousel = React.forwardRef<
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</CarouselContext.Provider>
|
</CarouselContext.Provider>
|
||||||
)
|
);
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
Carousel.displayName = "Carousel"
|
Carousel.displayName = "Carousel";
|
||||||
|
|
||||||
const CarouselContent = React.forwardRef<
|
const CarouselContent = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
>(({ className, ...props }, ref) => {
|
>(({ className, ...props }, ref) => {
|
||||||
const { carouselRef, orientation } = useCarousel()
|
const { carouselRef, orientation } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={carouselRef} className="overflow-hidden">
|
<div ref={carouselRef} className="overflow-hidden">
|
||||||
@ -161,20 +161,20 @@ const CarouselContent = React.forwardRef<
|
|||||||
className={cn(
|
className={cn(
|
||||||
"flex",
|
"flex",
|
||||||
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
CarouselContent.displayName = "CarouselContent"
|
CarouselContent.displayName = "CarouselContent";
|
||||||
|
|
||||||
const CarouselItem = React.forwardRef<
|
const CarouselItem = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
>(({ className, ...props }, ref) => {
|
>(({ className, ...props }, ref) => {
|
||||||
const { orientation } = useCarousel()
|
const { orientation } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -184,19 +184,19 @@ const CarouselItem = React.forwardRef<
|
|||||||
className={cn(
|
className={cn(
|
||||||
"min-w-0 shrink-0 grow-0 basis-full",
|
"min-w-0 shrink-0 grow-0 basis-full",
|
||||||
orientation === "horizontal" ? "pl-4" : "pt-4",
|
orientation === "horizontal" ? "pl-4" : "pt-4",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
CarouselItem.displayName = "CarouselItem"
|
CarouselItem.displayName = "CarouselItem";
|
||||||
|
|
||||||
const CarouselPrevious = React.forwardRef<
|
const CarouselPrevious = React.forwardRef<
|
||||||
HTMLButtonElement,
|
HTMLButtonElement,
|
||||||
React.ComponentProps<typeof Button>
|
React.ComponentProps<typeof Button>
|
||||||
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
||||||
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@ -204,12 +204,13 @@ const CarouselPrevious = React.forwardRef<
|
|||||||
variant={variant}
|
variant={variant}
|
||||||
size={size}
|
size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute h-8 w-8 rounded-full",
|
"absolute h-8 w-8 rounded-full",
|
||||||
orientation === "horizontal"
|
orientation === "horizontal"
|
||||||
? "-left-12 top-1/2 -translate-y-1/2"
|
? "-left-12 top-1/2 -translate-y-1/2"
|
||||||
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
|
aria-label="Previous slide"
|
||||||
disabled={!canScrollPrev}
|
disabled={!canScrollPrev}
|
||||||
onClick={scrollPrev}
|
onClick={scrollPrev}
|
||||||
{...props}
|
{...props}
|
||||||
@ -217,15 +218,15 @@ const CarouselPrevious = React.forwardRef<
|
|||||||
<ArrowLeft className="h-4 w-4" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
<span className="sr-only">Previous slide</span>
|
<span className="sr-only">Previous slide</span>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
CarouselPrevious.displayName = "CarouselPrevious"
|
CarouselPrevious.displayName = "CarouselPrevious";
|
||||||
|
|
||||||
const CarouselNext = React.forwardRef<
|
const CarouselNext = React.forwardRef<
|
||||||
HTMLButtonElement,
|
HTMLButtonElement,
|
||||||
React.ComponentProps<typeof Button>
|
React.ComponentProps<typeof Button>
|
||||||
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
||||||
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
const { orientation, scrollNext, canScrollNext } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@ -237,8 +238,9 @@ const CarouselNext = React.forwardRef<
|
|||||||
orientation === "horizontal"
|
orientation === "horizontal"
|
||||||
? "-right-12 top-1/2 -translate-y-1/2"
|
? "-right-12 top-1/2 -translate-y-1/2"
|
||||||
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
|
aria-label="Next slide"
|
||||||
disabled={!canScrollNext}
|
disabled={!canScrollNext}
|
||||||
onClick={scrollNext}
|
onClick={scrollNext}
|
||||||
{...props}
|
{...props}
|
||||||
@ -246,9 +248,9 @@ const CarouselNext = React.forwardRef<
|
|||||||
<ArrowRight className="h-4 w-4" />
|
<ArrowRight className="h-4 w-4" />
|
||||||
<span className="sr-only">Next slide</span>
|
<span className="sr-only">Next slide</span>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
CarouselNext.displayName = "CarouselNext"
|
CarouselNext.displayName = "CarouselNext";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
type CarouselApi,
|
type CarouselApi,
|
||||||
@ -257,4 +259,4 @@ export {
|
|||||||
CarouselItem,
|
CarouselItem,
|
||||||
CarouselPrevious,
|
CarouselPrevious,
|
||||||
CarouselNext,
|
CarouselNext,
|
||||||
}
|
};
|
||||||
|
@ -192,6 +192,7 @@ function ConfigEditor() {
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
|
aria-label="Copy config"
|
||||||
onClick={() => handleCopyConfig()}
|
onClick={() => handleCopyConfig()}
|
||||||
>
|
>
|
||||||
<LuCopy className="text-secondary-foreground" />
|
<LuCopy className="text-secondary-foreground" />
|
||||||
@ -200,6 +201,7 @@ function ConfigEditor() {
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
|
aria-label="Save and restart"
|
||||||
onClick={() => onHandleSaveConfig("restart")}
|
onClick={() => onHandleSaveConfig("restart")}
|
||||||
>
|
>
|
||||||
<div className="relative size-5">
|
<div className="relative size-5">
|
||||||
@ -211,6 +213,7 @@ function ConfigEditor() {
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
|
aria-label="Save only without restarting"
|
||||||
onClick={() => onHandleSaveConfig("saveonly")}
|
onClick={() => onHandleSaveConfig("saveonly")}
|
||||||
>
|
>
|
||||||
<LuSave className="text-secondary-foreground" />
|
<LuSave className="text-secondary-foreground" />
|
||||||
|
@ -125,6 +125,7 @@ function Exports() {
|
|||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
<Button
|
<Button
|
||||||
className="text-white"
|
className="text-white"
|
||||||
|
aria-label="Delete Export"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={() => onHandleDelete()}
|
onClick={() => onHandleDelete()}
|
||||||
>
|
>
|
||||||
|
@ -339,6 +339,7 @@ function Logs() {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center justify-between gap-2"
|
className="flex items-center justify-between gap-2"
|
||||||
|
aria-label="Copy logs to clipboard"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleCopyLogs}
|
onClick={handleCopyLogs}
|
||||||
>
|
>
|
||||||
@ -349,6 +350,7 @@ function Logs() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center justify-between gap-2"
|
className="flex items-center justify-between gap-2"
|
||||||
|
aria-label="Download logs"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleDownloadLogs}
|
onClick={handleDownloadLogs}
|
||||||
>
|
>
|
||||||
@ -365,6 +367,7 @@ function Logs() {
|
|||||||
{initialScroll && !endVisible && (
|
{initialScroll && !endVisible && (
|
||||||
<Button
|
<Button
|
||||||
className="absolute bottom-8 left-[50%] z-20 flex -translate-x-[50%] items-center gap-1 rounded-md p-2"
|
className="absolute bottom-8 left-[50%] z-20 flex -translate-x-[50%] items-center gap-1 rounded-md p-2"
|
||||||
|
aria-label="Jump to bottom of logs"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
contentRef.current?.scrollTo({
|
contentRef.current?.scrollTo({
|
||||||
top: contentRef.current?.scrollHeight,
|
top: contentRef.current?.scrollHeight,
|
||||||
|
@ -252,6 +252,7 @@ function CameraSelectButton({
|
|||||||
const trigger = (
|
const trigger = (
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2 bg-selected capitalize hover:bg-selected"
|
className="flex items-center gap-2 bg-selected capitalize hover:bg-selected"
|
||||||
|
aria-label="Select a camera"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
<FaVideo className="text-background dark:text-primary" />
|
<FaVideo className="text-background dark:text-primary" />
|
||||||
|
@ -737,6 +737,7 @@ function DetectionReview({
|
|||||||
<div className="col-span-full flex items-center justify-center">
|
<div className="col-span-full flex items-center justify-center">
|
||||||
<Button
|
<Button
|
||||||
className="text-white"
|
className="text-white"
|
||||||
|
aria-label="Mark these items as reviewed"
|
||||||
variant="select"
|
variant="select"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedReviews([]);
|
setSelectedReviews([]);
|
||||||
|
@ -144,6 +144,7 @@ export default function LiveBirdseyeView({
|
|||||||
{!fullscreen ? (
|
{!fullscreen ? (
|
||||||
<Button
|
<Button
|
||||||
className={`flex items-center gap-2 rounded-lg ${isMobile ? "ml-2" : "ml-0"}`}
|
className={`flex items-center gap-2 rounded-lg ${isMobile ? "ml-2" : "ml-0"}`}
|
||||||
|
aria-label="Go Back"
|
||||||
size={isMobile ? "icon" : "sm"}
|
size={isMobile ? "icon" : "sm"}
|
||||||
onClick={() => navigate(-1)}
|
onClick={() => navigate(-1)}
|
||||||
>
|
>
|
||||||
|
@ -352,6 +352,7 @@ export default function LiveCameraView({
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className={`flex items-center gap-2.5 rounded-lg`}
|
className={`flex items-center gap-2.5 rounded-lg`}
|
||||||
|
aria-label="Go back"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => navigate(-1)}
|
onClick={() => navigate(-1)}
|
||||||
>
|
>
|
||||||
@ -360,6 +361,7 @@ export default function LiveCameraView({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2.5 rounded-lg"
|
className="flex items-center gap-2.5 rounded-lg"
|
||||||
|
aria-label="Show historical footage"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate("review", {
|
navigate("review", {
|
||||||
@ -388,6 +390,7 @@ export default function LiveCameraView({
|
|||||||
{fullscreen && (
|
{fullscreen && (
|
||||||
<Button
|
<Button
|
||||||
className="bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-primary"
|
className="bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-primary"
|
||||||
|
aria-label="Go back"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => navigate(-1)}
|
onClick={() => navigate(-1)}
|
||||||
>
|
>
|
||||||
@ -603,6 +606,7 @@ function PtzControlPanel({
|
|||||||
{ptz?.features?.includes("pt") && (
|
{ptz?.features?.includes("pt") && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Move PTZ camera to the left"
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
sendPtz("MOVE_LEFT");
|
sendPtz("MOVE_LEFT");
|
||||||
@ -617,6 +621,7 @@ function PtzControlPanel({
|
|||||||
<FaAngleLeft />
|
<FaAngleLeft />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Move PTZ camera up"
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
sendPtz("MOVE_UP");
|
sendPtz("MOVE_UP");
|
||||||
@ -631,6 +636,7 @@ function PtzControlPanel({
|
|||||||
<FaAngleUp />
|
<FaAngleUp />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Move PTZ camera down"
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
sendPtz("MOVE_DOWN");
|
sendPtz("MOVE_DOWN");
|
||||||
@ -645,6 +651,7 @@ function PtzControlPanel({
|
|||||||
<FaAngleDown />
|
<FaAngleDown />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Move PTZ camera to the right"
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
sendPtz("MOVE_RIGHT");
|
sendPtz("MOVE_RIGHT");
|
||||||
@ -663,6 +670,7 @@ function PtzControlPanel({
|
|||||||
{ptz?.features?.includes("zoom") && (
|
{ptz?.features?.includes("zoom") && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Zoom PTZ camera in"
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
sendPtz("ZOOM_IN");
|
sendPtz("ZOOM_IN");
|
||||||
@ -677,6 +685,7 @@ function PtzControlPanel({
|
|||||||
<MdZoomIn />
|
<MdZoomIn />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Zoom PTZ camera out"
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
sendPtz("ZOOM_OUT");
|
sendPtz("ZOOM_OUT");
|
||||||
@ -696,6 +705,7 @@ function PtzControlPanel({
|
|||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
className={`${clickOverlay ? "text-selected" : "text-primary"}`}
|
className={`${clickOverlay ? "text-selected" : "text-primary"}`}
|
||||||
|
aria-label="Click in the frame to center the PTZ camera"
|
||||||
onClick={() => setClickOverlay(!clickOverlay)}
|
onClick={() => setClickOverlay(!clickOverlay)}
|
||||||
>
|
>
|
||||||
<TbViewfinder />
|
<TbViewfinder />
|
||||||
@ -705,7 +715,7 @@ function PtzControlPanel({
|
|||||||
{(ptz?.presets?.length ?? 0) > 0 && (
|
{(ptz?.presets?.length ?? 0) > 0 && (
|
||||||
<DropdownMenu modal={!isDesktop}>
|
<DropdownMenu modal={!isDesktop}>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button>
|
<Button aria-label="PTZ camera presets">
|
||||||
<BsThreeDotsVertical />
|
<BsThreeDotsVertical />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@ -717,6 +727,7 @@ function PtzControlPanel({
|
|||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={preset}
|
key={preset}
|
||||||
|
aria-label={preset}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onSelect={() => sendPtz(`preset_${preset}`)}
|
onSelect={() => sendPtz(`preset_${preset}`)}
|
||||||
>
|
>
|
||||||
|
@ -240,6 +240,7 @@ export default function LiveDashboardView({
|
|||||||
? "bg-blue-900 bg-opacity-60 focus:bg-blue-900 focus:bg-opacity-60"
|
? "bg-blue-900 bg-opacity-60 focus:bg-blue-900 focus:bg-opacity-60"
|
||||||
: "bg-secondary"
|
: "bg-secondary"
|
||||||
}`}
|
}`}
|
||||||
|
aria-label="Use mobile grid layout"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => setMobileLayout("grid")}
|
onClick={() => setMobileLayout("grid")}
|
||||||
>
|
>
|
||||||
@ -251,6 +252,7 @@ export default function LiveDashboardView({
|
|||||||
? "bg-blue-900 bg-opacity-60 focus:bg-blue-900 focus:bg-opacity-60"
|
? "bg-blue-900 bg-opacity-60 focus:bg-blue-900 focus:bg-opacity-60"
|
||||||
: "bg-secondary"
|
: "bg-secondary"
|
||||||
}`}
|
}`}
|
||||||
|
aria-label="Use mobile list layout"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => setMobileLayout("list")}
|
onClick={() => setMobileLayout("list")}
|
||||||
>
|
>
|
||||||
@ -267,6 +269,7 @@ export default function LiveDashboardView({
|
|||||||
? "bg-selected text-primary"
|
? "bg-selected text-primary"
|
||||||
: "bg-secondary text-secondary-foreground",
|
: "bg-secondary text-secondary-foreground",
|
||||||
)}
|
)}
|
||||||
|
aria-label="Enter layout editing mode"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setIsEditMode((prevIsEditMode) => !prevIsEditMode)
|
setIsEditMode((prevIsEditMode) => !prevIsEditMode)
|
||||||
|
@ -380,6 +380,7 @@ export function RecordingView({
|
|||||||
<div className={cn("flex items-center gap-2")}>
|
<div className={cn("flex items-center gap-2")}>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2.5 rounded-lg"
|
className="flex items-center gap-2.5 rounded-lg"
|
||||||
|
aria-label="Go back"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => navigate(-1)}
|
onClick={() => navigate(-1)}
|
||||||
>
|
>
|
||||||
@ -388,6 +389,7 @@ export function RecordingView({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2.5 rounded-lg"
|
className="flex items-center gap-2.5 rounded-lg"
|
||||||
|
aria-label="Go to the main camera live view"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/#${mainCamera}`);
|
navigate(`/#${mainCamera}`);
|
||||||
|
@ -95,6 +95,7 @@ export default function AuthenticationView() {
|
|||||||
</Heading>
|
</Heading>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
|
aria-label="Add a new user"
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowCreate(true);
|
setShowCreate(true);
|
||||||
@ -114,6 +115,7 @@ export default function AuthenticationView() {
|
|||||||
<div className="flex flex-1 justify-end space-x-2">
|
<div className="flex flex-1 justify-end space-x-2">
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
|
aria-label="Update the user's password"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowSetPassword(true);
|
setShowSetPassword(true);
|
||||||
@ -125,6 +127,7 @@ export default function AuthenticationView() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
|
aria-label="Delete the user"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowDelete(true);
|
setShowDelete(true);
|
||||||
|
@ -475,6 +475,7 @@ export default function CameraSettingsView({
|
|||||||
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
|
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
|
||||||
<Button
|
<Button
|
||||||
className="flex flex-1"
|
className="flex flex-1"
|
||||||
|
aria-label="Cancel"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@ -484,6 +485,7 @@ export default function CameraSettingsView({
|
|||||||
variant="select"
|
variant="select"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="flex flex-1"
|
className="flex flex-1"
|
||||||
|
aria-label="Save"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
@ -459,6 +459,7 @@ export default function MasksAndZonesView({
|
|||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
|
className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
|
||||||
|
aria-label="Add a new zone"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditPane("zone");
|
setEditPane("zone");
|
||||||
handleNewPolygon("zone");
|
handleNewPolygon("zone");
|
||||||
@ -527,6 +528,7 @@ export default function MasksAndZonesView({
|
|||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
|
className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
|
||||||
|
aria-label="Add a new motion mask"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditPane("motion_mask");
|
setEditPane("motion_mask");
|
||||||
handleNewPolygon("motion_mask");
|
handleNewPolygon("motion_mask");
|
||||||
@ -596,6 +598,7 @@ export default function MasksAndZonesView({
|
|||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
|
className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
|
||||||
|
aria-label="Add a new object mask"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditPane("object_mask");
|
setEditPane("object_mask");
|
||||||
handleNewPolygon("object_mask");
|
handleNewPolygon("object_mask");
|
||||||
|
@ -284,13 +284,18 @@ export default function MotionTunerView({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 flex-col justify-end">
|
<div className="flex flex-1 flex-col justify-end">
|
||||||
<div className="flex flex-row gap-2 pt-5">
|
<div className="flex flex-row gap-2 pt-5">
|
||||||
<Button className="flex flex-1" onClick={onCancel}>
|
<Button
|
||||||
|
className="flex flex-1"
|
||||||
|
aria-label="Reset"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
Reset
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="select"
|
variant="select"
|
||||||
disabled={!changedValue || isLoading}
|
disabled={!changedValue || isLoading}
|
||||||
className="flex flex-1"
|
className="flex flex-1"
|
||||||
|
aria-label="Save"
|
||||||
onClick={saveToConfig}
|
onClick={saveToConfig}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
@ -270,6 +270,7 @@ export default function NotificationView({
|
|||||||
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
|
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
|
||||||
<Button
|
<Button
|
||||||
className="flex flex-1"
|
className="flex flex-1"
|
||||||
|
aria-label="Cancel"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@ -279,6 +280,7 @@ export default function NotificationView({
|
|||||||
variant="select"
|
variant="select"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="flex flex-1"
|
className="flex flex-1"
|
||||||
|
aria-label="Save"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@ -298,6 +300,7 @@ export default function NotificationView({
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<Separator className="my-2 flex bg-secondary" />
|
<Separator className="my-2 flex bg-secondary" />
|
||||||
<Button
|
<Button
|
||||||
|
aria-label="Register or unregister notifications for this device"
|
||||||
disabled={
|
disabled={
|
||||||
!config?.notifications.enabled || publicKey == undefined
|
!config?.notifications.enabled || publicKey == undefined
|
||||||
}
|
}
|
||||||
|
@ -266,13 +266,14 @@ export default function SearchSettingsView({
|
|||||||
<Separator className="my-2 flex bg-secondary" />
|
<Separator className="my-2 flex bg-secondary" />
|
||||||
|
|
||||||
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
|
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
|
||||||
<Button className="flex flex-1" onClick={onCancel}>
|
<Button className="flex flex-1" aria-label="Reset" onClick={onCancel}>
|
||||||
Reset
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="select"
|
variant="select"
|
||||||
disabled={!changedValue || isLoading}
|
disabled={!changedValue || isLoading}
|
||||||
className="flex flex-1"
|
className="flex flex-1"
|
||||||
|
aria-label="Save"
|
||||||
onClick={saveToConfig}
|
onClick={saveToConfig}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
@ -125,7 +125,12 @@ export default function UiSettingsView() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={clearStoredLayouts}>Clear All Layouts</Button>
|
<Button
|
||||||
|
aria-label="Clear all saved layouts"
|
||||||
|
onClick={clearStoredLayouts}
|
||||||
|
>
|
||||||
|
Clear All Layouts
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator className="my-2 flex bg-secondary" />
|
<Separator className="my-2 flex bg-secondary" />
|
||||||
|
@ -541,6 +541,7 @@ export default function GeneralMetrics({
|
|||||||
{canGetGpuInfo && (
|
{canGetGpuInfo && (
|
||||||
<Button
|
<Button
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
|
aria-label="Hardware information"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setShowVainfo(true)}
|
onClick={() => setShowVainfo(true)}
|
||||||
>
|
>
|
||||||
|
Loading…
Reference in New Issue
Block a user