@ai-matrx/meet 0.11.36 → 0.11.37

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.
@@ -138,6 +138,19 @@ function normalizeDeviceError(error, operation) {
138
138
  }
139
139
  }
140
140
 
141
+ // src/core/leave.ts
142
+ var DEFAULT_LEAVE_POLICY = { hostLeavePrompt: "leave_or_end", endConfirm: false };
143
+ function leaveChoices(args) {
144
+ if (args.role !== "host") return ["leave"];
145
+ switch (args.policy.hostLeavePrompt) {
146
+ case "must_assign_or_end":
147
+ return args.othersPresent ? ["assign_and_leave", "end_for_all"] : ["leave", "end_for_all"];
148
+ case "leave_only":
149
+ case "leave_or_end":
150
+ return ["leave", "end_for_all"];
151
+ }
152
+ }
153
+
141
154
  // src/core/tokens.ts
142
155
  var MEET_ROUTES = {
143
156
  token: "/api/v1/meet/token",
@@ -1127,16 +1140,20 @@ function useRoom() {
1127
1140
  async () => requireEngine(engine, "toggleCamera").setCameraEnabled(!(local?.cameraEnabled ?? false)),
1128
1141
  [engine, local]
1129
1142
  ),
1130
- toggleScreenShare: useCallback2(
1131
- async () => requireEngine(engine, "toggleScreenShare").setScreenShareEnabled(
1132
- !(local?.screenSharing ?? false)
1133
- ),
1134
- [engine, local]
1135
- ),
1136
- toggleHand: useCallback2(
1137
- async () => requireEngine(engine, "toggleHand").raiseHand(!(local?.handRaised ?? false)),
1138
- [engine, local]
1139
- ),
1143
+ toggleScreenShare: useCallback2(async () => {
1144
+ const on = !(local?.screenSharing ?? false);
1145
+ await requireEngine(engine, "toggleScreenShare").setScreenShareEnabled(on);
1146
+ const sharing = host?.store.snapshot().participants.find((p) => p.isLocal)?.screenSharing ?? false;
1147
+ if (host !== null && sharing === on) {
1148
+ await host.inRoom.moderate({ action: "present", on }).catch(() => void 0);
1149
+ }
1150
+ }, [engine, host, local]),
1151
+ // S8: a hand is SERVER state (`/moderate raise_hand · lower_hand`, server time), replicated
1152
+ // back as the `matrx.hand` attribute — no client sets its own hand on the wire.
1153
+ toggleHand: useCallback2(async () => {
1154
+ if (host === null) throw noRuntime("toggleHand");
1155
+ await host.inRoom.moderate({ action: local?.handRaised === true ? "lower_hand" : "raise_hand" });
1156
+ }, [host, local]),
1140
1157
  react: useCallback2(
1141
1158
  async (emoji) => requireEngine(engine, "react").react(emoji),
1142
1159
  [engine]
@@ -1265,6 +1282,31 @@ function useWaiting() {
1265
1282
  askAgain: useCallback2(async () => requireSession(session, "askAgain").askAgain(), [session])
1266
1283
  };
1267
1284
  }
1285
+ function useLeave() {
1286
+ const host = useMeetHost();
1287
+ const snapshot = useMeetSnapshot();
1288
+ const session = host?.meetings ?? null;
1289
+ const engine = host?.engine ?? null;
1290
+ const policy = snapshot?.leavePolicy ?? DEFAULT_LEAVE_POLICY;
1291
+ const participants = snapshot?.participants ?? [];
1292
+ const me = participants.find((p) => p.isLocal);
1293
+ const candidates = participants.filter((p) => !p.isLocal && !p.isAgent && p.role !== "viewer");
1294
+ const choices = leaveChoices({ policy, role: me?.role ?? null, othersPresent: candidates.length > 0 });
1295
+ return {
1296
+ choices,
1297
+ policy,
1298
+ candidates,
1299
+ leave: useCallback2(async () => requireEngine(engine, "leave").leave(), [engine]),
1300
+ endForAll: useCallback2(async () => requireSession(session, "endForAll").hostLeave("end_for_all"), [session]),
1301
+ assignAndLeave: useCallback2(
1302
+ async (person) => {
1303
+ await requireSession(session, "assignAndLeave").hostLeave({ assignTo: String(person.identity) });
1304
+ await requireEngine(engine, "leave").leave();
1305
+ },
1306
+ [session, engine]
1307
+ )
1308
+ };
1309
+ }
1268
1310
  function useRecording() {
1269
1311
  const host = useMeetHost();
1270
1312
  const snapshot = useMeetSnapshot();
@@ -3429,19 +3471,75 @@ function ControlBar({ onLeave }) {
3429
3471
  ] }) : null
3430
3472
  ] }) }) : null
3431
3473
  ] }),
3432
- /* @__PURE__ */ jsx6(
3433
- ControlButton,
3474
+ /* @__PURE__ */ jsx6(LeaveControl, { onLeave })
3475
+ ] })
3476
+ ] });
3477
+ }
3478
+ var LEAVE_WORDS = {
3479
+ leave_or_end: { leave: "Leave call", assign_and_leave: "Assign a new host", end_for_all: "End call for everyone" },
3480
+ must_assign_or_end: { leave: "Leave meeting", assign_and_leave: "Assign a new host", end_for_all: "End meeting for all" },
3481
+ leave_only: { leave: "Leave", assign_and_leave: "Assign a new host", end_for_all: "End meeting" }
3482
+ };
3483
+ function LeaveControl({ onLeave }) {
3484
+ const leave = useLeave();
3485
+ const [open, setOpen] = useState4("none");
3486
+ const [failure, setFailure] = useState4(null);
3487
+ const words = LEAVE_WORDS[leave.policy.hostLeavePrompt];
3488
+ const run = (work, leaving) => {
3489
+ setFailure(null);
3490
+ void work().then(() => {
3491
+ setOpen("none");
3492
+ if (leaving) onLeave?.();
3493
+ }).catch((error) => setFailure(error instanceof Error ? error.message : String(error)));
3494
+ };
3495
+ const leaveNow = () => run(leave.leave, true);
3496
+ const endNow = () => run(leave.endForAll, false);
3497
+ return /* @__PURE__ */ jsxs3("span", { className: "mx-meet__anchor", children: [
3498
+ /* @__PURE__ */ jsx6(
3499
+ ControlButton,
3500
+ {
3501
+ label: "Leave",
3502
+ danger: true,
3503
+ active: open !== "none",
3504
+ onClick: () => {
3505
+ if (leave.choices.length === 1 && leave.choices[0] === "leave") leaveNow();
3506
+ else setOpen(open === "none" ? "menu" : "none");
3507
+ },
3508
+ children: /* @__PURE__ */ jsx6(PhoneOffIcon, {})
3509
+ }
3510
+ ),
3511
+ open !== "none" ? /* @__PURE__ */ jsx6(Popover, { label: open === "assign" ? words.assign_and_leave : "Leave", onClose: () => setOpen("none"), children: /* @__PURE__ */ jsxs3("div", { className: "mx-meet__menu", "data-meet-leave-menu": leave.policy.hostLeavePrompt, children: [
3512
+ open === "menu" ? leave.choices.map((choice) => /* @__PURE__ */ jsx6(
3513
+ "button",
3434
3514
  {
3435
- label: "Leave",
3436
- danger: true,
3515
+ type: "button",
3516
+ className: choice === "end_for_all" ? "mx-meet__menu-item mx-meet__menu-item--danger" : "mx-meet__menu-item",
3437
3517
  onClick: () => {
3438
- void room.leave();
3439
- onLeave?.();
3518
+ if (choice === "leave") leaveNow();
3519
+ else if (choice === "assign_and_leave") setOpen("assign");
3520
+ else if (leave.policy.endConfirm) setOpen("confirm");
3521
+ else endNow();
3440
3522
  },
3441
- children: /* @__PURE__ */ jsx6(PhoneOffIcon, {})
3442
- }
3443
- )
3444
- ] })
3523
+ children: words[choice]
3524
+ },
3525
+ choice
3526
+ )) : null,
3527
+ open === "assign" ? leave.candidates.map((person) => /* @__PURE__ */ jsx6(
3528
+ "button",
3529
+ {
3530
+ type: "button",
3531
+ className: "mx-meet__menu-item",
3532
+ onClick: () => run(() => leave.assignAndLeave(person), true),
3533
+ children: person.displayName
3534
+ },
3535
+ String(person.identity)
3536
+ )) : null,
3537
+ open === "confirm" ? /* @__PURE__ */ jsxs3(Fragment, { children: [
3538
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__menu-item mx-meet__menu-item--danger", onClick: endNow, children: words.end_for_all }),
3539
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__menu-item", onClick: () => setOpen("menu"), children: "Cancel" })
3540
+ ] }) : null,
3541
+ failure !== null ? /* @__PURE__ */ jsx6("p", { className: "mx-meet__menu-note", role: "alert", children: failure }) : null
3542
+ ] }) }) : null
3445
3543
  ] });
3446
3544
  }
3447
3545
  function useUnreadChat(open) {