pptx-react-viewer 1.0.11 → 1.0.12

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/dist/index.js CHANGED
@@ -43328,7 +43328,7 @@ var require_use_sync_external_store_shim_development = __commonJS({
43328
43328
  return x2 === y && (0 !== x2 || 1 / x2 === 1 / y) || x2 !== x2 && y !== y;
43329
43329
  }
43330
43330
  function useSyncExternalStore$2(subscribe3, getSnapshot2) {
43331
- didWarnOld18Alpha || void 0 === React95.startTransition || (didWarnOld18Alpha = true, console.error(
43331
+ didWarnOld18Alpha || void 0 === React96.startTransition || (didWarnOld18Alpha = true, console.error(
43332
43332
  "You are using an outdated, pre-release alpha of React 18 that does not support useSyncExternalStore. The use-sync-external-store shim will not work correctly. Upgrade to a newer pre-release."
43333
43333
  ));
43334
43334
  var value = getSnapshot2();
@@ -43338,7 +43338,7 @@ var require_use_sync_external_store_shim_development = __commonJS({
43338
43338
  "The result of getSnapshot should be cached to avoid an infinite loop"
43339
43339
  ), didWarnUncachedGetSnapshot = true);
43340
43340
  }
43341
- cachedValue = useState84({
43341
+ cachedValue = useState85({
43342
43342
  inst: { value, getSnapshot: getSnapshot2 }
43343
43343
  });
43344
43344
  var inst = cachedValue[0].inst, forceUpdate = cachedValue[1];
@@ -43350,7 +43350,7 @@ var require_use_sync_external_store_shim_development = __commonJS({
43350
43350
  },
43351
43351
  [subscribe3, value, getSnapshot2]
43352
43352
  );
43353
- useEffect68(
43353
+ useEffect71(
43354
43354
  function() {
43355
43355
  checkIfSnapshotChanged(inst) && forceUpdate({ inst });
43356
43356
  return subscribe3(function() {
@@ -43376,8 +43376,8 @@ var require_use_sync_external_store_shim_development = __commonJS({
43376
43376
  return getSnapshot2();
43377
43377
  }
43378
43378
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
43379
- var React95 = __require("react"), objectIs = "function" === typeof Object.is ? Object.is : is2, useState84 = React95.useState, useEffect68 = React95.useEffect, useLayoutEffect7 = React95.useLayoutEffect, useDebugValue = React95.useDebugValue, didWarnOld18Alpha = false, didWarnUncachedGetSnapshot = false, shim = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? useSyncExternalStore$1 : useSyncExternalStore$2;
43380
- exports$1.useSyncExternalStore = void 0 !== React95.useSyncExternalStore ? React95.useSyncExternalStore : shim;
43379
+ var React96 = __require("react"), objectIs = "function" === typeof Object.is ? Object.is : is2, useState85 = React96.useState, useEffect71 = React96.useEffect, useLayoutEffect7 = React96.useLayoutEffect, useDebugValue = React96.useDebugValue, didWarnOld18Alpha = false, didWarnUncachedGetSnapshot = false, shim = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? useSyncExternalStore$1 : useSyncExternalStore$2;
43380
+ exports$1.useSyncExternalStore = void 0 !== React96.useSyncExternalStore ? React96.useSyncExternalStore : shim;
43381
43381
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
43382
43382
  })();
43383
43383
  }
@@ -43400,9 +43400,9 @@ var require_with_selector_development = __commonJS({
43400
43400
  return x2 === y && (0 !== x2 || 1 / x2 === 1 / y) || x2 !== x2 && y !== y;
43401
43401
  }
43402
43402
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
43403
- var React95 = __require("react"), shim = require_shim(), objectIs = "function" === typeof Object.is ? Object.is : is2, useSyncExternalStore3 = shim.useSyncExternalStore, useRef69 = React95.useRef, useEffect68 = React95.useEffect, useMemo41 = React95.useMemo, useDebugValue = React95.useDebugValue;
43403
+ var React96 = __require("react"), shim = require_shim(), objectIs = "function" === typeof Object.is ? Object.is : is2, useSyncExternalStore3 = shim.useSyncExternalStore, useRef72 = React96.useRef, useEffect71 = React96.useEffect, useMemo41 = React96.useMemo, useDebugValue = React96.useDebugValue;
43404
43404
  exports$1.useSyncExternalStoreWithSelector = function(subscribe3, getSnapshot2, getServerSnapshot2, selector, isEqual) {
43405
- var instRef = useRef69(null);
43405
+ var instRef = useRef72(null);
43406
43406
  if (null === instRef.current) {
43407
43407
  var inst = { hasValue: false, value: null };
43408
43408
  instRef.current = inst;
@@ -43443,7 +43443,7 @@ var require_with_selector_development = __commonJS({
43443
43443
  [getSnapshot2, getServerSnapshot2, selector, isEqual]
43444
43444
  );
43445
43445
  var value = useSyncExternalStore3(subscribe3, instRef[0], instRef[1]);
43446
- useEffect68(
43446
+ useEffect71(
43447
43447
  function() {
43448
43448
  inst.hasValue = true;
43449
43449
  inst.value = value;
@@ -91128,6 +91128,542 @@ function useVirtualizedSlides({
91128
91128
  scrollToIndex
91129
91129
  };
91130
91130
  }
91131
+
91132
+ // src/viewer/hooks/collaboration/sanitize.ts
91133
+ var ROOM_ID_REGEX = /^[a-zA-Z0-9_-]{1,128}$/;
91134
+ function validateRoomId(roomId) {
91135
+ if (!ROOM_ID_REGEX.test(roomId)) {
91136
+ throw new Error(
91137
+ `Invalid collaboration room ID: "${roomId}". Must be 1-128 alphanumeric characters, hyphens, or underscores.`
91138
+ );
91139
+ }
91140
+ return roomId;
91141
+ }
91142
+ function sanitizeUserName(name) {
91143
+ if (typeof name !== "string") {
91144
+ return "Anonymous";
91145
+ }
91146
+ const stripped = name.replace(/<[^>]*>/g, "");
91147
+ const trimmed = stripped.trim().slice(0, 64);
91148
+ return trimmed || "Anonymous";
91149
+ }
91150
+ function clampCursorPosition(value, min2, max2) {
91151
+ if (typeof value !== "number" || !Number.isFinite(value)) {
91152
+ return 0;
91153
+ }
91154
+ const margin = 20;
91155
+ return Math.max(min2 - margin, Math.min(max2 + margin, value));
91156
+ }
91157
+ var HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
91158
+ function sanitizeColor(color, fallback = "#6366f1") {
91159
+ if (typeof color !== "string") {
91160
+ return fallback;
91161
+ }
91162
+ return HEX_COLOR_REGEX.test(color) ? color : fallback;
91163
+ }
91164
+ function sanitizeAvatarUrl(url) {
91165
+ if (typeof url !== "string") {
91166
+ return void 0;
91167
+ }
91168
+ try {
91169
+ const parsed = new URL(url);
91170
+ if (parsed.protocol === "https:" || parsed.protocol === "http:" || parsed.protocol === "data:") {
91171
+ return url;
91172
+ }
91173
+ } catch {
91174
+ }
91175
+ return void 0;
91176
+ }
91177
+ function sanitizeSlideIndex(value) {
91178
+ if (typeof value !== "number" || !Number.isFinite(value)) {
91179
+ return 0;
91180
+ }
91181
+ return Math.max(0, Math.floor(value));
91182
+ }
91183
+ function sanitizePresence(raw, canvasWidth, canvasHeight) {
91184
+ if (typeof raw.clientId !== "number") {
91185
+ return null;
91186
+ }
91187
+ return {
91188
+ clientId: raw.clientId,
91189
+ userName: sanitizeUserName(raw.userName),
91190
+ userAvatar: sanitizeAvatarUrl(raw.userAvatar),
91191
+ userColor: sanitizeColor(raw.userColor),
91192
+ activeSlideIndex: sanitizeSlideIndex(raw.activeSlideIndex),
91193
+ cursorX: clampCursorPosition(raw.cursorX, 0, canvasWidth),
91194
+ cursorY: clampCursorPosition(raw.cursorY, 0, canvasHeight),
91195
+ lastUpdated: typeof raw.lastUpdated === "string" ? raw.lastUpdated : (/* @__PURE__ */ new Date()).toISOString(),
91196
+ selectedElementId: typeof raw.selectedElementId === "string" ? raw.selectedElementId.slice(0, 128) : void 0,
91197
+ role: raw.role === "broadcaster" || raw.role === "viewer" || raw.role === "collaborator" ? raw.role : void 0
91198
+ };
91199
+ }
91200
+ var BROADCAST_THROTTLE_MS = 50;
91201
+ var STALE_PRESENCE_MS = 3e4;
91202
+ function usePresenceTracking({
91203
+ awareness,
91204
+ localClientId,
91205
+ userName,
91206
+ userColor,
91207
+ userAvatar,
91208
+ role,
91209
+ canvasWidth,
91210
+ canvasHeight
91211
+ }) {
91212
+ const [remoteUsers, setRemoteUsers] = React10.useState([]);
91213
+ const lastBroadcastRef = React10.useRef(0);
91214
+ const pendingBroadcastRef = React10.useRef(null);
91215
+ const latestLocalState = React10.useRef({});
91216
+ const broadcastPresence = React10.useCallback(
91217
+ (update2) => {
91218
+ if (!awareness) {
91219
+ return;
91220
+ }
91221
+ Object.assign(latestLocalState.current, update2);
91222
+ const now = Date.now();
91223
+ const elapsed = now - lastBroadcastRef.current;
91224
+ const flush = () => {
91225
+ const state2 = {
91226
+ ...latestLocalState.current,
91227
+ userName,
91228
+ userColor,
91229
+ userAvatar,
91230
+ role,
91231
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
91232
+ };
91233
+ awareness.setLocalStateField("presence", state2);
91234
+ lastBroadcastRef.current = Date.now();
91235
+ };
91236
+ if (elapsed >= BROADCAST_THROTTLE_MS) {
91237
+ if (pendingBroadcastRef.current) {
91238
+ clearTimeout(pendingBroadcastRef.current);
91239
+ pendingBroadcastRef.current = null;
91240
+ }
91241
+ flush();
91242
+ } else if (!pendingBroadcastRef.current) {
91243
+ pendingBroadcastRef.current = setTimeout(() => {
91244
+ pendingBroadcastRef.current = null;
91245
+ flush();
91246
+ }, BROADCAST_THROTTLE_MS - elapsed);
91247
+ }
91248
+ },
91249
+ [awareness, userName, userColor, userAvatar, role]
91250
+ );
91251
+ React10.useEffect(() => {
91252
+ if (!awareness) {
91253
+ return;
91254
+ }
91255
+ awareness.setLocalStateField("presence", {
91256
+ userName,
91257
+ userColor,
91258
+ userAvatar,
91259
+ role,
91260
+ activeSlideIndex: 0,
91261
+ cursorX: 0,
91262
+ cursorY: 0,
91263
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
91264
+ });
91265
+ }, [awareness, userName, userColor, userAvatar, role]);
91266
+ React10.useEffect(() => {
91267
+ if (!awareness || localClientId === null) {
91268
+ return;
91269
+ }
91270
+ const handleChange = () => {
91271
+ const now = Date.now();
91272
+ const states = awareness.getStates();
91273
+ const users = [];
91274
+ states.forEach((state2, cid) => {
91275
+ if (cid === localClientId) {
91276
+ return;
91277
+ }
91278
+ const raw = state2?.presence;
91279
+ if (!raw || typeof raw !== "object") {
91280
+ return;
91281
+ }
91282
+ const sanitized = sanitizePresence({ ...raw, clientId: cid }, canvasWidth, canvasHeight);
91283
+ if (!sanitized) {
91284
+ return;
91285
+ }
91286
+ const updatedAt = new Date(sanitized.lastUpdated).getTime();
91287
+ if (Number.isNaN(updatedAt) || now - updatedAt > STALE_PRESENCE_MS) {
91288
+ return;
91289
+ }
91290
+ users.push(sanitized);
91291
+ });
91292
+ setRemoteUsers(users);
91293
+ };
91294
+ awareness.on("change", handleChange);
91295
+ awareness.on("update", handleChange);
91296
+ handleChange();
91297
+ return () => {
91298
+ awareness.off("change", handleChange);
91299
+ awareness.off("update", handleChange);
91300
+ };
91301
+ }, [awareness, localClientId, canvasWidth, canvasHeight]);
91302
+ React10.useEffect(() => {
91303
+ if (!awareness) {
91304
+ return;
91305
+ }
91306
+ const interval = setInterval(() => {
91307
+ awareness.setLocalStateField("presence", {
91308
+ ...latestLocalState.current,
91309
+ userName,
91310
+ userColor,
91311
+ userAvatar,
91312
+ role,
91313
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
91314
+ });
91315
+ }, 1e4);
91316
+ return () => clearInterval(interval);
91317
+ }, [awareness, userName, userColor, userAvatar, role]);
91318
+ React10.useEffect(() => {
91319
+ return () => {
91320
+ if (pendingBroadcastRef.current) {
91321
+ clearTimeout(pendingBroadcastRef.current);
91322
+ }
91323
+ };
91324
+ }, []);
91325
+ return { remoteUsers, broadcastPresence };
91326
+ }
91327
+ function useYjsProvider({ config }) {
91328
+ const [status, setStatus] = React10.useState("disconnected");
91329
+ const [awareness, setAwareness] = React10.useState(null);
91330
+ const [doc2, setDoc] = React10.useState(null);
91331
+ const [clientId, setClientId] = React10.useState(null);
91332
+ const cleanupRef = React10.useRef(null);
91333
+ const init = React10.useCallback(async () => {
91334
+ const roomId = validateRoomId(config.roomId);
91335
+ setStatus("connecting");
91336
+ try {
91337
+ const [Y, { WebsocketProvider: WebsocketProvider2 }] = await Promise.all([Promise.resolve().then(() => (init_yjs(), yjs_exports)), Promise.resolve().then(() => (init_y_websocket(), y_websocket_exports))]);
91338
+ const yDoc = new Y.Doc();
91339
+ const provider = new WebsocketProvider2(
91340
+ config.serverUrl,
91341
+ roomId,
91342
+ yDoc,
91343
+ // eslint-disable-line @typescript-eslint/no-explicit-any
91344
+ {
91345
+ params: config.authToken ? { token: config.authToken } : void 0
91346
+ }
91347
+ );
91348
+ const handleStatus = (event) => {
91349
+ if (event.status === "connected") {
91350
+ setStatus("connected");
91351
+ } else if (event.status === "disconnected") {
91352
+ setStatus("disconnected");
91353
+ }
91354
+ };
91355
+ provider.on("status", handleStatus);
91356
+ if (provider.wsconnected) {
91357
+ setStatus("connected");
91358
+ }
91359
+ setDoc(yDoc);
91360
+ setAwareness(provider.awareness);
91361
+ setClientId(provider.awareness.clientID);
91362
+ cleanupRef.current = () => {
91363
+ provider.off("status", handleStatus);
91364
+ provider.destroy();
91365
+ yDoc.destroy();
91366
+ setDoc(null);
91367
+ setAwareness(null);
91368
+ setClientId(null);
91369
+ setStatus("disconnected");
91370
+ };
91371
+ } catch (err) {
91372
+ console.warn(
91373
+ "[pptx-viewer] Collaboration packages not available:",
91374
+ err instanceof Error ? err.message : err
91375
+ );
91376
+ setStatus("error");
91377
+ }
91378
+ }, [config.roomId, config.serverUrl, config.authToken]);
91379
+ React10.useEffect(() => {
91380
+ init();
91381
+ return () => {
91382
+ cleanupRef.current?.();
91383
+ cleanupRef.current = null;
91384
+ };
91385
+ }, [init]);
91386
+ return { status, awareness, doc: doc2, clientId };
91387
+ }
91388
+
91389
+ // src/viewer/hooks/collaboration/useCollaborativeState.ts
91390
+ function useCollaborativeState({
91391
+ config,
91392
+ canvasWidth,
91393
+ canvasHeight
91394
+ }) {
91395
+ const userColor = sanitizeColor(config.userColor, "#6366f1");
91396
+ const { status, awareness, doc: doc2, clientId } = useYjsProvider({ config });
91397
+ const { remoteUsers, broadcastPresence } = usePresenceTracking({
91398
+ awareness,
91399
+ localClientId: clientId,
91400
+ userName: config.userName,
91401
+ userColor,
91402
+ userAvatar: config.userAvatar,
91403
+ role: config.role,
91404
+ canvasWidth,
91405
+ canvasHeight
91406
+ });
91407
+ const connectedCount = status === "connected" ? remoteUsers.length + 1 : remoteUsers.length;
91408
+ return {
91409
+ status,
91410
+ remoteUsers,
91411
+ broadcastPresence,
91412
+ connectedCount,
91413
+ config,
91414
+ doc: doc2
91415
+ };
91416
+ }
91417
+ var CollaborationContext = React10.createContext(null);
91418
+ function useCollaboration() {
91419
+ return React10.useContext(CollaborationContext);
91420
+ }
91421
+ function CollaborationProvider({
91422
+ config,
91423
+ canvasWidth,
91424
+ canvasHeight,
91425
+ children
91426
+ }) {
91427
+ const value = useCollaborativeState({
91428
+ config,
91429
+ canvasWidth,
91430
+ canvasHeight
91431
+ });
91432
+ return /* @__PURE__ */ jsxRuntime.jsx(CollaborationContext.Provider, { value, children });
91433
+ }
91434
+ function RemoteUserCursors({
91435
+ remoteUsers,
91436
+ activeSlideIndex,
91437
+ canvasWidth,
91438
+ canvasHeight
91439
+ }) {
91440
+ const visibleUsers = remoteUsers.filter((u2) => u2.activeSlideIndex === activeSlideIndex);
91441
+ if (visibleUsers.length === 0) {
91442
+ return null;
91443
+ }
91444
+ return /* @__PURE__ */ jsxRuntime.jsx(
91445
+ "svg",
91446
+ {
91447
+ "data-testid": "remote-user-cursors",
91448
+ "data-export-ignore": "true",
91449
+ className: "absolute inset-0 pointer-events-none",
91450
+ style: { zIndex: 9999 },
91451
+ width: canvasWidth,
91452
+ height: canvasHeight,
91453
+ viewBox: `0 0 ${canvasWidth} ${canvasHeight}`,
91454
+ "aria-hidden": "true",
91455
+ children: visibleUsers.map((user) => /* @__PURE__ */ jsxRuntime.jsxs(
91456
+ "g",
91457
+ {
91458
+ transform: `translate(${user.cursorX}, ${user.cursorY})`,
91459
+ "data-testid": `remote-cursor-${user.clientId}`,
91460
+ children: [
91461
+ /* @__PURE__ */ jsxRuntime.jsx(
91462
+ "path",
91463
+ {
91464
+ d: "M0 0 L0 16 L4.5 12.5 L8 20 L10.5 19 L7 11.5 L12 11 Z",
91465
+ fill: user.userColor,
91466
+ stroke: "#fff",
91467
+ strokeWidth: 1,
91468
+ opacity: 0.9
91469
+ }
91470
+ ),
91471
+ /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: "translate(14, 18)", children: [
91472
+ /* @__PURE__ */ jsxRuntime.jsx(
91473
+ "rect",
91474
+ {
91475
+ rx: 3,
91476
+ ry: 3,
91477
+ x: -2,
91478
+ y: -10,
91479
+ width: Math.min(user.userName.length * 7 + 8, 150),
91480
+ height: 16,
91481
+ fill: user.userColor,
91482
+ opacity: 0.85
91483
+ }
91484
+ ),
91485
+ /* @__PURE__ */ jsxRuntime.jsx(
91486
+ "text",
91487
+ {
91488
+ fill: "#fff",
91489
+ fontSize: 10,
91490
+ fontFamily: "system-ui, sans-serif",
91491
+ fontWeight: 500,
91492
+ dominantBaseline: "central",
91493
+ y: -2,
91494
+ x: 2,
91495
+ children: user.userName.length > 20 ? `${user.userName.slice(0, 18)}...` : user.userName
91496
+ }
91497
+ )
91498
+ ] })
91499
+ ]
91500
+ },
91501
+ user.clientId
91502
+ ))
91503
+ }
91504
+ );
91505
+ }
91506
+ var STATUS_STYLES = {
91507
+ connected: {
91508
+ dot: "bg-green-400",
91509
+ text: "text-green-400",
91510
+ label: "Connected"
91511
+ },
91512
+ connecting: {
91513
+ dot: "bg-yellow-400 animate-pulse",
91514
+ text: "text-yellow-400",
91515
+ label: "Connecting..."
91516
+ },
91517
+ disconnected: {
91518
+ dot: "bg-gray-500",
91519
+ text: "text-gray-500",
91520
+ label: "Disconnected"
91521
+ },
91522
+ error: {
91523
+ dot: "bg-red-400",
91524
+ text: "text-red-400",
91525
+ label: "Connection error"
91526
+ }
91527
+ };
91528
+ function CollaborationStatusIndicator({
91529
+ status,
91530
+ connectedCount
91531
+ }) {
91532
+ const { t: t2 } = reactI18next.useTranslation();
91533
+ const style = STATUS_STYLES[status];
91534
+ return /* @__PURE__ */ jsxRuntime.jsxs(
91535
+ "div",
91536
+ {
91537
+ "data-testid": "collaboration-status",
91538
+ className: "flex items-center gap-1.5",
91539
+ "aria-label": t2("pptx.collaboration.statusAriaLabel", {
91540
+ status: t2(`pptx.collaboration.status.${status}`),
91541
+ count: connectedCount
91542
+ }),
91543
+ children: [
91544
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-block w-2 h-2 rounded-full ${style.dot}`, "aria-hidden": "true" }),
91545
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-[10px] ${style.text}`, children: status === "connected" ? t2("pptx.collaboration.userCount", { count: connectedCount }) : t2(`pptx.collaboration.status.${status}`) })
91546
+ ]
91547
+ }
91548
+ );
91549
+ }
91550
+ function CollaborationCursorOverlay({
91551
+ activeSlideIndex,
91552
+ canvasWidth,
91553
+ canvasHeight,
91554
+ selectedElementId
91555
+ }) {
91556
+ const collab = useCollaboration();
91557
+ const containerRef = React10.useRef(null);
91558
+ const prevSelectionRef = React10.useRef(selectedElementId);
91559
+ React10.useEffect(() => {
91560
+ if (!collab || selectedElementId === prevSelectionRef.current) {
91561
+ return;
91562
+ }
91563
+ prevSelectionRef.current = selectedElementId;
91564
+ collab.broadcastPresence({
91565
+ selectedElementId: selectedElementId ?? void 0,
91566
+ activeSlideIndex
91567
+ });
91568
+ }, [collab, selectedElementId, activeSlideIndex]);
91569
+ React10.useEffect(() => {
91570
+ if (!collab) {
91571
+ return;
91572
+ }
91573
+ const parent = containerRef.current?.parentElement;
91574
+ if (!parent) {
91575
+ return;
91576
+ }
91577
+ const handler = (e2) => {
91578
+ const rect = parent.getBoundingClientRect();
91579
+ const x2 = (e2.clientX - rect.left) / rect.width * canvasWidth;
91580
+ const y = (e2.clientY - rect.top) / rect.height * canvasHeight;
91581
+ collab.broadcastPresence({
91582
+ cursorX: x2,
91583
+ cursorY: y,
91584
+ activeSlideIndex
91585
+ });
91586
+ };
91587
+ parent.addEventListener("pointermove", handler);
91588
+ return () => parent.removeEventListener("pointermove", handler);
91589
+ }, [collab, canvasWidth, canvasHeight, activeSlideIndex]);
91590
+ if (!collab) {
91591
+ return null;
91592
+ }
91593
+ return /* @__PURE__ */ jsxRuntime.jsx(
91594
+ "div",
91595
+ {
91596
+ ref: containerRef,
91597
+ "data-testid": "collab-pointer-tracker",
91598
+ "data-export-ignore": "true",
91599
+ style: { display: "contents" },
91600
+ children: /* @__PURE__ */ jsxRuntime.jsx(
91601
+ RemoteUserCursors,
91602
+ {
91603
+ remoteUsers: collab.remoteUsers,
91604
+ activeSlideIndex,
91605
+ canvasWidth,
91606
+ canvasHeight
91607
+ }
91608
+ )
91609
+ }
91610
+ );
91611
+ }
91612
+ function RemoteSelectionOverlay({
91613
+ elements,
91614
+ activeSlideIndex
91615
+ }) {
91616
+ const collab = useCollaboration();
91617
+ if (!collab) {
91618
+ return null;
91619
+ }
91620
+ const elementMap = /* @__PURE__ */ new Map();
91621
+ for (const el of elements) {
91622
+ elementMap.set(el.id, el);
91623
+ }
91624
+ const selections = [];
91625
+ for (const user of collab.remoteUsers) {
91626
+ if (user.activeSlideIndex === activeSlideIndex && user.selectedElementId) {
91627
+ const el = elementMap.get(user.selectedElementId);
91628
+ if (el) {
91629
+ selections.push({
91630
+ userName: user.userName,
91631
+ userColor: user.userColor,
91632
+ element: el
91633
+ });
91634
+ }
91635
+ }
91636
+ }
91637
+ if (selections.length === 0) {
91638
+ return null;
91639
+ }
91640
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: selections.map((sel) => /* @__PURE__ */ jsxRuntime.jsx(
91641
+ "div",
91642
+ {
91643
+ "data-testid": `remote-selection-${sel.element.id}`,
91644
+ "data-export-ignore": "true",
91645
+ className: "absolute pointer-events-none",
91646
+ style: {
91647
+ left: sel.element.x,
91648
+ top: sel.element.y,
91649
+ width: sel.element.width,
91650
+ height: sel.element.height,
91651
+ zIndex: 9997,
91652
+ border: `2px solid ${sel.userColor}`,
91653
+ borderRadius: 2
91654
+ },
91655
+ children: /* @__PURE__ */ jsxRuntime.jsx(
91656
+ "span",
91657
+ {
91658
+ className: "absolute -top-5 left-0 px-1 py-0.5 text-[9px] font-medium text-white rounded-sm whitespace-nowrap leading-none",
91659
+ style: { backgroundColor: sel.userColor },
91660
+ children: sel.userName
91661
+ }
91662
+ )
91663
+ },
91664
+ `remote-sel-${sel.element.id}`
91665
+ )) });
91666
+ }
91131
91667
  function SectionContextMenu({
91132
91668
  state: state2,
91133
91669
  sectionGroups,
@@ -91409,6 +91945,7 @@ function SlideItemInner({
91409
91945
  canvasSize,
91410
91946
  canEdit,
91411
91947
  rehearsalTimings,
91948
+ presenceUsers,
91412
91949
  onSelectSlide,
91413
91950
  onSlideContextMenu,
91414
91951
  onAddSection,
@@ -91451,16 +91988,27 @@ function SlideItemInner({
91451
91988
  onDragOver,
91452
91989
  onDrop: (e2) => onDrop(e2, slideIndex),
91453
91990
  children: [
91454
- /* @__PURE__ */ jsxRuntime.jsx(
91455
- "span",
91456
- {
91457
- className: cn(
91458
- "text-[10px] tabular-nums w-5 text-right shrink-0 select-none",
91459
- isActive ? "text-primary font-medium" : "text-muted-foreground"
91460
- ),
91461
- children: slideIndex + 1
91462
- }
91463
- ),
91991
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5 w-5 shrink-0", children: [
91992
+ /* @__PURE__ */ jsxRuntime.jsx(
91993
+ "span",
91994
+ {
91995
+ className: cn(
91996
+ "text-[10px] tabular-nums text-right select-none w-full",
91997
+ isActive ? "text-primary font-medium" : "text-muted-foreground"
91998
+ ),
91999
+ children: slideIndex + 1
92000
+ }
92001
+ ),
92002
+ presenceUsers && presenceUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap justify-center gap-px", children: presenceUsers.slice(0, 4).map((u2, i3) => /* @__PURE__ */ jsxRuntime.jsx(
92003
+ "span",
92004
+ {
92005
+ className: "w-[6px] h-[6px] rounded-full",
92006
+ style: { backgroundColor: u2.userColor },
92007
+ title: u2.userName
92008
+ },
92009
+ i3
92010
+ )) })
92011
+ ] }),
91464
92012
  /* @__PURE__ */ jsxRuntime.jsxs(
91465
92013
  "div",
91466
92014
  {
@@ -91610,8 +92158,26 @@ function SlidesPaneSidebar({
91610
92158
  panelWidth
91611
92159
  }) {
91612
92160
  const { t: t2 } = reactI18next.useTranslation();
92161
+ const collab = useCollaboration();
91613
92162
  const slideRefs = React10.useRef(/* @__PURE__ */ new Map());
91614
92163
  const renameInputRef = React10.useRef(null);
92164
+ const slidePresenceMap = React10.useMemo(() => {
92165
+ if (!collab || collab.remoteUsers.length === 0) {
92166
+ return void 0;
92167
+ }
92168
+ const map3 = /* @__PURE__ */ new Map();
92169
+ for (const user of collab.remoteUsers) {
92170
+ const idx = user.activeSlideIndex;
92171
+ const existing = map3.get(idx);
92172
+ const entry = { userName: user.userName, userColor: user.userColor };
92173
+ if (existing) {
92174
+ existing.push(entry);
92175
+ } else {
92176
+ map3.set(idx, [entry]);
92177
+ }
92178
+ }
92179
+ return map3;
92180
+ }, [collab]);
91615
92181
  const estimatedItemHeight = React10.useMemo(
91616
92182
  () => estimateSlideItemHeight(canvasSize.width, canvasSize.height),
91617
92183
  [canvasSize.width, canvasSize.height]
@@ -91733,6 +92299,7 @@ function SlidesPaneSidebar({
91733
92299
  canvasSize,
91734
92300
  canEdit,
91735
92301
  rehearsalTimings,
92302
+ presenceUsers: slidePresenceMap?.get(item.slideIndex),
91736
92303
  onSelectSlide,
91737
92304
  onSlideContextMenu,
91738
92305
  onAddSection,
@@ -91786,6 +92353,7 @@ function SlidesPaneSidebar({
91786
92353
  canvasSize,
91787
92354
  canEdit,
91788
92355
  rehearsalTimings,
92356
+ presenceUsers: slidePresenceMap?.get(idx),
91789
92357
  onSelectSlide,
91790
92358
  onSlideContextMenu,
91791
92359
  onAddSection,
@@ -96552,16 +97120,31 @@ var ALIGN_BTNS = [
96552
97120
  { k: "bottom", el: /* @__PURE__ */ jsxRuntime.jsx(lu.LuChevronDown, { className: ic2 }) }
96553
97121
  ];
96554
97122
  var DRAW_TOOLS = [
96555
- { id: "select", icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuMoveRight, { className: ic2 }), t: "Select" },
96556
- { id: "pen", icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuPencil, { className: ic2 }), t: "Pen" },
97123
+ {
97124
+ id: "select",
97125
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuMoveRight, { className: ic2 }),
97126
+ t: "Select",
97127
+ ac: "bg-primary text-primary-foreground"
97128
+ },
97129
+ {
97130
+ id: "pen",
97131
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuPencil, { className: ic2 }),
97132
+ t: "Pen",
97133
+ ac: "bg-primary text-primary-foreground"
97134
+ },
96557
97135
  {
96558
97136
  id: "highlighter",
96559
97137
  icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuType, { className: ic2 }),
96560
97138
  t: "Highlighter",
96561
97139
  ac: "bg-yellow-600 text-white"
96562
97140
  },
96563
- { id: "eraser", icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuMinus, { className: ic2 }), t: "Eraser" },
96564
- { id: "freeform", icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuSpline, { className: ic2 }), t: "Freeform" }
97141
+ { id: "eraser", icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuMinus, { className: ic2 }), t: "Eraser", ac: "bg-red-600 text-white" },
97142
+ {
97143
+ id: "freeform",
97144
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuSpline, { className: ic2 }),
97145
+ t: "Freeform",
97146
+ ac: "bg-primary text-primary-foreground"
97147
+ }
96565
97148
  ];
96566
97149
  var OV = [
96567
97150
  {
@@ -96767,7 +97350,7 @@ function AnimationsSection(p3) {
96767
97350
  "button",
96768
97351
  {
96769
97352
  type: "button",
96770
- onClick: p3.onToggleInspector,
97353
+ onClick: p3.onOpenAnimationPanel ?? p3.onToggleInspector,
96771
97354
  className: cn(
96772
97355
  pill,
96773
97356
  p3.isInspectorPaneOpen ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
@@ -98294,347 +98877,6 @@ function TextSection(p3) {
98294
98877
  ] })
98295
98878
  ] });
98296
98879
  }
98297
-
98298
- // src/viewer/hooks/collaboration/sanitize.ts
98299
- var ROOM_ID_REGEX = /^[a-zA-Z0-9_-]{1,128}$/;
98300
- function validateRoomId(roomId) {
98301
- if (!ROOM_ID_REGEX.test(roomId)) {
98302
- throw new Error(
98303
- `Invalid collaboration room ID: "${roomId}". Must be 1-128 alphanumeric characters, hyphens, or underscores.`
98304
- );
98305
- }
98306
- return roomId;
98307
- }
98308
- function sanitizeUserName(name) {
98309
- if (typeof name !== "string") {
98310
- return "Anonymous";
98311
- }
98312
- const stripped = name.replace(/<[^>]*>/g, "");
98313
- const trimmed = stripped.trim().slice(0, 64);
98314
- return trimmed || "Anonymous";
98315
- }
98316
- function clampCursorPosition(value, min2, max2) {
98317
- if (typeof value !== "number" || !Number.isFinite(value)) {
98318
- return 0;
98319
- }
98320
- const margin = 20;
98321
- return Math.max(min2 - margin, Math.min(max2 + margin, value));
98322
- }
98323
- var HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
98324
- function sanitizeColor(color, fallback = "#6366f1") {
98325
- if (typeof color !== "string") {
98326
- return fallback;
98327
- }
98328
- return HEX_COLOR_REGEX.test(color) ? color : fallback;
98329
- }
98330
- function sanitizeAvatarUrl(url) {
98331
- if (typeof url !== "string") {
98332
- return void 0;
98333
- }
98334
- try {
98335
- const parsed = new URL(url);
98336
- if (parsed.protocol === "https:" || parsed.protocol === "http:" || parsed.protocol === "data:") {
98337
- return url;
98338
- }
98339
- } catch {
98340
- }
98341
- return void 0;
98342
- }
98343
- function sanitizeSlideIndex(value) {
98344
- if (typeof value !== "number" || !Number.isFinite(value)) {
98345
- return 0;
98346
- }
98347
- return Math.max(0, Math.floor(value));
98348
- }
98349
- function sanitizePresence(raw, canvasWidth, canvasHeight) {
98350
- if (typeof raw.clientId !== "number") {
98351
- return null;
98352
- }
98353
- return {
98354
- clientId: raw.clientId,
98355
- userName: sanitizeUserName(raw.userName),
98356
- userAvatar: sanitizeAvatarUrl(raw.userAvatar),
98357
- userColor: sanitizeColor(raw.userColor),
98358
- activeSlideIndex: sanitizeSlideIndex(raw.activeSlideIndex),
98359
- cursorX: clampCursorPosition(raw.cursorX, 0, canvasWidth),
98360
- cursorY: clampCursorPosition(raw.cursorY, 0, canvasHeight),
98361
- lastUpdated: typeof raw.lastUpdated === "string" ? raw.lastUpdated : (/* @__PURE__ */ new Date()).toISOString(),
98362
- selectedElementId: typeof raw.selectedElementId === "string" ? raw.selectedElementId.slice(0, 128) : void 0
98363
- };
98364
- }
98365
- var BROADCAST_THROTTLE_MS = 50;
98366
- var STALE_PRESENCE_MS = 3e4;
98367
- function usePresenceTracking({
98368
- awareness,
98369
- localClientId,
98370
- userName,
98371
- userColor,
98372
- userAvatar,
98373
- canvasWidth,
98374
- canvasHeight
98375
- }) {
98376
- const [remoteUsers, setRemoteUsers] = React10.useState([]);
98377
- const lastBroadcastRef = React10.useRef(0);
98378
- const pendingBroadcastRef = React10.useRef(null);
98379
- const latestLocalState = React10.useRef({});
98380
- const broadcastPresence = React10.useCallback(
98381
- (update2) => {
98382
- if (!awareness) {
98383
- return;
98384
- }
98385
- Object.assign(latestLocalState.current, update2);
98386
- const now = Date.now();
98387
- const elapsed = now - lastBroadcastRef.current;
98388
- const flush = () => {
98389
- const state2 = {
98390
- ...latestLocalState.current,
98391
- userName,
98392
- userColor,
98393
- userAvatar,
98394
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98395
- };
98396
- awareness.setLocalStateField("presence", state2);
98397
- lastBroadcastRef.current = Date.now();
98398
- };
98399
- if (elapsed >= BROADCAST_THROTTLE_MS) {
98400
- if (pendingBroadcastRef.current) {
98401
- clearTimeout(pendingBroadcastRef.current);
98402
- pendingBroadcastRef.current = null;
98403
- }
98404
- flush();
98405
- } else if (!pendingBroadcastRef.current) {
98406
- pendingBroadcastRef.current = setTimeout(() => {
98407
- pendingBroadcastRef.current = null;
98408
- flush();
98409
- }, BROADCAST_THROTTLE_MS - elapsed);
98410
- }
98411
- },
98412
- [awareness, userName, userColor, userAvatar]
98413
- );
98414
- React10.useEffect(() => {
98415
- if (!awareness) {
98416
- return;
98417
- }
98418
- awareness.setLocalStateField("presence", {
98419
- userName,
98420
- userColor,
98421
- userAvatar,
98422
- activeSlideIndex: 0,
98423
- cursorX: 0,
98424
- cursorY: 0,
98425
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98426
- });
98427
- }, [awareness, userName, userColor, userAvatar]);
98428
- React10.useEffect(() => {
98429
- if (!awareness || localClientId === null) {
98430
- return;
98431
- }
98432
- const handleChange = () => {
98433
- const now = Date.now();
98434
- const states = awareness.getStates();
98435
- const users = [];
98436
- states.forEach((state2, cid) => {
98437
- if (cid === localClientId) {
98438
- return;
98439
- }
98440
- const raw = state2?.presence;
98441
- if (!raw || typeof raw !== "object") {
98442
- return;
98443
- }
98444
- const sanitized = sanitizePresence({ ...raw, clientId: cid }, canvasWidth, canvasHeight);
98445
- if (!sanitized) {
98446
- return;
98447
- }
98448
- const updatedAt = new Date(sanitized.lastUpdated).getTime();
98449
- if (Number.isNaN(updatedAt) || now - updatedAt > STALE_PRESENCE_MS) {
98450
- return;
98451
- }
98452
- users.push(sanitized);
98453
- });
98454
- setRemoteUsers(users);
98455
- };
98456
- awareness.on("change", handleChange);
98457
- awareness.on("update", handleChange);
98458
- handleChange();
98459
- return () => {
98460
- awareness.off("change", handleChange);
98461
- awareness.off("update", handleChange);
98462
- };
98463
- }, [awareness, localClientId, canvasWidth, canvasHeight]);
98464
- React10.useEffect(() => {
98465
- if (!awareness) {
98466
- return;
98467
- }
98468
- const interval = setInterval(() => {
98469
- awareness.setLocalStateField("presence", {
98470
- ...latestLocalState.current,
98471
- userName,
98472
- userColor,
98473
- userAvatar,
98474
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98475
- });
98476
- }, 1e4);
98477
- return () => clearInterval(interval);
98478
- }, [awareness, userName, userColor, userAvatar]);
98479
- React10.useEffect(() => {
98480
- return () => {
98481
- if (pendingBroadcastRef.current) {
98482
- clearTimeout(pendingBroadcastRef.current);
98483
- }
98484
- };
98485
- }, []);
98486
- return { remoteUsers, broadcastPresence };
98487
- }
98488
- function useYjsProvider({ config }) {
98489
- const [status, setStatus] = React10.useState("disconnected");
98490
- const [awareness, setAwareness] = React10.useState(null);
98491
- const [doc2, setDoc] = React10.useState(null);
98492
- const [clientId, setClientId] = React10.useState(null);
98493
- const cleanupRef = React10.useRef(null);
98494
- const init = React10.useCallback(async () => {
98495
- const roomId = validateRoomId(config.roomId);
98496
- setStatus("connecting");
98497
- try {
98498
- const [Y, { WebsocketProvider: WebsocketProvider2 }] = await Promise.all([Promise.resolve().then(() => (init_yjs(), yjs_exports)), Promise.resolve().then(() => (init_y_websocket(), y_websocket_exports))]);
98499
- const yDoc = new Y.Doc();
98500
- const provider = new WebsocketProvider2(
98501
- config.serverUrl,
98502
- roomId,
98503
- yDoc,
98504
- // eslint-disable-line @typescript-eslint/no-explicit-any
98505
- {
98506
- params: config.authToken ? { token: config.authToken } : void 0
98507
- }
98508
- );
98509
- const handleStatus = (event) => {
98510
- if (event.status === "connected") {
98511
- setStatus("connected");
98512
- } else if (event.status === "disconnected") {
98513
- setStatus("disconnected");
98514
- }
98515
- };
98516
- provider.on("status", handleStatus);
98517
- if (provider.wsconnected) {
98518
- setStatus("connected");
98519
- }
98520
- setDoc(yDoc);
98521
- setAwareness(provider.awareness);
98522
- setClientId(provider.awareness.clientID);
98523
- cleanupRef.current = () => {
98524
- provider.off("status", handleStatus);
98525
- provider.destroy();
98526
- yDoc.destroy();
98527
- setDoc(null);
98528
- setAwareness(null);
98529
- setClientId(null);
98530
- setStatus("disconnected");
98531
- };
98532
- } catch (err) {
98533
- console.warn(
98534
- "[pptx-viewer] Collaboration packages not available:",
98535
- err instanceof Error ? err.message : err
98536
- );
98537
- setStatus("error");
98538
- }
98539
- }, [config.roomId, config.serverUrl, config.authToken]);
98540
- React10.useEffect(() => {
98541
- init();
98542
- return () => {
98543
- cleanupRef.current?.();
98544
- cleanupRef.current = null;
98545
- };
98546
- }, [init]);
98547
- return { status, awareness, doc: doc2, clientId };
98548
- }
98549
-
98550
- // src/viewer/hooks/collaboration/useCollaborativeState.ts
98551
- function useCollaborativeState({
98552
- config,
98553
- canvasWidth,
98554
- canvasHeight
98555
- }) {
98556
- const userColor = sanitizeColor(config.userColor, "#6366f1");
98557
- const { status, awareness, doc: doc2, clientId } = useYjsProvider({ config });
98558
- const { remoteUsers, broadcastPresence } = usePresenceTracking({
98559
- awareness,
98560
- localClientId: clientId,
98561
- userName: config.userName,
98562
- userColor,
98563
- userAvatar: config.userAvatar,
98564
- canvasWidth,
98565
- canvasHeight
98566
- });
98567
- const connectedCount = status === "connected" ? remoteUsers.length + 1 : remoteUsers.length;
98568
- return {
98569
- status,
98570
- remoteUsers,
98571
- broadcastPresence,
98572
- connectedCount,
98573
- config,
98574
- doc: doc2
98575
- };
98576
- }
98577
- var CollaborationContext = React10.createContext(null);
98578
- function useCollaboration() {
98579
- return React10.useContext(CollaborationContext);
98580
- }
98581
- function CollaborationProvider({
98582
- config,
98583
- canvasWidth,
98584
- canvasHeight,
98585
- children
98586
- }) {
98587
- const value = useCollaborativeState({
98588
- config,
98589
- canvasWidth,
98590
- canvasHeight
98591
- });
98592
- return /* @__PURE__ */ jsxRuntime.jsx(CollaborationContext.Provider, { value, children });
98593
- }
98594
- var STATUS_STYLES = {
98595
- connected: {
98596
- dot: "bg-green-400",
98597
- text: "text-green-400",
98598
- label: "Connected"
98599
- },
98600
- connecting: {
98601
- dot: "bg-yellow-400 animate-pulse",
98602
- text: "text-yellow-400",
98603
- label: "Connecting..."
98604
- },
98605
- disconnected: {
98606
- dot: "bg-gray-500",
98607
- text: "text-gray-500",
98608
- label: "Disconnected"
98609
- },
98610
- error: {
98611
- dot: "bg-red-400",
98612
- text: "text-red-400",
98613
- label: "Connection error"
98614
- }
98615
- };
98616
- function CollaborationStatusIndicator({
98617
- status,
98618
- connectedCount
98619
- }) {
98620
- const { t: t2 } = reactI18next.useTranslation();
98621
- const style = STATUS_STYLES[status];
98622
- return /* @__PURE__ */ jsxRuntime.jsxs(
98623
- "div",
98624
- {
98625
- "data-testid": "collaboration-status",
98626
- className: "flex items-center gap-1.5",
98627
- "aria-label": t2("pptx.collaboration.statusAriaLabel", {
98628
- status: t2(`pptx.collaboration.status.${status}`),
98629
- count: connectedCount
98630
- }),
98631
- children: [
98632
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-block w-2 h-2 rounded-full ${style.dot}`, "aria-hidden": "true" }),
98633
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-[10px] ${style.text}`, children: status === "connected" ? t2("pptx.collaboration.userCount", { count: connectedCount }) : t2(`pptx.collaboration.status.${status}`) })
98634
- ]
98635
- }
98636
- );
98637
- }
98638
98880
  function CustomShowsControls({
98639
98881
  customShows,
98640
98882
  activeCustomShowId,
@@ -99073,29 +99315,38 @@ function ToolbarPrimaryRow(p3) {
99073
99315
  ]
99074
99316
  }
99075
99317
  ),
99076
- collab && (collab.status === "connected" || collab.status === "connecting") && collab.remoteUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center -space-x-1.5 mx-1", children: [
99077
- collab.remoteUsers.slice(0, 4).map((user) => /* @__PURE__ */ jsxRuntime.jsx(
99078
- "div",
99079
- {
99080
- className: "w-6 h-6 rounded-full border-2 border-background flex items-center justify-center text-[8px] font-semibold text-white shrink-0",
99081
- style: { backgroundColor: user.userColor },
99082
- title: user.userName,
99083
- children: user.userAvatar ? /* @__PURE__ */ jsxRuntime.jsx(
99084
- "img",
99318
+ collab && (collab.status === "connected" || collab.status === "connecting") && collab.remoteUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
99319
+ "button",
99320
+ {
99321
+ type: "button",
99322
+ onClick: p3.onOpenShareDialog,
99323
+ className: "flex items-center -space-x-1.5 mx-1 rounded-sm px-1 py-0.5 hover:bg-accent/60 transition-colors cursor-pointer",
99324
+ title: t2("pptx.toolbar.sharingUsers", { count: collab.connectedCount }),
99325
+ children: [
99326
+ collab.remoteUsers.slice(0, 4).map((user) => /* @__PURE__ */ jsxRuntime.jsx(
99327
+ "div",
99085
99328
  {
99086
- src: user.userAvatar,
99087
- alt: user.userName,
99088
- className: "w-full h-full rounded-full object-cover"
99089
- }
99090
- ) : user.userName.slice(0, 2).toUpperCase()
99091
- },
99092
- user.clientId
99093
- )),
99094
- collab.remoteUsers.length > 4 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-6 h-6 rounded-full border-2 border-background bg-muted flex items-center justify-center text-[8px] text-muted-foreground shrink-0", children: [
99095
- "+",
99096
- collab.remoteUsers.length - 4
99097
- ] })
99098
- ] }),
99329
+ className: "w-6 h-6 rounded-full border-2 border-background flex items-center justify-center text-[8px] font-semibold text-white shrink-0",
99330
+ style: { backgroundColor: user.userColor },
99331
+ title: user.userName,
99332
+ children: user.userAvatar ? /* @__PURE__ */ jsxRuntime.jsx(
99333
+ "img",
99334
+ {
99335
+ src: user.userAvatar,
99336
+ alt: user.userName,
99337
+ className: "w-full h-full rounded-full object-cover"
99338
+ }
99339
+ ) : user.userName.slice(0, 2).toUpperCase()
99340
+ },
99341
+ user.clientId
99342
+ )),
99343
+ collab.remoteUsers.length > 4 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-6 h-6 rounded-full border-2 border-background bg-muted flex items-center justify-center text-[8px] text-muted-foreground shrink-0", children: [
99344
+ "+",
99345
+ collab.remoteUsers.length - 4
99346
+ ] })
99347
+ ]
99348
+ }
99349
+ ),
99099
99350
  /* @__PURE__ */ jsxRuntime.jsx(
99100
99351
  ModeSwitcher,
99101
99352
  {
@@ -99472,7 +99723,8 @@ function Toolbar(p3) {
99472
99723
  canEdit: p3.canEdit,
99473
99724
  selectedElement: p3.selectedElement,
99474
99725
  isInspectorPaneOpen: p3.isInspectorPaneOpen,
99475
- onToggleInspector: p3.onToggleInspector
99726
+ onToggleInspector: p3.onToggleInspector,
99727
+ onOpenAnimationPanel: p3.onOpenAnimationPanel
99476
99728
  }
99477
99729
  ),
99478
99730
  sSlw && /* @__PURE__ */ jsxRuntime.jsx(
@@ -106917,9 +107169,75 @@ function SetUpSlideShowDialog({
106917
107169
  }
106918
107170
  function BroadcastDialog({
106919
107171
  open,
106920
- onClose
107172
+ onClose,
107173
+ onStartBroadcast,
107174
+ onStopBroadcast,
107175
+ onStartPresenting,
107176
+ defaultRoomId,
107177
+ defaultUserName,
107178
+ defaultServerUrl
106921
107179
  }) {
106922
107180
  const { t: t2 } = reactI18next.useTranslation();
107181
+ const collab = useCollaboration();
107182
+ const isBroadcasting = collab !== null && collab.status !== "disconnected" && collab.status !== "error" && collab.config.role === "broadcaster";
107183
+ const [roomId, setRoomId] = React10.useState("");
107184
+ const [userName, setUserName] = React10.useState("");
107185
+ const [serverUrl, setServerUrl] = React10.useState("");
107186
+ const [copied, setCopied] = React10.useState(false);
107187
+ const dialogRef = React10.useRef(null);
107188
+ React10.useEffect(() => {
107189
+ if (open && !isBroadcasting) {
107190
+ const broadcastRoom = defaultRoomId ? `broadcast-${defaultRoomId}` : `broadcast-${Math.random().toString(36).slice(2, 10)}`;
107191
+ setRoomId(broadcastRoom);
107192
+ setUserName(defaultUserName ?? "");
107193
+ setServerUrl(defaultServerUrl ?? "ws://localhost:1234");
107194
+ }
107195
+ }, [open, isBroadcasting, defaultRoomId, defaultUserName, defaultServerUrl]);
107196
+ React10.useEffect(() => {
107197
+ if (!open) {
107198
+ return;
107199
+ }
107200
+ function handleKeyDown(e2) {
107201
+ if (e2.key === "Escape") {
107202
+ onClose();
107203
+ }
107204
+ }
107205
+ document.addEventListener("keydown", handleKeyDown);
107206
+ return () => document.removeEventListener("keydown", handleKeyDown);
107207
+ }, [open, onClose]);
107208
+ React10.useEffect(() => {
107209
+ if (open && dialogRef.current) {
107210
+ dialogRef.current.focus();
107211
+ }
107212
+ }, [open]);
107213
+ const broadcastUrl = typeof window !== "undefined" ? `${window.location.origin}${window.location.pathname}?broadcast=${encodeURIComponent(isBroadcasting ? collab.config.roomId : roomId)}&server=${encodeURIComponent(isBroadcasting ? collab.config.serverUrl : serverUrl)}` : roomId;
107214
+ const handleCopyUrl = React10.useCallback(() => {
107215
+ void navigator.clipboard.writeText(broadcastUrl).then(() => {
107216
+ setCopied(true);
107217
+ setTimeout(() => setCopied(false), 2e3);
107218
+ return void 0;
107219
+ });
107220
+ }, [broadcastUrl]);
107221
+ const handleStartBroadcast = React10.useCallback(() => {
107222
+ if (!roomId.trim() || !userName.trim()) {
107223
+ return;
107224
+ }
107225
+ onStartBroadcast?.({
107226
+ roomId: roomId.trim(),
107227
+ serverUrl: serverUrl.trim(),
107228
+ userName: userName.trim(),
107229
+ role: "broadcaster"
107230
+ });
107231
+ setTimeout(() => {
107232
+ onStartPresenting?.();
107233
+ }, 100);
107234
+ onClose();
107235
+ }, [roomId, userName, serverUrl, onStartBroadcast, onStartPresenting, onClose]);
107236
+ const handleStopBroadcast = React10.useCallback(() => {
107237
+ onStopBroadcast?.();
107238
+ onClose();
107239
+ }, [onStopBroadcast, onClose]);
107240
+ const canStart = roomId.trim().length > 0 && userName.trim().length > 0 && serverUrl.trim().length > 0;
106923
107241
  if (!open) {
106924
107242
  return null;
106925
107243
  }
@@ -106933,42 +107251,225 @@ function BroadcastDialog({
106933
107251
  onClick: onClose
106934
107252
  }
106935
107253
  ),
106936
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[201] flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pointer-events-auto w-[380px] rounded-xl border border-border bg-background shadow-2xl", children: [
106937
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-3 border-b border-border", children: [
106938
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold text-foreground", children: t2("pptx.broadcast.title") }),
107254
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[201] flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsxs(
107255
+ "div",
107256
+ {
107257
+ ref: dialogRef,
107258
+ role: "dialog",
107259
+ "aria-modal": "true",
107260
+ "aria-label": t2("pptx.broadcast.title"),
107261
+ tabIndex: -1,
107262
+ className: "pointer-events-auto w-full max-w-md rounded-xl border border-border bg-popover text-foreground shadow-2xl outline-none",
107263
+ children: [
107264
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-3 border-b border-border", children: [
107265
+ /* @__PURE__ */ jsxRuntime.jsxs("h2", { className: "text-sm font-semibold text-foreground flex items-center gap-2", children: [
107266
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCast, { className: "w-4 h-4" }),
107267
+ isBroadcasting ? t2("pptx.broadcast.broadcasting") : t2("pptx.broadcast.title")
107268
+ ] }),
107269
+ /* @__PURE__ */ jsxRuntime.jsx(
107270
+ "button",
107271
+ {
107272
+ type: "button",
107273
+ onClick: onClose,
107274
+ className: "text-muted-foreground hover:text-foreground text-lg leading-none",
107275
+ "aria-label": "Close",
107276
+ children: "\xD7"
107277
+ }
107278
+ )
107279
+ ] }),
107280
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-5 py-4", children: isBroadcasting ? /* @__PURE__ */ jsxRuntime.jsx(
107281
+ ActiveBroadcastView,
107282
+ {
107283
+ collab,
107284
+ broadcastUrl,
107285
+ copied,
107286
+ onCopyUrl: handleCopyUrl,
107287
+ onStopBroadcast: handleStopBroadcast
107288
+ }
107289
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
107290
+ StartBroadcastForm,
107291
+ {
107292
+ roomId,
107293
+ userName,
107294
+ serverUrl,
107295
+ onRoomIdChange: setRoomId,
107296
+ onUserNameChange: setUserName,
107297
+ onServerUrlChange: setServerUrl
107298
+ }
107299
+ ) }),
107300
+ !isBroadcasting && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2 px-5 py-3 border-t border-border", children: [
107301
+ /* @__PURE__ */ jsxRuntime.jsx(
107302
+ "button",
107303
+ {
107304
+ type: "button",
107305
+ onClick: onClose,
107306
+ className: "px-3 py-1.5 rounded bg-muted hover:bg-accent text-[12px] text-foreground transition-colors",
107307
+ children: t2("common.close")
107308
+ }
107309
+ ),
107310
+ /* @__PURE__ */ jsxRuntime.jsx(
107311
+ "button",
107312
+ {
107313
+ type: "button",
107314
+ disabled: !canStart,
107315
+ onClick: handleStartBroadcast,
107316
+ className: "px-3 py-1.5 rounded bg-primary hover:bg-primary/90 text-[12px] text-primary-foreground transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
107317
+ children: t2("pptx.broadcast.startBroadcast")
107318
+ }
107319
+ )
107320
+ ] })
107321
+ ]
107322
+ }
107323
+ ) })
107324
+ ] });
107325
+ }
107326
+ function StartBroadcastForm({
107327
+ roomId,
107328
+ userName,
107329
+ serverUrl,
107330
+ onRoomIdChange,
107331
+ onUserNameChange,
107332
+ onServerUrlChange
107333
+ }) {
107334
+ const { t: t2 } = reactI18next.useTranslation();
107335
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
107336
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[13px] text-muted-foreground leading-relaxed", children: t2("pptx.broadcast.description") }),
107337
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107338
+ /* @__PURE__ */ jsxRuntime.jsx(
107339
+ "label",
107340
+ {
107341
+ htmlFor: "broadcast-room-id",
107342
+ className: "block text-[12px] font-medium text-foreground",
107343
+ children: t2("pptx.broadcast.sessionName")
107344
+ }
107345
+ ),
107346
+ /* @__PURE__ */ jsxRuntime.jsx(
107347
+ "input",
107348
+ {
107349
+ id: "broadcast-room-id",
107350
+ type: "text",
107351
+ value: roomId,
107352
+ onChange: (e2) => onRoomIdChange(e2.target.value),
107353
+ placeholder: "broadcast-abc123",
107354
+ className: "w-full px-3 py-1.5 rounded border border-border bg-background text-foreground text-[13px] placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary"
107355
+ }
107356
+ )
107357
+ ] }),
107358
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107359
+ /* @__PURE__ */ jsxRuntime.jsx(
107360
+ "label",
107361
+ {
107362
+ htmlFor: "broadcast-user-name",
107363
+ className: "block text-[12px] font-medium text-foreground",
107364
+ children: t2("pptx.broadcast.displayName")
107365
+ }
107366
+ ),
107367
+ /* @__PURE__ */ jsxRuntime.jsx(
107368
+ "input",
107369
+ {
107370
+ id: "broadcast-user-name",
107371
+ type: "text",
107372
+ value: userName,
107373
+ onChange: (e2) => onUserNameChange(e2.target.value),
107374
+ placeholder: "Presenter",
107375
+ className: "w-full px-3 py-1.5 rounded border border-border bg-background text-foreground text-[13px] placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary"
107376
+ }
107377
+ )
107378
+ ] }),
107379
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107380
+ /* @__PURE__ */ jsxRuntime.jsx(
107381
+ "label",
107382
+ {
107383
+ htmlFor: "broadcast-server-url",
107384
+ className: "block text-[12px] font-medium text-foreground",
107385
+ children: t2("pptx.broadcast.serverLabel")
107386
+ }
107387
+ ),
107388
+ /* @__PURE__ */ jsxRuntime.jsx(
107389
+ "input",
107390
+ {
107391
+ id: "broadcast-server-url",
107392
+ type: "text",
107393
+ value: serverUrl,
107394
+ onChange: (e2) => onServerUrlChange(e2.target.value),
107395
+ placeholder: "ws://localhost:1234",
107396
+ className: "w-full px-3 py-1.5 rounded border border-border bg-background text-foreground text-[13px] placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary"
107397
+ }
107398
+ )
107399
+ ] }),
107400
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground/70 leading-relaxed", children: t2("pptx.broadcast.hint") })
107401
+ ] });
107402
+ }
107403
+ function ActiveBroadcastView({
107404
+ collab,
107405
+ broadcastUrl,
107406
+ copied,
107407
+ onCopyUrl,
107408
+ onStopBroadcast
107409
+ }) {
107410
+ const { t: t2 } = reactI18next.useTranslation();
107411
+ const statusColor = collab.status === "connected" ? "text-green-400" : collab.status === "connecting" ? "text-yellow-400" : "text-red-400";
107412
+ const statusIcon = collab.status === "connected" || collab.status === "connecting" ? /* @__PURE__ */ jsxRuntime.jsx(lu.LuWifi, { className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lu.LuWifiOff, { className: "w-4 h-4" });
107413
+ const viewerCount = collab.remoteUsers.filter((u2) => u2.role === "viewer").length;
107414
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
107415
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
107416
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: statusColor, children: statusIcon }),
107417
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[13px] font-medium text-foreground capitalize", children: collab.status }),
107418
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[12px] text-muted-foreground ml-auto flex items-center gap-1", children: [
107419
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuUsers, { className: "w-3.5 h-3.5" }),
107420
+ t2("pptx.broadcast.viewerCount", { count: viewerCount })
107421
+ ] })
107422
+ ] }),
107423
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107424
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t2("pptx.broadcast.viewerLink") }),
107425
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
107426
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 px-3 py-1.5 rounded border border-border bg-background text-[11px] text-foreground select-all font-mono truncate", children: broadcastUrl }),
106939
107427
  /* @__PURE__ */ jsxRuntime.jsx(
106940
107428
  "button",
106941
107429
  {
106942
107430
  type: "button",
106943
- onClick: onClose,
106944
- className: "text-muted-foreground hover:text-foreground text-lg leading-none",
106945
- "aria-label": "Close",
106946
- children: "\xD7"
107431
+ onClick: onCopyUrl,
107432
+ className: "flex items-center gap-1 px-2.5 py-1.5 rounded border border-border bg-muted hover:bg-accent text-[12px] text-foreground transition-colors shrink-0",
107433
+ title: t2("pptx.broadcast.copyLink"),
107434
+ children: copied ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
107435
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCheck, { className: "w-3.5 h-3.5 text-green-400" }),
107436
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t2("pptx.share.copied") })
107437
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
107438
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCopy, { className: "w-3.5 h-3.5" }),
107439
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t2("pptx.share.copyUrl") })
107440
+ ] })
106947
107441
  }
106948
107442
  )
106949
107443
  ] }),
106950
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-5 py-6 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[13px] text-muted-foreground leading-relaxed", children: t2("pptx.broadcast.description") }) }),
106951
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2 px-5 py-3 border-t border-border", children: [
107444
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("pptx.broadcast.shareHint") })
107445
+ ] }),
107446
+ collab.remoteUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107447
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t2("pptx.broadcast.viewers") }),
107448
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded border border-border bg-background divide-y divide-border max-h-[120px] overflow-y-auto", children: collab.remoteUsers.map((user) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
106952
107449
  /* @__PURE__ */ jsxRuntime.jsx(
106953
- "button",
107450
+ "div",
106954
107451
  {
106955
- type: "button",
106956
- onClick: onClose,
106957
- className: "px-3 py-1.5 rounded bg-muted hover:bg-accent text-[12px] text-foreground transition-colors",
106958
- children: t2("common.close")
107452
+ className: "w-5 h-5 rounded-full flex items-center justify-center text-[8px] font-semibold text-white shrink-0",
107453
+ style: { backgroundColor: user.userColor },
107454
+ children: user.userName.slice(0, 2).toUpperCase()
106959
107455
  }
106960
107456
  ),
106961
- /* @__PURE__ */ jsxRuntime.jsx(
106962
- "button",
106963
- {
106964
- type: "button",
106965
- disabled: true,
106966
- className: "px-3 py-1.5 rounded bg-primary/40 text-[12px] text-white/50 cursor-not-allowed",
106967
- children: t2("pptx.broadcast.startBroadcast")
106968
- }
106969
- )
106970
- ] })
106971
- ] }) })
107457
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[12px] text-foreground truncate", children: user.userName }),
107458
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] text-muted-foreground ml-auto", children: [
107459
+ "Slide ",
107460
+ user.activeSlideIndex + 1
107461
+ ] })
107462
+ ] }, user.clientId)) })
107463
+ ] }),
107464
+ /* @__PURE__ */ jsxRuntime.jsx(
107465
+ "button",
107466
+ {
107467
+ type: "button",
107468
+ onClick: onStopBroadcast,
107469
+ className: "w-full px-3 py-2 rounded border border-red-500/30 bg-red-500/10 hover:bg-red-500/20 text-[12px] text-red-400 font-medium transition-colors",
107470
+ children: t2("pptx.broadcast.stopBroadcast")
107471
+ }
107472
+ )
106972
107473
  ] });
106973
107474
  }
106974
107475
  function getInitials(name) {
@@ -108552,7 +109053,8 @@ function useDrawingOverlay({
108552
109053
  drawingWidth,
108553
109054
  isDrawingRef,
108554
109055
  onAddInkElement,
108555
- onAddFreeformShape
109056
+ onAddFreeformShape,
109057
+ onEraseInkElement
108556
109058
  }) {
108557
109059
  const isDrawing = activeTool !== "select";
108558
109060
  const [currentStrokePoints, setCurrentStrokePoints] = React10.useState(
@@ -108601,6 +109103,7 @@ function useDrawingOverlay({
108601
109103
  continue;
108602
109104
  }
108603
109105
  if (pt2.x >= el.x - HIT_RADIUS && pt2.x <= el.x + el.width + HIT_RADIUS && pt2.y >= el.y - HIT_RADIUS && pt2.y <= el.y + el.height + HIT_RADIUS) {
109106
+ onEraseInkElement?.(el.id);
108604
109107
  break;
108605
109108
  }
108606
109109
  }
@@ -108619,7 +109122,7 @@ function useDrawingOverlay({
108619
109122
  isDrawingRef.current = true;
108620
109123
  }
108621
109124
  },
108622
- [activeTool, activeSlide, pointerToCanvasCoords, isDrawingRef]
109125
+ [activeTool, activeSlide, pointerToCanvasCoords, isDrawingRef, onEraseInkElement]
108623
109126
  );
108624
109127
  const handleDrawPointerMove = React10.useCallback(
108625
109128
  (e2) => {
@@ -108689,6 +109192,9 @@ function useDrawingOverlay({
108689
109192
  i3 === 0 ? { type: "moveTo", pt: scaledPt } : { type: "lineTo", pt: scaledPt }
108690
109193
  );
108691
109194
  }
109195
+ if (segments.length > 2) {
109196
+ segments.push({ type: "close" });
109197
+ }
108692
109198
  const freeformShape = {
108693
109199
  id: `shape-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
108694
109200
  type: "shape",
@@ -108698,7 +109204,7 @@ function useDrawingOverlay({
108698
109204
  height: h2,
108699
109205
  shapeType: "custom",
108700
109206
  shapeStyle: {
108701
- fillColor: drawingColor,
109207
+ fillColor: "transparent",
108702
109208
  strokeColor: drawingColor,
108703
109209
  strokeWidth: drawingWidth
108704
109210
  },
@@ -108848,6 +109354,7 @@ function SlideCanvas({
108848
109354
  isDrawingRef,
108849
109355
  onAddInkElement,
108850
109356
  onAddFreeformShape,
109357
+ onEraseInkElement,
108851
109358
  onActionClick,
108852
109359
  onHyperlinkClick,
108853
109360
  comments,
@@ -108933,7 +109440,8 @@ function SlideCanvas({
108933
109440
  drawingWidth,
108934
109441
  isDrawingRef,
108935
109442
  onAddInkElement,
108936
- onAddFreeformShape
109443
+ onAddFreeformShape,
109444
+ onEraseInkElement
108937
109445
  });
108938
109446
  const rulerOffset = showRulers ? RULER_THICKNESS : 0;
108939
109447
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -109429,6 +109937,10 @@ function ViewerToolbarSection(props) {
109429
109937
  onSetMode,
109430
109938
  onToggleSidebar: () => s.setIsSlidesPaneOpen((p3) => !p3),
109431
109939
  onToggleInspector: () => s.setIsInspectorPaneOpen((p3) => !p3),
109940
+ onOpenAnimationPanel: () => {
109941
+ s.setIsInspectorPaneOpen(true);
109942
+ s.setSidebarPanelMode("properties");
109943
+ },
109432
109944
  onToggleCompactToolbar: () => s.setIsCompactToolbarOpen((p3) => !p3),
109433
109945
  onSetToolbarSection: s.setToolbarSection,
109434
109946
  onZoomIn: zoom.handleZoomIn,
@@ -111100,13 +111612,6 @@ function ViewerDialogGroup(props) {
111100
111612
  slideCount: slides.length
111101
111613
  }
111102
111614
  ),
111103
- /* @__PURE__ */ jsxRuntime.jsx(
111104
- BroadcastDialog,
111105
- {
111106
- open: dialogs.isBroadcastDialogOpen,
111107
- onClose: () => dialogs.setIsBroadcastDialogOpen(false)
111108
- }
111109
- ),
111110
111615
  /* @__PURE__ */ jsxRuntime.jsx(
111111
111616
  PrintDialog,
111112
111617
  {
@@ -111349,6 +111854,7 @@ function ViewerCanvasArea(props) {
111349
111854
  isDrawingRef: s.isDrawingRef,
111350
111855
  onAddInkElement: insertHandlers.handleAddInkElement,
111351
111856
  onAddFreeformShape: insertHandlers.handleAddFreeformShape,
111857
+ onEraseInkElement: insertHandlers.handleEraseInkElement,
111352
111858
  onActionClick: handleActionClick,
111353
111859
  onHyperlinkClick: handleHyperlinkClick,
111354
111860
  allSlides: mode === "present" ? slides : void 0,
@@ -111356,6 +111862,24 @@ function ViewerCanvasArea(props) {
111356
111862
  sourceSlideIndex: mode === "present" ? activeSlideIndex : void 0,
111357
111863
  fieldContext,
111358
111864
  tableStyleContext,
111865
+ collaborationOverlay: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
111866
+ /* @__PURE__ */ jsxRuntime.jsx(
111867
+ RemoteSelectionOverlay,
111868
+ {
111869
+ elements: effectiveSlide?.elements ?? [],
111870
+ activeSlideIndex
111871
+ }
111872
+ ),
111873
+ /* @__PURE__ */ jsxRuntime.jsx(
111874
+ CollaborationCursorOverlay,
111875
+ {
111876
+ activeSlideIndex,
111877
+ canvasWidth: canvasSize.width,
111878
+ canvasHeight: canvasSize.height,
111879
+ selectedElementId: s.selectedElementId
111880
+ }
111881
+ )
111882
+ ] }),
111359
111883
  comments: activeSlide?.comments,
111360
111884
  showCommentMarkers: s.sidebarPanelMode === "comments",
111361
111885
  onCommentMarkerClick: () => s.setSidebarPanelMode("comments"),
@@ -112937,6 +113461,37 @@ function useYjsDocumentSync({
112937
113461
  };
112938
113462
  }, [doc2, isConnected, getDocMap, setSlides]);
112939
113463
  }
113464
+ function useBroadcastFollower({
113465
+ collab,
113466
+ activeSlideIndex,
113467
+ setActiveSlideIndex,
113468
+ slideCount
113469
+ }) {
113470
+ const lastBroadcasterSlide = React10.useRef(-1);
113471
+ React10.useEffect(() => {
113472
+ if (!collab) {
113473
+ return;
113474
+ }
113475
+ if (collab.config.role !== "viewer") {
113476
+ return;
113477
+ }
113478
+ const broadcaster = collab.remoteUsers.find((u2) => u2.role === "broadcaster");
113479
+ if (!broadcaster) {
113480
+ return;
113481
+ }
113482
+ const targetSlide = broadcaster.activeSlideIndex;
113483
+ if (targetSlide < 0 || targetSlide >= slideCount) {
113484
+ return;
113485
+ }
113486
+ if (targetSlide === lastBroadcasterSlide.current) {
113487
+ return;
113488
+ }
113489
+ lastBroadcasterSlide.current = targetSlide;
113490
+ if (targetSlide !== activeSlideIndex) {
113491
+ setActiveSlideIndex(targetSlide);
113492
+ }
113493
+ }, [collab, activeSlideIndex, setActiveSlideIndex, slideCount]);
113494
+ }
112940
113495
  function computeGridSpacingPx(presentationGridSpacing) {
112941
113496
  if (presentationGridSpacing) {
112942
113497
  const px2 = Math.round(presentationGridSpacing.cx / EMU_PER_PX);
@@ -115277,6 +115832,17 @@ function useInsertElements(input) {
115277
115832
  }
115278
115833
  addElement(shape);
115279
115834
  };
115835
+ const handleEraseInkElement = (elementId) => {
115836
+ if (!activeSlide) {
115837
+ return;
115838
+ }
115839
+ ops.updateSlides(
115840
+ (prev) => prev.map(
115841
+ (s, i3) => i3 === activeSlideIndex ? { ...s, elements: s.elements.filter((el) => el.id !== elementId) } : s
115842
+ )
115843
+ );
115844
+ history.markDirty();
115845
+ };
115280
115846
  return {
115281
115847
  handleAddTextBox,
115282
115848
  handleAddShape,
@@ -115284,6 +115850,7 @@ function useInsertElements(input) {
115284
115850
  ...structured,
115285
115851
  handleAddInkElement,
115286
115852
  handleAddFreeformShape,
115853
+ handleEraseInkElement,
115287
115854
  ...fileHandlers
115288
115855
  };
115289
115856
  }
@@ -116113,7 +116680,8 @@ function useEditorOperations(input) {
116113
116680
  selectedElementIds,
116114
116681
  canvasSize,
116115
116682
  dialogs,
116116
- presentation
116683
+ presentation,
116684
+ userName
116117
116685
  } = input;
116118
116686
  const ops = useElementOperations({
116119
116687
  activeSlide,
@@ -116147,6 +116715,7 @@ function useEditorOperations(input) {
116147
116715
  const comments = useComments({
116148
116716
  slides,
116149
116717
  canEdit,
116718
+ userName,
116150
116719
  selectedElementId: state2.selectedElementId,
116151
116720
  onUpdateSlides: ops.updateSlides,
116152
116721
  onMarkDirty: history.markDirty
@@ -122893,6 +123462,7 @@ var PowerPointViewer = React10.forwardRef(
122893
123462
  onDirtyChange,
122894
123463
  onActiveSlideChange,
122895
123464
  theme,
123465
+ authorName,
122896
123466
  collaboration,
122897
123467
  onStartCollaboration,
122898
123468
  onStopCollaboration,
@@ -123050,7 +123620,8 @@ var PowerPointViewer = React10.forwardRef(
123050
123620
  selectedElementIds,
123051
123621
  canvasSize,
123052
123622
  dialogs,
123053
- presentation
123623
+ presentation,
123624
+ userName: authorName ?? collaboration?.userName
123054
123625
  });
123055
123626
  const {
123056
123627
  exportHandlers,
@@ -123253,6 +123824,19 @@ var PowerPointViewer = React10.forwardRef(
123253
123824
  defaultServerUrl: shareDefaults?.serverUrl
123254
123825
  }
123255
123826
  ),
123827
+ /* @__PURE__ */ jsxRuntime.jsx(
123828
+ BroadcastDialog,
123829
+ {
123830
+ open: dialogs.isBroadcastDialogOpen,
123831
+ onClose: () => dialogs.setIsBroadcastDialogOpen(false),
123832
+ onStartBroadcast: onStartCollaboration,
123833
+ onStopBroadcast: onStopCollaboration,
123834
+ onStartPresenting: () => handleSetMode("present"),
123835
+ defaultRoomId: shareDefaults?.roomId,
123836
+ defaultUserName: shareDefaults?.userName,
123837
+ defaultServerUrl: shareDefaults?.serverUrl
123838
+ }
123839
+ ),
123256
123840
  /* @__PURE__ */ jsxRuntime.jsx(
123257
123841
  ViewerOverlays,
123258
123842
  {
@@ -123302,6 +123886,14 @@ var PowerPointViewer = React10.forwardRef(
123302
123886
  canvasHeight: canvasSize.height,
123303
123887
  children: [
123304
123888
  /* @__PURE__ */ jsxRuntime.jsx(CollaborationDocumentSync, { slides, setSlides: state2.setSlides }),
123889
+ /* @__PURE__ */ jsxRuntime.jsx(
123890
+ BroadcastFollowerSync,
123891
+ {
123892
+ activeSlideIndex,
123893
+ setActiveSlideIndex: state2.setActiveSlideIndex,
123894
+ slideCount: slides.length
123895
+ }
123896
+ ),
123305
123897
  viewerContent
123306
123898
  ]
123307
123899
  }
@@ -123329,6 +123921,20 @@ function CollaborationDocumentSync({
123329
123921
  });
123330
123922
  return null;
123331
123923
  }
123924
+ function BroadcastFollowerSync({
123925
+ activeSlideIndex,
123926
+ setActiveSlideIndex,
123927
+ slideCount
123928
+ }) {
123929
+ const collab = useCollaboration();
123930
+ useBroadcastFollower({
123931
+ collab,
123932
+ activeSlideIndex,
123933
+ setActiveSlideIndex,
123934
+ slideCount
123935
+ });
123936
+ return null;
123937
+ }
123332
123938
  /*! Bundled license information:
123333
123939
 
123334
123940
  three/build/three.core.js: