@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
package/dist/index.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The office you can see.
3
+ *
4
+ * React DOM, built on unitykit, and deliberately not platform-agnostic: this is
5
+ * the package that holds the web-only half, so everything below it can run on a
6
+ * phone. In this app the map fills the window; in unityofis the same components
7
+ * render inside the app shell, which adds the header and the switchers and
8
+ * changes nothing here.
9
+ *
10
+ * Nothing here chooses a colour. Every surface is a kit component with a
11
+ * theme-aware background, because the office background is whatever an author
12
+ * generated and nothing legible can be guaranteed on top of it.
13
+ */
14
+ export { ThemeProvider, useTheme } from './theme.js';
15
+ export { AnnouncerProvider, useAnnounce } from './Announcer.js';
16
+ export { KnockDock, OutgoingKnock, } from './Knocks.js';
17
+ export { MaximiseButton, OfficeMap, RoomListView, ViewToggle, useMaximised, } from './OfficeMap.js';
18
+ export { CallAudio } from './CallAudio.js';
19
+ export { CallControls } from './CallControls.js';
20
+ export { CallTiles, TILES_VISIBLE } from './CallTiles.js';
21
+ export { REACTION_VISIBLE_MS, speakerOrder, useCallMedia, useReactions, useSpeakerOrder, } from './useCall.js';
22
+ export { ReactionFloat, ReactionPicker, describeReaction, } from './Reactions.js';
23
+ /**
24
+ * Showing a screen, and the two questions that sometimes come first.
25
+ *
26
+ * The pickers are a seam rather than a feature: on the web the browser asks, and a
27
+ * desktop host supplies its own list of screens and windows because its browser
28
+ * cannot. Nothing here knows Electron exists.
29
+ */
30
+ export { ScreenSourcePicker, ShareStage, SharingBanner, TakeOverDialog, describeShare, } from './Share.js';
31
+ export { useShare, useShareView } from './useShare.js';
32
+ export { createSounds } from './sounds.js';
33
+ export { useSounds } from './useSounds.js';
34
+ export { tileLayout, VIDEO_ASPECT } from './tileLayout.js';
35
+ export { Control, controlClasses } from './controls.js';
36
+ export { DevicePanel, PermissionPrimer } from './DevicePanel.js';
37
+ export { RoomBar } from './RoomBar.js';
38
+ export { StatusControl } from './StatusControl.js';
39
+ export { PersonAvatar } from './PersonAvatar.js';
40
+ export { OVERFLOW_COLUMNS, OVERFLOW_ROWS, OverflowAvatar, } from './Overflow.js';
41
+ export { StatusDot, describeStatus, statusLabel, STATUS_LOOKS } from './status.js';
42
+ export { useClientEvents, useIdleReporting, useMeasured, useMediaQuery, useOffice, usePersisted, useReducedMotion, } from './hooks.js';
43
+ export { fitCanvas, readingOrder, roomInDirection, tilePlacement, toPixels, } from './layout.js';
44
+ export { placeInRoom, tokensFor, } from './placement.js';
45
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAgC,MAAM,YAAY,CAAA;AAElF,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAE/D,OAAO,EACL,SAAS,EACT,aAAa,GAKd,MAAM,aAAa,CAAA;AAEpB,OAAO,EACL,cAAc,EACd,SAAS,EACT,YAAY,EACZ,UAAU,EACV,YAAY,GAGb,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EAAE,SAAS,EAAuB,MAAM,gBAAgB,CAAA;AAC/D,OAAO,EAAE,YAAY,EAA0B,MAAM,mBAAmB,CAAA;AACxE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAuB,MAAM,gBAAgB,CAAA;AAC9E,OAAO,EACL,mBAAmB,EACnB,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,eAAe,GAKhB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,aAAa,EACb,cAAc,EACd,gBAAgB,GAGjB,MAAM,gBAAgB,CAAA;AACvB;;;;;;GAMG;AACH,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,aAAa,EACb,cAAc,EACd,aAAa,GAGd,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAoC,MAAM,eAAe,CAAA;AACxF,OAAO,EAAE,YAAY,EAAe,MAAM,aAAa,CAAA;AACvD,OAAO,EAAE,SAAS,EAAyB,MAAM,gBAAgB,CAAA;AACjE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAmB,MAAM,iBAAiB,CAAA;AAC3E,OAAO,EAAE,OAAO,EAAE,cAAc,EAAqB,MAAM,eAAe,CAAA;AAC1E,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAyB,MAAM,kBAAkB,CAAA;AACvF,OAAO,EAAE,OAAO,EAAqB,MAAM,cAAc,CAAA;AACzD,OAAO,EAAE,aAAa,EAA2B,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,YAAY,EAA0B,MAAM,mBAAmB,CAAA;AACxE,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,cAAc,GAEf,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAuB,MAAM,aAAa,CAAA;AAEvG,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,SAAS,EACT,YAAY,EACZ,gBAAgB,GACjB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,SAAS,EACT,YAAY,EACZ,eAAe,EACf,aAAa,EACb,QAAQ,GAET,MAAM,aAAa,CAAA;AAEpB,OAAO,EACL,WAAW,EACX,SAAS,GAKV,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,65 @@
1
+ import { type CanvasShape, type Rect } from '@unityevolv/ofiskit-template';
2
+ /**
3
+ * Turning the template's normalized geometry into pixels.
4
+ *
5
+ * Everything in a template is 0..1 against the canvas, so the layout holds at
6
+ * any width. What changes per screen is where the canvas itself sits, and that
7
+ * is the only thing this file works out.
8
+ */
9
+ export interface CanvasBox {
10
+ /** Offset of the canvas inside its container. */
11
+ left: number;
12
+ top: number;
13
+ width: number;
14
+ height: number;
15
+ }
16
+ /**
17
+ * Fit the canvas into the space available, letterboxed rather than cropped.
18
+ *
19
+ * Cropping would be prettier on an awkward screen and is not an option: a room
20
+ * drawn at the edge of the canvas would be cut off, and somebody would be
21
+ * standing in a room nobody can click.
22
+ */
23
+ export declare function fitCanvas(shape: CanvasShape, container: {
24
+ width: number;
25
+ height: number;
26
+ }): CanvasBox;
27
+ /** A normalized rectangle, in pixels, positioned inside the canvas. */
28
+ export declare function toPixels(rect: Rect, canvas: CanvasBox): {
29
+ left: number;
30
+ top: number;
31
+ width: number;
32
+ height: number;
33
+ };
34
+ /**
35
+ * Where the call tiles go, which the canvas shape decides.
36
+ *
37
+ * A landscape office has width to spare and height at a premium, so the tiles
38
+ * go across the top. A square or portrait one is the other way round, so they
39
+ * go down the right. The author sees this in the builder preview, so the
40
+ * decision is visible before anybody is in the room.
41
+ */
42
+ export declare function tilePlacement(shape: CanvasShape): 'top' | 'right';
43
+ /**
44
+ * Reading order for keyboard navigation.
45
+ *
46
+ * Left to right, top to bottom, as the canvas is drawn. Sorting by row first
47
+ * with a tolerance stops two rooms whose tops differ by a pixel being read in
48
+ * the wrong order, which is the difference between a tab order that makes sense
49
+ * and one that jumps around the screen.
50
+ */
51
+ export declare function readingOrder<T extends {
52
+ rect: Rect;
53
+ }>(rooms: readonly T[]): T[];
54
+ /**
55
+ * The nearest room in a direction, for the arrow keys.
56
+ *
57
+ * Measured from centre to centre, only considering rooms that are genuinely in
58
+ * that direction, and preferring ones that are close on the other axis too, so
59
+ * pressing right moves to the room beside you rather than one diagonally across
60
+ * the office that happens to be nearer.
61
+ */
62
+ export declare function roomInDirection<T extends {
63
+ rect: Rect;
64
+ }>(rooms: readonly T[], from: T, direction: 'left' | 'right' | 'up' | 'down'): T | null;
65
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,WAAW,EAAE,KAAK,IAAI,EAAE,MAAM,8BAA8B,CAAA;AAExF;;;;;;GAMG;AAEH,MAAM,WAAW,SAAS;IACxB,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3C,SAAS,CAgBX;AAED,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS;;;;;EAOrD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,GAAG,OAAO,CAEjE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAM/E;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,EACtD,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,IAAI,EAAE,CAAC,EACP,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,GAC1C,CAAC,GAAG,IAAI,CA8BV"}
package/dist/layout.js ADDED
@@ -0,0 +1,93 @@
1
+ import { CANVAS_RATIO } from '@unityevolv/ofiskit-template';
2
+ /**
3
+ * Fit the canvas into the space available, letterboxed rather than cropped.
4
+ *
5
+ * Cropping would be prettier on an awkward screen and is not an option: a room
6
+ * drawn at the edge of the canvas would be cut off, and somebody would be
7
+ * standing in a room nobody can click.
8
+ */
9
+ export function fitCanvas(shape, container) {
10
+ const ratio = CANVAS_RATIO[shape];
11
+ if (container.width <= 0 || container.height <= 0) {
12
+ return { left: 0, top: 0, width: 0, height: 0 };
13
+ }
14
+ const byWidth = container.width / ratio <= container.height;
15
+ const width = byWidth ? container.width : container.height * ratio;
16
+ const height = byWidth ? container.width / ratio : container.height;
17
+ return {
18
+ left: (container.width - width) / 2,
19
+ top: (container.height - height) / 2,
20
+ width,
21
+ height,
22
+ };
23
+ }
24
+ /** A normalized rectangle, in pixels, positioned inside the canvas. */
25
+ export function toPixels(rect, canvas) {
26
+ return {
27
+ left: canvas.left + rect.x * canvas.width,
28
+ top: canvas.top + rect.y * canvas.height,
29
+ width: rect.width * canvas.width,
30
+ height: rect.height * canvas.height,
31
+ };
32
+ }
33
+ /**
34
+ * Where the call tiles go, which the canvas shape decides.
35
+ *
36
+ * A landscape office has width to spare and height at a premium, so the tiles
37
+ * go across the top. A square or portrait one is the other way round, so they
38
+ * go down the right. The author sees this in the builder preview, so the
39
+ * decision is visible before anybody is in the room.
40
+ */
41
+ export function tilePlacement(shape) {
42
+ return shape === 'landscape' ? 'top' : 'right';
43
+ }
44
+ /**
45
+ * Reading order for keyboard navigation.
46
+ *
47
+ * Left to right, top to bottom, as the canvas is drawn. Sorting by row first
48
+ * with a tolerance stops two rooms whose tops differ by a pixel being read in
49
+ * the wrong order, which is the difference between a tab order that makes sense
50
+ * and one that jumps around the screen.
51
+ */
52
+ export function readingOrder(rooms) {
53
+ const ROW_TOLERANCE = 0.08;
54
+ return [...rooms].sort((a, b) => {
55
+ const sameRow = Math.abs(a.rect.y - b.rect.y) < ROW_TOLERANCE;
56
+ return sameRow ? a.rect.x - b.rect.x : a.rect.y - b.rect.y;
57
+ });
58
+ }
59
+ /**
60
+ * The nearest room in a direction, for the arrow keys.
61
+ *
62
+ * Measured from centre to centre, only considering rooms that are genuinely in
63
+ * that direction, and preferring ones that are close on the other axis too, so
64
+ * pressing right moves to the room beside you rather than one diagonally across
65
+ * the office that happens to be nearer.
66
+ */
67
+ export function roomInDirection(rooms, from, direction) {
68
+ const centre = (rect) => ({ x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 });
69
+ const origin = centre(from.rect);
70
+ let best = null;
71
+ let bestScore = Number.POSITIVE_INFINITY;
72
+ for (const room of rooms) {
73
+ if (room === from)
74
+ continue;
75
+ const target = centre(room.rect);
76
+ const dx = target.x - origin.x;
77
+ const dy = target.y - origin.y;
78
+ const goes = direction === 'left' ? dx < -0.001 : direction === 'right' ? dx > 0.001 : direction === 'up' ? dy < -0.001 : dy > 0.001;
79
+ if (!goes)
80
+ continue;
81
+ // Distance along the direction, plus a penalty for drifting off to the
82
+ // side. The penalty is what makes this feel like moving through a grid.
83
+ const along = direction === 'left' || direction === 'right' ? Math.abs(dx) : Math.abs(dy);
84
+ const across = direction === 'left' || direction === 'right' ? Math.abs(dy) : Math.abs(dx);
85
+ const score = along + across * 2;
86
+ if (score < bestScore) {
87
+ bestScore = score;
88
+ best = room;
89
+ }
90
+ }
91
+ return best;
92
+ }
93
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ import type { PublicPresence } from '@unityevolv/ofiskit-realtime-client';
2
+ import { type AvatarSize, type CanvasShape, type Rect, type Room } from '@unityevolv/ofiskit-template';
3
+ /**
4
+ * Putting people into a room's cells.
5
+ *
6
+ * User areas are **display slots, not seats**. Capacity is a separate,
7
+ * office-level number, so a room can perfectly well hold more people than it has
8
+ * cells — and when it does, the last cell becomes a counter rather than the
9
+ * layout breaking or avatars piling on top of each other.
10
+ */
11
+ /** One avatar to draw: usually a person, sometimes one of their devices. */
12
+ export interface Token {
13
+ key: string;
14
+ person: PublicPresence;
15
+ /** Set when this avatar is one device of a person drawn once per device. */
16
+ deviceId?: string;
17
+ /** Linked to another avatar for the same person, so the pair reads as one. */
18
+ linked: boolean;
19
+ }
20
+ /**
21
+ * Which of somebody's devices get an avatar of their own.
22
+ *
23
+ * One avatar per person is the answer almost always, because **presence is per
24
+ * user**: somebody with a laptop and a phone open is one person standing in one
25
+ * room, and drawing them twice would say the opposite of what the rest of the
26
+ * product says. The device badge is how you tell they are on a phone.
27
+ *
28
+ * The exception is a call. Two devices in one call are two real legs in the mesh,
29
+ * each with its own camera and microphone, and each takes a place in it — so they
30
+ * are drawn as two linked avatars. That predicate needs call state, which is why
31
+ * this is a parameter rather than a rule written in here, and why the caller that
32
+ * passes one arrives with the call stories.
33
+ */
34
+ export type DevicesToDraw = (person: PublicPresence) => PublicPresence['devices'];
35
+ /**
36
+ * Expand the people in a room into the avatars to draw.
37
+ *
38
+ * Arrival order, so somebody who has been in the corner of a room for an hour is
39
+ * still in that corner after four other people have come and gone.
40
+ */
41
+ export declare function tokensFor(people: readonly PublicPresence[], devicesToDraw?: DevicesToDraw): Token[];
42
+ export interface PlacedAvatar {
43
+ token: Token;
44
+ /** Normalized against the canvas, like everything else in a template. */
45
+ rect: Rect;
46
+ }
47
+ export interface Placement {
48
+ placed: PlacedAvatar[];
49
+ /** The cell the counter goes in, when there are more people than cells. */
50
+ overflowAt: Rect | null;
51
+ /** Everyone the counter stands for. */
52
+ overflow: Token[];
53
+ /** The size of one cell, normalized, for sizing the avatars. */
54
+ cell: {
55
+ width: number;
56
+ height: number;
57
+ };
58
+ }
59
+ /**
60
+ * Lay a room's avatars out over its user areas.
61
+ *
62
+ * Cells are filled in the order the author placed the areas, and within an area
63
+ * left to right then top to bottom, which is how somebody reads the picture.
64
+ *
65
+ * When there are more people than cells, the **last** cell becomes a counter.
66
+ * Four cells and twelve people shows three faces and a `+9`, rather than
67
+ * shrinking everyone to nothing or spilling outside the room.
68
+ */
69
+ export declare function placeInRoom(room: Room, people: readonly PublicPresence[], shape: CanvasShape, avatarSize: AvatarSize, devicesToDraw?: DevicesToDraw): Placement;
70
+ //# sourceMappingURL=placement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placement.d.ts","sourceRoot":"","sources":["../src/placement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAA;AACzE,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,IAAI,EACT,KAAK,IAAI,EACV,MAAM,8BAA8B,CAAA;AAErC;;;;;;;GAOG;AAEH,4EAA4E;AAC5E,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,cAAc,CAAA;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8EAA8E;IAC9E,MAAM,EAAE,OAAO,CAAA;CAChB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,cAAc,KAAK,cAAc,CAAC,SAAS,CAAC,CAAA;AAKjF;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,SAAS,cAAc,EAAE,EACjC,aAAa,GAAE,aAA2B,GACzC,KAAK,EAAE,CAuBT;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,KAAK,CAAA;IACZ,yEAAyE;IACzE,IAAI,EAAE,IAAI,CAAA;CACX;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,YAAY,EAAE,CAAA;IACtB,2EAA2E;IAC3E,UAAU,EAAE,IAAI,GAAG,IAAI,CAAA;IACvB,uCAAuC;IACvC,QAAQ,EAAE,KAAK,EAAE,CAAA;IACjB,gEAAgE;IAChE,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CACxC;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,SAAS,cAAc,EAAE,EACjC,KAAK,EAAE,WAAW,EAClB,UAAU,EAAE,UAAU,EACtB,aAAa,CAAC,EAAE,aAAa,GAC5B,SAAS,CA4CX"}
@@ -0,0 +1,83 @@
1
+ import { avatarUnit, } from '@unityevolv/ofiskit-template';
2
+ /** One avatar per person. What the map uses until calls exist. */
3
+ const asOnePerson = () => [];
4
+ /**
5
+ * Expand the people in a room into the avatars to draw.
6
+ *
7
+ * Arrival order, so somebody who has been in the corner of a room for an hour is
8
+ * still in that corner after four other people have come and gone.
9
+ */
10
+ export function tokensFor(people, devicesToDraw = asOnePerson) {
11
+ const tokens = [];
12
+ for (const person of people) {
13
+ const separately = devicesToDraw(person);
14
+ // One device drawn separately is still just the person, so it gets the plain
15
+ // avatar rather than a link to nothing.
16
+ if (separately.length > 1) {
17
+ for (const device of separately) {
18
+ tokens.push({
19
+ key: `${person.userId}:${device.deviceId}`,
20
+ person,
21
+ deviceId: device.deviceId,
22
+ linked: true,
23
+ });
24
+ }
25
+ }
26
+ else {
27
+ tokens.push({ key: person.userId, person, linked: false });
28
+ }
29
+ }
30
+ return tokens;
31
+ }
32
+ /**
33
+ * Lay a room's avatars out over its user areas.
34
+ *
35
+ * Cells are filled in the order the author placed the areas, and within an area
36
+ * left to right then top to bottom, which is how somebody reads the picture.
37
+ *
38
+ * When there are more people than cells, the **last** cell becomes a counter.
39
+ * Four cells and twelve people shows three faces and a `+9`, rather than
40
+ * shrinking everyone to nothing or spilling outside the room.
41
+ */
42
+ export function placeInRoom(room, people, shape, avatarSize, devicesToDraw) {
43
+ const unit = avatarUnit(shape, avatarSize);
44
+ const tokens = tokensFor(people, devicesToDraw);
45
+ const cells = [];
46
+ for (const area of room.areas) {
47
+ for (let row = 0; row < area.rows; row += 1) {
48
+ for (let column = 0; column < area.columns; column += 1) {
49
+ cells.push({
50
+ x: area.x + column * unit.width,
51
+ y: area.y + row * unit.height,
52
+ width: unit.width,
53
+ height: unit.height,
54
+ });
55
+ }
56
+ }
57
+ }
58
+ if (cells.length === 0 || tokens.length === 0) {
59
+ return { placed: [], overflowAt: null, overflow: [], cell: unit };
60
+ }
61
+ if (tokens.length <= cells.length) {
62
+ return {
63
+ placed: tokens.flatMap((token, index) => {
64
+ const rect = cells[index];
65
+ return rect ? [{ token, rect }] : [];
66
+ }),
67
+ overflowAt: null,
68
+ overflow: [],
69
+ cell: unit,
70
+ };
71
+ }
72
+ const shown = cells.length - 1;
73
+ return {
74
+ placed: tokens.slice(0, shown).flatMap((token, index) => {
75
+ const rect = cells[index];
76
+ return rect ? [{ token, rect }] : [];
77
+ }),
78
+ overflowAt: cells[shown] ?? null,
79
+ overflow: tokens.slice(shown),
80
+ cell: unit,
81
+ };
82
+ }
83
+ //# sourceMappingURL=placement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placement.js","sourceRoot":"","sources":["../src/placement.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The office's sounds: a knock at the door, and a chime when somebody arrives.
3
+ *
4
+ * Made here with the Web Audio API rather than played from files. There is no audio
5
+ * file to ship, license or fetch — this repository never reaches anything it does not
6
+ * contain — and each sound is a few lines of arithmetic that can be tuned in place.
7
+ *
8
+ * Both are short and quiet on purpose. A notification sound in an office is heard by
9
+ * everybody near the person's desk, and a sound that is too long or too loud is one
10
+ * people turn off, after which it tells nobody anything.
11
+ */
12
+ export interface Sounds {
13
+ /** Somebody is knocking on the door of the room you are in. */
14
+ knock(): void;
15
+ /** Somebody walked into the room you are in, or you were let into one. */
16
+ chime(): void;
17
+ /** Send the sounds to the speaker chosen for calls, where the browser can. */
18
+ useSpeaker(deviceId: string | undefined): void;
19
+ }
20
+ /**
21
+ * The browser's sounds, made on demand.
22
+ *
23
+ * The audio context is created on the first sound rather than up front: browsers
24
+ * refuse to start one before the page has been interacted with, and by the time
25
+ * there is somebody to knock, the person has at least pressed "Walk in". If the
26
+ * browser still refuses, or has no Web Audio at all, the sound is silently skipped —
27
+ * a missing chime is never worth an error.
28
+ */
29
+ export declare function createSounds(): Sounds;
30
+ //# sourceMappingURL=sounds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sounds.d.ts","sourceRoot":"","sources":["../src/sounds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,MAAM;IACrB,+DAA+D;IAC/D,KAAK,IAAI,IAAI,CAAA;IACb,0EAA0E;IAC1E,KAAK,IAAI,IAAI,CAAA;IACb,8EAA8E;IAC9E,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAA;CAC/C;AAOD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAmCrC"}
package/dist/sounds.js ADDED
@@ -0,0 +1,125 @@
1
+ /**
2
+ * The office's sounds: a knock at the door, and a chime when somebody arrives.
3
+ *
4
+ * Made here with the Web Audio API rather than played from files. There is no audio
5
+ * file to ship, license or fetch — this repository never reaches anything it does not
6
+ * contain — and each sound is a few lines of arithmetic that can be tuned in place.
7
+ *
8
+ * Both are short and quiet on purpose. A notification sound in an office is heard by
9
+ * everybody near the person's desk, and a sound that is too long or too loud is one
10
+ * people turn off, after which it tells nobody anything.
11
+ */
12
+ /** The loudest either sound gets. Well under full scale: this is a nudge, not an alarm. */
13
+ const VOLUME = 0.35;
14
+ /**
15
+ * The browser's sounds, made on demand.
16
+ *
17
+ * The audio context is created on the first sound rather than up front: browsers
18
+ * refuse to start one before the page has been interacted with, and by the time
19
+ * there is somebody to knock, the person has at least pressed "Walk in". If the
20
+ * browser still refuses, or has no Web Audio at all, the sound is silently skipped —
21
+ * a missing chime is never worth an error.
22
+ */
23
+ export function createSounds() {
24
+ let context = null;
25
+ let speaker;
26
+ const ready = () => {
27
+ const Context = globalThis.AudioContext;
28
+ if (!Context)
29
+ return null;
30
+ try {
31
+ context ??= new Context();
32
+ if (context.state === 'suspended')
33
+ void context.resume().catch(() => { });
34
+ if (speaker && typeof context.setSinkId === 'function') {
35
+ void context.setSinkId(speaker).catch(() => { });
36
+ }
37
+ return context;
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ };
43
+ return {
44
+ knock() {
45
+ const audio = ready();
46
+ if (audio)
47
+ knockAt(audio, audio.currentTime);
48
+ },
49
+ chime() {
50
+ const audio = ready();
51
+ if (audio)
52
+ chimeAt(audio, audio.currentTime);
53
+ },
54
+ useSpeaker(deviceId) {
55
+ speaker = deviceId;
56
+ if (context && deviceId && typeof context.setSinkId === 'function') {
57
+ void context.setSinkId(deviceId).catch(() => { });
58
+ }
59
+ },
60
+ };
61
+ }
62
+ /**
63
+ * Two knuckle taps on a wooden door: knock-knock.
64
+ *
65
+ * Each tap is a low tone that drops in pitch as it dies — the body of the door —
66
+ * over a short burst of filtered noise, which is the knuckle. Two of them, a little
67
+ * under a fifth of a second apart, is what reads as a knock rather than a thud.
68
+ */
69
+ function knockAt(audio, start) {
70
+ for (const offset of [0, 0.17]) {
71
+ const at = start + offset;
72
+ const body = audio.createOscillator();
73
+ body.type = 'triangle';
74
+ body.frequency.setValueAtTime(180, at);
75
+ body.frequency.exponentialRampToValueAtTime(70, at + 0.09);
76
+ const bodyGain = audio.createGain();
77
+ bodyGain.gain.setValueAtTime(0.0001, at);
78
+ bodyGain.gain.exponentialRampToValueAtTime(VOLUME, at + 0.004);
79
+ bodyGain.gain.exponentialRampToValueAtTime(0.0001, at + 0.14);
80
+ body.connect(bodyGain).connect(audio.destination);
81
+ body.start(at);
82
+ body.stop(at + 0.15);
83
+ const noise = audio.createBufferSource();
84
+ noise.buffer = noiseBuffer(audio, 0.04);
85
+ const knuckle = audio.createBiquadFilter();
86
+ knuckle.type = 'lowpass';
87
+ knuckle.frequency.value = 1200;
88
+ const noiseGain = audio.createGain();
89
+ noiseGain.gain.setValueAtTime(VOLUME * 0.6, at);
90
+ noiseGain.gain.exponentialRampToValueAtTime(0.0001, at + 0.04);
91
+ noise.connect(knuckle).connect(noiseGain).connect(audio.destination);
92
+ noise.start(at);
93
+ noise.stop(at + 0.05);
94
+ }
95
+ }
96
+ /**
97
+ * A short, bright "bling": two bell-like notes a fifth apart, the second a beat
98
+ * behind the first, both fading over about half a second.
99
+ */
100
+ function chimeAt(audio, start) {
101
+ for (const [frequency, offset] of [
102
+ [1046.5, 0],
103
+ [1568, 0.09],
104
+ ]) {
105
+ const at = start + offset;
106
+ const tone = audio.createOscillator();
107
+ tone.type = 'sine';
108
+ tone.frequency.setValueAtTime(frequency, at);
109
+ const gain = audio.createGain();
110
+ gain.gain.setValueAtTime(0.0001, at);
111
+ gain.gain.exponentialRampToValueAtTime(VOLUME * 0.7, at + 0.01);
112
+ gain.gain.exponentialRampToValueAtTime(0.0001, at + 0.55);
113
+ tone.connect(gain).connect(audio.destination);
114
+ tone.start(at);
115
+ tone.stop(at + 0.6);
116
+ }
117
+ }
118
+ function noiseBuffer(audio, seconds) {
119
+ const buffer = audio.createBuffer(1, Math.max(1, Math.floor(audio.sampleRate * seconds)), audio.sampleRate);
120
+ const samples = buffer.getChannelData(0);
121
+ for (let index = 0; index < samples.length; index += 1)
122
+ samples[index] = Math.random() * 2 - 1;
123
+ return buffer;
124
+ }
125
+ //# sourceMappingURL=sounds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sounds.js","sourceRoot":"","sources":["../src/sounds.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import type { Status } from '@unityevolv/ofiskit-realtime-client';
2
+ /**
3
+ * One visual language for status, used on the map, in the list, on the room bar,
4
+ * on a video tile and next to a name.
5
+ *
6
+ * The rule that shapes this file: **nothing is conveyed by colour alone.** Every
7
+ * status is a different shape as well as a different colour, so it survives
8
+ * being looked at by somebody who cannot tell green from amber, and it survives
9
+ * being printed, screenshotted or rendered on a projector that eats saturation.
10
+ *
11
+ * Colours are token classes rather than values, so both themes come from the
12
+ * kit and this file never knows either exists.
13
+ */
14
+ export interface StatusLook {
15
+ /** What a screen reader says, and what a tooltip shows. */
16
+ label: string;
17
+ /** A token-backed text colour class; the shape inherits it. */
18
+ tone: string;
19
+ /** Drawn inside a 12x12 viewBox, in `currentColor`. */
20
+ shape: React.ReactNode;
21
+ }
22
+ export declare const STATUS_LOOKS: Record<Status, StatusLook>;
23
+ export interface StatusDotProps {
24
+ status: Status;
25
+ size?: number;
26
+ /**
27
+ * Whether to name the status for assistive technology.
28
+ *
29
+ * Off where the surrounding text already says it, which is most places in a
30
+ * list; on where the dot is the only thing carrying the meaning.
31
+ */
32
+ labelled?: boolean;
33
+ className?: string;
34
+ }
35
+ export declare function StatusDot({ status, size, labelled, className }: StatusDotProps): import("react").JSX.Element;
36
+ export declare function statusLabel(status: Status): string;
37
+ /**
38
+ * Status and custom status as one sentence.
39
+ *
40
+ * What a screen reader hears next to a name, and what a tooltip shows. Built in
41
+ * one place so the map, the list and a tile never word it differently.
42
+ */
43
+ export declare function describeStatus(status: Status, custom?: {
44
+ text: string;
45
+ emoji?: string;
46
+ }): string;
47
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAA;AAEjE;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,UAAU;IACzB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAA;IACb,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAA;IACZ,uDAAuD;IACvD,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;CACvB;AAID,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAiFnD,CAAA;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,IAAS,EAAE,QAAe,EAAE,SAAS,EAAE,EAAE,cAAc,+BAgB1F;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAIhG"}