forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscreenFrames.ts
More file actions
99 lines (91 loc) · 3.5 KB
/
Copy pathscreenFrames.ts
File metadata and controls
99 lines (91 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* Pure helpers for the meeting-note screenshot carousel/lightbox.
*
* Deliberately dumb: banner promotion (which surviving frame becomes the
* banner after a delete) is a server-side decision — the client-facing
* `ConversationScreenFrame` doesn't even carry the `banner_suitability` score
* the server ranks on (contract §7.3), so this module never guesses a
* promotion. Every mutation hook call returns a fresh, authoritative
* `ConversationScreenFrameSet` from the server; these helpers only work out
* how the *local UI* (lightbox index) should react to a strip that just
* changed length.
*/
import type {
ConversationScreenFrame,
ConversationScreenFrameSet,
} from '@/types/conversation';
/**
* Steps a lightbox index by one frame, wrapping around both ends of the strip.
* Returns 0 for an empty strip (caller should not be showing a lightbox then).
*/
export function stepFrameIndex(
currentIndex: number,
length: number,
delta: 1 | -1,
): number {
if (length <= 0) return 0;
return (currentIndex + delta + length) % length;
}
/**
* After the strip shrinks (a frame was deleted), works out which index the
* lightbox should now show. Returns null when the strip is now empty — the
* caller should close the lightbox rather than show a blank one.
*
* Clamping (not re-finding a specific id) is intentional: the frame that
* slid into the deleted position is the natural "next" thing to look at, and
* if the deleted frame was last, this lands on the new last frame.
*/
export function resolveIndexAfterRemoval(
currentIndex: number,
newLength: number,
): number | null {
if (newLength <= 0) return null;
return Math.min(currentIndex, newLength - 1);
}
/** Locates a frame's index within the strip by id, or -1 if it's not there. */
export function findFrameIndex(
strip: ConversationScreenFrame[],
frameId: string,
): number {
return strip.findIndex((frame) => frame.id === frameId);
}
/** The lightbox index the banner always occupies when present. */
export const BANNER_LIGHTBOX_INDEX = 0;
/**
* The lightbox's fixed navigation order: the banner first (when present),
* then the strip in rank order. The banner is the largest image on the page
* and is clickable like any strip thumbnail, so it belongs in the same
* step-through sequence rather than a disjoint one — deleting it goes
* through the same server-decided-promotion path as deleting a strip frame
* (contract §7.3), and the client only ever re-reads what the server
* returns.
*/
export function buildLightboxFrames(
set: ConversationScreenFrameSet | null | undefined,
): ConversationScreenFrame[] {
if (!set) return [];
// `strip` is optional in the generated schema (absent means empty).
const strip = set.strip ?? [];
return set.banner ? [set.banner, ...strip] : strip;
}
/**
* Maps a strip-relative index (what `ConversationScreenFrameCarousel`
* reports on click) to its index in the combined banner+strip lightbox
* order — offset by one when a banner occupies slot 0.
*/
export function stripFrameLightboxIndex(
set: ConversationScreenFrameSet | null | undefined,
stripIndex: number,
): number {
return (set?.banner ? 1 : 0) + stripIndex;
}
/**
* True when there is nothing to render at all: no banner and an empty strip.
* Both the banner and carousel must render nothing (no empty state) in this
* case — see contract §9.
*/
export function isFrameSetEmpty(
set: ConversationScreenFrameSet | null | undefined,
): boolean {
return !set || (!set.banner && (set.strip ?? []).length === 0);
}