Miscellaneous fixes (#22924)

* apply annotation offset to frigate+ submission frame time

* fix broken docs links with hash fragments that resolve wrong on reload

* undo

* use recording snapshot for frigate+ frame submission from VideoControls

rather than a canvas grab/paint, which may not always align with an ffmpeg snapshot due to keyframes

* add more docs links

- display docs link for main sections on collapsible fields

* dialog button consistency
This commit is contained in:
Josh Hawkins
2026-04-20 07:19:09 -06:00
committed by GitHub
parent 043c746a8b
commit 1a5d15ba81
14 changed files with 147 additions and 30 deletions
@@ -113,18 +113,19 @@ export function DebugReplayContent({
{isDesktop && <SelectSeparator className="my-4 bg-secondary" />}
<DialogFooter
className={isDesktop ? "" : "mt-3 flex flex-col-reverse gap-4"}
className={isDesktop ? "" : "mt-3 flex flex-col-reverse gap-2"}
>
<div
className={`cursor-pointer p-2 text-center ${isDesktop ? "" : "w-full"}`}
<Button
className={isDesktop ? "" : "w-full"}
aria-label={t("button.cancel", { ns: "common" })}
variant="outline"
onClick={onCancel}
>
{t("button.cancel", { ns: "common" })}
</div>
</Button>
<Button
className={isDesktop ? "" : "w-full"}
variant="select"
size="sm"
disabled={isStarting}
onClick={() => {
if (selectedOption === "timeline") {
+6 -5
View File
@@ -1043,20 +1043,21 @@ export function ExportContent({
{isDesktop && <SelectSeparator className="my-4 bg-secondary" />}
<DialogFooter
className={isDesktop ? "" : "mt-3 flex flex-col-reverse gap-4"}
className={isDesktop ? "" : "mt-3 flex flex-col-reverse gap-2"}
>
<div
className={`cursor-pointer p-2 text-center ${isDesktop ? "" : "w-full"}`}
<Button
className={isDesktop ? "" : "w-full"}
aria-label={t("button.cancel", { ns: "common" })}
variant="outline"
onClick={onCancel}
>
{t("button.cancel", { ns: "common" })}
</div>
</Button>
{activeTab === "export" ? (
<Button
className={isDesktop ? "" : "w-full"}
aria-label={t("export.selectOrExport")}
variant="select"
size="sm"
disabled={isStartingExport}
onClick={async () => {
if (selectedOption == "timeline") {
@@ -197,24 +197,21 @@ export function ShareTimestampContent({
{isDesktop && <Separator className="my-4 bg-secondary" />}
<DialogFooter
className={cn("mt-4", !isDesktop && "flex flex-col-reverse gap-4")}
className={cn("mt-4", !isDesktop && "flex flex-col-reverse gap-2")}
>
{onCancel && (
<button
type="button"
className={cn(
"cursor-pointer p-2 text-center",
!isDesktop && "w-full",
)}
<Button
className={cn(!isDesktop && "w-full")}
aria-label={t("button.cancel", { ns: "common" })}
variant="outline"
onClick={onCancel}
>
{t("button.cancel", { ns: "common" })}
</button>
</Button>
)}
<Button
className={cn(!isDesktop && "w-full")}
variant="select"
size="sm"
onClick={() => onShareTimestamp(Math.floor(selectedTimestamp))}
>
{t("recording.shareTimestamp.button", { ns: "components/dialog" })}
@@ -636,6 +636,13 @@ export function TrackingDetails({
return axios.post(`/${event.camera}/plus/${currentTime}`);
}, [event.camera, currentTime]);
const getSnapshotUrlForPlus = useCallback(() => {
if (!currentTime) {
return undefined;
}
return `${apiHost}api/${event.camera}/recordings/${currentTime}/snapshot.jpg?height=500`;
}, [apiHost, event.camera, currentTime]);
if (!config) {
return <ActivityIndicator />;
}
@@ -683,6 +690,7 @@ export function TrackingDetails({
onTimeUpdate={handleTimeUpdate}
onSeekToTime={handleSeekToTime}
onUploadFrame={onUploadFrameToPlus}
getSnapshotUrl={getSnapshotUrlForPlus}
onPlaying={() => setIsVideoLoading(false)}
setFullResolution={setFullResolution}
toggleFullscreen={toggleFullscreen}
@@ -867,6 +875,7 @@ export function TrackingDetails({
getZoneColor={getZoneColor}
effectiveTime={effectiveTime}
isTimelineActive={isWithinEventRange}
annotationOffset={annotationOffset}
/>
</div>
);
@@ -890,6 +899,7 @@ type LifecycleIconRowProps = {
getZoneColor: (zoneName: string) => number[] | undefined;
effectiveTime?: number;
isTimelineActive?: boolean;
annotationOffset: number;
};
function LifecycleIconRow({
@@ -900,6 +910,7 @@ function LifecycleIconRow({
getZoneColor,
effectiveTime,
isTimelineActive,
annotationOffset,
}: LifecycleIconRowProps) {
const { t } = useTranslation(["views/explore", "components/player"]);
const { data: config } = useSWR<FrigateConfig>("config");
@@ -1206,7 +1217,7 @@ function LifecycleIconRow({
className="cursor-pointer"
onSelect={async () => {
const resp = await axios.post(
`/${item.camera}/plus/${item.timestamp}`,
`/${item.camera}/plus/${item.timestamp + annotationOffset / 1000}`,
);
if (resp && resp.status == 200) {