Skip to content

Commit 1f5eaa8

Browse files
authored
Merge pull request #4914 from voxel51/release/v1.0.1
Release/v1.0.1
2 parents 7e73fec + b48fbce commit 1f5eaa8

File tree

38 files changed

+735
-310
lines changed

38 files changed

+735
-310
lines changed

app/packages/core/src/components/ColorModal/ColorFooter.tsx

+4-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as foq from "@fiftyone/relay";
33
import * as fos from "@fiftyone/state";
44
import React, { useEffect } from "react";
55
import { useMutation } from "react-relay";
6-
import { useRecoilState, useRecoilValue } from "recoil";
6+
import { useRecoilValue } from "recoil";
77
import { ButtonGroup, ModalActionButtonContainer } from "./ShareStyledDiv";
88
import { activeColorEntry } from "./state";
99

@@ -14,8 +14,7 @@ const ColorFooter: React.FC = () => {
1414
? canEditCustomColors.message
1515
: "Save to dataset app config";
1616
const setColorScheme = fos.useSetSessionColorScheme();
17-
const [activeColorModalField, setActiveColorModalField] =
18-
useRecoilState(activeColorEntry);
17+
const activeColorModalField = useRecoilValue(activeColorEntry);
1918
const [setDatasetColorScheme] =
2019
useMutation<foq.setDatasetColorSchemeMutation>(foq.setDatasetColorScheme);
2120
const colorScheme = useRecoilValue(fos.colorScheme);
@@ -26,8 +25,8 @@ const ColorFooter: React.FC = () => {
2625
const subscription = useRecoilValue(fos.stateSubscription);
2726

2827
useEffect(
29-
() => foq.subscribe(() => setActiveColorModalField(null)),
30-
[setActiveColorModalField]
28+
() => foq.subscribe((_, { set }) => set(activeColorEntry, null)),
29+
[]
3130
);
3231

3332
if (!activeColorModalField) return null;

app/packages/core/src/components/Modal/ImaVidLooker.tsx

+7-18
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@ import React, {
1717
import { useErrorHandler } from "react-error-boundary";
1818
import { useRecoilValue, useSetRecoilState } from "recoil";
1919
import { v4 as uuid } from "uuid";
20-
import { useInitializeImaVidSubscriptions, useModalContext } from "./hooks";
20+
import { useClearSelectedLabels, useShowOverlays } from "./ModalLooker";
2121
import {
22-
shortcutToHelpItems,
23-
useClearSelectedLabels,
22+
useInitializeImaVidSubscriptions,
2423
useLookerOptionsUpdate,
25-
useShowOverlays,
26-
} from "./ModalLooker";
24+
useModalContext,
25+
} from "./hooks";
26+
import useKeyEvents from "./use-key-events";
27+
import { shortcutToHelpItems } from "./utils";
2728

2829
interface ImaVidLookerReactProps {
2930
sample: fos.ModalSample;
@@ -132,19 +133,7 @@ export const ImaVidLookerReact = React.memo(
132133

133134
useEventHandler(looker, "clear", useClearSelectedLabels());
134135

135-
const hoveredSample = useRecoilValue(fos.hoveredSample);
136-
137-
useEffect(() => {
138-
const hoveredSampleId = hoveredSample?._id;
139-
looker.updater((state) => ({
140-
...state,
141-
// todo: always setting it to true might not be wise
142-
shouldHandleKeyEvents: true,
143-
options: {
144-
...state.options,
145-
},
146-
}));
147-
}, [hoveredSample, sample, looker]);
136+
useKeyEvents(initialRef, sample._id, looker);
148137

149138
const ref = useRef<HTMLDivElement>(null);
150139
useEffect(() => {
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,11 @@
11
import { useTheme } from "@fiftyone/components";
2-
import { AbstractLooker } from "@fiftyone/looker";
3-
import { BaseState } from "@fiftyone/looker/src/state";
2+
import type { ImageLooker } from "@fiftyone/looker";
43
import * as fos from "@fiftyone/state";
5-
import { useEventHandler, useOnSelectLabel } from "@fiftyone/state";
6-
import React, { useEffect, useMemo, useRef, useState } from "react";
7-
import { useErrorHandler } from "react-error-boundary";
8-
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from "recoil";
9-
import { v4 as uuid } from "uuid";
10-
import { useModalContext } from "./hooks";
4+
import React, { useMemo } from "react";
5+
import { useRecoilCallback, useRecoilValue } from "recoil";
116
import { ImaVidLookerReact } from "./ImaVidLooker";
12-
13-
export const useLookerOptionsUpdate = () => {
14-
return useRecoilCallback(
15-
({ snapshot, set }) =>
16-
async (update: object, updater?: (updated: {}) => void) => {
17-
const currentOptions = await snapshot.getPromise(
18-
fos.savedLookerOptions
19-
);
20-
21-
const panels = await snapshot.getPromise(fos.lookerPanels);
22-
const updated = {
23-
...currentOptions,
24-
...update,
25-
showJSON: panels.json.isOpen,
26-
showHelp: panels.help.isOpen,
27-
};
28-
set(fos.savedLookerOptions, updated);
29-
if (updater) updater(updated);
30-
}
31-
);
32-
};
7+
import { VideoLookerReact } from "./VideoLooker";
8+
import useLooker from "./use-looker";
339

3410
export const useShowOverlays = () => {
3511
return useRecoilCallback(({ set }) => async (event: CustomEvent) => {
@@ -47,137 +23,27 @@ export const useClearSelectedLabels = () => {
4723
};
4824

4925
interface LookerProps {
50-
sample?: fos.ModalSample;
51-
onClick?: React.MouseEventHandler<HTMLDivElement>;
26+
sample: fos.ModalSample;
5227
}
5328

54-
const ModalLookerNoTimeline = React.memo(
55-
({ sample: sampleDataWithExtraParams }: LookerProps) => {
56-
const [id] = useState(() => uuid());
57-
const colorScheme = useRecoilValue(fos.colorScheme);
58-
59-
const { sample } = sampleDataWithExtraParams;
60-
61-
const theme = useTheme();
62-
const initialRef = useRef<boolean>(true);
63-
const lookerOptions = fos.useLookerOptions(true);
64-
const [reset, setReset] = useState(false);
65-
const selectedMediaField = useRecoilValue(fos.selectedMediaField(true));
66-
const setModalLooker = useSetRecoilState(fos.modalLooker);
67-
68-
const createLooker = fos.useCreateLooker(true, false, {
69-
...lookerOptions,
70-
});
71-
72-
const { setActiveLookerRef } = useModalContext();
73-
74-
const looker = React.useMemo(
75-
() => createLooker.current(sampleDataWithExtraParams),
76-
[reset, createLooker, selectedMediaField]
77-
) as AbstractLooker<BaseState>;
78-
79-
useEffect(() => {
80-
setModalLooker(looker);
81-
}, [looker]);
82-
83-
useEffect(() => {
84-
if (looker) {
85-
setActiveLookerRef(looker as fos.Lookers);
86-
}
87-
}, [looker]);
88-
89-
useEffect(() => {
90-
!initialRef.current && looker.updateOptions(lookerOptions);
91-
}, [lookerOptions]);
92-
93-
useEffect(() => {
94-
!initialRef.current && looker.updateSample(sample);
95-
}, [sample, colorScheme]);
96-
97-
useEffect(() => {
98-
return () => looker?.destroy();
99-
}, [looker]);
100-
101-
const handleError = useErrorHandler();
102-
103-
const updateLookerOptions = useLookerOptionsUpdate();
104-
useEventHandler(looker, "options", (e) => updateLookerOptions(e.detail));
105-
useEventHandler(looker, "showOverlays", useShowOverlays());
106-
useEventHandler(looker, "reset", () => {
107-
setReset((c) => !c);
108-
});
109-
110-
const jsonPanel = fos.useJSONPanel();
111-
const helpPanel = fos.useHelpPanel();
112-
113-
useEventHandler(looker, "select", useOnSelectLabel());
114-
useEventHandler(looker, "error", (event) => handleError(event.detail));
115-
useEventHandler(
116-
looker,
117-
"panels",
118-
async ({ detail: { showJSON, showHelp, SHORTCUTS } }) => {
119-
if (showJSON) {
120-
jsonPanel[showJSON](sample);
121-
}
122-
if (showHelp) {
123-
if (showHelp == "close") {
124-
helpPanel.close();
125-
} else {
126-
helpPanel[showHelp](shortcutToHelpItems(SHORTCUTS));
127-
}
128-
}
129-
130-
updateLookerOptions({}, (updatedOptions) =>
131-
looker.updateOptions(updatedOptions)
132-
);
133-
}
134-
);
135-
136-
useEffect(() => {
137-
initialRef.current = false;
138-
}, []);
139-
140-
useEffect(() => {
141-
looker.attach(id);
142-
}, [looker, id]);
143-
144-
useEventHandler(looker, "clear", useClearSelectedLabels());
145-
146-
const hoveredSample = useRecoilValue(fos.hoveredSample);
147-
148-
useEffect(() => {
149-
const hoveredSampleId = hoveredSample?._id;
150-
looker.updater((state) => ({
151-
...state,
152-
shouldHandleKeyEvents: hoveredSampleId === sample._id,
153-
options: {
154-
...state.options,
155-
},
156-
}));
157-
}, [hoveredSample, sample, looker]);
158-
159-
const ref = useRef<HTMLDivElement>(null);
160-
useEffect(() => {
161-
ref.current?.dispatchEvent(
162-
new CustomEvent(`looker-attached`, { bubbles: true })
163-
);
164-
}, [ref]);
165-
166-
return (
167-
<div
168-
ref={ref}
169-
id={id}
170-
data-cy="modal-looker-container"
171-
style={{
172-
width: "100%",
173-
height: "100%",
174-
background: theme.background.level2,
175-
position: "relative",
176-
}}
177-
/>
178-
);
179-
}
180-
);
29+
const ModalLookerNoTimeline = React.memo((props: LookerProps) => {
30+
const { id, ref } = useLooker<ImageLooker>(props);
31+
const theme = useTheme();
32+
33+
return (
34+
<div
35+
ref={ref}
36+
id={id}
37+
data-cy="modal-looker-container"
38+
style={{
39+
width: "100%",
40+
height: "100%",
41+
background: theme.background.level2,
42+
position: "relative",
43+
}}
44+
/>
45+
);
46+
});
18147

18248
export const ModalLooker = React.memo(
18349
({ sample: propsSampleData }: LookerProps) => {
@@ -197,21 +63,16 @@ export const ModalLooker = React.memo(
19763
const shouldRenderImavid = useRecoilValue(
19864
fos.shouldRenderImaVidLooker(true)
19965
);
66+
const video = useRecoilValue(fos.isVideoDataset);
20067

20168
if (shouldRenderImavid) {
20269
return <ImaVidLookerReact sample={sample} />;
20370
}
20471

72+
if (video) {
73+
return <VideoLookerReact sample={sample} />;
74+
}
75+
20576
return <ModalLookerNoTimeline sample={sample} />;
20677
}
20778
);
208-
209-
export function shortcutToHelpItems(SHORTCUTS) {
210-
return Object.values(
211-
Object.values(SHORTCUTS).reduce((acc, v) => {
212-
acc[v.shortcut] = v;
213-
214-
return acc;
215-
}, {})
216-
);
217-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { useTheme } from "@fiftyone/components";
2+
import type { VideoLooker } from "@fiftyone/looker";
3+
import { getFrameNumber } from "@fiftyone/looker";
4+
import {
5+
useCreateTimeline,
6+
useDefaultTimelineNameImperative,
7+
useTimeline,
8+
} from "@fiftyone/playback";
9+
import * as fos from "@fiftyone/state";
10+
import React, { useEffect, useMemo, useState } from "react";
11+
import useLooker from "./use-looker";
12+
13+
interface VideoLookerReactProps {
14+
sample: fos.ModalSample;
15+
}
16+
17+
export const VideoLookerReact = (props: VideoLookerReactProps) => {
18+
const theme = useTheme();
19+
const { id, looker, sample } = useLooker<VideoLooker>(props);
20+
const [totalFrames, setTotalFrames] = useState<number>();
21+
const frameRate = useMemo(() => {
22+
return sample.frameRate;
23+
}, [sample]);
24+
25+
useEffect(() => {
26+
const load = () => {
27+
const duration = looker.getVideo().duration;
28+
setTotalFrames(getFrameNumber(duration, duration, frameRate));
29+
looker.removeEventListener("load", load);
30+
};
31+
looker.addEventListener("load", load);
32+
}, [frameRate, looker]);
33+
34+
return (
35+
<>
36+
<div
37+
id={id}
38+
data-cy="modal-looker-container"
39+
style={{
40+
width: "100%",
41+
height: "100%",
42+
background: theme.background.level2,
43+
position: "relative",
44+
}}
45+
/>
46+
{totalFrames !== undefined && (
47+
<TimelineController looker={looker} totalFrames={totalFrames} />
48+
)}
49+
</>
50+
);
51+
};
52+
53+
const TimelineController = ({
54+
looker,
55+
totalFrames,
56+
}: {
57+
looker: VideoLooker;
58+
totalFrames: number;
59+
}) => {
60+
const { getName } = useDefaultTimelineNameImperative();
61+
const timelineName = React.useMemo(() => getName(), [getName]);
62+
63+
useCreateTimeline({
64+
name: timelineName,
65+
config: totalFrames
66+
? {
67+
totalFrames,
68+
loop: true,
69+
}
70+
: undefined,
71+
optOutOfAnimation: true,
72+
});
73+
74+
const { pause, play } = useTimeline(timelineName);
75+
76+
fos.useEventHandler(looker, "pause", pause);
77+
fos.useEventHandler(looker, "play", play);
78+
79+
return null;
80+
};

0 commit comments

Comments
 (0)