@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.
Files changed (103) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +34 -0
  3. package/dist/Announcer.d.ts +21 -0
  4. package/dist/Announcer.d.ts.map +1 -0
  5. package/dist/Announcer.js +32 -0
  6. package/dist/Announcer.js.map +1 -0
  7. package/dist/CallAudio.d.ts +32 -0
  8. package/dist/CallAudio.d.ts.map +1 -0
  9. package/dist/CallAudio.js +72 -0
  10. package/dist/CallAudio.js.map +1 -0
  11. package/dist/CallControls.d.ts +60 -0
  12. package/dist/CallControls.d.ts.map +1 -0
  13. package/dist/CallControls.js +62 -0
  14. package/dist/CallControls.js.map +1 -0
  15. package/dist/CallTiles.d.ts +48 -0
  16. package/dist/CallTiles.d.ts.map +1 -0
  17. package/dist/CallTiles.js +134 -0
  18. package/dist/CallTiles.js.map +1 -0
  19. package/dist/DevicePanel.d.ts +32 -0
  20. package/dist/DevicePanel.d.ts.map +1 -0
  21. package/dist/DevicePanel.js +133 -0
  22. package/dist/DevicePanel.js.map +1 -0
  23. package/dist/Knocks.d.ts +46 -0
  24. package/dist/Knocks.d.ts.map +1 -0
  25. package/dist/Knocks.js +46 -0
  26. package/dist/Knocks.js.map +1 -0
  27. package/dist/OfficeMap.d.ts +69 -0
  28. package/dist/OfficeMap.d.ts.map +1 -0
  29. package/dist/OfficeMap.js +222 -0
  30. package/dist/OfficeMap.js.map +1 -0
  31. package/dist/Overflow.d.ts +41 -0
  32. package/dist/Overflow.d.ts.map +1 -0
  33. package/dist/Overflow.js +82 -0
  34. package/dist/Overflow.js.map +1 -0
  35. package/dist/PersonAvatar.d.ts +32 -0
  36. package/dist/PersonAvatar.d.ts.map +1 -0
  37. package/dist/PersonAvatar.js +80 -0
  38. package/dist/PersonAvatar.js.map +1 -0
  39. package/dist/Reactions.d.ts +47 -0
  40. package/dist/Reactions.d.ts.map +1 -0
  41. package/dist/Reactions.js +61 -0
  42. package/dist/Reactions.js.map +1 -0
  43. package/dist/RoomBar.d.ts +43 -0
  44. package/dist/RoomBar.d.ts.map +1 -0
  45. package/dist/RoomBar.js +120 -0
  46. package/dist/RoomBar.js.map +1 -0
  47. package/dist/Share.d.ts +74 -0
  48. package/dist/Share.d.ts.map +1 -0
  49. package/dist/Share.js +115 -0
  50. package/dist/Share.js.map +1 -0
  51. package/dist/StatusControl.d.ts +25 -0
  52. package/dist/StatusControl.d.ts.map +1 -0
  53. package/dist/StatusControl.js +77 -0
  54. package/dist/StatusControl.js.map +1 -0
  55. package/dist/controls.d.ts +32 -0
  56. package/dist/controls.d.ts.map +1 -0
  57. package/dist/controls.js +24 -0
  58. package/dist/controls.js.map +1 -0
  59. package/dist/hooks.d.ts +60 -0
  60. package/dist/hooks.d.ts.map +1 -0
  61. package/dist/hooks.js +182 -0
  62. package/dist/hooks.js.map +1 -0
  63. package/dist/index.d.ts +45 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +45 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/layout.d.ts +65 -0
  68. package/dist/layout.d.ts.map +1 -0
  69. package/dist/layout.js +93 -0
  70. package/dist/layout.js.map +1 -0
  71. package/dist/placement.d.ts +70 -0
  72. package/dist/placement.d.ts.map +1 -0
  73. package/dist/placement.js +83 -0
  74. package/dist/placement.js.map +1 -0
  75. package/dist/sounds.d.ts +30 -0
  76. package/dist/sounds.d.ts.map +1 -0
  77. package/dist/sounds.js +125 -0
  78. package/dist/sounds.js.map +1 -0
  79. package/dist/status.d.ts +47 -0
  80. package/dist/status.d.ts.map +1 -0
  81. package/dist/status.js +65 -0
  82. package/dist/status.js.map +1 -0
  83. package/dist/theme.d.ts +27 -0
  84. package/dist/theme.d.ts.map +1 -0
  85. package/dist/theme.js +57 -0
  86. package/dist/theme.js.map +1 -0
  87. package/dist/tileLayout.d.ts +27 -0
  88. package/dist/tileLayout.d.ts.map +1 -0
  89. package/dist/tileLayout.js +41 -0
  90. package/dist/tileLayout.js.map +1 -0
  91. package/dist/useCall.d.ts +107 -0
  92. package/dist/useCall.d.ts.map +1 -0
  93. package/dist/useCall.js +238 -0
  94. package/dist/useCall.js.map +1 -0
  95. package/dist/useShare.d.ts +54 -0
  96. package/dist/useShare.d.ts.map +1 -0
  97. package/dist/useShare.js +114 -0
  98. package/dist/useShare.js.map +1 -0
  99. package/dist/useSounds.d.ts +31 -0
  100. package/dist/useSounds.d.ts.map +1 -0
  101. package/dist/useSounds.js +56 -0
  102. package/dist/useSounds.js.map +1 -0
  103. package/package.json +25 -0
@@ -0,0 +1,133 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Alert, Button, Icon, Modal, Select } from '@unityevolv/unitykit';
3
+ import { labelFor, listDevices, requestPermission, stillAvailable, watchDevices, } from '@unityevolv/ofiskit-realtime-client';
4
+ import { useCallback, useEffect, useRef, useState } from 'react';
5
+ export function DevicePanel(props) {
6
+ const { open, choice, onChoose } = props;
7
+ const [devices, setDevices] = useState({ microphones: [], cameras: [], speakers: [] });
8
+ const [problem, setProblem] = useState(null);
9
+ const [inUseElsewhere, setInUseElsewhere] = useState(false);
10
+ const [granted, setGranted] = useState(false);
11
+ const [level, setLevel] = useState(0);
12
+ const [notice, setNotice] = useState(null);
13
+ const video = useRef(null);
14
+ const stream = useRef(null);
15
+ const audio = useRef(null);
16
+ const stop = useCallback(() => {
17
+ for (const track of stream.current?.getTracks() ?? [])
18
+ track.stop();
19
+ stream.current = null;
20
+ if (audio.current) {
21
+ clearInterval(audio.current.timer);
22
+ void audio.current.context.close().catch(() => { });
23
+ audio.current = null;
24
+ }
25
+ setLevel(0);
26
+ }, []);
27
+ /**
28
+ * Ask for permission, then list.
29
+ *
30
+ * In that order on purpose: device labels are empty until permission has been
31
+ * granted once, and a picker offering "Microphone 1" and "Microphone 2" helps
32
+ * nobody choose their headset.
33
+ */
34
+ const start = useCallback(async () => {
35
+ const outcome = await requestPermission({ audio: true, video: props.preview ?? false }, choice);
36
+ setGranted(outcome.granted);
37
+ setProblem(outcome.problem ?? null);
38
+ setInUseElsewhere(Boolean(outcome.inUseElsewhere));
39
+ if (outcome.stream) {
40
+ stream.current = outcome.stream;
41
+ if (video.current)
42
+ video.current.srcObject = outcome.stream;
43
+ // A level meter, so nobody joins with a dead microphone and finds out
44
+ // when somebody asks why they have been silent for two minutes.
45
+ try {
46
+ const context = new AudioContext();
47
+ const analyser = context.createAnalyser();
48
+ analyser.fftSize = 256;
49
+ context.createMediaStreamSource(outcome.stream).connect(analyser);
50
+ const samples = new Uint8Array(analyser.frequencyBinCount);
51
+ const timer = setInterval(() => {
52
+ analyser.getByteFrequencyData(samples);
53
+ let total = 0;
54
+ for (const sample of samples)
55
+ total += sample;
56
+ setLevel(Math.min(1, total / samples.length / 128));
57
+ }, 100);
58
+ audio.current = { context, timer };
59
+ }
60
+ catch {
61
+ // No meter is survivable; the picker still works.
62
+ }
63
+ }
64
+ setDevices(await listDevices());
65
+ }, [choice, props.preview]);
66
+ useEffect(() => {
67
+ if (!open)
68
+ return;
69
+ /*
70
+ * Opening the panel asks the browser for the camera and microphone.
71
+ *
72
+ * This is an effect synchronising with an external system, which is what
73
+ * effects are for: nothing is set until the browser answers, several
74
+ * awaits later. The lint rule cannot see through an async call and reads
75
+ * the setState calls inside `start` as synchronous ones, so it is disabled
76
+ * here with the reason rather than the effect being contorted to please it.
77
+ */
78
+ // eslint-disable-next-line react-hooks/set-state-in-effect
79
+ void start();
80
+ // Closing the panel runs this cleanup, which is what stops the camera and
81
+ // the level meter. Doing it in the body as well would be a second stop.
82
+ return stop;
83
+ }, [open, start, stop]);
84
+ /**
85
+ * Follow the operating system when something is plugged in or pulled out.
86
+ *
87
+ * With a notice, never silently: somebody who unplugs a headset mid-sentence
88
+ * needs to know their voice is now going through the laptop microphone, and
89
+ * a silent switch is how people end up talking into a dead device.
90
+ */
91
+ useEffect(() => {
92
+ if (!open)
93
+ return;
94
+ return watchDevices((next) => {
95
+ setDevices(next);
96
+ const surviving = stillAvailable(choice, next);
97
+ if (choice.audioDeviceId && !surviving.audioDeviceId) {
98
+ setNotice('Your microphone was unplugged. The call switched to the built-in one.');
99
+ onChoose(surviving);
100
+ props.onApply?.(surviving);
101
+ }
102
+ else if (choice.videoDeviceId && !surviving.videoDeviceId) {
103
+ setNotice('Your camera was unplugged.');
104
+ onChoose(surviving);
105
+ props.onApply?.(surviving);
106
+ }
107
+ });
108
+ }, [open, choice, onChoose, props]);
109
+ const update = (patch) => {
110
+ const next = { ...choice, ...patch };
111
+ onChoose(next);
112
+ props.onApply?.(next);
113
+ // Stop the current preview before opening the newly chosen device, or two
114
+ // cameras end up running and the meter reads the wrong microphone.
115
+ stop();
116
+ void start();
117
+ };
118
+ return (_jsx(Modal, { open: open, onOpenChange: (next) => {
119
+ if (!next)
120
+ props.onClose();
121
+ }, title: "Microphone, camera and speaker", children: _jsxs("div", { className: "space-y-4", children: [notice && (_jsx(Alert, { variant: "info", onDismiss: () => setNotice(null), children: notice })), problem && (_jsxs(Alert, { variant: inUseElsewhere ? 'warn' : 'danger', children: [_jsx("p", { children: problem }), _jsx(Button, { size: "sm", variant: "ghost", className: "mt-2", onClick: () => void start(), children: "Try again" })] })), props.preview && (_jsx("div", { className: "overflow-hidden rounded-lg bg-base-300", children: _jsx("video", { ref: video, autoPlay: true, playsInline: true, muted: true, "aria-label": "Camera preview", className: "aspect-video w-full scale-x-[-1] object-cover" }) })), _jsxs("div", { children: [_jsxs("p", { className: "mb-1 flex items-center gap-1 text-xs font-medium text-base-content/70", children: [_jsx(Icon, { name: "mic", size: "xs" }), "Microphone level"] }), _jsx("div", { role: "meter", "aria-label": "Microphone level", "aria-valuenow": Math.round(level * 100), "aria-valuemin": 0, "aria-valuemax": 100, className: "h-2 w-full overflow-hidden rounded-full bg-base-300", children: _jsx("div", { className: "h-full rounded-full bg-success transition-[width] duration-100", style: { width: `${Math.round(level * 100)}%` } }) }), granted && level === 0 && (_jsx("p", { className: "mt-1 text-xs text-base-content/70", children: "Say something \u2014 if this stays still, the microphone is not picking you up." }))] }), _jsxs(Select, { label: "Microphone", value: choice.audioDeviceId ?? '', onChange: (event) => update({ audioDeviceId: event.target.value || undefined }), children: [_jsx("option", { value: "", children: "System default" }), devices.microphones.map((device, index) => (_jsx("option", { value: device.deviceId, children: labelFor(device, index) }, device.deviceId)))] }), _jsxs(Select, { label: "Camera", value: choice.videoDeviceId ?? '', onChange: (event) => update({ videoDeviceId: event.target.value || undefined }), children: [_jsx("option", { value: "", children: "System default" }), devices.cameras.map((device, index) => (_jsx("option", { value: device.deviceId, children: labelFor(device, index) }, device.deviceId)))] }), devices.speakers.length > 0 && (_jsxs(Select, { label: "Speaker", value: choice.speakerDeviceId ?? '', onChange: (event) => update({ speakerDeviceId: event.target.value || undefined }), children: [_jsx("option", { value: "", children: "System default" }), devices.speakers.map((device, index) => (_jsx("option", { value: device.deviceId, children: labelFor(device, index) }, device.deviceId)))] })), _jsx("p", { className: "text-xs text-base-content/60", children: "Noise suppression and echo cancellation are on. Your choice is remembered on this device." })] }) }));
122
+ }
123
+ /**
124
+ * Said before the browser's own prompt, not after.
125
+ *
126
+ * The browser asks a blunt question with no context, and somebody who says no
127
+ * to it has a much harder time saying yes later. Explaining first is the
128
+ * difference between a prompt that gets accepted and a support ticket.
129
+ */
130
+ export function PermissionPrimer({ onContinue }) {
131
+ return (_jsxs("div", { className: "mx-auto max-w-sm rounded-lg bg-base-100 p-4 text-center ring-1 ring-base-300", children: [_jsx(Icon, { name: "mic", size: "lg" }), _jsx("h2", { className: "mt-2 text-lg font-semibold", children: "Let your browser use the microphone" }), _jsx("p", { className: "mt-1 text-sm text-base-content/75", children: "Your browser is about to ask. Nothing is recorded, and nothing is sent anywhere until you press the microphone button in a room." }), _jsx(Button, { className: "mt-3", onClick: onContinue, children: "Continue" })] }));
132
+ }
133
+ //# sourceMappingURL=DevicePanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevicePanel.js","sourceRoot":"","sources":["../src/DevicePanel.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Somebody is at the door, and what happens next.
3
+ *
4
+ * Two sides of the same moment. Inside the room, a knock with a name and two
5
+ * buttons. Outside, feedback that the knock was sent and then a clear outcome —
6
+ * because the worst version of this feature is knocking into silence and never
7
+ * learning whether anybody saw it.
8
+ *
9
+ * Admitting lets that one person in without unlocking the room for anybody else,
10
+ * and the card says so, because "let them in" reads like "open the door".
11
+ */
12
+ export interface IncomingKnock {
13
+ knockId: string;
14
+ roomId: string;
15
+ userId: string;
16
+ displayName: string;
17
+ photoUrl?: string;
18
+ /** True when everybody inside is on do not disturb: it arrived without a sound. */
19
+ silent: boolean;
20
+ }
21
+ export interface KnockDockProps {
22
+ knocks: IncomingKnock[];
23
+ onAdmit(knockId: string): void;
24
+ onDecline(knockId: string): void;
25
+ }
26
+ export declare function KnockDock({ knocks, onAdmit, onDecline }: KnockDockProps): import("react").JSX.Element | null;
27
+ export type KnockOutcome = 'waiting' | 'admitted' | 'declined' | 'expired' | 'refused';
28
+ export interface OutgoingKnockProps {
29
+ roomName: string;
30
+ outcome: KnockOutcome;
31
+ /** The reason, when the knock was refused outright — rate limited, say. */
32
+ message?: string | null;
33
+ /** True when it arrived silently, so the knocker knows why it may go unanswered. */
34
+ silent?: boolean;
35
+ onDismiss(): void;
36
+ }
37
+ /**
38
+ * What the person who knocked sees.
39
+ *
40
+ * Every outcome is said plainly, **including the one where nobody answered**,
41
+ * because an unanswered knock is otherwise indistinguishable from a broken one.
42
+ * A refusal says why: repeated knocking is rate limited, and a button that
43
+ * appears to do nothing is worse than a button that says no.
44
+ */
45
+ export declare function OutgoingKnock({ roomName, outcome, message, silent, onDismiss, }: OutgoingKnockProps): import("react").JSX.Element;
46
+ //# sourceMappingURL=Knocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Knocks.d.ts","sourceRoot":"","sources":["../src/Knocks.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mFAAmF;IACnF,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAED,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,cAAc,sCA4CvE;AAED,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAA;AAEtF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,YAAY,CAAA;IACrB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,oFAAoF;IACpF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,SAAS,IAAI,IAAI,CAAA;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,OAAO,EACP,OAAO,EACP,MAAc,EACd,SAAS,GACV,EAAE,kBAAkB,+BAgDpB"}
package/dist/Knocks.js ADDED
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Icon } from '@unityevolv/unitykit';
3
+ import { useEffect } from 'react';
4
+ export function KnockDock({ knocks, onAdmit, onDecline }) {
5
+ if (knocks.length === 0)
6
+ return null;
7
+ return (
8
+ // A section rather than a div: an aria-label on a plain div is exposed to
9
+ // nothing, so the label would have been decoration. As a landmark it is
10
+ // something somebody can jump to while a colleague waits outside.
11
+ _jsx("section", { className: "pointer-events-auto absolute bottom-4 right-4 z-30 flex w-72 flex-col gap-2", "aria-label": "People knocking", children: knocks.map((knock) => (_jsxs("div", { className: "rounded-lg bg-base-100 p-3 shadow-lg ring-1 ring-base-300", "data-testid": `knock-${knock.knockId}`, children: [_jsxs("p", { className: "flex items-center gap-2 text-sm", children: [_jsx(Icon, { name: "knock", size: "sm" }), _jsxs("span", { children: [_jsx("strong", { children: knock.displayName }), " would like to come in."] })] }), _jsxs("div", { className: "mt-2 flex gap-2", children: [_jsx(Button, { size: "sm", onClick: () => onAdmit(knock.knockId), children: "Let them in" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => onDecline(knock.knockId), children: "Not now" })] }), _jsx("p", { className: "mt-1.5 text-xs text-base-content/60", children: "Letting them in does not unlock the room for anyone else." })] }, knock.knockId))) }));
12
+ }
13
+ /**
14
+ * What the person who knocked sees.
15
+ *
16
+ * Every outcome is said plainly, **including the one where nobody answered**,
17
+ * because an unanswered knock is otherwise indistinguishable from a broken one.
18
+ * A refusal says why: repeated knocking is rate limited, and a button that
19
+ * appears to do nothing is worse than a button that says no.
20
+ */
21
+ export function OutgoingKnock({ roomName, outcome, message, silent = false, onDismiss, }) {
22
+ // Anything final clears itself after a few seconds. Only "waiting" stays, for
23
+ // as long as the waiting does.
24
+ useEffect(() => {
25
+ if (outcome === 'waiting')
26
+ return;
27
+ const timer = setTimeout(onDismiss, 6000);
28
+ return () => clearTimeout(timer);
29
+ }, [outcome, onDismiss]);
30
+ const text = {
31
+ waiting: silent
32
+ ? `Knocked on ${roomName}. Everybody inside is on do not disturb, so it arrived silently.`
33
+ : `Knocked on ${roomName}. Waiting for an answer…`,
34
+ admitted: `You were let into ${roomName}.`,
35
+ declined: `Not right now — try ${roomName} again in a bit.`,
36
+ expired: `Nobody answered in ${roomName}.`,
37
+ refused: message ?? 'That knock could not be sent.',
38
+ };
39
+ const tone = outcome === 'admitted'
40
+ ? 'bg-success text-success-content'
41
+ : outcome === 'waiting'
42
+ ? 'bg-base-100 text-base-content ring-1 ring-base-300'
43
+ : 'bg-base-300 text-base-content';
44
+ return (_jsx("div", { role: "status", "data-testid": "outgoing-knock", className: ['pointer-events-auto rounded-lg px-3 py-2 text-sm shadow-lg', tone].join(' '), children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: outcome === 'admitted' ? 'check' : 'knock', size: "sm" }), _jsx("span", { children: text[outcome] }), _jsx("button", { type: "button", onClick: onDismiss, "aria-label": "Dismiss", className: "ml-auto rounded p-0.5 hover:bg-base-200/50 focus-visible:outline-2 focus-visible:outline-primary", children: _jsx(Icon, { name: "close", size: "xs" }) })] }) }));
45
+ }
46
+ //# sourceMappingURL=Knocks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Knocks.js","sourceRoot":"","sources":["../src/Knocks.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import type { OfficeState } from '@unityevolv/ofiskit-realtime-client';
2
+ import { type Room, type Template } from '@unityevolv/ofiskit-template';
3
+ import type { LiveReaction } from './useCall.js';
4
+ /**
5
+ * The office: the background image with rooms drawn over it, and people in them.
6
+ *
7
+ * The picture is the point of the product, and it is also the hardest thing here
8
+ * to use without sight or a mouse. So the map is a landmark region, every room is
9
+ * a labelled group in reading order, arrow keys move between rooms, Enter joins or
10
+ * knocks, and there is a list view carrying exactly the same state and the same
11
+ * actions for anybody who would rather not have a picture at all.
12
+ *
13
+ * Nothing is drawn directly onto the image. Every bar, avatar and dot is a kit
14
+ * surface with its own contrast, because the image is whatever an author
15
+ * generated and nothing legible can be guaranteed on top of it.
16
+ */
17
+ export interface OfficeMapProps {
18
+ template: Template;
19
+ state: OfficeState;
20
+ /** Turns a template's image name into something the browser can fetch. */
21
+ imageUrl(name: string): string;
22
+ /** Capacity is an office setting; the free office has none. */
23
+ capacityOf?(room: Room): number | null;
24
+ /**
25
+ * Reactions in the air, by person. From `useReactions`.
26
+ *
27
+ * By person rather than by device here, because an avatar is a person — the same
28
+ * reaction is keyed by device for the tiles, which are screens.
29
+ */
30
+ reactions?: ReadonlyMap<string, LiveReaction[]>;
31
+ onJoin(roomId: string): void;
32
+ onKnock(roomId: string): void;
33
+ onLock(roomId: string): void;
34
+ onUnlock(roomId: string): void;
35
+ }
36
+ export declare function OfficeMap(props: OfficeMapProps): import("react").JSX.Element;
37
+ /**
38
+ * The same office as a list.
39
+ *
40
+ * The accessible alternative to the map, and genuinely useful on a small laptop
41
+ * where the picture is too small to click. It shows the same state, offers the
42
+ * same actions and updates from the same realtime state, so the two can never
43
+ * disagree — which is the only reason it is safe to offer at all.
44
+ */
45
+ export declare function RoomListView(props: OfficeMapProps): import("react").JSX.Element;
46
+ /** Which of the two views somebody is looking at. Remembered, because it is a preference. */
47
+ export type OfficeView = 'map' | 'list';
48
+ export declare function ViewToggle({ view, onChange, }: {
49
+ view: OfficeView;
50
+ onChange(view: OfficeView): void;
51
+ }): import("react").JSX.Element;
52
+ /**
53
+ * Hide everything but the office.
54
+ *
55
+ * Exported from here although this app has no chrome to hide: the control belongs
56
+ * to the shared package because the wrapper's app shell is what it hides, and a
57
+ * second copy of it there would be a second set of behaviour to keep right.
58
+ *
59
+ * Somebody who works maximised should not have to set it every session, so the
60
+ * choice is remembered. Escape gets out, because a mode with no visible way back
61
+ * is a trap.
62
+ */
63
+ export declare function useMaximised(): [boolean, (next: boolean) => void];
64
+ /** The control that toggles it, so the label and the icon stay in one place. */
65
+ export declare function MaximiseButton({ maximised, onToggle, }: {
66
+ maximised: boolean;
67
+ onToggle(): void;
68
+ }): import("react").JSX.Element;
69
+ //# sourceMappingURL=OfficeMap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OfficeMap.d.ts","sourceRoot":"","sources":["../src/OfficeMap.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAA4B,MAAM,qCAAqC,CAAA;AAEhG,OAAO,EAAW,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,8BAA8B,CAAA;AAShF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAGhD;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,QAAQ,CAAA;IAClB,KAAK,EAAE,WAAW,CAAA;IAClB,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9B,+DAA+D;IAC/D,UAAU,CAAC,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IACtC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAA;IAC/C,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,+BAuP9C;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,+BA4DjD;AAuED,6FAA6F;AAC7F,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,CAAA;AAEvC,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,UAAU,CAAA;IAChB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAA;CACjC,+BAuBA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC,CAajE;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,QAAQ,GACT,EAAE;IACD,SAAS,EAAE,OAAO,CAAA;IAClB,QAAQ,IAAI,IAAI,CAAA;CACjB,+BAYA"}
@@ -0,0 +1,222 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Icon } from '@unityevolv/unitykit';
3
+ import { callIn, isLocked, occupancy, peopleIn, yourRoom } from '@unityevolv/ofiskit-realtime-client';
4
+ import { barRect } from '@unityevolv/ofiskit-template';
5
+ import { useCallback, useEffect, useState } from 'react';
6
+ import { OverflowAvatar } from './Overflow.js';
7
+ import { PersonAvatar } from './PersonAvatar.js';
8
+ import { RoomBar } from './RoomBar.js';
9
+ import { fitCanvas, readingOrder, roomInDirection, toPixels } from './layout.js';
10
+ import { placeInRoom } from './placement.js';
11
+ import { useMeasured, usePersisted, useReducedMotion } from './hooks.js';
12
+ import { useTheme } from './theme.js';
13
+ export function OfficeMap(props) {
14
+ const { template, state, imageUrl, capacityOf, reactions } = props;
15
+ const { theme } = useTheme();
16
+ const reducedMotion = useReducedMotion();
17
+ // Measured rather than guessed: the map shares its space with the tile strip,
18
+ // which appears and disappears as calls start and end.
19
+ const [container, box] = useMeasured();
20
+ const [focused, setFocused] = useState(null);
21
+ const canvas = fitCanvas(template.canvas, box);
22
+ const ordered = readingOrder(template.rooms);
23
+ const yourRoomId = yourRoom(state);
24
+ /**
25
+ * The template's dark image in dark mode, the light one otherwise.
26
+ *
27
+ * Swapped in place without reloading the office, so changing theme mid-call is a
28
+ * repaint rather than an interruption. Theme is per person, so two people in the
29
+ * same room may be looking at different pictures over identical geometry.
30
+ */
31
+ const background = theme === 'dark' && template.images.dark ? template.images.dark : template.images.light;
32
+ const onKeyDown = useCallback((event, room) => {
33
+ /*
34
+ * Only keys pressed on the room itself.
35
+ *
36
+ * The room is a focusable group with controls inside it — Join, Lock, Knock,
37
+ * the overflow counter — and a key pressed on any of them bubbles up to here.
38
+ * Handling it here as well turned Enter on Lock into nothing, Enter on the
39
+ * counter into walking into the room, and the arrow keys inside the counter's
40
+ * list into jumping to the next room. React carries events up through
41
+ * portals too, so the counter's popover is inside this group as far as this
42
+ * handler is concerned, even though it is drawn somewhere else in the page.
43
+ */
44
+ if (event.target !== event.currentTarget)
45
+ return;
46
+ const directions = {
47
+ ArrowLeft: 'left',
48
+ ArrowRight: 'right',
49
+ ArrowUp: 'up',
50
+ ArrowDown: 'down',
51
+ };
52
+ const direction = directions[event.key];
53
+ if (direction) {
54
+ const next = roomInDirection(ordered, room, direction);
55
+ if (next) {
56
+ event.preventDefault();
57
+ setFocused(next.id);
58
+ container.current?.querySelector(`[data-room="${next.id}"]`)?.focus();
59
+ }
60
+ return;
61
+ }
62
+ if (event.key === 'Enter' || event.key === ' ') {
63
+ event.preventDefault();
64
+ if (room.id === yourRoomId)
65
+ return;
66
+ if (isLocked(state, room.id))
67
+ props.onKnock(room.id);
68
+ else
69
+ props.onJoin(room.id);
70
+ }
71
+ }, [container, ordered, state, yourRoomId, props]);
72
+ return (_jsxs("section", { ref: container, "aria-label": `${template.name} office map`, className: "relative h-full w-full overflow-hidden bg-base-200", children: [!state.ready && (_jsx("p", { className: "absolute inset-x-0 top-1/2 text-center text-sm text-base-content/60", role: "status", children: "Looking around the office\u2026" })), canvas.width > 0 && state.ready && (_jsxs(_Fragment, { children: [_jsx("img", { src: imageUrl(background), alt: "", "aria-hidden": "true", draggable: false, className: "absolute select-none", style: {
73
+ left: canvas.left,
74
+ top: canvas.top,
75
+ width: canvas.width,
76
+ height: canvas.height,
77
+ } }), ordered.map((room) => {
78
+ const pixels = toPixels(room.rect, canvas);
79
+ const people = peopleIn(state, room.id);
80
+ const locked = isLocked(state, room.id);
81
+ const capacity = capacityOf?.(room) ?? null;
82
+ const inside = room.id === yourRoomId;
83
+ const count = occupancy(state, room.id);
84
+ const call = callIn(state, room.id);
85
+ const placement = placeInRoom(room, people, template.canvas, template.avatarSize);
86
+ const bar = toPixels(barRect(room.rect, room.bar, template.canvas), canvas);
87
+ return (_jsxs("div", { "data-room": room.id, role: "group", tabIndex: 0, "aria-label": [
88
+ room.name,
89
+ room.type === 'break' ? 'break room' : room.type,
90
+ `${count} ${count === 1 ? 'person' : 'people'}`,
91
+ capacity === null ? '' : `of ${capacity}`,
92
+ // Announced while arrowing between rooms, which is how somebody
93
+ // who cannot see the map decides where to go. Without it the call
94
+ // is drawn on the bar and said nowhere.
95
+ call
96
+ ? `call with ${call.participants.length} ${call.participants.length === 1 ? 'person' : 'people'}${call.participants.length >= call.limit ? ', full' : ''}`
97
+ : '',
98
+ locked ? 'locked' : 'open',
99
+ inside ? 'you are here' : locked ? 'press Enter to knock' : 'press Enter to join',
100
+ ]
101
+ .filter(Boolean)
102
+ .join(', '), onKeyDown: (event) => onKeyDown(event, room), onFocus: () => setFocused(room.id), className: [
103
+ 'absolute rounded-md outline-offset-2',
104
+ 'focus-visible:outline-2 focus-visible:outline-primary',
105
+ inside ? 'ring-2 ring-primary' : 'ring-1 ring-base-content/10',
106
+ focused === room.id ? 'z-20' : 'z-10',
107
+ ].join(' '), style: {
108
+ left: pixels.left,
109
+ top: pixels.top,
110
+ width: pixels.width,
111
+ height: pixels.height,
112
+ }, children: [_jsx("div", { className: "pointer-events-none absolute z-10", style: { left: 0, top: bar.top - pixels.top, width: pixels.width }, children: _jsx(RoomBar, { room: room, occupancy: count, capacity: capacity, locked: locked, inside: inside, call: call, width: pixels.width, onJoin: () => props.onJoin(room.id), onKnock: () => props.onKnock(room.id), onLock: () => props.onLock(room.id), onUnlock: () => props.onUnlock(room.id) }) }), _jsxs("ul", { className: "contents", "aria-label": `People in ${room.name}`, children: [placement.placed.map(({ token, rect }) => {
113
+ const cell = toPixels(rect, canvas);
114
+ return (_jsx("li", { className: "absolute flex items-start justify-center", style: {
115
+ left: cell.left - pixels.left,
116
+ top: cell.top - pixels.top,
117
+ width: cell.width,
118
+ height: cell.height,
119
+ // A move is a position change, so the browser animates
120
+ // it and the eye can follow who went where. Reduced
121
+ // motion means they simply appear in the new room.
122
+ transition: reducedMotion ? undefined : 'left 220ms ease, top 220ms ease',
123
+ }, children: _jsx(PersonAvatar, { person: token.person, size: cell.width, ...(token.deviceId ? { deviceId: token.deviceId } : {}), linked: token.linked, reducedMotion: reducedMotion, reactions: reactions?.get(token.person.userId) ?? [] }) }, token.key));
124
+ }), placement.overflowAt &&
125
+ (() => {
126
+ const cell = toPixels(placement.overflowAt, canvas);
127
+ return (_jsx("li", { className: "absolute flex items-start justify-center", style: {
128
+ left: cell.left - pixels.left,
129
+ top: cell.top - pixels.top,
130
+ width: cell.width,
131
+ height: cell.height,
132
+ }, children: _jsx(OverflowAvatar, { roomName: room.name, tokens: placement.overflow, size: cell.width, reducedMotion: reducedMotion, ...(reactions ? { reactions } : {}) }) }));
133
+ })()] })] }, room.id));
134
+ })] }))] }));
135
+ }
136
+ /**
137
+ * The same office as a list.
138
+ *
139
+ * The accessible alternative to the map, and genuinely useful on a small laptop
140
+ * where the picture is too small to click. It shows the same state, offers the
141
+ * same actions and updates from the same realtime state, so the two can never
142
+ * disagree — which is the only reason it is safe to offer at all.
143
+ */
144
+ export function RoomListView(props) {
145
+ const { template, state, capacityOf, reactions } = props;
146
+ const yourRoomId = yourRoom(state);
147
+ const reducedMotion = useReducedMotion();
148
+ if (!state.ready) {
149
+ return (_jsx("nav", { "aria-label": `${template.name} rooms`, className: "h-full overflow-auto p-2", children: _jsx("p", { className: "p-2 text-sm text-base-content/60", role: "status", children: "Looking around the office\u2026" }) }));
150
+ }
151
+ /*
152
+ * The two rooms every office has come first, side by side, and then everything
153
+ * else in the order the map reads.
154
+ *
155
+ * Reception is where everybody arrives and the break room is where they go to
156
+ * step away, so they are the two rooms somebody looks for without knowing the
157
+ * office — and they are the same two in every office, which makes the top of the
158
+ * list the one part of it that never moves. Placed first rather than wherever the
159
+ * author drew them, so a phone shows them without scrolling.
160
+ */
161
+ const ordered = readingOrder(template.rooms);
162
+ const shared = ['reception', 'break'].flatMap((type) => ordered.filter((room) => room.type === type));
163
+ const rest = ordered.filter((room) => room.type !== 'reception' && room.type !== 'break');
164
+ const card = (room, half) => (_jsx(RoomCard, { room: room, half: half, people: peopleIn(state, room.id), locked: isLocked(state, room.id), inside: room.id === yourRoomId, call: callIn(state, room.id), capacity: capacityOf?.(room) ?? null, reducedMotion: reducedMotion, ...(reactions ? { reactions } : {}), onJoin: () => props.onJoin(room.id), onKnock: () => props.onKnock(room.id), onLock: () => props.onLock(room.id), onUnlock: () => props.onUnlock(room.id) }, room.id));
165
+ return (_jsx("nav", { "aria-label": `${template.name} rooms`, className: "h-full overflow-auto p-2", children: _jsxs("ul", { className: "grid grid-cols-2 gap-2", children: [shared.map((room) => card(room, true)), rest.map((room) => card(room, false))] }) }));
166
+ }
167
+ /**
168
+ * One room in the list: its bar, and the people in it.
169
+ *
170
+ * A half-width card measures itself, because on a narrow phone half the screen is
171
+ * too little for a room bar's full form: without the compact one, the name is what
172
+ * gets crushed. A full-width card never needs to — it is the width somebody chose
173
+ * this view for, and there is no room rectangle constraining it.
174
+ */
175
+ function RoomCard(props) {
176
+ const { room, half, people } = props;
177
+ const [measured, size] = useMeasured();
178
+ // Until the first measurement arrives, assume there is room: a bar that starts in
179
+ // its full form and tightens is better than one that starts cramped.
180
+ const width = half && size.width > 0 ? size.width : Number.MAX_SAFE_INTEGER;
181
+ return (_jsxs("li", { ref: measured, className: [
182
+ 'min-w-0 rounded-lg bg-base-100 p-2 ring-1 ring-base-300',
183
+ half ? '' : 'col-span-2',
184
+ ].join(' '), children: [_jsx(RoomBar, { room: room, occupancy: people.length, capacity: props.capacity, locked: props.locked, inside: props.inside, call: props.call, width: width, onJoin: props.onJoin, onKnock: props.onKnock, onLock: props.onLock, onUnlock: props.onUnlock }), people.length > 0 && (_jsx("ul", { className: "mt-2 flex flex-wrap gap-3 px-1", "aria-label": `People in ${room.name}`, children: people.map((person) => (_jsx("li", { children: _jsx(PersonAvatar, { person: person, size: 56, reducedMotion: props.reducedMotion, reactions: props.reactions?.get(person.userId) ?? [] }) }, person.userId))) }))] }));
185
+ }
186
+ export function ViewToggle({ view, onChange, }) {
187
+ return (_jsx("div", { className: "inline-flex rounded-md ring-1 ring-base-300", role: "group", "aria-label": "Office view", children: ['map', 'list'].map((candidate) => (_jsxs("button", { type: "button", onClick: () => onChange(candidate), "aria-pressed": view === candidate, className: [
188
+ 'inline-flex min-h-8 items-center gap-1 px-2 py-1 text-xs first:rounded-l-md last:rounded-r-md',
189
+ 'focus-visible:outline-2 focus-visible:outline-primary',
190
+ view === candidate ? 'bg-primary text-primary-content' : 'hover:bg-base-200',
191
+ ].join(' '), children: [_jsx(Icon, { name: candidate === 'map' ? 'office' : 'menu', size: "xs" }), _jsx("span", { className: "max-sm:sr-only", children: candidate === 'map' ? 'Map' : 'List' })] }, candidate))) }));
192
+ }
193
+ /**
194
+ * Hide everything but the office.
195
+ *
196
+ * Exported from here although this app has no chrome to hide: the control belongs
197
+ * to the shared package because the wrapper's app shell is what it hides, and a
198
+ * second copy of it there would be a second set of behaviour to keep right.
199
+ *
200
+ * Somebody who works maximised should not have to set it every session, so the
201
+ * choice is remembered. Escape gets out, because a mode with no visible way back
202
+ * is a trap.
203
+ */
204
+ export function useMaximised() {
205
+ const [maximised, setMaximised] = usePersisted('ofiskit:maximised', false);
206
+ useEffect(() => {
207
+ if (!maximised)
208
+ return;
209
+ const onKey = (event) => {
210
+ if (event.key === 'Escape')
211
+ setMaximised(false);
212
+ };
213
+ globalThis.addEventListener('keydown', onKey);
214
+ return () => globalThis.removeEventListener('keydown', onKey);
215
+ }, [maximised, setMaximised]);
216
+ return [maximised, setMaximised];
217
+ }
218
+ /** The control that toggles it, so the label and the icon stay in one place. */
219
+ export function MaximiseButton({ maximised, onToggle, }) {
220
+ return (_jsxs("button", { type: "button", onClick: onToggle, className: "inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm hover:bg-base-200 focus-visible:outline-2 focus-visible:outline-primary", "aria-pressed": maximised, children: [_jsx(Icon, { name: maximised ? 'minimize' : 'maximize', size: "sm" }), maximised ? 'Restore' : 'Maximise'] }));
221
+ }
222
+ //# sourceMappingURL=OfficeMap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OfficeMap.js","sourceRoot":"","sources":["../src/OfficeMap.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { Token } from './placement.js';
2
+ import type { LiveReaction } from './useCall.js';
3
+ /**
4
+ * The people a room has no cell for.
5
+ *
6
+ * User areas are display slots, not seats, so a room can hold more people than it
7
+ * has cells — and when it does, the last cell counts the rest. The counter is drawn
8
+ * **as an avatar** rather than as a badge: it takes a person's place in the room,
9
+ * it sits in a person's cell, and the thing it stands for is people. A small pill
10
+ * in a row of faces reads as a notification rather than as four more colleagues.
11
+ *
12
+ * Opening it shows those people the same way the room shows everybody else: as
13
+ * avatars, with their status and whatever they are doing in the call, three
14
+ * across and three down, scrolling for the rest. A list of names would be the one
15
+ * place in the office where people stopped looking like people.
16
+ */
17
+ /** Three across and three down, then it scrolls. */
18
+ export declare const OVERFLOW_COLUMNS = 3;
19
+ export declare const OVERFLOW_ROWS = 3;
20
+ /**
21
+ * How tall the list may be.
22
+ *
23
+ * Three rows, and the rest scroll: a room of forty people is a panel of nine faces
24
+ * and a scrollbar, not a panel taller than the map. Fewer when the screen has less
25
+ * room than that on either side of the counter — Radix measures what is available,
26
+ * and a short phone may have less than three rows' worth above or below. The
27
+ * subtraction is the panel's own padding.
28
+ */
29
+ export declare const OVERFLOW_MAX_HEIGHT = "min(224px, calc(var(--radix-popover-content-available-height, 100vh) - 3.5rem))";
30
+ export interface OverflowAvatarProps {
31
+ roomName: string;
32
+ /** Everyone the counter stands for, in arrival order. */
33
+ tokens: readonly Token[];
34
+ /** The room's cell width, so the counter is the size of the faces beside it. */
35
+ size: number;
36
+ reducedMotion?: boolean;
37
+ /** Reactions by person. The same map the room's avatars read. */
38
+ reactions?: ReadonlyMap<string, LiveReaction[]>;
39
+ }
40
+ export declare function OverflowAvatar({ roomName, tokens, size, reducedMotion, reactions, }: OverflowAvatarProps): import("react").JSX.Element;
41
+ //# sourceMappingURL=Overflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Overflow.d.ts","sourceRoot":"","sources":["../src/Overflow.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD;;;;;;;;;;;;;GAaG;AAEH,oDAAoD;AACpD,eAAO,MAAM,gBAAgB,IAAI,CAAA;AACjC,eAAO,MAAM,aAAa,IAAI,CAAA;AAY9B;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,oFAAmI,CAAA;AAenK,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,yDAAyD;IACzD,MAAM,EAAE,SAAS,KAAK,EAAE,CAAA;IACxB,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,iEAAiE;IACjE,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAA;CAChD;AAED,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,MAAM,EACN,IAAI,EACJ,aAAqB,EACrB,SAAS,GACV,EAAE,mBAAmB,+BA8ErB"}