@unityevolv/ofiskit-ui-map 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +34 -0
- package/dist/Announcer.d.ts +21 -0
- package/dist/Announcer.d.ts.map +1 -0
- package/dist/Announcer.js +32 -0
- package/dist/Announcer.js.map +1 -0
- package/dist/CallAudio.d.ts +32 -0
- package/dist/CallAudio.d.ts.map +1 -0
- package/dist/CallAudio.js +72 -0
- package/dist/CallAudio.js.map +1 -0
- package/dist/CallControls.d.ts +60 -0
- package/dist/CallControls.d.ts.map +1 -0
- package/dist/CallControls.js +62 -0
- package/dist/CallControls.js.map +1 -0
- package/dist/CallTiles.d.ts +48 -0
- package/dist/CallTiles.d.ts.map +1 -0
- package/dist/CallTiles.js +134 -0
- package/dist/CallTiles.js.map +1 -0
- package/dist/DevicePanel.d.ts +32 -0
- package/dist/DevicePanel.d.ts.map +1 -0
- package/dist/DevicePanel.js +133 -0
- package/dist/DevicePanel.js.map +1 -0
- package/dist/Knocks.d.ts +46 -0
- package/dist/Knocks.d.ts.map +1 -0
- package/dist/Knocks.js +46 -0
- package/dist/Knocks.js.map +1 -0
- package/dist/OfficeMap.d.ts +69 -0
- package/dist/OfficeMap.d.ts.map +1 -0
- package/dist/OfficeMap.js +222 -0
- package/dist/OfficeMap.js.map +1 -0
- package/dist/Overflow.d.ts +41 -0
- package/dist/Overflow.d.ts.map +1 -0
- package/dist/Overflow.js +82 -0
- package/dist/Overflow.js.map +1 -0
- package/dist/PersonAvatar.d.ts +32 -0
- package/dist/PersonAvatar.d.ts.map +1 -0
- package/dist/PersonAvatar.js +80 -0
- package/dist/PersonAvatar.js.map +1 -0
- package/dist/Reactions.d.ts +47 -0
- package/dist/Reactions.d.ts.map +1 -0
- package/dist/Reactions.js +61 -0
- package/dist/Reactions.js.map +1 -0
- package/dist/RoomBar.d.ts +43 -0
- package/dist/RoomBar.d.ts.map +1 -0
- package/dist/RoomBar.js +120 -0
- package/dist/RoomBar.js.map +1 -0
- package/dist/Share.d.ts +74 -0
- package/dist/Share.d.ts.map +1 -0
- package/dist/Share.js +115 -0
- package/dist/Share.js.map +1 -0
- package/dist/StatusControl.d.ts +25 -0
- package/dist/StatusControl.d.ts.map +1 -0
- package/dist/StatusControl.js +77 -0
- package/dist/StatusControl.js.map +1 -0
- package/dist/controls.d.ts +32 -0
- package/dist/controls.d.ts.map +1 -0
- package/dist/controls.js +24 -0
- package/dist/controls.js.map +1 -0
- package/dist/hooks.d.ts +60 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +182 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +65 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +93 -0
- package/dist/layout.js.map +1 -0
- package/dist/placement.d.ts +70 -0
- package/dist/placement.d.ts.map +1 -0
- package/dist/placement.js +83 -0
- package/dist/placement.js.map +1 -0
- package/dist/sounds.d.ts +30 -0
- package/dist/sounds.d.ts.map +1 -0
- package/dist/sounds.js +125 -0
- package/dist/sounds.js.map +1 -0
- package/dist/status.d.ts +47 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +65 -0
- package/dist/status.js.map +1 -0
- package/dist/theme.d.ts +27 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +57 -0
- package/dist/theme.js.map +1 -0
- package/dist/tileLayout.d.ts +27 -0
- package/dist/tileLayout.d.ts.map +1 -0
- package/dist/tileLayout.js +41 -0
- package/dist/tileLayout.js.map +1 -0
- package/dist/useCall.d.ts +107 -0
- package/dist/useCall.d.ts.map +1 -0
- package/dist/useCall.js +238 -0
- package/dist/useCall.js.map +1 -0
- package/dist/useShare.d.ts +54 -0
- package/dist/useShare.d.ts.map +1 -0
- package/dist/useShare.js +114 -0
- package/dist/useShare.js.map +1 -0
- package/dist/useSounds.d.ts +31 -0
- package/dist/useSounds.d.ts.map +1 -0
- package/dist/useSounds.js +56 -0
- package/dist/useSounds.js.map +1 -0
- package/package.json +25 -0
package/dist/status.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
const RING = _jsx("circle", { cx: "6", cy: "6", r: "4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.75" });
|
|
3
|
+
export const STATUS_LOOKS = {
|
|
4
|
+
available: {
|
|
5
|
+
label: 'Available',
|
|
6
|
+
tone: 'text-success',
|
|
7
|
+
// Solid: the most "present" shape there is.
|
|
8
|
+
shape: _jsx("circle", { cx: "6", cy: "6", r: "5", fill: "currentColor" }),
|
|
9
|
+
},
|
|
10
|
+
in_call: {
|
|
11
|
+
label: 'In a call',
|
|
12
|
+
tone: 'text-primary',
|
|
13
|
+
// A ring around a core, which reads as something happening inside.
|
|
14
|
+
shape: (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "6", cy: "6", r: "5", fill: "currentColor" }), _jsx("circle", { cx: "6", cy: "6", r: "1.75", fill: "var(--color-base-100, #fff)" })] })),
|
|
15
|
+
},
|
|
16
|
+
in_meeting: {
|
|
17
|
+
label: 'In a meeting',
|
|
18
|
+
tone: 'text-primary',
|
|
19
|
+
// Square, because the meeting is elsewhere and this is deliberately not
|
|
20
|
+
// the same thing as being in a call here.
|
|
21
|
+
shape: _jsx("rect", { x: "1.5", y: "1.5", width: "9", height: "9", rx: "1.5", fill: "currentColor" }),
|
|
22
|
+
},
|
|
23
|
+
away: {
|
|
24
|
+
label: 'Away',
|
|
25
|
+
tone: 'text-warning',
|
|
26
|
+
// A clock, which is what away actually means: it has been a while.
|
|
27
|
+
shape: (_jsxs(_Fragment, { children: [RING, _jsx("path", { d: "M6 3.5V6l1.75 1.25", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })] })),
|
|
28
|
+
},
|
|
29
|
+
dnd: {
|
|
30
|
+
label: 'Do not disturb',
|
|
31
|
+
tone: 'text-error',
|
|
32
|
+
shape: (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "6", cy: "6", r: "5", fill: "currentColor" }), _jsx("rect", { x: "2.75", y: "5.1", width: "6.5", height: "1.8", rx: "0.9", fill: "var(--color-base-100, #fff)" })] })),
|
|
33
|
+
},
|
|
34
|
+
reconnecting: {
|
|
35
|
+
label: 'Reconnecting',
|
|
36
|
+
tone: 'text-warning',
|
|
37
|
+
// Broken ring: present, but not all there.
|
|
38
|
+
shape: (_jsx("circle", { cx: "6", cy: "6", r: "4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeDasharray: "2.2 2", strokeLinecap: "round" })),
|
|
39
|
+
},
|
|
40
|
+
offline: {
|
|
41
|
+
label: 'Offline',
|
|
42
|
+
tone: 'text-base-content/40',
|
|
43
|
+
shape: RING,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
export function StatusDot({ status, size = 12, labelled = true, className }) {
|
|
47
|
+
const look = STATUS_LOOKS[status];
|
|
48
|
+
return (_jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, className: [look.tone, 'shrink-0', className].filter(Boolean).join(' '), role: labelled ? 'img' : 'presentation', "aria-label": labelled ? look.label : undefined, "aria-hidden": labelled ? undefined : true, focusable: "false", children: look.shape }));
|
|
49
|
+
}
|
|
50
|
+
export function statusLabel(status) {
|
|
51
|
+
return STATUS_LOOKS[status].label;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Status and custom status as one sentence.
|
|
55
|
+
*
|
|
56
|
+
* What a screen reader hears next to a name, and what a tooltip shows. Built in
|
|
57
|
+
* one place so the map, the list and a tile never word it differently.
|
|
58
|
+
*/
|
|
59
|
+
export function describeStatus(status, custom) {
|
|
60
|
+
const base = statusLabel(status);
|
|
61
|
+
if (!custom?.text)
|
|
62
|
+
return base;
|
|
63
|
+
return `${base} — ${custom.emoji ? `${custom.emoji} ` : ''}${custom.text}`;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.tsx"],"names":[],"mappings":";AAwBA,MAAM,IAAI,GAAG,iBAAQ,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,GAAG,EAAC,CAAC,EAAC,KAAK,EAAC,IAAI,EAAC,MAAM,EAAC,MAAM,EAAC,cAAc,EAAC,WAAW,EAAC,MAAM,GAAG,CAAA;AAElG,MAAM,CAAC,MAAM,YAAY,GAA+B;IACtD,SAAS,EAAE;QACT,KAAK,EAAE,WAAW;QAClB,IAAI,EAAE,cAAc;QACpB,4CAA4C;QAC5C,KAAK,EAAE,iBAAQ,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,GAAG,EAAC,CAAC,EAAC,GAAG,EAAC,IAAI,EAAC,cAAc,GAAG;KAC1D;IACD,OAAO,EAAE;QACP,KAAK,EAAE,WAAW;QAClB,IAAI,EAAE,cAAc;QACpB,mEAAmE;QACnE,KAAK,EAAE,CACL,8BACE,iBAAQ,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,GAAG,EAAC,CAAC,EAAC,GAAG,EAAC,IAAI,EAAC,cAAc,GAAG,EAClD,iBAAQ,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,GAAG,EAAC,CAAC,EAAC,MAAM,EAAC,IAAI,EAAC,6BAA6B,GAAG,IACnE,CACJ;KACF;IACD,UAAU,EAAE;QACV,KAAK,EAAE,cAAc;QACrB,IAAI,EAAE,cAAc;QACpB,wEAAwE;QACxE,0CAA0C;QAC1C,KAAK,EAAE,eAAM,CAAC,EAAC,KAAK,EAAC,CAAC,EAAC,KAAK,EAAC,KAAK,EAAC,GAAG,EAAC,MAAM,EAAC,GAAG,EAAC,EAAE,EAAC,KAAK,EAAC,IAAI,EAAC,cAAc,GAAG;KAClF;IACD,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM;QACb,IAAI,EAAE,cAAc;QACpB,mEAAmE;QACnE,KAAK,EAAE,CACL,8BACG,IAAI,EACL,eACE,CAAC,EAAC,oBAAoB,EACtB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,KAAK,EACjB,aAAa,EAAC,OAAO,GACrB,IACD,CACJ;KACF;IACD,GAAG,EAAE;QACH,KAAK,EAAE,gBAAgB;QACvB,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,CACL,8BACE,iBAAQ,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,GAAG,EAAC,CAAC,EAAC,GAAG,EAAC,IAAI,EAAC,cAAc,GAAG,EAClD,eACE,CAAC,EAAC,MAAM,EACR,CAAC,EAAC,KAAK,EACP,KAAK,EAAC,KAAK,EACX,MAAM,EAAC,KAAK,EACZ,EAAE,EAAC,KAAK,EACR,IAAI,EAAC,6BAA6B,GAClC,IACD,CACJ;KACF;IACD,YAAY,EAAE;QACZ,KAAK,EAAE,cAAc;QACrB,IAAI,EAAE,cAAc;QACpB,2CAA2C;QAC3C,KAAK,EAAE,CACL,iBACE,EAAE,EAAC,GAAG,EACN,EAAE,EAAC,GAAG,EACN,CAAC,EAAC,KAAK,EACP,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,MAAM,EAClB,eAAe,EAAC,OAAO,EACvB,aAAa,EAAC,OAAO,GACrB,CACH;KACF;IACD,OAAO,EAAE;QACP,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,sBAAsB;QAC5B,KAAK,EAAE,IAAI;KACZ;CACF,CAAA;AAeD,MAAM,UAAU,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,QAAQ,GAAG,IAAI,EAAE,SAAS,EAAkB;IACzF,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;IACjC,OAAO,CACL,cACE,OAAO,EAAC,WAAW,EACnB,KAAK,EAAE,IAAI,EACX,MAAM,EAAE,IAAI,EACZ,SAAS,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EACvE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,cAAc,gBAC3B,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,iBAChC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EACxC,SAAS,EAAC,OAAO,YAEhB,IAAI,CAAC,KAAK,GACP,CACP,CAAA;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC,KAAK,CAAA;AACnC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,MAAyC;IACtF,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;IAChC,IAAI,CAAC,MAAM,EAAE,IAAI;QAAE,OAAO,IAAI,CAAA;IAC9B,OAAO,GAAG,IAAI,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,CAAA;AAC5E,CAAC"}
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Light and dark, per person.
|
|
3
|
+
*
|
|
4
|
+
* Theme is a per-user setting rather than a per-office one, which has a visible
|
|
5
|
+
* consequence: two people standing in the same room may be looking at different
|
|
6
|
+
* background images over identical geometry. That is deliberate, and it is why
|
|
7
|
+
* the template carries two image slots against one set of room coordinates.
|
|
8
|
+
*
|
|
9
|
+
* Everything else follows from `data-theme` on the document element, because
|
|
10
|
+
* every colour in the product comes from a unitykit token and the tokens are
|
|
11
|
+
* defined per theme. No component here chooses a colour.
|
|
12
|
+
*/
|
|
13
|
+
export type Theme = 'light' | 'dark';
|
|
14
|
+
export type ThemeChoice = Theme | 'system';
|
|
15
|
+
interface ThemeValue {
|
|
16
|
+
/** What is actually being shown right now. */
|
|
17
|
+
theme: Theme;
|
|
18
|
+
/** What the person chose, which may be to follow the system. */
|
|
19
|
+
choice: ThemeChoice;
|
|
20
|
+
setChoice(choice: ThemeChoice): void;
|
|
21
|
+
}
|
|
22
|
+
export declare function ThemeProvider({ children }: {
|
|
23
|
+
children: React.ReactNode;
|
|
24
|
+
}): import("react").JSX.Element;
|
|
25
|
+
export declare function useTheme(): ThemeValue;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=theme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,KAAK,GAAG,OAAO,GAAG,MAAM,CAAA;AACpC,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,QAAQ,CAAA;AAE1C,UAAU,UAAU;IAClB,8CAA8C;IAC9C,KAAK,EAAE,KAAK,CAAA;IACZ,gEAAgE;IAChE,MAAM,EAAE,WAAW,CAAA;IACnB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;CACrC;AAgBD,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,+BAoCxE;AAED,wBAAgB,QAAQ,IAAI,UAAU,CAQrC"}
|
package/dist/theme.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
const ThemeContext = createContext(null);
|
|
4
|
+
const STORAGE_KEY = 'ofiskit:theme';
|
|
5
|
+
/** Reading storage throws in some privacy modes, and that must not break the app. */
|
|
6
|
+
function readChoice() {
|
|
7
|
+
try {
|
|
8
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
9
|
+
return stored === 'light' || stored === 'dark' ? stored : 'system';
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return 'system';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function ThemeProvider({ children }) {
|
|
16
|
+
const [choice, setChoiceState] = useState(readChoice);
|
|
17
|
+
const [system, setSystem] = useState(() => globalThis.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const query = globalThis.matchMedia?.('(prefers-color-scheme: dark)');
|
|
20
|
+
if (!query)
|
|
21
|
+
return;
|
|
22
|
+
const onChange = (event) => setSystem(event.matches ? 'dark' : 'light');
|
|
23
|
+
query.addEventListener('change', onChange);
|
|
24
|
+
return () => query.removeEventListener('change', onChange);
|
|
25
|
+
}, []);
|
|
26
|
+
const theme = choice === 'system' ? system : choice;
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
29
|
+
// Tells the browser to draw form controls, scrollbars and the address bar
|
|
30
|
+
// to match, which is the difference between a dark page and a dark app.
|
|
31
|
+
document.documentElement.style.colorScheme = theme;
|
|
32
|
+
}, [theme]);
|
|
33
|
+
const setChoice = useCallback((next) => {
|
|
34
|
+
setChoiceState(next);
|
|
35
|
+
try {
|
|
36
|
+
if (next === 'system')
|
|
37
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
38
|
+
else
|
|
39
|
+
localStorage.setItem(STORAGE_KEY, next);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// A remembered preference is a convenience. Losing it is survivable.
|
|
43
|
+
}
|
|
44
|
+
}, []);
|
|
45
|
+
const value = useMemo(() => ({ theme, choice, setChoice }), [theme, choice, setChoice]);
|
|
46
|
+
return _jsx(ThemeContext.Provider, { value: value, children: children });
|
|
47
|
+
}
|
|
48
|
+
export function useTheme() {
|
|
49
|
+
const value = useContext(ThemeContext);
|
|
50
|
+
if (!value) {
|
|
51
|
+
// A sensible answer rather than a crash: a component rendered outside the
|
|
52
|
+
// provider should still draw, in light mode, rather than take the page down.
|
|
53
|
+
return { theme: 'light', choice: 'system', setChoice: () => { } };
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How to lay a call's tiles out in the space it has.
|
|
3
|
+
*
|
|
4
|
+
* The way every video product fills a screen: try every number of columns, work out
|
|
5
|
+
* how big a tile of the video's own shape could be for each, and keep the biggest.
|
|
6
|
+
* Four people on a desktop come out as a two-by-two filling the window; four on a
|
|
7
|
+
* phone held upright come out as a column, because four landscape pictures are
|
|
8
|
+
* bigger stacked than squeezed side by side.
|
|
9
|
+
*
|
|
10
|
+
* The tile is the video's shape, so nothing has to be cropped to fill it. That is
|
|
11
|
+
* the difference from dividing the space into equal cells and cropping each
|
|
12
|
+
* picture to its cell, which fills every pixel and cuts off whoever is at the edge
|
|
13
|
+
* of the frame.
|
|
14
|
+
*
|
|
15
|
+
* Pure, so the arithmetic is tested without a screen.
|
|
16
|
+
*/
|
|
17
|
+
/** A camera's shape. Most are 16:9, and a tile of any other shape crops or letterboxes. */
|
|
18
|
+
export declare const VIDEO_ASPECT: number;
|
|
19
|
+
export interface TileLayout {
|
|
20
|
+
columns: number;
|
|
21
|
+
rows: number;
|
|
22
|
+
/** Whole pixels, rounded down, so the row always fits in the width it was given. */
|
|
23
|
+
tileWidth: number;
|
|
24
|
+
tileHeight: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function tileLayout(count: number, width: number, height: number, gap?: number, aspect?: number): TileLayout;
|
|
27
|
+
//# sourceMappingURL=tileLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tileLayout.d.ts","sourceRoot":"","sources":["../src/tileLayout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,2FAA2F;AAC3F,eAAO,MAAM,YAAY,QAAS,CAAA;AAElC,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,oFAAoF;IACpF,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,GAAG,SAAI,EACP,MAAM,SAAe,GACpB,UAAU,CAyBZ"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How to lay a call's tiles out in the space it has.
|
|
3
|
+
*
|
|
4
|
+
* The way every video product fills a screen: try every number of columns, work out
|
|
5
|
+
* how big a tile of the video's own shape could be for each, and keep the biggest.
|
|
6
|
+
* Four people on a desktop come out as a two-by-two filling the window; four on a
|
|
7
|
+
* phone held upright come out as a column, because four landscape pictures are
|
|
8
|
+
* bigger stacked than squeezed side by side.
|
|
9
|
+
*
|
|
10
|
+
* The tile is the video's shape, so nothing has to be cropped to fill it. That is
|
|
11
|
+
* the difference from dividing the space into equal cells and cropping each
|
|
12
|
+
* picture to its cell, which fills every pixel and cuts off whoever is at the edge
|
|
13
|
+
* of the frame.
|
|
14
|
+
*
|
|
15
|
+
* Pure, so the arithmetic is tested without a screen.
|
|
16
|
+
*/
|
|
17
|
+
/** A camera's shape. Most are 16:9, and a tile of any other shape crops or letterboxes. */
|
|
18
|
+
export const VIDEO_ASPECT = 16 / 9;
|
|
19
|
+
export function tileLayout(count, width, height, gap = 8, aspect = VIDEO_ASPECT) {
|
|
20
|
+
if (count <= 0 || width <= 0 || height <= 0) {
|
|
21
|
+
return { columns: 1, rows: Math.max(1, count), tileWidth: 0, tileHeight: 0 };
|
|
22
|
+
}
|
|
23
|
+
let best = { columns: 1, rows: count, tileWidth: 0, tileHeight: 0 };
|
|
24
|
+
for (let columns = 1; columns <= count; columns += 1) {
|
|
25
|
+
const rows = Math.ceil(count / columns);
|
|
26
|
+
// As wide as the columns allow, unless the rows make it too tall for the space.
|
|
27
|
+
const byWidth = (width - gap * (columns - 1)) / columns;
|
|
28
|
+
const byHeight = ((height - gap * (rows - 1)) / rows) * aspect;
|
|
29
|
+
const tileWidth = Math.min(byWidth, byHeight);
|
|
30
|
+
if (tileWidth > best.tileWidth) {
|
|
31
|
+
best = { columns, rows, tileWidth, tileHeight: tileWidth / aspect };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
columns: best.columns,
|
|
36
|
+
rows: best.rows,
|
|
37
|
+
tileWidth: Math.max(0, Math.floor(best.tileWidth)),
|
|
38
|
+
tileHeight: Math.max(0, Math.floor(best.tileHeight)),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=tileLayout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tileLayout.js","sourceRoot":"","sources":["../src/tileLayout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,2FAA2F;AAC3F,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,GAAG,CAAC,CAAA;AAUlC,MAAM,UAAU,UAAU,CACxB,KAAa,EACb,KAAa,EACb,MAAc,EACd,GAAG,GAAG,CAAC,EACP,MAAM,GAAG,YAAY;IAErB,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QAC5C,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAA;IAC9E,CAAC;IAED,IAAI,IAAI,GAAe,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAA;IAE/E,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,KAAK,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,CAAA;QACvC,gFAAgF;QAChF,MAAM,OAAO,GAAG,CAAC,KAAK,GAAG,GAAG,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,CAAA;QACvD,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,MAAM,CAAA;QAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;QAE7C,IAAI,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YAC/B,IAAI,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,GAAG,MAAM,EAAE,CAAA;QACrE,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAClD,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;KACrD,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { OfisClient, PublicPresence } from '@unityevolv/ofiskit-realtime-client';
|
|
2
|
+
/**
|
|
3
|
+
* Everything the call UI needs, gathered from the adapter's events.
|
|
4
|
+
*
|
|
5
|
+
* The tiles, the controls and the indicators all read from here, and none of them
|
|
6
|
+
* knows which provider is behind it. That is the point of the adapter emitting one
|
|
7
|
+
* shape: swapping the built-in mesh for somebody's SFU changes nothing in this
|
|
8
|
+
* file or in anything that uses it.
|
|
9
|
+
*/
|
|
10
|
+
export interface PeerMedia {
|
|
11
|
+
camera?: MediaStream;
|
|
12
|
+
screen?: MediaStream;
|
|
13
|
+
audio?: MediaStream;
|
|
14
|
+
}
|
|
15
|
+
export interface PeerQuality {
|
|
16
|
+
relayed: boolean;
|
|
17
|
+
packetLoss: number;
|
|
18
|
+
roundTripMs: number;
|
|
19
|
+
}
|
|
20
|
+
export interface CallMedia {
|
|
21
|
+
/** Remote streams, by device. */
|
|
22
|
+
peers: Map<string, PeerMedia>;
|
|
23
|
+
/** Your own camera and share, for your own tile and the sharing indicator. */
|
|
24
|
+
local: {
|
|
25
|
+
camera: MediaStream | null;
|
|
26
|
+
screen: MediaStream | null;
|
|
27
|
+
};
|
|
28
|
+
quality: Map<string, PeerQuality>;
|
|
29
|
+
/** Peers you are muting for yourself only. Nobody else is affected. */
|
|
30
|
+
mutedForMe: Set<string>;
|
|
31
|
+
/** Problems worth telling somebody about, newest last. */
|
|
32
|
+
problems: Array<{
|
|
33
|
+
id: number;
|
|
34
|
+
deviceId?: string;
|
|
35
|
+
message: string;
|
|
36
|
+
}>;
|
|
37
|
+
/** Set when your own video was reduced or dropped, so nobody wonders why. */
|
|
38
|
+
degraded: string | null;
|
|
39
|
+
}
|
|
40
|
+
export declare function useCallMedia(client: OfisClient): CallMedia & {
|
|
41
|
+
muteForMe(deviceId: string, muted: boolean): void;
|
|
42
|
+
dismiss(id: number): void;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Who should be on screen, and in what order.
|
|
46
|
+
*
|
|
47
|
+
* **Raised hands first, in the order they went up**, then the most recent speaker,
|
|
48
|
+
* then everybody else. Both halves matter for the same reason: only five tiles are
|
|
49
|
+
* visible, so being last in this list means being paged off screen — and somebody
|
|
50
|
+
* who has asked to speak is the last person who should disappear. Whoever asked
|
|
51
|
+
* first is listed first, because the queue is the whole value of a raised hand.
|
|
52
|
+
*
|
|
53
|
+
* The ordering is a **pure function of the office state**. `lastSpokeAt` and
|
|
54
|
+
* `handRaisedAt` are both stamped by the server, so everybody in the call sees the
|
|
55
|
+
* same five faces in the same order — a client that joined a minute ago has no
|
|
56
|
+
* less idea of who spoke recently, or who asked first, than one that has been
|
|
57
|
+
* listening throughout.
|
|
58
|
+
*/
|
|
59
|
+
export declare function speakerOrder(participants: ReadonlyArray<{
|
|
60
|
+
userId: string;
|
|
61
|
+
deviceId: string;
|
|
62
|
+
}>, people: ReadonlyMap<string, PublicPresence>): string[];
|
|
63
|
+
/**
|
|
64
|
+
* How long a reaction floats before it is gone.
|
|
65
|
+
*
|
|
66
|
+
* Long enough to be seen across a room full of tiles, short enough that two in a
|
|
67
|
+
* row do not stack into a wall. Nothing is stored anywhere: when this elapses the
|
|
68
|
+
* reaction has not been archived, it has stopped existing.
|
|
69
|
+
*/
|
|
70
|
+
export declare const REACTION_VISIBLE_MS = 4000;
|
|
71
|
+
/** One reaction, in the air. */
|
|
72
|
+
export interface LiveReaction {
|
|
73
|
+
/** Local, and only so React can key two identical emoji apart. */
|
|
74
|
+
id: number;
|
|
75
|
+
userId: string;
|
|
76
|
+
deviceId: string;
|
|
77
|
+
reaction: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The reactions in the air right now.
|
|
81
|
+
*
|
|
82
|
+
* Held here and nowhere else — not in the office state, not on the server. A
|
|
83
|
+
* reaction is an event, and treating it as state is how a product ends up with a
|
|
84
|
+
* screen full of yesterday's applause.
|
|
85
|
+
*
|
|
86
|
+
* Grouped two ways because it is drawn in two places: over a tile, which is a
|
|
87
|
+
* device, and over an avatar on the map, which is a person.
|
|
88
|
+
*/
|
|
89
|
+
export declare function useReactions(client: OfisClient, visibleMs?: number): {
|
|
90
|
+
byDevice: ReadonlyMap<string, LiveReaction[]>;
|
|
91
|
+
byUser: ReadonlyMap<string, LiveReaction[]>;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* The order, held still for a moment after it changes.
|
|
95
|
+
*
|
|
96
|
+
* Two people trading short sentences would otherwise swap tiles constantly, which
|
|
97
|
+
* is far more distracting than a tile being a beat out of date.
|
|
98
|
+
*
|
|
99
|
+
* The hold applies **only to re-ordering**: somebody joining or leaving is applied
|
|
100
|
+
* at once, because a tile that is not there yet is worse than one in the wrong
|
|
101
|
+
* place.
|
|
102
|
+
*/
|
|
103
|
+
export declare function useSpeakerOrder(participants: ReadonlyArray<{
|
|
104
|
+
userId: string;
|
|
105
|
+
deviceId: string;
|
|
106
|
+
}>, people: ReadonlyMap<string, PublicPresence>, holdMs?: number): string[];
|
|
107
|
+
//# sourceMappingURL=useCall.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCall.d.ts","sourceRoot":"","sources":["../src/useCall.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAY,MAAM,qCAAqC,CAAA;AAK/F;;;;;;;GAOG;AAEH,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,KAAK,CAAC,EAAE,WAAW,CAAA;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,iCAAiC;IACjC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAC7B,8EAA8E;IAC9E,KAAK,EAAE;QAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;KAAE,CAAA;IACjE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACjC,uEAAuE;IACvE,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACnE,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAkGD,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG;IAC5D,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;IACjD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B,CAoBA;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAC1B,YAAY,EAAE,aAAa,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EACjE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,GAC1C,MAAM,EAAE,CA6BV;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,OAAQ,CAAA;AAExC,gCAAgC;AAChC,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAiBD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,UAAU,EAClB,SAAS,GAAE,MAA4B,GACtC;IAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAA;CAAE,CA8ChG;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,aAAa,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EACjE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,EAC3C,MAAM,SAAO,GACZ,MAAM,EAAE,CAyBV"}
|
package/dist/useCall.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
|
|
2
|
+
import { useClientEvents } from './hooks.js';
|
|
3
|
+
const EMPTY = {
|
|
4
|
+
peers: new Map(),
|
|
5
|
+
local: { camera: null, screen: null },
|
|
6
|
+
quality: new Map(),
|
|
7
|
+
mutedForMe: new Set(),
|
|
8
|
+
problems: [],
|
|
9
|
+
degraded: null,
|
|
10
|
+
};
|
|
11
|
+
let problemId = 0;
|
|
12
|
+
function reduce(state, action) {
|
|
13
|
+
switch (action.kind) {
|
|
14
|
+
case 'mute-for-me': {
|
|
15
|
+
const mutedForMe = new Set(state.mutedForMe);
|
|
16
|
+
if (action.muted)
|
|
17
|
+
mutedForMe.add(action.deviceId);
|
|
18
|
+
else
|
|
19
|
+
mutedForMe.delete(action.deviceId);
|
|
20
|
+
return { ...state, mutedForMe };
|
|
21
|
+
}
|
|
22
|
+
case 'dismiss':
|
|
23
|
+
return { ...state, problems: state.problems.filter((problem) => problem.id !== action.id) };
|
|
24
|
+
case 'rtc': {
|
|
25
|
+
const event = action.event;
|
|
26
|
+
switch (event.type) {
|
|
27
|
+
case 'track': {
|
|
28
|
+
const peers = new Map(state.peers);
|
|
29
|
+
peers.set(event.deviceId, { ...peers.get(event.deviceId), [event.source]: event.stream });
|
|
30
|
+
return { ...state, peers };
|
|
31
|
+
}
|
|
32
|
+
case 'track.ended': {
|
|
33
|
+
const peers = new Map(state.peers);
|
|
34
|
+
const media = { ...peers.get(event.deviceId) };
|
|
35
|
+
delete media[event.source];
|
|
36
|
+
peers.set(event.deviceId, media);
|
|
37
|
+
return { ...state, peers };
|
|
38
|
+
}
|
|
39
|
+
case 'participant.left': {
|
|
40
|
+
const peers = new Map(state.peers);
|
|
41
|
+
const quality = new Map(state.quality);
|
|
42
|
+
peers.delete(event.deviceId);
|
|
43
|
+
quality.delete(event.deviceId);
|
|
44
|
+
// Muting somebody for yourself does not survive them leaving, and does
|
|
45
|
+
// not persist across calls: the next one starts clean.
|
|
46
|
+
const mutedForMe = new Set(state.mutedForMe);
|
|
47
|
+
mutedForMe.delete(event.deviceId);
|
|
48
|
+
return { ...state, peers, quality, mutedForMe };
|
|
49
|
+
}
|
|
50
|
+
case 'quality': {
|
|
51
|
+
const quality = new Map(state.quality);
|
|
52
|
+
quality.set(event.deviceId, {
|
|
53
|
+
relayed: event.relayed,
|
|
54
|
+
packetLoss: event.packetLoss,
|
|
55
|
+
roundTripMs: event.roundTripMs,
|
|
56
|
+
});
|
|
57
|
+
return { ...state, quality };
|
|
58
|
+
}
|
|
59
|
+
case 'local':
|
|
60
|
+
return { ...state, local: { ...state.local, [event.source]: event.stream } };
|
|
61
|
+
case 'failed': {
|
|
62
|
+
problemId += 1;
|
|
63
|
+
return {
|
|
64
|
+
...state,
|
|
65
|
+
problems: [
|
|
66
|
+
...state.problems,
|
|
67
|
+
{
|
|
68
|
+
id: problemId,
|
|
69
|
+
...(event.deviceId ? { deviceId: event.deviceId } : {}),
|
|
70
|
+
message: event.reason,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
case 'degraded':
|
|
76
|
+
return { ...state, degraded: event.reason };
|
|
77
|
+
default:
|
|
78
|
+
return state;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function useCallMedia(client) {
|
|
84
|
+
const [state, dispatch] = useReducer(reduce, EMPTY);
|
|
85
|
+
useEffect(() => client.rtc.on((event) => {
|
|
86
|
+
dispatch({ kind: 'rtc', event });
|
|
87
|
+
}), [client]);
|
|
88
|
+
return useMemo(() => ({
|
|
89
|
+
...state,
|
|
90
|
+
muteForMe: (deviceId, muted) => dispatch({ kind: 'mute-for-me', deviceId, muted }),
|
|
91
|
+
dismiss: (id) => dispatch({ kind: 'dismiss', id }),
|
|
92
|
+
}), [state]);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Who should be on screen, and in what order.
|
|
96
|
+
*
|
|
97
|
+
* **Raised hands first, in the order they went up**, then the most recent speaker,
|
|
98
|
+
* then everybody else. Both halves matter for the same reason: only five tiles are
|
|
99
|
+
* visible, so being last in this list means being paged off screen — and somebody
|
|
100
|
+
* who has asked to speak is the last person who should disappear. Whoever asked
|
|
101
|
+
* first is listed first, because the queue is the whole value of a raised hand.
|
|
102
|
+
*
|
|
103
|
+
* The ordering is a **pure function of the office state**. `lastSpokeAt` and
|
|
104
|
+
* `handRaisedAt` are both stamped by the server, so everybody in the call sees the
|
|
105
|
+
* same five faces in the same order — a client that joined a minute ago has no
|
|
106
|
+
* less idea of who spoke recently, or who asked first, than one that has been
|
|
107
|
+
* listening throughout.
|
|
108
|
+
*/
|
|
109
|
+
export function speakerOrder(participants, people) {
|
|
110
|
+
const legOf = (deviceId, userId) => people.get(userId)?.devices.find((one) => one.deviceId === deviceId);
|
|
111
|
+
return participants
|
|
112
|
+
.map((one) => {
|
|
113
|
+
const leg = legOf(one.deviceId, one.userId);
|
|
114
|
+
return {
|
|
115
|
+
...one,
|
|
116
|
+
raisedAt: leg?.handRaisedAt ? Date.parse(leg.handRaisedAt) : null,
|
|
117
|
+
spokeAt: leg?.lastSpokeAt ? Date.parse(leg.lastSpokeAt) : 0,
|
|
118
|
+
};
|
|
119
|
+
})
|
|
120
|
+
.sort((a, b) => {
|
|
121
|
+
// A hand up outranks anything anybody has said, and the earlier hand
|
|
122
|
+
// outranks the later one. Compared as two nullable values rather than by
|
|
123
|
+
// sorting on a number, because "no hand" is not a time and arithmetic on a
|
|
124
|
+
// sentinel is how an ordering like this quietly goes wrong.
|
|
125
|
+
if (a.raisedAt !== null && b.raisedAt !== null) {
|
|
126
|
+
if (a.raisedAt !== b.raisedAt)
|
|
127
|
+
return a.raisedAt - b.raisedAt;
|
|
128
|
+
}
|
|
129
|
+
else if (a.raisedAt !== null)
|
|
130
|
+
return -1;
|
|
131
|
+
else if (b.raisedAt !== null)
|
|
132
|
+
return 1;
|
|
133
|
+
if (a.spokeAt !== b.spokeAt)
|
|
134
|
+
return b.spokeAt - a.spokeAt;
|
|
135
|
+
// A stable tie-break, so people who have never spoken keep their places
|
|
136
|
+
// rather than shuffling on every render.
|
|
137
|
+
return a.deviceId.localeCompare(b.deviceId);
|
|
138
|
+
})
|
|
139
|
+
.map((one) => one.deviceId);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* How long a reaction floats before it is gone.
|
|
143
|
+
*
|
|
144
|
+
* Long enough to be seen across a room full of tiles, short enough that two in a
|
|
145
|
+
* row do not stack into a wall. Nothing is stored anywhere: when this elapses the
|
|
146
|
+
* reaction has not been archived, it has stopped existing.
|
|
147
|
+
*/
|
|
148
|
+
export const REACTION_VISIBLE_MS = 4_000;
|
|
149
|
+
let reactionId = 0;
|
|
150
|
+
const groupBy = (reactions, key) => {
|
|
151
|
+
const grouped = new Map();
|
|
152
|
+
for (const one of reactions) {
|
|
153
|
+
const list = grouped.get(one[key]);
|
|
154
|
+
if (list)
|
|
155
|
+
list.push(one);
|
|
156
|
+
else
|
|
157
|
+
grouped.set(one[key], [one]);
|
|
158
|
+
}
|
|
159
|
+
return grouped;
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* The reactions in the air right now.
|
|
163
|
+
*
|
|
164
|
+
* Held here and nowhere else — not in the office state, not on the server. A
|
|
165
|
+
* reaction is an event, and treating it as state is how a product ends up with a
|
|
166
|
+
* screen full of yesterday's applause.
|
|
167
|
+
*
|
|
168
|
+
* Grouped two ways because it is drawn in two places: over a tile, which is a
|
|
169
|
+
* device, and over an avatar on the map, which is a person.
|
|
170
|
+
*/
|
|
171
|
+
export function useReactions(client, visibleMs = REACTION_VISIBLE_MS) {
|
|
172
|
+
const [live, setLive] = useState([]);
|
|
173
|
+
const timers = useRef(new Set());
|
|
174
|
+
useClientEvents(client, useCallback((event) => {
|
|
175
|
+
if (event.type !== 'reaction')
|
|
176
|
+
return;
|
|
177
|
+
reactionId += 1;
|
|
178
|
+
const id = reactionId;
|
|
179
|
+
setLive((current) => [
|
|
180
|
+
...current,
|
|
181
|
+
{
|
|
182
|
+
id,
|
|
183
|
+
userId: event.userId,
|
|
184
|
+
deviceId: event.deviceId,
|
|
185
|
+
reaction: event.reaction,
|
|
186
|
+
},
|
|
187
|
+
]);
|
|
188
|
+
const timer = setTimeout(() => {
|
|
189
|
+
timers.current.delete(timer);
|
|
190
|
+
setLive((current) => current.filter((one) => one.id !== id));
|
|
191
|
+
}, visibleMs);
|
|
192
|
+
timers.current.add(timer);
|
|
193
|
+
}, [visibleMs]));
|
|
194
|
+
// Leaving the office mid-reaction must not leave a timer holding a setState for
|
|
195
|
+
// a component that has gone.
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
const pending = timers.current;
|
|
198
|
+
return () => {
|
|
199
|
+
for (const timer of pending)
|
|
200
|
+
clearTimeout(timer);
|
|
201
|
+
pending.clear();
|
|
202
|
+
};
|
|
203
|
+
}, []);
|
|
204
|
+
return useMemo(() => ({ byDevice: groupBy(live, 'deviceId'), byUser: groupBy(live, 'userId') }), [live]);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* The order, held still for a moment after it changes.
|
|
208
|
+
*
|
|
209
|
+
* Two people trading short sentences would otherwise swap tiles constantly, which
|
|
210
|
+
* is far more distracting than a tile being a beat out of date.
|
|
211
|
+
*
|
|
212
|
+
* The hold applies **only to re-ordering**: somebody joining or leaving is applied
|
|
213
|
+
* at once, because a tile that is not there yet is worse than one in the wrong
|
|
214
|
+
* place.
|
|
215
|
+
*/
|
|
216
|
+
export function useSpeakerOrder(participants, people, holdMs = 1500) {
|
|
217
|
+
const target = useMemo(() => speakerOrder(participants, people), [participants, people]);
|
|
218
|
+
/** Who is in the call, regardless of order. Changes when somebody comes or goes. */
|
|
219
|
+
const membership = useMemo(() => [...target].sort().join(','), [target]);
|
|
220
|
+
const [shown, setShown] = useState(target);
|
|
221
|
+
const [shownMembership, setShownMembership] = useState(membership);
|
|
222
|
+
// Somebody arrived or left, so there is a tile to add or remove: applied during
|
|
223
|
+
// this render rather than after it, because waiting a beat would show a call
|
|
224
|
+
// that does not match who is in it.
|
|
225
|
+
if (membership !== shownMembership) {
|
|
226
|
+
setShownMembership(membership);
|
|
227
|
+
setShown(target);
|
|
228
|
+
}
|
|
229
|
+
// The same people in a different order: held.
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
if (shown.length === target.length && shown.every((id, index) => id === target[index]))
|
|
232
|
+
return;
|
|
233
|
+
const timer = setTimeout(() => setShown(target), holdMs);
|
|
234
|
+
return () => clearTimeout(timer);
|
|
235
|
+
}, [target, shown, holdMs]);
|
|
236
|
+
return shown;
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=useCall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCall.js","sourceRoot":"","sources":["../src/useCall.ts"],"names":[],"mappings":"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"}
|