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.
@@ -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, useMemo42 = 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, useMemo42 = 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;
@@ -91117,6 +91117,637 @@ function useVirtualizedSlides({
91117
91117
  scrollToIndex
91118
91118
  };
91119
91119
  }
91120
+
91121
+ // src/viewer/hooks/collaboration/sanitize.ts
91122
+ var ROOM_ID_REGEX = /^[a-zA-Z0-9_-]{1,128}$/;
91123
+ function validateRoomId(roomId) {
91124
+ if (!ROOM_ID_REGEX.test(roomId)) {
91125
+ throw new Error(
91126
+ `Invalid collaboration room ID: "${roomId}". Must be 1-128 alphanumeric characters, hyphens, or underscores.`
91127
+ );
91128
+ }
91129
+ return roomId;
91130
+ }
91131
+ function sanitizeUserName(name) {
91132
+ if (typeof name !== "string") {
91133
+ return "Anonymous";
91134
+ }
91135
+ const stripped = name.replace(/<[^>]*>/g, "");
91136
+ const trimmed = stripped.trim().slice(0, 64);
91137
+ return trimmed || "Anonymous";
91138
+ }
91139
+ function clampCursorPosition(value, min2, max2) {
91140
+ if (typeof value !== "number" || !Number.isFinite(value)) {
91141
+ return 0;
91142
+ }
91143
+ const margin = 20;
91144
+ return Math.max(min2 - margin, Math.min(max2 + margin, value));
91145
+ }
91146
+ var HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
91147
+ function sanitizeColor(color, fallback = "#6366f1") {
91148
+ if (typeof color !== "string") {
91149
+ return fallback;
91150
+ }
91151
+ return HEX_COLOR_REGEX.test(color) ? color : fallback;
91152
+ }
91153
+ function sanitizeAvatarUrl(url) {
91154
+ if (typeof url !== "string") {
91155
+ return void 0;
91156
+ }
91157
+ try {
91158
+ const parsed = new URL(url);
91159
+ if (parsed.protocol === "https:" || parsed.protocol === "http:" || parsed.protocol === "data:") {
91160
+ return url;
91161
+ }
91162
+ } catch {
91163
+ }
91164
+ return void 0;
91165
+ }
91166
+ function sanitizeSlideIndex(value) {
91167
+ if (typeof value !== "number" || !Number.isFinite(value)) {
91168
+ return 0;
91169
+ }
91170
+ return Math.max(0, Math.floor(value));
91171
+ }
91172
+ function sanitizePresence(raw, canvasWidth, canvasHeight) {
91173
+ if (typeof raw.clientId !== "number") {
91174
+ return null;
91175
+ }
91176
+ return {
91177
+ clientId: raw.clientId,
91178
+ userName: sanitizeUserName(raw.userName),
91179
+ userAvatar: sanitizeAvatarUrl(raw.userAvatar),
91180
+ userColor: sanitizeColor(raw.userColor),
91181
+ activeSlideIndex: sanitizeSlideIndex(raw.activeSlideIndex),
91182
+ cursorX: clampCursorPosition(raw.cursorX, 0, canvasWidth),
91183
+ cursorY: clampCursorPosition(raw.cursorY, 0, canvasHeight),
91184
+ lastUpdated: typeof raw.lastUpdated === "string" ? raw.lastUpdated : (/* @__PURE__ */ new Date()).toISOString(),
91185
+ selectedElementId: typeof raw.selectedElementId === "string" ? raw.selectedElementId.slice(0, 128) : void 0,
91186
+ role: raw.role === "broadcaster" || raw.role === "viewer" || raw.role === "collaborator" ? raw.role : void 0
91187
+ };
91188
+ }
91189
+ var BROADCAST_THROTTLE_MS = 50;
91190
+ var STALE_PRESENCE_MS = 3e4;
91191
+ function usePresenceTracking({
91192
+ awareness,
91193
+ localClientId,
91194
+ userName,
91195
+ userColor,
91196
+ userAvatar,
91197
+ role,
91198
+ canvasWidth,
91199
+ canvasHeight
91200
+ }) {
91201
+ const [remoteUsers, setRemoteUsers] = React10.useState([]);
91202
+ const lastBroadcastRef = React10.useRef(0);
91203
+ const pendingBroadcastRef = React10.useRef(null);
91204
+ const latestLocalState = React10.useRef({});
91205
+ const broadcastPresence = React10.useCallback(
91206
+ (update2) => {
91207
+ if (!awareness) {
91208
+ return;
91209
+ }
91210
+ Object.assign(latestLocalState.current, update2);
91211
+ const now = Date.now();
91212
+ const elapsed = now - lastBroadcastRef.current;
91213
+ const flush = () => {
91214
+ const state2 = {
91215
+ ...latestLocalState.current,
91216
+ userName,
91217
+ userColor,
91218
+ userAvatar,
91219
+ role,
91220
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
91221
+ };
91222
+ awareness.setLocalStateField("presence", state2);
91223
+ lastBroadcastRef.current = Date.now();
91224
+ };
91225
+ if (elapsed >= BROADCAST_THROTTLE_MS) {
91226
+ if (pendingBroadcastRef.current) {
91227
+ clearTimeout(pendingBroadcastRef.current);
91228
+ pendingBroadcastRef.current = null;
91229
+ }
91230
+ flush();
91231
+ } else if (!pendingBroadcastRef.current) {
91232
+ pendingBroadcastRef.current = setTimeout(() => {
91233
+ pendingBroadcastRef.current = null;
91234
+ flush();
91235
+ }, BROADCAST_THROTTLE_MS - elapsed);
91236
+ }
91237
+ },
91238
+ [awareness, userName, userColor, userAvatar, role]
91239
+ );
91240
+ React10.useEffect(() => {
91241
+ if (!awareness) {
91242
+ return;
91243
+ }
91244
+ awareness.setLocalStateField("presence", {
91245
+ userName,
91246
+ userColor,
91247
+ userAvatar,
91248
+ role,
91249
+ activeSlideIndex: 0,
91250
+ cursorX: 0,
91251
+ cursorY: 0,
91252
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
91253
+ });
91254
+ }, [awareness, userName, userColor, userAvatar, role]);
91255
+ React10.useEffect(() => {
91256
+ if (!awareness || localClientId === null) {
91257
+ return;
91258
+ }
91259
+ const handleChange = () => {
91260
+ const now = Date.now();
91261
+ const states = awareness.getStates();
91262
+ const users = [];
91263
+ states.forEach((state2, cid) => {
91264
+ if (cid === localClientId) {
91265
+ return;
91266
+ }
91267
+ const raw = state2?.presence;
91268
+ if (!raw || typeof raw !== "object") {
91269
+ return;
91270
+ }
91271
+ const sanitized = sanitizePresence({ ...raw, clientId: cid }, canvasWidth, canvasHeight);
91272
+ if (!sanitized) {
91273
+ return;
91274
+ }
91275
+ const updatedAt = new Date(sanitized.lastUpdated).getTime();
91276
+ if (Number.isNaN(updatedAt) || now - updatedAt > STALE_PRESENCE_MS) {
91277
+ return;
91278
+ }
91279
+ users.push(sanitized);
91280
+ });
91281
+ setRemoteUsers(users);
91282
+ };
91283
+ awareness.on("change", handleChange);
91284
+ awareness.on("update", handleChange);
91285
+ handleChange();
91286
+ return () => {
91287
+ awareness.off("change", handleChange);
91288
+ awareness.off("update", handleChange);
91289
+ };
91290
+ }, [awareness, localClientId, canvasWidth, canvasHeight]);
91291
+ React10.useEffect(() => {
91292
+ if (!awareness) {
91293
+ return;
91294
+ }
91295
+ const interval = setInterval(() => {
91296
+ awareness.setLocalStateField("presence", {
91297
+ ...latestLocalState.current,
91298
+ userName,
91299
+ userColor,
91300
+ userAvatar,
91301
+ role,
91302
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
91303
+ });
91304
+ }, 1e4);
91305
+ return () => clearInterval(interval);
91306
+ }, [awareness, userName, userColor, userAvatar, role]);
91307
+ React10.useEffect(() => {
91308
+ return () => {
91309
+ if (pendingBroadcastRef.current) {
91310
+ clearTimeout(pendingBroadcastRef.current);
91311
+ }
91312
+ };
91313
+ }, []);
91314
+ return { remoteUsers, broadcastPresence };
91315
+ }
91316
+ function useYjsProvider({ config }) {
91317
+ const [status, setStatus] = React10.useState("disconnected");
91318
+ const [awareness, setAwareness] = React10.useState(null);
91319
+ const [doc2, setDoc] = React10.useState(null);
91320
+ const [clientId, setClientId] = React10.useState(null);
91321
+ const cleanupRef = React10.useRef(null);
91322
+ const init = React10.useCallback(async () => {
91323
+ const roomId = validateRoomId(config.roomId);
91324
+ setStatus("connecting");
91325
+ try {
91326
+ const [Y, { WebsocketProvider: WebsocketProvider2 }] = await Promise.all([Promise.resolve().then(() => (init_yjs(), yjs_exports)), Promise.resolve().then(() => (init_y_websocket(), y_websocket_exports))]);
91327
+ const yDoc = new Y.Doc();
91328
+ const provider = new WebsocketProvider2(
91329
+ config.serverUrl,
91330
+ roomId,
91331
+ yDoc,
91332
+ // eslint-disable-line @typescript-eslint/no-explicit-any
91333
+ {
91334
+ params: config.authToken ? { token: config.authToken } : void 0
91335
+ }
91336
+ );
91337
+ const handleStatus = (event) => {
91338
+ if (event.status === "connected") {
91339
+ setStatus("connected");
91340
+ } else if (event.status === "disconnected") {
91341
+ setStatus("disconnected");
91342
+ }
91343
+ };
91344
+ provider.on("status", handleStatus);
91345
+ if (provider.wsconnected) {
91346
+ setStatus("connected");
91347
+ }
91348
+ setDoc(yDoc);
91349
+ setAwareness(provider.awareness);
91350
+ setClientId(provider.awareness.clientID);
91351
+ cleanupRef.current = () => {
91352
+ provider.off("status", handleStatus);
91353
+ provider.destroy();
91354
+ yDoc.destroy();
91355
+ setDoc(null);
91356
+ setAwareness(null);
91357
+ setClientId(null);
91358
+ setStatus("disconnected");
91359
+ };
91360
+ } catch (err) {
91361
+ console.warn(
91362
+ "[pptx-viewer] Collaboration packages not available:",
91363
+ err instanceof Error ? err.message : err
91364
+ );
91365
+ setStatus("error");
91366
+ }
91367
+ }, [config.roomId, config.serverUrl, config.authToken]);
91368
+ React10.useEffect(() => {
91369
+ init();
91370
+ return () => {
91371
+ cleanupRef.current?.();
91372
+ cleanupRef.current = null;
91373
+ };
91374
+ }, [init]);
91375
+ return { status, awareness, doc: doc2, clientId };
91376
+ }
91377
+
91378
+ // src/viewer/hooks/collaboration/useCollaborativeState.ts
91379
+ function useCollaborativeState({
91380
+ config,
91381
+ canvasWidth,
91382
+ canvasHeight
91383
+ }) {
91384
+ const userColor = sanitizeColor(config.userColor, "#6366f1");
91385
+ const { status, awareness, doc: doc2, clientId } = useYjsProvider({ config });
91386
+ const { remoteUsers, broadcastPresence } = usePresenceTracking({
91387
+ awareness,
91388
+ localClientId: clientId,
91389
+ userName: config.userName,
91390
+ userColor,
91391
+ userAvatar: config.userAvatar,
91392
+ role: config.role,
91393
+ canvasWidth,
91394
+ canvasHeight
91395
+ });
91396
+ const connectedCount = status === "connected" ? remoteUsers.length + 1 : remoteUsers.length;
91397
+ return {
91398
+ status,
91399
+ remoteUsers,
91400
+ broadcastPresence,
91401
+ connectedCount,
91402
+ config,
91403
+ doc: doc2
91404
+ };
91405
+ }
91406
+ var CollaborationContext = React10.createContext(null);
91407
+ function useCollaboration() {
91408
+ return React10.useContext(CollaborationContext);
91409
+ }
91410
+ function CollaborationProvider({
91411
+ config,
91412
+ canvasWidth,
91413
+ canvasHeight,
91414
+ children
91415
+ }) {
91416
+ const value = useCollaborativeState({
91417
+ config,
91418
+ canvasWidth,
91419
+ canvasHeight
91420
+ });
91421
+ return /* @__PURE__ */ jsxRuntime.jsx(CollaborationContext.Provider, { value, children });
91422
+ }
91423
+ function RemoteUserCursors({
91424
+ remoteUsers,
91425
+ activeSlideIndex,
91426
+ canvasWidth,
91427
+ canvasHeight
91428
+ }) {
91429
+ const visibleUsers = remoteUsers.filter((u2) => u2.activeSlideIndex === activeSlideIndex);
91430
+ if (visibleUsers.length === 0) {
91431
+ return null;
91432
+ }
91433
+ return /* @__PURE__ */ jsxRuntime.jsx(
91434
+ "svg",
91435
+ {
91436
+ "data-testid": "remote-user-cursors",
91437
+ "data-export-ignore": "true",
91438
+ className: "absolute inset-0 pointer-events-none",
91439
+ style: { zIndex: 9999 },
91440
+ width: canvasWidth,
91441
+ height: canvasHeight,
91442
+ viewBox: `0 0 ${canvasWidth} ${canvasHeight}`,
91443
+ "aria-hidden": "true",
91444
+ children: visibleUsers.map((user) => /* @__PURE__ */ jsxRuntime.jsxs(
91445
+ "g",
91446
+ {
91447
+ transform: `translate(${user.cursorX}, ${user.cursorY})`,
91448
+ "data-testid": `remote-cursor-${user.clientId}`,
91449
+ children: [
91450
+ /* @__PURE__ */ jsxRuntime.jsx(
91451
+ "path",
91452
+ {
91453
+ d: "M0 0 L0 16 L4.5 12.5 L8 20 L10.5 19 L7 11.5 L12 11 Z",
91454
+ fill: user.userColor,
91455
+ stroke: "#fff",
91456
+ strokeWidth: 1,
91457
+ opacity: 0.9
91458
+ }
91459
+ ),
91460
+ /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: "translate(14, 18)", children: [
91461
+ /* @__PURE__ */ jsxRuntime.jsx(
91462
+ "rect",
91463
+ {
91464
+ rx: 3,
91465
+ ry: 3,
91466
+ x: -2,
91467
+ y: -10,
91468
+ width: Math.min(user.userName.length * 7 + 8, 150),
91469
+ height: 16,
91470
+ fill: user.userColor,
91471
+ opacity: 0.85
91472
+ }
91473
+ ),
91474
+ /* @__PURE__ */ jsxRuntime.jsx(
91475
+ "text",
91476
+ {
91477
+ fill: "#fff",
91478
+ fontSize: 10,
91479
+ fontFamily: "system-ui, sans-serif",
91480
+ fontWeight: 500,
91481
+ dominantBaseline: "central",
91482
+ y: -2,
91483
+ x: 2,
91484
+ children: user.userName.length > 20 ? `${user.userName.slice(0, 18)}...` : user.userName
91485
+ }
91486
+ )
91487
+ ] })
91488
+ ]
91489
+ },
91490
+ user.clientId
91491
+ ))
91492
+ }
91493
+ );
91494
+ }
91495
+ function getInitials(name) {
91496
+ const parts = name.trim().split(/\s+/);
91497
+ if (parts.length >= 2) {
91498
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
91499
+ }
91500
+ return name.slice(0, 2).toUpperCase();
91501
+ }
91502
+ function AvatarCircle({
91503
+ name,
91504
+ color,
91505
+ avatar,
91506
+ isLocal
91507
+ }) {
91508
+ const { t: t2 } = reactI18next.useTranslation();
91509
+ const initials = getInitials(name);
91510
+ const title = isLocal ? t2("pptx.collaboration.youLabel", { name }) : name;
91511
+ return /* @__PURE__ */ jsxRuntime.jsx(
91512
+ "div",
91513
+ {
91514
+ className: "relative w-7 h-7 rounded-full flex items-center justify-center text-[10px] font-semibold text-white border-2 -ml-1 first:ml-0",
91515
+ style: {
91516
+ backgroundColor: color,
91517
+ borderColor: isLocal ? "#fff" : color
91518
+ },
91519
+ title,
91520
+ "aria-label": title,
91521
+ children: avatar ? /* @__PURE__ */ jsxRuntime.jsx(
91522
+ "img",
91523
+ {
91524
+ src: avatar,
91525
+ alt: "",
91526
+ className: "w-full h-full rounded-full object-cover",
91527
+ onError: (e2) => {
91528
+ e2.target.style.display = "none";
91529
+ }
91530
+ }
91531
+ ) : initials
91532
+ }
91533
+ );
91534
+ }
91535
+ function UserAvatarBar({
91536
+ remoteUsers,
91537
+ localUserName,
91538
+ localUserColor,
91539
+ localUserAvatar,
91540
+ status,
91541
+ maxVisible = 5
91542
+ }) {
91543
+ const { t: t2 } = reactI18next.useTranslation();
91544
+ if (status === "disconnected" || status === "error") {
91545
+ return null;
91546
+ }
91547
+ const allUsers = [
91548
+ { name: localUserName, color: localUserColor, avatar: localUserAvatar, isLocal: true },
91549
+ ...remoteUsers.map((u2) => ({
91550
+ name: u2.userName,
91551
+ color: u2.userColor,
91552
+ avatar: u2.userAvatar,
91553
+ isLocal: false
91554
+ }))
91555
+ ];
91556
+ const visible = allUsers.slice(0, maxVisible);
91557
+ const overflow = allUsers.length - maxVisible;
91558
+ return /* @__PURE__ */ jsxRuntime.jsxs(
91559
+ "div",
91560
+ {
91561
+ "data-testid": "user-avatar-bar",
91562
+ className: "flex items-center px-2",
91563
+ "aria-label": t2("pptx.collaboration.usersConnected", { count: allUsers.length }),
91564
+ children: [
91565
+ visible.map((user, i3) => /* @__PURE__ */ jsxRuntime.jsx(
91566
+ AvatarCircle,
91567
+ {
91568
+ name: user.name,
91569
+ color: user.color,
91570
+ avatar: user.avatar,
91571
+ isLocal: user.isLocal
91572
+ },
91573
+ user.isLocal ? "local" : `remote-${i3}`
91574
+ )),
91575
+ overflow > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
91576
+ "div",
91577
+ {
91578
+ className: "w-7 h-7 rounded-full flex items-center justify-center text-[10px] font-semibold text-gray-300 bg-gray-700 border-2 border-gray-600 -ml-1",
91579
+ title: t2("pptx.collaboration.moreUsers", { count: overflow }),
91580
+ children: [
91581
+ "+",
91582
+ overflow
91583
+ ]
91584
+ }
91585
+ )
91586
+ ]
91587
+ }
91588
+ );
91589
+ }
91590
+ var STATUS_STYLES = {
91591
+ connected: {
91592
+ dot: "bg-green-400",
91593
+ text: "text-green-400",
91594
+ label: "Connected"
91595
+ },
91596
+ connecting: {
91597
+ dot: "bg-yellow-400 animate-pulse",
91598
+ text: "text-yellow-400",
91599
+ label: "Connecting..."
91600
+ },
91601
+ disconnected: {
91602
+ dot: "bg-gray-500",
91603
+ text: "text-gray-500",
91604
+ label: "Disconnected"
91605
+ },
91606
+ error: {
91607
+ dot: "bg-red-400",
91608
+ text: "text-red-400",
91609
+ label: "Connection error"
91610
+ }
91611
+ };
91612
+ function CollaborationStatusIndicator({
91613
+ status,
91614
+ connectedCount
91615
+ }) {
91616
+ const { t: t2 } = reactI18next.useTranslation();
91617
+ const style = STATUS_STYLES[status];
91618
+ return /* @__PURE__ */ jsxRuntime.jsxs(
91619
+ "div",
91620
+ {
91621
+ "data-testid": "collaboration-status",
91622
+ className: "flex items-center gap-1.5",
91623
+ "aria-label": t2("pptx.collaboration.statusAriaLabel", {
91624
+ status: t2(`pptx.collaboration.status.${status}`),
91625
+ count: connectedCount
91626
+ }),
91627
+ children: [
91628
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-block w-2 h-2 rounded-full ${style.dot}`, "aria-hidden": "true" }),
91629
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-[10px] ${style.text}`, children: status === "connected" ? t2("pptx.collaboration.userCount", { count: connectedCount }) : t2(`pptx.collaboration.status.${status}`) })
91630
+ ]
91631
+ }
91632
+ );
91633
+ }
91634
+ function CollaborationCursorOverlay({
91635
+ activeSlideIndex,
91636
+ canvasWidth,
91637
+ canvasHeight,
91638
+ selectedElementId
91639
+ }) {
91640
+ const collab = useCollaboration();
91641
+ const containerRef = React10.useRef(null);
91642
+ const prevSelectionRef = React10.useRef(selectedElementId);
91643
+ React10.useEffect(() => {
91644
+ if (!collab || selectedElementId === prevSelectionRef.current) {
91645
+ return;
91646
+ }
91647
+ prevSelectionRef.current = selectedElementId;
91648
+ collab.broadcastPresence({
91649
+ selectedElementId: selectedElementId ?? void 0,
91650
+ activeSlideIndex
91651
+ });
91652
+ }, [collab, selectedElementId, activeSlideIndex]);
91653
+ React10.useEffect(() => {
91654
+ if (!collab) {
91655
+ return;
91656
+ }
91657
+ const parent = containerRef.current?.parentElement;
91658
+ if (!parent) {
91659
+ return;
91660
+ }
91661
+ const handler = (e2) => {
91662
+ const rect = parent.getBoundingClientRect();
91663
+ const x2 = (e2.clientX - rect.left) / rect.width * canvasWidth;
91664
+ const y = (e2.clientY - rect.top) / rect.height * canvasHeight;
91665
+ collab.broadcastPresence({
91666
+ cursorX: x2,
91667
+ cursorY: y,
91668
+ activeSlideIndex
91669
+ });
91670
+ };
91671
+ parent.addEventListener("pointermove", handler);
91672
+ return () => parent.removeEventListener("pointermove", handler);
91673
+ }, [collab, canvasWidth, canvasHeight, activeSlideIndex]);
91674
+ if (!collab) {
91675
+ return null;
91676
+ }
91677
+ return /* @__PURE__ */ jsxRuntime.jsx(
91678
+ "div",
91679
+ {
91680
+ ref: containerRef,
91681
+ "data-testid": "collab-pointer-tracker",
91682
+ "data-export-ignore": "true",
91683
+ style: { display: "contents" },
91684
+ children: /* @__PURE__ */ jsxRuntime.jsx(
91685
+ RemoteUserCursors,
91686
+ {
91687
+ remoteUsers: collab.remoteUsers,
91688
+ activeSlideIndex,
91689
+ canvasWidth,
91690
+ canvasHeight
91691
+ }
91692
+ )
91693
+ }
91694
+ );
91695
+ }
91696
+ function RemoteSelectionOverlay({
91697
+ elements,
91698
+ activeSlideIndex
91699
+ }) {
91700
+ const collab = useCollaboration();
91701
+ if (!collab) {
91702
+ return null;
91703
+ }
91704
+ const elementMap = /* @__PURE__ */ new Map();
91705
+ for (const el of elements) {
91706
+ elementMap.set(el.id, el);
91707
+ }
91708
+ const selections = [];
91709
+ for (const user of collab.remoteUsers) {
91710
+ if (user.activeSlideIndex === activeSlideIndex && user.selectedElementId) {
91711
+ const el = elementMap.get(user.selectedElementId);
91712
+ if (el) {
91713
+ selections.push({
91714
+ userName: user.userName,
91715
+ userColor: user.userColor,
91716
+ element: el
91717
+ });
91718
+ }
91719
+ }
91720
+ }
91721
+ if (selections.length === 0) {
91722
+ return null;
91723
+ }
91724
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: selections.map((sel) => /* @__PURE__ */ jsxRuntime.jsx(
91725
+ "div",
91726
+ {
91727
+ "data-testid": `remote-selection-${sel.element.id}`,
91728
+ "data-export-ignore": "true",
91729
+ className: "absolute pointer-events-none",
91730
+ style: {
91731
+ left: sel.element.x,
91732
+ top: sel.element.y,
91733
+ width: sel.element.width,
91734
+ height: sel.element.height,
91735
+ zIndex: 9997,
91736
+ border: `2px solid ${sel.userColor}`,
91737
+ borderRadius: 2
91738
+ },
91739
+ children: /* @__PURE__ */ jsxRuntime.jsx(
91740
+ "span",
91741
+ {
91742
+ className: "absolute -top-5 left-0 px-1 py-0.5 text-[9px] font-medium text-white rounded-sm whitespace-nowrap leading-none",
91743
+ style: { backgroundColor: sel.userColor },
91744
+ children: sel.userName
91745
+ }
91746
+ )
91747
+ },
91748
+ `remote-sel-${sel.element.id}`
91749
+ )) });
91750
+ }
91120
91751
  function SectionContextMenu({
91121
91752
  state: state2,
91122
91753
  sectionGroups,
@@ -91398,6 +92029,7 @@ function SlideItemInner({
91398
92029
  canvasSize,
91399
92030
  canEdit,
91400
92031
  rehearsalTimings,
92032
+ presenceUsers,
91401
92033
  onSelectSlide,
91402
92034
  onSlideContextMenu,
91403
92035
  onAddSection,
@@ -91440,16 +92072,27 @@ function SlideItemInner({
91440
92072
  onDragOver,
91441
92073
  onDrop: (e2) => onDrop(e2, slideIndex),
91442
92074
  children: [
91443
- /* @__PURE__ */ jsxRuntime.jsx(
91444
- "span",
91445
- {
91446
- className: cn(
91447
- "text-[10px] tabular-nums w-5 text-right shrink-0 select-none",
91448
- isActive ? "text-primary font-medium" : "text-muted-foreground"
91449
- ),
91450
- children: slideIndex + 1
91451
- }
91452
- ),
92075
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5 w-5 shrink-0", children: [
92076
+ /* @__PURE__ */ jsxRuntime.jsx(
92077
+ "span",
92078
+ {
92079
+ className: cn(
92080
+ "text-[10px] tabular-nums text-right select-none w-full",
92081
+ isActive ? "text-primary font-medium" : "text-muted-foreground"
92082
+ ),
92083
+ children: slideIndex + 1
92084
+ }
92085
+ ),
92086
+ 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(
92087
+ "span",
92088
+ {
92089
+ className: "w-[6px] h-[6px] rounded-full",
92090
+ style: { backgroundColor: u2.userColor },
92091
+ title: u2.userName
92092
+ },
92093
+ i3
92094
+ )) })
92095
+ ] }),
91453
92096
  /* @__PURE__ */ jsxRuntime.jsxs(
91454
92097
  "div",
91455
92098
  {
@@ -91599,8 +92242,26 @@ function SlidesPaneSidebar({
91599
92242
  panelWidth
91600
92243
  }) {
91601
92244
  const { t: t2 } = reactI18next.useTranslation();
92245
+ const collab = useCollaboration();
91602
92246
  const slideRefs = React10.useRef(/* @__PURE__ */ new Map());
91603
92247
  const renameInputRef = React10.useRef(null);
92248
+ const slidePresenceMap = React10.useMemo(() => {
92249
+ if (!collab || collab.remoteUsers.length === 0) {
92250
+ return void 0;
92251
+ }
92252
+ const map3 = /* @__PURE__ */ new Map();
92253
+ for (const user of collab.remoteUsers) {
92254
+ const idx = user.activeSlideIndex;
92255
+ const existing = map3.get(idx);
92256
+ const entry = { userName: user.userName, userColor: user.userColor };
92257
+ if (existing) {
92258
+ existing.push(entry);
92259
+ } else {
92260
+ map3.set(idx, [entry]);
92261
+ }
92262
+ }
92263
+ return map3;
92264
+ }, [collab]);
91604
92265
  const estimatedItemHeight = React10.useMemo(
91605
92266
  () => estimateSlideItemHeight(canvasSize.width, canvasSize.height),
91606
92267
  [canvasSize.width, canvasSize.height]
@@ -91722,6 +92383,7 @@ function SlidesPaneSidebar({
91722
92383
  canvasSize,
91723
92384
  canEdit,
91724
92385
  rehearsalTimings,
92386
+ presenceUsers: slidePresenceMap?.get(item.slideIndex),
91725
92387
  onSelectSlide,
91726
92388
  onSlideContextMenu,
91727
92389
  onAddSection,
@@ -91775,6 +92437,7 @@ function SlidesPaneSidebar({
91775
92437
  canvasSize,
91776
92438
  canEdit,
91777
92439
  rehearsalTimings,
92440
+ presenceUsers: slidePresenceMap?.get(idx),
91778
92441
  onSelectSlide,
91779
92442
  onSlideContextMenu,
91780
92443
  onAddSection,
@@ -96541,16 +97204,31 @@ var ALIGN_BTNS = [
96541
97204
  { k: "bottom", el: /* @__PURE__ */ jsxRuntime.jsx(lu.LuChevronDown, { className: ic2 }) }
96542
97205
  ];
96543
97206
  var DRAW_TOOLS = [
96544
- { id: "select", icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuMoveRight, { className: ic2 }), t: "Select" },
96545
- { id: "pen", icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuPencil, { className: ic2 }), t: "Pen" },
97207
+ {
97208
+ id: "select",
97209
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuMoveRight, { className: ic2 }),
97210
+ t: "Select",
97211
+ ac: "bg-primary text-primary-foreground"
97212
+ },
97213
+ {
97214
+ id: "pen",
97215
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuPencil, { className: ic2 }),
97216
+ t: "Pen",
97217
+ ac: "bg-primary text-primary-foreground"
97218
+ },
96546
97219
  {
96547
97220
  id: "highlighter",
96548
97221
  icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuType, { className: ic2 }),
96549
97222
  t: "Highlighter",
96550
97223
  ac: "bg-yellow-600 text-white"
96551
97224
  },
96552
- { id: "eraser", icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuMinus, { className: ic2 }), t: "Eraser" },
96553
- { id: "freeform", icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuSpline, { className: ic2 }), t: "Freeform" }
97225
+ { id: "eraser", icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuMinus, { className: ic2 }), t: "Eraser", ac: "bg-red-600 text-white" },
97226
+ {
97227
+ id: "freeform",
97228
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lu.LuSpline, { className: ic2 }),
97229
+ t: "Freeform",
97230
+ ac: "bg-primary text-primary-foreground"
97231
+ }
96554
97232
  ];
96555
97233
  var OV = [
96556
97234
  {
@@ -96756,7 +97434,7 @@ function AnimationsSection(p3) {
96756
97434
  "button",
96757
97435
  {
96758
97436
  type: "button",
96759
- onClick: p3.onToggleInspector,
97437
+ onClick: p3.onOpenAnimationPanel ?? p3.onToggleInspector,
96760
97438
  className: cn(
96761
97439
  pill,
96762
97440
  p3.isInspectorPaneOpen ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
@@ -97855,413 +98533,106 @@ function SlideShowSection(p3) {
97855
98533
  /* @__PURE__ */ jsxRuntime.jsx(lu.LuSettings, { className: ic2 }),
97856
98534
  "Set Up Slide Show"
97857
98535
  ] }),
97858
- /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onOpenBroadcastDialog, className: pill, title: "Broadcast slide show", children: [
97859
- /* @__PURE__ */ jsxRuntime.jsx(lu.LuCast, { className: ic2 }),
97860
- "Broadcast"
97861
- ] }),
97862
- sep,
97863
- /* @__PURE__ */ jsxRuntime.jsxs(
97864
- "button",
97865
- {
97866
- onClick: p3.onToggleSubtitles,
97867
- className: cn(
97868
- pill,
97869
- p3.showSubtitles ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
97870
- ),
97871
- title: "Toggle subtitles",
97872
- children: [
97873
- /* @__PURE__ */ jsxRuntime.jsx(lu.LuCaptions, { className: ic2 }),
97874
- "Subtitles"
97875
- ]
97876
- }
97877
- )
97878
- ] });
97879
- }
97880
- var FONT_COLOR_PRESETS = [
97881
- "#000000",
97882
- "#ffffff",
97883
- "#ff0000",
97884
- "#00aa00",
97885
- "#0000ff",
97886
- "#ff8800",
97887
- "#8800cc",
97888
- "#00cccc",
97889
- "#ff69b4",
97890
- "#808080"
97891
- ];
97892
- var HIGHLIGHT_COLOR_PRESETS = [
97893
- "#ffff00",
97894
- "#00ff00",
97895
- "#00ffff",
97896
- "#ff00ff",
97897
- "#0000ff",
97898
- "#ff0000",
97899
- "#000080",
97900
- "#008080",
97901
- "#008000",
97902
- "#800080"
97903
- ];
97904
- function TextSection(p3) {
97905
- const hasSel = Boolean(p3.selectedElement);
97906
- const canMut = hasSel && p3.canEdit;
97907
- const isTextEl = hasSel && p3.selectedElement !== null && pptxViewerCore.hasTextProperties(p3.selectedElement);
97908
- const isTable = hasSel && p3.selectedElement?.type === "table";
97909
- const canFormat = isTextEl || isTable;
97910
- const currentColor = isTextEl && p3.selectedElement && pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textSegments?.[0]?.style?.color ?? p3.selectedElement.textStyle?.color ?? "#000000" : "#000000";
97911
- const currentHighlight = isTextEl && p3.selectedElement && pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textSegments?.[0]?.style?.highlightColor ?? p3.selectedElement.textStyle?.highlightColor ?? "#ffff00" : "#ffff00";
97912
- const colorInputRef = React10.useRef(null);
97913
- const highlightInputRef = React10.useRef(null);
97914
- const handleColorChange = React10.useCallback(
97915
- (color) => {
97916
- if (!canFormat) {
97917
- return;
97918
- }
97919
- p3.onUpdateTextStyle({ color });
97920
- },
97921
- [canFormat, p3]
97922
- );
97923
- const handleHighlightChange = React10.useCallback(
97924
- (highlightColor) => {
97925
- if (!canFormat) {
97926
- return;
97927
- }
97928
- p3.onUpdateTextStyle({ highlightColor });
97929
- },
97930
- [canFormat, p3]
97931
- );
97932
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
97933
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
97934
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
97935
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: grp, children: FMT.map((b2, i3, a2) => {
97936
- const handleClick = () => {
97937
- if (!canFormat || !p3.selectedElement) {
97938
- return;
97939
- }
97940
- const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
97941
- switch (b2.t) {
97942
- case "Bold":
97943
- p3.onUpdateTextStyle({ bold: !ts?.bold });
97944
- break;
97945
- case "Italic":
97946
- p3.onUpdateTextStyle({ italic: !ts?.italic });
97947
- break;
97948
- case "Underline":
97949
- p3.onUpdateTextStyle({
97950
- underline: !ts?.underline
97951
- });
97952
- break;
97953
- case "Strikethrough":
97954
- p3.onUpdateTextStyle({
97955
- strikethrough: !ts?.strikethrough
97956
- });
97957
- break;
97958
- }
97959
- };
97960
- return /* @__PURE__ */ jsxRuntime.jsx(
97961
- "button",
97962
- {
97963
- type: "button",
97964
- disabled: !canMut,
97965
- onMouseDown: (e2) => e2.preventDefault(),
97966
- onClick: handleClick,
97967
- className: i3 < a2.length - 1 ? gB : gL,
97968
- title: b2.t,
97969
- children: b2.i
97970
- },
97971
- b2.t
97972
- );
97973
- }) }),
97974
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: grp, children: [
97975
- /* @__PURE__ */ jsxRuntime.jsx(
97976
- "button",
97977
- {
97978
- type: "button",
97979
- disabled: !canMut,
97980
- onMouseDown: (e2) => e2.preventDefault(),
97981
- onClick: () => {
97982
- if (!canFormat || !p3.selectedElement) {
97983
- return;
97984
- }
97985
- const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
97986
- const current = ts?.fontSize ?? 18;
97987
- p3.onUpdateTextStyle({ fontSize: current + 2 });
97988
- },
97989
- className: gB,
97990
- title: "Increase Font Size",
97991
- children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuAArrowUp, { className: ic2 })
97992
- }
97993
- ),
97994
- /* @__PURE__ */ jsxRuntime.jsx(
97995
- "button",
97996
- {
97997
- type: "button",
97998
- disabled: !canMut,
97999
- onMouseDown: (e2) => e2.preventDefault(),
98000
- onClick: () => {
98001
- if (!canFormat || !p3.selectedElement) {
98002
- return;
98003
- }
98004
- const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98005
- const current = ts?.fontSize ?? 18;
98006
- p3.onUpdateTextStyle({ fontSize: Math.max(1, current - 2) });
98007
- },
98008
- className: gB,
98009
- title: "Decrease Font Size",
98010
- children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuAArrowDown, { className: ic2 })
98011
- }
98012
- ),
98013
- /* @__PURE__ */ jsxRuntime.jsx(
98014
- "button",
98015
- {
98016
- type: "button",
98017
- disabled: !canMut,
98018
- onMouseDown: (e2) => e2.preventDefault(),
98019
- onClick: () => {
98020
- if (!canFormat) {
98021
- return;
98022
- }
98023
- p3.onUpdateTextStyle({
98024
- bold: false,
98025
- italic: false,
98026
- underline: false,
98027
- strikethrough: false,
98028
- highlightColor: void 0
98029
- });
98030
- },
98031
- className: gL,
98032
- title: "Clear Formatting",
98033
- children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuRemoveFormatting, { className: ic2 })
98034
- }
98035
- )
98036
- ] }),
98037
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
98038
- /* @__PURE__ */ jsxRuntime.jsxs(
98039
- "button",
98040
- {
98041
- type: "button",
98042
- disabled: !canMut,
98043
- onMouseDown: (e2) => e2.preventDefault(),
98044
- className: pill,
98045
- title: "Font Color",
98046
- children: [
98047
- /* @__PURE__ */ jsxRuntime.jsx(
98048
- "svg",
98049
- {
98050
- className: ic2,
98051
- viewBox: "0 0 24 24",
98052
- fill: "none",
98053
- stroke: "currentColor",
98054
- strokeWidth: "2",
98055
- strokeLinecap: "round",
98056
- strokeLinejoin: "round",
98057
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 20h12M9.5 4h5L18 16H6L9.5 4z" })
98058
- }
98059
- ),
98060
- /* @__PURE__ */ jsxRuntime.jsx(
98061
- "div",
98062
- {
98063
- className: "w-4 h-1 rounded-sm -mt-0.5",
98064
- style: { backgroundColor: currentColor }
98065
- }
98066
- )
98067
- ]
98068
- }
98069
- ),
98070
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 top-full z-50 hidden group-hover:block pt-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl p-2 w-36", children: [
98071
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1.5 mb-2", children: FONT_COLOR_PRESETS.map((c2) => /* @__PURE__ */ jsxRuntime.jsx(
98072
- "button",
98073
- {
98074
- type: "button",
98075
- className: `w-5 h-5 rounded-full border transition-transform hover:scale-125 ${currentColor?.toLowerCase() === c2 ? "border-primary ring-1 ring-primary" : "border-border"}`,
98076
- style: { backgroundColor: c2 },
98077
- onMouseDown: (e2) => e2.preventDefault(),
98078
- onClick: () => handleColorChange(c2)
98079
- },
98080
- c2
98081
- )) }),
98082
- /* @__PURE__ */ jsxRuntime.jsx(
98083
- "button",
98084
- {
98085
- type: "button",
98086
- className: "w-full text-[10px] text-muted-foreground hover:text-foreground py-1 transition-colors",
98087
- onMouseDown: (e2) => e2.preventDefault(),
98088
- onClick: () => colorInputRef.current?.click(),
98089
- children: "Custom colour..."
98090
- }
98091
- ),
98092
- /* @__PURE__ */ jsxRuntime.jsx(
98093
- "input",
98094
- {
98095
- ref: colorInputRef,
98096
- type: "color",
98097
- className: "sr-only",
98098
- value: currentColor,
98099
- onChange: (e2) => handleColorChange(e2.target.value)
98100
- }
98101
- )
98102
- ] }) })
98103
- ] }),
98104
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
98105
- /* @__PURE__ */ jsxRuntime.jsxs(
98106
- "button",
98107
- {
98108
- type: "button",
98109
- disabled: !canMut,
98110
- onMouseDown: (e2) => e2.preventDefault(),
98111
- className: pill,
98112
- title: "Text Highlight Color",
98113
- children: [
98114
- /* @__PURE__ */ jsxRuntime.jsx(lu.LuHighlighter, { className: ic2 }),
98115
- /* @__PURE__ */ jsxRuntime.jsx(
98116
- "div",
98117
- {
98118
- className: "w-4 h-1 rounded-sm -mt-0.5",
98119
- style: { backgroundColor: currentHighlight }
98120
- }
98121
- )
98122
- ]
98123
- }
98124
- ),
98125
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 top-full z-50 hidden group-hover:block pt-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl p-2 w-36", children: [
98126
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1.5 mb-2", children: HIGHLIGHT_COLOR_PRESETS.map((c2) => /* @__PURE__ */ jsxRuntime.jsx(
98127
- "button",
98128
- {
98129
- type: "button",
98130
- className: `w-5 h-5 rounded-full border transition-transform hover:scale-125 ${currentHighlight?.toLowerCase() === c2 ? "border-primary ring-1 ring-primary" : "border-border"}`,
98131
- style: { backgroundColor: c2 },
98132
- onMouseDown: (e2) => e2.preventDefault(),
98133
- onClick: () => handleHighlightChange(c2)
98134
- },
98135
- c2
98136
- )) }),
98137
- /* @__PURE__ */ jsxRuntime.jsx(
98138
- "button",
98139
- {
98140
- type: "button",
98141
- className: "w-full text-[10px] text-muted-foreground hover:text-foreground py-1 transition-colors",
98142
- onMouseDown: (e2) => e2.preventDefault(),
98143
- onClick: () => highlightInputRef.current?.click(),
98144
- children: "Custom colour..."
98145
- }
98146
- ),
98147
- /* @__PURE__ */ jsxRuntime.jsx(
98148
- "input",
98149
- {
98150
- ref: highlightInputRef,
98151
- type: "color",
98152
- className: "sr-only",
98153
- value: currentHighlight,
98154
- onChange: (e2) => handleHighlightChange(e2.target.value)
98155
- }
98156
- )
98157
- ] }) })
98158
- ] })
98159
- ] }),
98160
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Font" })
98161
- ] }),
98536
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onOpenBroadcastDialog, className: pill, title: "Broadcast slide show", children: [
98537
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCast, { className: ic2 }),
98538
+ "Broadcast"
98539
+ ] }),
98162
98540
  sep,
98541
+ /* @__PURE__ */ jsxRuntime.jsxs(
98542
+ "button",
98543
+ {
98544
+ onClick: p3.onToggleSubtitles,
98545
+ className: cn(
98546
+ pill,
98547
+ p3.showSubtitles ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
98548
+ ),
98549
+ title: "Toggle subtitles",
98550
+ children: [
98551
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCaptions, { className: ic2 }),
98552
+ "Subtitles"
98553
+ ]
98554
+ }
98555
+ )
98556
+ ] });
98557
+ }
98558
+ var FONT_COLOR_PRESETS = [
98559
+ "#000000",
98560
+ "#ffffff",
98561
+ "#ff0000",
98562
+ "#00aa00",
98563
+ "#0000ff",
98564
+ "#ff8800",
98565
+ "#8800cc",
98566
+ "#00cccc",
98567
+ "#ff69b4",
98568
+ "#808080"
98569
+ ];
98570
+ var HIGHLIGHT_COLOR_PRESETS = [
98571
+ "#ffff00",
98572
+ "#00ff00",
98573
+ "#00ffff",
98574
+ "#ff00ff",
98575
+ "#0000ff",
98576
+ "#ff0000",
98577
+ "#000080",
98578
+ "#008080",
98579
+ "#008000",
98580
+ "#800080"
98581
+ ];
98582
+ function TextSection(p3) {
98583
+ const hasSel = Boolean(p3.selectedElement);
98584
+ const canMut = hasSel && p3.canEdit;
98585
+ const isTextEl = hasSel && p3.selectedElement !== null && pptxViewerCore.hasTextProperties(p3.selectedElement);
98586
+ const isTable = hasSel && p3.selectedElement?.type === "table";
98587
+ const canFormat = isTextEl || isTable;
98588
+ const currentColor = isTextEl && p3.selectedElement && pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textSegments?.[0]?.style?.color ?? p3.selectedElement.textStyle?.color ?? "#000000" : "#000000";
98589
+ const currentHighlight = isTextEl && p3.selectedElement && pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textSegments?.[0]?.style?.highlightColor ?? p3.selectedElement.textStyle?.highlightColor ?? "#ffff00" : "#ffff00";
98590
+ const colorInputRef = React10.useRef(null);
98591
+ const highlightInputRef = React10.useRef(null);
98592
+ const handleColorChange = React10.useCallback(
98593
+ (color) => {
98594
+ if (!canFormat) {
98595
+ return;
98596
+ }
98597
+ p3.onUpdateTextStyle({ color });
98598
+ },
98599
+ [canFormat, p3]
98600
+ );
98601
+ const handleHighlightChange = React10.useCallback(
98602
+ (highlightColor) => {
98603
+ if (!canFormat) {
98604
+ return;
98605
+ }
98606
+ p3.onUpdateTextStyle({ highlightColor });
98607
+ },
98608
+ [canFormat, p3]
98609
+ );
98610
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
98163
98611
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
98164
98612
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
98165
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: grp, children: [
98166
- /* @__PURE__ */ jsxRuntime.jsx(
98167
- "button",
98168
- {
98169
- type: "button",
98170
- disabled: !canMut,
98171
- onMouseDown: (e2) => e2.preventDefault(),
98172
- onClick: () => {
98173
- if (!canFormat || !p3.selectedElement) {
98174
- return;
98175
- }
98176
- const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98177
- p3.onUpdateTextStyle({
98178
- listType: ts?.listType === "bullet" ? "none" : "bullet"
98179
- });
98180
- },
98181
- className: gB,
98182
- title: "Bullet List",
98183
- children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuList, { className: ic2 })
98184
- }
98185
- ),
98186
- /* @__PURE__ */ jsxRuntime.jsx(
98187
- "button",
98188
- {
98189
- type: "button",
98190
- disabled: !canMut,
98191
- onMouseDown: (e2) => e2.preventDefault(),
98192
- onClick: () => {
98193
- if (!canFormat || !p3.selectedElement) {
98194
- return;
98195
- }
98196
- const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98197
- p3.onUpdateTextStyle({
98198
- listType: ts?.listType === "numbered" ? "none" : "numbered"
98199
- });
98200
- },
98201
- className: gL,
98202
- title: "Numbered List",
98203
- children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuListOrdered, { className: ic2 })
98613
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: grp, children: FMT.map((b2, i3, a2) => {
98614
+ const handleClick = () => {
98615
+ if (!canFormat || !p3.selectedElement) {
98616
+ return;
98204
98617
  }
98205
- )
98206
- ] }),
98207
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: grp, children: [
98208
- /* @__PURE__ */ jsxRuntime.jsx(
98209
- "button",
98210
- {
98211
- type: "button",
98212
- disabled: !canMut,
98213
- onMouseDown: (e2) => e2.preventDefault(),
98214
- onClick: () => {
98215
- if (!canFormat || !p3.selectedElement) {
98216
- return;
98217
- }
98218
- const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98219
- const current = ts?.paragraphMarginLeft ?? 0;
98618
+ const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98619
+ switch (b2.t) {
98620
+ case "Bold":
98621
+ p3.onUpdateTextStyle({ bold: !ts?.bold });
98622
+ break;
98623
+ case "Italic":
98624
+ p3.onUpdateTextStyle({ italic: !ts?.italic });
98625
+ break;
98626
+ case "Underline":
98220
98627
  p3.onUpdateTextStyle({
98221
- paragraphMarginLeft: Math.max(0, current - 24)
98628
+ underline: !ts?.underline
98222
98629
  });
98223
- },
98224
- className: gB,
98225
- title: "Decrease Indent",
98226
- children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuIndentDecrease, { className: ic2 })
98227
- }
98228
- ),
98229
- /* @__PURE__ */ jsxRuntime.jsx(
98230
- "button",
98231
- {
98232
- type: "button",
98233
- disabled: !canMut,
98234
- onMouseDown: (e2) => e2.preventDefault(),
98235
- onClick: () => {
98236
- if (!canFormat || !p3.selectedElement) {
98237
- return;
98238
- }
98239
- const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98240
- const current = ts?.paragraphMarginLeft ?? 0;
98630
+ break;
98631
+ case "Strikethrough":
98241
98632
  p3.onUpdateTextStyle({
98242
- paragraphMarginLeft: current + 24
98633
+ strikethrough: !ts?.strikethrough
98243
98634
  });
98244
- },
98245
- className: gL,
98246
- title: "Increase Indent",
98247
- children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuIndentIncrease, { className: ic2 })
98248
- }
98249
- )
98250
- ] }),
98251
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: grp, children: ATXT.map((b2, i3, a2) => {
98252
- const handleClick = () => {
98253
- if (!canFormat) {
98254
- return;
98255
- }
98256
- const alignMap = {
98257
- "Align left": "left",
98258
- "Align center": "center",
98259
- "Align right": "right",
98260
- Justify: "justify"
98261
- };
98262
- const align = alignMap[b2.t];
98263
- if (align) {
98264
- p3.onUpdateTextStyle({ align });
98635
+ break;
98265
98636
  }
98266
98637
  };
98267
98638
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -98277,518 +98648,318 @@ function TextSection(p3) {
98277
98648
  },
98278
98649
  b2.t
98279
98650
  );
98280
- }) })
98281
- ] }),
98282
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Paragraph" })
98283
- ] })
98284
- ] });
98285
- }
98286
-
98287
- // src/viewer/hooks/collaboration/sanitize.ts
98288
- var ROOM_ID_REGEX = /^[a-zA-Z0-9_-]{1,128}$/;
98289
- function validateRoomId(roomId) {
98290
- if (!ROOM_ID_REGEX.test(roomId)) {
98291
- throw new Error(
98292
- `Invalid collaboration room ID: "${roomId}". Must be 1-128 alphanumeric characters, hyphens, or underscores.`
98293
- );
98294
- }
98295
- return roomId;
98296
- }
98297
- function sanitizeUserName(name) {
98298
- if (typeof name !== "string") {
98299
- return "Anonymous";
98300
- }
98301
- const stripped = name.replace(/<[^>]*>/g, "");
98302
- const trimmed = stripped.trim().slice(0, 64);
98303
- return trimmed || "Anonymous";
98304
- }
98305
- function clampCursorPosition(value, min2, max2) {
98306
- if (typeof value !== "number" || !Number.isFinite(value)) {
98307
- return 0;
98308
- }
98309
- const margin = 20;
98310
- return Math.max(min2 - margin, Math.min(max2 + margin, value));
98311
- }
98312
- var HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
98313
- function sanitizeColor(color, fallback = "#6366f1") {
98314
- if (typeof color !== "string") {
98315
- return fallback;
98316
- }
98317
- return HEX_COLOR_REGEX.test(color) ? color : fallback;
98318
- }
98319
- function sanitizeAvatarUrl(url) {
98320
- if (typeof url !== "string") {
98321
- return void 0;
98322
- }
98323
- try {
98324
- const parsed = new URL(url);
98325
- if (parsed.protocol === "https:" || parsed.protocol === "http:" || parsed.protocol === "data:") {
98326
- return url;
98327
- }
98328
- } catch {
98329
- }
98330
- return void 0;
98331
- }
98332
- function sanitizeSlideIndex(value) {
98333
- if (typeof value !== "number" || !Number.isFinite(value)) {
98334
- return 0;
98335
- }
98336
- return Math.max(0, Math.floor(value));
98337
- }
98338
- function sanitizePresence(raw, canvasWidth, canvasHeight) {
98339
- if (typeof raw.clientId !== "number") {
98340
- return null;
98341
- }
98342
- return {
98343
- clientId: raw.clientId,
98344
- userName: sanitizeUserName(raw.userName),
98345
- userAvatar: sanitizeAvatarUrl(raw.userAvatar),
98346
- userColor: sanitizeColor(raw.userColor),
98347
- activeSlideIndex: sanitizeSlideIndex(raw.activeSlideIndex),
98348
- cursorX: clampCursorPosition(raw.cursorX, 0, canvasWidth),
98349
- cursorY: clampCursorPosition(raw.cursorY, 0, canvasHeight),
98350
- lastUpdated: typeof raw.lastUpdated === "string" ? raw.lastUpdated : (/* @__PURE__ */ new Date()).toISOString(),
98351
- selectedElementId: typeof raw.selectedElementId === "string" ? raw.selectedElementId.slice(0, 128) : void 0
98352
- };
98353
- }
98354
- var BROADCAST_THROTTLE_MS = 50;
98355
- var STALE_PRESENCE_MS = 3e4;
98356
- function usePresenceTracking({
98357
- awareness,
98358
- localClientId,
98359
- userName,
98360
- userColor,
98361
- userAvatar,
98362
- canvasWidth,
98363
- canvasHeight
98364
- }) {
98365
- const [remoteUsers, setRemoteUsers] = React10.useState([]);
98366
- const lastBroadcastRef = React10.useRef(0);
98367
- const pendingBroadcastRef = React10.useRef(null);
98368
- const latestLocalState = React10.useRef({});
98369
- const broadcastPresence = React10.useCallback(
98370
- (update2) => {
98371
- if (!awareness) {
98372
- return;
98373
- }
98374
- Object.assign(latestLocalState.current, update2);
98375
- const now = Date.now();
98376
- const elapsed = now - lastBroadcastRef.current;
98377
- const flush = () => {
98378
- const state2 = {
98379
- ...latestLocalState.current,
98380
- userName,
98381
- userColor,
98382
- userAvatar,
98383
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98384
- };
98385
- awareness.setLocalStateField("presence", state2);
98386
- lastBroadcastRef.current = Date.now();
98387
- };
98388
- if (elapsed >= BROADCAST_THROTTLE_MS) {
98389
- if (pendingBroadcastRef.current) {
98390
- clearTimeout(pendingBroadcastRef.current);
98391
- pendingBroadcastRef.current = null;
98392
- }
98393
- flush();
98394
- } else if (!pendingBroadcastRef.current) {
98395
- pendingBroadcastRef.current = setTimeout(() => {
98396
- pendingBroadcastRef.current = null;
98397
- flush();
98398
- }, BROADCAST_THROTTLE_MS - elapsed);
98399
- }
98400
- },
98401
- [awareness, userName, userColor, userAvatar]
98402
- );
98403
- React10.useEffect(() => {
98404
- if (!awareness) {
98405
- return;
98406
- }
98407
- awareness.setLocalStateField("presence", {
98408
- userName,
98409
- userColor,
98410
- userAvatar,
98411
- activeSlideIndex: 0,
98412
- cursorX: 0,
98413
- cursorY: 0,
98414
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98415
- });
98416
- }, [awareness, userName, userColor, userAvatar]);
98417
- React10.useEffect(() => {
98418
- if (!awareness || localClientId === null) {
98419
- return;
98420
- }
98421
- const handleChange = () => {
98422
- const now = Date.now();
98423
- const states = awareness.getStates();
98424
- const users = [];
98425
- states.forEach((state2, cid) => {
98426
- if (cid === localClientId) {
98427
- return;
98428
- }
98429
- const raw = state2?.presence;
98430
- if (!raw || typeof raw !== "object") {
98431
- return;
98432
- }
98433
- const sanitized = sanitizePresence({ ...raw, clientId: cid }, canvasWidth, canvasHeight);
98434
- if (!sanitized) {
98435
- return;
98436
- }
98437
- const updatedAt = new Date(sanitized.lastUpdated).getTime();
98438
- if (Number.isNaN(updatedAt) || now - updatedAt > STALE_PRESENCE_MS) {
98439
- return;
98440
- }
98441
- users.push(sanitized);
98442
- });
98443
- setRemoteUsers(users);
98444
- };
98445
- awareness.on("change", handleChange);
98446
- awareness.on("update", handleChange);
98447
- handleChange();
98448
- return () => {
98449
- awareness.off("change", handleChange);
98450
- awareness.off("update", handleChange);
98451
- };
98452
- }, [awareness, localClientId, canvasWidth, canvasHeight]);
98453
- React10.useEffect(() => {
98454
- if (!awareness) {
98455
- return;
98456
- }
98457
- const interval = setInterval(() => {
98458
- awareness.setLocalStateField("presence", {
98459
- ...latestLocalState.current,
98460
- userName,
98461
- userColor,
98462
- userAvatar,
98463
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98464
- });
98465
- }, 1e4);
98466
- return () => clearInterval(interval);
98467
- }, [awareness, userName, userColor, userAvatar]);
98468
- React10.useEffect(() => {
98469
- return () => {
98470
- if (pendingBroadcastRef.current) {
98471
- clearTimeout(pendingBroadcastRef.current);
98472
- }
98473
- };
98474
- }, []);
98475
- return { remoteUsers, broadcastPresence };
98476
- }
98477
- function useYjsProvider({ config }) {
98478
- const [status, setStatus] = React10.useState("disconnected");
98479
- const [awareness, setAwareness] = React10.useState(null);
98480
- const [doc2, setDoc] = React10.useState(null);
98481
- const [clientId, setClientId] = React10.useState(null);
98482
- const cleanupRef = React10.useRef(null);
98483
- const init = React10.useCallback(async () => {
98484
- const roomId = validateRoomId(config.roomId);
98485
- setStatus("connecting");
98486
- try {
98487
- const [Y, { WebsocketProvider: WebsocketProvider2 }] = await Promise.all([Promise.resolve().then(() => (init_yjs(), yjs_exports)), Promise.resolve().then(() => (init_y_websocket(), y_websocket_exports))]);
98488
- const yDoc = new Y.Doc();
98489
- const provider = new WebsocketProvider2(
98490
- config.serverUrl,
98491
- roomId,
98492
- yDoc,
98493
- // eslint-disable-line @typescript-eslint/no-explicit-any
98494
- {
98495
- params: config.authToken ? { token: config.authToken } : void 0
98496
- }
98497
- );
98498
- const handleStatus = (event) => {
98499
- if (event.status === "connected") {
98500
- setStatus("connected");
98501
- } else if (event.status === "disconnected") {
98502
- setStatus("disconnected");
98503
- }
98504
- };
98505
- provider.on("status", handleStatus);
98506
- if (provider.wsconnected) {
98507
- setStatus("connected");
98508
- }
98509
- setDoc(yDoc);
98510
- setAwareness(provider.awareness);
98511
- setClientId(provider.awareness.clientID);
98512
- cleanupRef.current = () => {
98513
- provider.off("status", handleStatus);
98514
- provider.destroy();
98515
- yDoc.destroy();
98516
- setDoc(null);
98517
- setAwareness(null);
98518
- setClientId(null);
98519
- setStatus("disconnected");
98520
- };
98521
- } catch (err) {
98522
- console.warn(
98523
- "[pptx-viewer] Collaboration packages not available:",
98524
- err instanceof Error ? err.message : err
98525
- );
98526
- setStatus("error");
98527
- }
98528
- }, [config.roomId, config.serverUrl, config.authToken]);
98529
- React10.useEffect(() => {
98530
- init();
98531
- return () => {
98532
- cleanupRef.current?.();
98533
- cleanupRef.current = null;
98534
- };
98535
- }, [init]);
98536
- return { status, awareness, doc: doc2, clientId };
98537
- }
98538
-
98539
- // src/viewer/hooks/collaboration/useCollaborativeState.ts
98540
- function useCollaborativeState({
98541
- config,
98542
- canvasWidth,
98543
- canvasHeight
98544
- }) {
98545
- const userColor = sanitizeColor(config.userColor, "#6366f1");
98546
- const { status, awareness, doc: doc2, clientId } = useYjsProvider({ config });
98547
- const { remoteUsers, broadcastPresence } = usePresenceTracking({
98548
- awareness,
98549
- localClientId: clientId,
98550
- userName: config.userName,
98551
- userColor,
98552
- userAvatar: config.userAvatar,
98553
- canvasWidth,
98554
- canvasHeight
98555
- });
98556
- const connectedCount = status === "connected" ? remoteUsers.length + 1 : remoteUsers.length;
98557
- return {
98558
- status,
98559
- remoteUsers,
98560
- broadcastPresence,
98561
- connectedCount,
98562
- config,
98563
- doc: doc2
98564
- };
98565
- }
98566
- var CollaborationContext = React10.createContext(null);
98567
- function useCollaboration() {
98568
- return React10.useContext(CollaborationContext);
98569
- }
98570
- function CollaborationProvider({
98571
- config,
98572
- canvasWidth,
98573
- canvasHeight,
98574
- children
98575
- }) {
98576
- const value = useCollaborativeState({
98577
- config,
98578
- canvasWidth,
98579
- canvasHeight
98580
- });
98581
- return /* @__PURE__ */ jsxRuntime.jsx(CollaborationContext.Provider, { value, children });
98582
- }
98583
- function RemoteUserCursors({
98584
- remoteUsers,
98585
- activeSlideIndex,
98586
- canvasWidth,
98587
- canvasHeight
98588
- }) {
98589
- const visibleUsers = remoteUsers.filter((u2) => u2.activeSlideIndex === activeSlideIndex);
98590
- if (visibleUsers.length === 0) {
98591
- return null;
98592
- }
98593
- return /* @__PURE__ */ jsxRuntime.jsx(
98594
- "svg",
98595
- {
98596
- "data-testid": "remote-user-cursors",
98597
- className: "absolute inset-0 pointer-events-none",
98598
- style: { zIndex: 9999 },
98599
- width: canvasWidth,
98600
- height: canvasHeight,
98601
- viewBox: `0 0 ${canvasWidth} ${canvasHeight}`,
98602
- "aria-hidden": "true",
98603
- children: visibleUsers.map((user) => /* @__PURE__ */ jsxRuntime.jsxs(
98604
- "g",
98605
- {
98606
- transform: `translate(${user.cursorX}, ${user.cursorY})`,
98607
- "data-testid": `remote-cursor-${user.clientId}`,
98608
- children: [
98651
+ }) }),
98652
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: grp, children: [
98653
+ /* @__PURE__ */ jsxRuntime.jsx(
98654
+ "button",
98655
+ {
98656
+ type: "button",
98657
+ disabled: !canMut,
98658
+ onMouseDown: (e2) => e2.preventDefault(),
98659
+ onClick: () => {
98660
+ if (!canFormat || !p3.selectedElement) {
98661
+ return;
98662
+ }
98663
+ const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98664
+ const current = ts?.fontSize ?? 18;
98665
+ p3.onUpdateTextStyle({ fontSize: current + 2 });
98666
+ },
98667
+ className: gB,
98668
+ title: "Increase Font Size",
98669
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuAArrowUp, { className: ic2 })
98670
+ }
98671
+ ),
98672
+ /* @__PURE__ */ jsxRuntime.jsx(
98673
+ "button",
98674
+ {
98675
+ type: "button",
98676
+ disabled: !canMut,
98677
+ onMouseDown: (e2) => e2.preventDefault(),
98678
+ onClick: () => {
98679
+ if (!canFormat || !p3.selectedElement) {
98680
+ return;
98681
+ }
98682
+ const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98683
+ const current = ts?.fontSize ?? 18;
98684
+ p3.onUpdateTextStyle({ fontSize: Math.max(1, current - 2) });
98685
+ },
98686
+ className: gB,
98687
+ title: "Decrease Font Size",
98688
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuAArrowDown, { className: ic2 })
98689
+ }
98690
+ ),
98691
+ /* @__PURE__ */ jsxRuntime.jsx(
98692
+ "button",
98693
+ {
98694
+ type: "button",
98695
+ disabled: !canMut,
98696
+ onMouseDown: (e2) => e2.preventDefault(),
98697
+ onClick: () => {
98698
+ if (!canFormat) {
98699
+ return;
98700
+ }
98701
+ p3.onUpdateTextStyle({
98702
+ bold: false,
98703
+ italic: false,
98704
+ underline: false,
98705
+ strikethrough: false,
98706
+ highlightColor: void 0
98707
+ });
98708
+ },
98709
+ className: gL,
98710
+ title: "Clear Formatting",
98711
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuRemoveFormatting, { className: ic2 })
98712
+ }
98713
+ )
98714
+ ] }),
98715
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
98716
+ /* @__PURE__ */ jsxRuntime.jsxs(
98717
+ "button",
98718
+ {
98719
+ type: "button",
98720
+ disabled: !canMut,
98721
+ onMouseDown: (e2) => e2.preventDefault(),
98722
+ className: pill,
98723
+ title: "Font Color",
98724
+ children: [
98725
+ /* @__PURE__ */ jsxRuntime.jsx(
98726
+ "svg",
98727
+ {
98728
+ className: ic2,
98729
+ viewBox: "0 0 24 24",
98730
+ fill: "none",
98731
+ stroke: "currentColor",
98732
+ strokeWidth: "2",
98733
+ strokeLinecap: "round",
98734
+ strokeLinejoin: "round",
98735
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 20h12M9.5 4h5L18 16H6L9.5 4z" })
98736
+ }
98737
+ ),
98738
+ /* @__PURE__ */ jsxRuntime.jsx(
98739
+ "div",
98740
+ {
98741
+ className: "w-4 h-1 rounded-sm -mt-0.5",
98742
+ style: { backgroundColor: currentColor }
98743
+ }
98744
+ )
98745
+ ]
98746
+ }
98747
+ ),
98748
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 top-full z-50 hidden group-hover:block pt-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl p-2 w-36", children: [
98749
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1.5 mb-2", children: FONT_COLOR_PRESETS.map((c2) => /* @__PURE__ */ jsxRuntime.jsx(
98750
+ "button",
98751
+ {
98752
+ type: "button",
98753
+ className: `w-5 h-5 rounded-full border transition-transform hover:scale-125 ${currentColor?.toLowerCase() === c2 ? "border-primary ring-1 ring-primary" : "border-border"}`,
98754
+ style: { backgroundColor: c2 },
98755
+ onMouseDown: (e2) => e2.preventDefault(),
98756
+ onClick: () => handleColorChange(c2)
98757
+ },
98758
+ c2
98759
+ )) }),
98609
98760
  /* @__PURE__ */ jsxRuntime.jsx(
98610
- "path",
98761
+ "button",
98611
98762
  {
98612
- d: "M0 0 L0 16 L4.5 12.5 L8 20 L10.5 19 L7 11.5 L12 11 Z",
98613
- fill: user.userColor,
98614
- stroke: "#fff",
98615
- strokeWidth: 1,
98616
- opacity: 0.9
98763
+ type: "button",
98764
+ className: "w-full text-[10px] text-muted-foreground hover:text-foreground py-1 transition-colors",
98765
+ onMouseDown: (e2) => e2.preventDefault(),
98766
+ onClick: () => colorInputRef.current?.click(),
98767
+ children: "Custom colour..."
98617
98768
  }
98618
98769
  ),
98619
- /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: "translate(14, 18)", children: [
98620
- /* @__PURE__ */ jsxRuntime.jsx(
98621
- "rect",
98622
- {
98623
- rx: 3,
98624
- ry: 3,
98625
- x: -2,
98626
- y: -10,
98627
- width: Math.min(user.userName.length * 7 + 8, 150),
98628
- height: 16,
98629
- fill: user.userColor,
98630
- opacity: 0.85
98770
+ /* @__PURE__ */ jsxRuntime.jsx(
98771
+ "input",
98772
+ {
98773
+ ref: colorInputRef,
98774
+ type: "color",
98775
+ className: "sr-only",
98776
+ value: currentColor,
98777
+ onChange: (e2) => handleColorChange(e2.target.value)
98778
+ }
98779
+ )
98780
+ ] }) })
98781
+ ] }),
98782
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
98783
+ /* @__PURE__ */ jsxRuntime.jsxs(
98784
+ "button",
98785
+ {
98786
+ type: "button",
98787
+ disabled: !canMut,
98788
+ onMouseDown: (e2) => e2.preventDefault(),
98789
+ className: pill,
98790
+ title: "Text Highlight Color",
98791
+ children: [
98792
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuHighlighter, { className: ic2 }),
98793
+ /* @__PURE__ */ jsxRuntime.jsx(
98794
+ "div",
98795
+ {
98796
+ className: "w-4 h-1 rounded-sm -mt-0.5",
98797
+ style: { backgroundColor: currentHighlight }
98798
+ }
98799
+ )
98800
+ ]
98801
+ }
98802
+ ),
98803
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 top-full z-50 hidden group-hover:block pt-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl p-2 w-36", children: [
98804
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1.5 mb-2", children: HIGHLIGHT_COLOR_PRESETS.map((c2) => /* @__PURE__ */ jsxRuntime.jsx(
98805
+ "button",
98806
+ {
98807
+ type: "button",
98808
+ className: `w-5 h-5 rounded-full border transition-transform hover:scale-125 ${currentHighlight?.toLowerCase() === c2 ? "border-primary ring-1 ring-primary" : "border-border"}`,
98809
+ style: { backgroundColor: c2 },
98810
+ onMouseDown: (e2) => e2.preventDefault(),
98811
+ onClick: () => handleHighlightChange(c2)
98812
+ },
98813
+ c2
98814
+ )) }),
98815
+ /* @__PURE__ */ jsxRuntime.jsx(
98816
+ "button",
98817
+ {
98818
+ type: "button",
98819
+ className: "w-full text-[10px] text-muted-foreground hover:text-foreground py-1 transition-colors",
98820
+ onMouseDown: (e2) => e2.preventDefault(),
98821
+ onClick: () => highlightInputRef.current?.click(),
98822
+ children: "Custom colour..."
98823
+ }
98824
+ ),
98825
+ /* @__PURE__ */ jsxRuntime.jsx(
98826
+ "input",
98827
+ {
98828
+ ref: highlightInputRef,
98829
+ type: "color",
98830
+ className: "sr-only",
98831
+ value: currentHighlight,
98832
+ onChange: (e2) => handleHighlightChange(e2.target.value)
98833
+ }
98834
+ )
98835
+ ] }) })
98836
+ ] })
98837
+ ] }),
98838
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Font" })
98839
+ ] }),
98840
+ sep,
98841
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
98842
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
98843
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: grp, children: [
98844
+ /* @__PURE__ */ jsxRuntime.jsx(
98845
+ "button",
98846
+ {
98847
+ type: "button",
98848
+ disabled: !canMut,
98849
+ onMouseDown: (e2) => e2.preventDefault(),
98850
+ onClick: () => {
98851
+ if (!canFormat || !p3.selectedElement) {
98852
+ return;
98631
98853
  }
98632
- ),
98633
- /* @__PURE__ */ jsxRuntime.jsx(
98634
- "text",
98635
- {
98636
- fill: "#fff",
98637
- fontSize: 10,
98638
- fontFamily: "system-ui, sans-serif",
98639
- fontWeight: 500,
98640
- dominantBaseline: "central",
98641
- y: -2,
98642
- x: 2,
98643
- children: user.userName.length > 20 ? `${user.userName.slice(0, 18)}...` : user.userName
98854
+ const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98855
+ p3.onUpdateTextStyle({
98856
+ listType: ts?.listType === "bullet" ? "none" : "bullet"
98857
+ });
98858
+ },
98859
+ className: gB,
98860
+ title: "Bullet List",
98861
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuList, { className: ic2 })
98862
+ }
98863
+ ),
98864
+ /* @__PURE__ */ jsxRuntime.jsx(
98865
+ "button",
98866
+ {
98867
+ type: "button",
98868
+ disabled: !canMut,
98869
+ onMouseDown: (e2) => e2.preventDefault(),
98870
+ onClick: () => {
98871
+ if (!canFormat || !p3.selectedElement) {
98872
+ return;
98644
98873
  }
98645
- )
98646
- ] })
98647
- ]
98648
- },
98649
- user.clientId
98650
- ))
98651
- }
98652
- );
98653
- }
98654
- function getInitials(name) {
98655
- const parts = name.trim().split(/\s+/);
98656
- if (parts.length >= 2) {
98657
- return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
98658
- }
98659
- return name.slice(0, 2).toUpperCase();
98660
- }
98661
- function AvatarCircle({
98662
- name,
98663
- color,
98664
- avatar,
98665
- isLocal
98666
- }) {
98667
- const { t: t2 } = reactI18next.useTranslation();
98668
- const initials = getInitials(name);
98669
- const title = isLocal ? t2("pptx.collaboration.youLabel", { name }) : name;
98670
- return /* @__PURE__ */ jsxRuntime.jsx(
98671
- "div",
98672
- {
98673
- className: "relative w-7 h-7 rounded-full flex items-center justify-center text-[10px] font-semibold text-white border-2 -ml-1 first:ml-0",
98674
- style: {
98675
- backgroundColor: color,
98676
- borderColor: isLocal ? "#fff" : color
98677
- },
98678
- title,
98679
- "aria-label": title,
98680
- children: avatar ? /* @__PURE__ */ jsxRuntime.jsx(
98681
- "img",
98682
- {
98683
- src: avatar,
98684
- alt: "",
98685
- className: "w-full h-full rounded-full object-cover",
98686
- onError: (e2) => {
98687
- e2.target.style.display = "none";
98688
- }
98689
- }
98690
- ) : initials
98691
- }
98692
- );
98693
- }
98694
- function UserAvatarBar({
98695
- remoteUsers,
98696
- localUserName,
98697
- localUserColor,
98698
- localUserAvatar,
98699
- status,
98700
- maxVisible = 5
98701
- }) {
98702
- const { t: t2 } = reactI18next.useTranslation();
98703
- if (status === "disconnected" || status === "error") {
98704
- return null;
98705
- }
98706
- const allUsers = [
98707
- { name: localUserName, color: localUserColor, avatar: localUserAvatar, isLocal: true },
98708
- ...remoteUsers.map((u2) => ({
98709
- name: u2.userName,
98710
- color: u2.userColor,
98711
- avatar: u2.userAvatar,
98712
- isLocal: false
98713
- }))
98714
- ];
98715
- const visible = allUsers.slice(0, maxVisible);
98716
- const overflow = allUsers.length - maxVisible;
98717
- return /* @__PURE__ */ jsxRuntime.jsxs(
98718
- "div",
98719
- {
98720
- "data-testid": "user-avatar-bar",
98721
- className: "flex items-center px-2",
98722
- "aria-label": t2("pptx.collaboration.usersConnected", { count: allUsers.length }),
98723
- children: [
98724
- visible.map((user, i3) => /* @__PURE__ */ jsxRuntime.jsx(
98725
- AvatarCircle,
98726
- {
98727
- name: user.name,
98728
- color: user.color,
98729
- avatar: user.avatar,
98730
- isLocal: user.isLocal
98731
- },
98732
- user.isLocal ? "local" : `remote-${i3}`
98733
- )),
98734
- overflow > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
98735
- "div",
98736
- {
98737
- className: "w-7 h-7 rounded-full flex items-center justify-center text-[10px] font-semibold text-gray-300 bg-gray-700 border-2 border-gray-600 -ml-1",
98738
- title: t2("pptx.collaboration.moreUsers", { count: overflow }),
98739
- children: [
98740
- "+",
98741
- overflow
98742
- ]
98743
- }
98744
- )
98745
- ]
98746
- }
98747
- );
98748
- }
98749
- var STATUS_STYLES = {
98750
- connected: {
98751
- dot: "bg-green-400",
98752
- text: "text-green-400",
98753
- label: "Connected"
98754
- },
98755
- connecting: {
98756
- dot: "bg-yellow-400 animate-pulse",
98757
- text: "text-yellow-400",
98758
- label: "Connecting..."
98759
- },
98760
- disconnected: {
98761
- dot: "bg-gray-500",
98762
- text: "text-gray-500",
98763
- label: "Disconnected"
98764
- },
98765
- error: {
98766
- dot: "bg-red-400",
98767
- text: "text-red-400",
98768
- label: "Connection error"
98769
- }
98770
- };
98771
- function CollaborationStatusIndicator({
98772
- status,
98773
- connectedCount
98774
- }) {
98775
- const { t: t2 } = reactI18next.useTranslation();
98776
- const style = STATUS_STYLES[status];
98777
- return /* @__PURE__ */ jsxRuntime.jsxs(
98778
- "div",
98779
- {
98780
- "data-testid": "collaboration-status",
98781
- className: "flex items-center gap-1.5",
98782
- "aria-label": t2("pptx.collaboration.statusAriaLabel", {
98783
- status: t2(`pptx.collaboration.status.${status}`),
98784
- count: connectedCount
98785
- }),
98786
- children: [
98787
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-block w-2 h-2 rounded-full ${style.dot}`, "aria-hidden": "true" }),
98788
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-[10px] ${style.text}`, children: status === "connected" ? t2("pptx.collaboration.userCount", { count: connectedCount }) : t2(`pptx.collaboration.status.${status}`) })
98789
- ]
98790
- }
98791
- );
98874
+ const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98875
+ p3.onUpdateTextStyle({
98876
+ listType: ts?.listType === "numbered" ? "none" : "numbered"
98877
+ });
98878
+ },
98879
+ className: gL,
98880
+ title: "Numbered List",
98881
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuListOrdered, { className: ic2 })
98882
+ }
98883
+ )
98884
+ ] }),
98885
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: grp, children: [
98886
+ /* @__PURE__ */ jsxRuntime.jsx(
98887
+ "button",
98888
+ {
98889
+ type: "button",
98890
+ disabled: !canMut,
98891
+ onMouseDown: (e2) => e2.preventDefault(),
98892
+ onClick: () => {
98893
+ if (!canFormat || !p3.selectedElement) {
98894
+ return;
98895
+ }
98896
+ const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98897
+ const current = ts?.paragraphMarginLeft ?? 0;
98898
+ p3.onUpdateTextStyle({
98899
+ paragraphMarginLeft: Math.max(0, current - 24)
98900
+ });
98901
+ },
98902
+ className: gB,
98903
+ title: "Decrease Indent",
98904
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuIndentDecrease, { className: ic2 })
98905
+ }
98906
+ ),
98907
+ /* @__PURE__ */ jsxRuntime.jsx(
98908
+ "button",
98909
+ {
98910
+ type: "button",
98911
+ disabled: !canMut,
98912
+ onMouseDown: (e2) => e2.preventDefault(),
98913
+ onClick: () => {
98914
+ if (!canFormat || !p3.selectedElement) {
98915
+ return;
98916
+ }
98917
+ const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98918
+ const current = ts?.paragraphMarginLeft ?? 0;
98919
+ p3.onUpdateTextStyle({
98920
+ paragraphMarginLeft: current + 24
98921
+ });
98922
+ },
98923
+ className: gL,
98924
+ title: "Increase Indent",
98925
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuIndentIncrease, { className: ic2 })
98926
+ }
98927
+ )
98928
+ ] }),
98929
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: grp, children: ATXT.map((b2, i3, a2) => {
98930
+ const handleClick = () => {
98931
+ if (!canFormat) {
98932
+ return;
98933
+ }
98934
+ const alignMap = {
98935
+ "Align left": "left",
98936
+ "Align center": "center",
98937
+ "Align right": "right",
98938
+ Justify: "justify"
98939
+ };
98940
+ const align = alignMap[b2.t];
98941
+ if (align) {
98942
+ p3.onUpdateTextStyle({ align });
98943
+ }
98944
+ };
98945
+ return /* @__PURE__ */ jsxRuntime.jsx(
98946
+ "button",
98947
+ {
98948
+ type: "button",
98949
+ disabled: !canMut,
98950
+ onMouseDown: (e2) => e2.preventDefault(),
98951
+ onClick: handleClick,
98952
+ className: i3 < a2.length - 1 ? gB : gL,
98953
+ title: b2.t,
98954
+ children: b2.i
98955
+ },
98956
+ b2.t
98957
+ );
98958
+ }) })
98959
+ ] }),
98960
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Paragraph" })
98961
+ ] })
98962
+ ] });
98792
98963
  }
98793
98964
  function CustomShowsControls({
98794
98965
  customShows,
@@ -99228,29 +99399,38 @@ function ToolbarPrimaryRow(p3) {
99228
99399
  ]
99229
99400
  }
99230
99401
  ),
99231
- 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: [
99232
- collab.remoteUsers.slice(0, 4).map((user) => /* @__PURE__ */ jsxRuntime.jsx(
99233
- "div",
99234
- {
99235
- className: "w-6 h-6 rounded-full border-2 border-background flex items-center justify-center text-[8px] font-semibold text-white shrink-0",
99236
- style: { backgroundColor: user.userColor },
99237
- title: user.userName,
99238
- children: user.userAvatar ? /* @__PURE__ */ jsxRuntime.jsx(
99239
- "img",
99402
+ collab && (collab.status === "connected" || collab.status === "connecting") && collab.remoteUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
99403
+ "button",
99404
+ {
99405
+ type: "button",
99406
+ onClick: p3.onOpenShareDialog,
99407
+ 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",
99408
+ title: t2("pptx.toolbar.sharingUsers", { count: collab.connectedCount }),
99409
+ children: [
99410
+ collab.remoteUsers.slice(0, 4).map((user) => /* @__PURE__ */ jsxRuntime.jsx(
99411
+ "div",
99240
99412
  {
99241
- src: user.userAvatar,
99242
- alt: user.userName,
99243
- className: "w-full h-full rounded-full object-cover"
99244
- }
99245
- ) : user.userName.slice(0, 2).toUpperCase()
99246
- },
99247
- user.clientId
99248
- )),
99249
- 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: [
99250
- "+",
99251
- collab.remoteUsers.length - 4
99252
- ] })
99253
- ] }),
99413
+ className: "w-6 h-6 rounded-full border-2 border-background flex items-center justify-center text-[8px] font-semibold text-white shrink-0",
99414
+ style: { backgroundColor: user.userColor },
99415
+ title: user.userName,
99416
+ children: user.userAvatar ? /* @__PURE__ */ jsxRuntime.jsx(
99417
+ "img",
99418
+ {
99419
+ src: user.userAvatar,
99420
+ alt: user.userName,
99421
+ className: "w-full h-full rounded-full object-cover"
99422
+ }
99423
+ ) : user.userName.slice(0, 2).toUpperCase()
99424
+ },
99425
+ user.clientId
99426
+ )),
99427
+ 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: [
99428
+ "+",
99429
+ collab.remoteUsers.length - 4
99430
+ ] })
99431
+ ]
99432
+ }
99433
+ ),
99254
99434
  /* @__PURE__ */ jsxRuntime.jsx(
99255
99435
  ModeSwitcher,
99256
99436
  {
@@ -99627,7 +99807,8 @@ function Toolbar(p3) {
99627
99807
  canEdit: p3.canEdit,
99628
99808
  selectedElement: p3.selectedElement,
99629
99809
  isInspectorPaneOpen: p3.isInspectorPaneOpen,
99630
- onToggleInspector: p3.onToggleInspector
99810
+ onToggleInspector: p3.onToggleInspector,
99811
+ onOpenAnimationPanel: p3.onOpenAnimationPanel
99631
99812
  }
99632
99813
  ),
99633
99814
  sSlw && /* @__PURE__ */ jsxRuntime.jsx(
@@ -107072,9 +107253,75 @@ function SetUpSlideShowDialog({
107072
107253
  }
107073
107254
  function BroadcastDialog({
107074
107255
  open,
107075
- onClose
107256
+ onClose,
107257
+ onStartBroadcast,
107258
+ onStopBroadcast,
107259
+ onStartPresenting,
107260
+ defaultRoomId,
107261
+ defaultUserName,
107262
+ defaultServerUrl
107076
107263
  }) {
107077
107264
  const { t: t2 } = reactI18next.useTranslation();
107265
+ const collab = useCollaboration();
107266
+ const isBroadcasting = collab !== null && collab.status !== "disconnected" && collab.status !== "error" && collab.config.role === "broadcaster";
107267
+ const [roomId, setRoomId] = React10.useState("");
107268
+ const [userName, setUserName] = React10.useState("");
107269
+ const [serverUrl, setServerUrl] = React10.useState("");
107270
+ const [copied, setCopied] = React10.useState(false);
107271
+ const dialogRef = React10.useRef(null);
107272
+ React10.useEffect(() => {
107273
+ if (open && !isBroadcasting) {
107274
+ const broadcastRoom = defaultRoomId ? `broadcast-${defaultRoomId}` : `broadcast-${Math.random().toString(36).slice(2, 10)}`;
107275
+ setRoomId(broadcastRoom);
107276
+ setUserName(defaultUserName ?? "");
107277
+ setServerUrl(defaultServerUrl ?? "ws://localhost:1234");
107278
+ }
107279
+ }, [open, isBroadcasting, defaultRoomId, defaultUserName, defaultServerUrl]);
107280
+ React10.useEffect(() => {
107281
+ if (!open) {
107282
+ return;
107283
+ }
107284
+ function handleKeyDown(e2) {
107285
+ if (e2.key === "Escape") {
107286
+ onClose();
107287
+ }
107288
+ }
107289
+ document.addEventListener("keydown", handleKeyDown);
107290
+ return () => document.removeEventListener("keydown", handleKeyDown);
107291
+ }, [open, onClose]);
107292
+ React10.useEffect(() => {
107293
+ if (open && dialogRef.current) {
107294
+ dialogRef.current.focus();
107295
+ }
107296
+ }, [open]);
107297
+ 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;
107298
+ const handleCopyUrl = React10.useCallback(() => {
107299
+ void navigator.clipboard.writeText(broadcastUrl).then(() => {
107300
+ setCopied(true);
107301
+ setTimeout(() => setCopied(false), 2e3);
107302
+ return void 0;
107303
+ });
107304
+ }, [broadcastUrl]);
107305
+ const handleStartBroadcast = React10.useCallback(() => {
107306
+ if (!roomId.trim() || !userName.trim()) {
107307
+ return;
107308
+ }
107309
+ onStartBroadcast?.({
107310
+ roomId: roomId.trim(),
107311
+ serverUrl: serverUrl.trim(),
107312
+ userName: userName.trim(),
107313
+ role: "broadcaster"
107314
+ });
107315
+ setTimeout(() => {
107316
+ onStartPresenting?.();
107317
+ }, 100);
107318
+ onClose();
107319
+ }, [roomId, userName, serverUrl, onStartBroadcast, onStartPresenting, onClose]);
107320
+ const handleStopBroadcast = React10.useCallback(() => {
107321
+ onStopBroadcast?.();
107322
+ onClose();
107323
+ }, [onStopBroadcast, onClose]);
107324
+ const canStart = roomId.trim().length > 0 && userName.trim().length > 0 && serverUrl.trim().length > 0;
107078
107325
  if (!open) {
107079
107326
  return null;
107080
107327
  }
@@ -107088,42 +107335,225 @@ function BroadcastDialog({
107088
107335
  onClick: onClose
107089
107336
  }
107090
107337
  ),
107091
- /* @__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: [
107092
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-3 border-b border-border", children: [
107093
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold text-foreground", children: t2("pptx.broadcast.title") }),
107338
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[201] flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsxs(
107339
+ "div",
107340
+ {
107341
+ ref: dialogRef,
107342
+ role: "dialog",
107343
+ "aria-modal": "true",
107344
+ "aria-label": t2("pptx.broadcast.title"),
107345
+ tabIndex: -1,
107346
+ className: "pointer-events-auto w-full max-w-md rounded-xl border border-border bg-popover text-foreground shadow-2xl outline-none",
107347
+ children: [
107348
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-3 border-b border-border", children: [
107349
+ /* @__PURE__ */ jsxRuntime.jsxs("h2", { className: "text-sm font-semibold text-foreground flex items-center gap-2", children: [
107350
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCast, { className: "w-4 h-4" }),
107351
+ isBroadcasting ? t2("pptx.broadcast.broadcasting") : t2("pptx.broadcast.title")
107352
+ ] }),
107353
+ /* @__PURE__ */ jsxRuntime.jsx(
107354
+ "button",
107355
+ {
107356
+ type: "button",
107357
+ onClick: onClose,
107358
+ className: "text-muted-foreground hover:text-foreground text-lg leading-none",
107359
+ "aria-label": "Close",
107360
+ children: "\xD7"
107361
+ }
107362
+ )
107363
+ ] }),
107364
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-5 py-4", children: isBroadcasting ? /* @__PURE__ */ jsxRuntime.jsx(
107365
+ ActiveBroadcastView,
107366
+ {
107367
+ collab,
107368
+ broadcastUrl,
107369
+ copied,
107370
+ onCopyUrl: handleCopyUrl,
107371
+ onStopBroadcast: handleStopBroadcast
107372
+ }
107373
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
107374
+ StartBroadcastForm,
107375
+ {
107376
+ roomId,
107377
+ userName,
107378
+ serverUrl,
107379
+ onRoomIdChange: setRoomId,
107380
+ onUserNameChange: setUserName,
107381
+ onServerUrlChange: setServerUrl
107382
+ }
107383
+ ) }),
107384
+ !isBroadcasting && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2 px-5 py-3 border-t border-border", children: [
107385
+ /* @__PURE__ */ jsxRuntime.jsx(
107386
+ "button",
107387
+ {
107388
+ type: "button",
107389
+ onClick: onClose,
107390
+ className: "px-3 py-1.5 rounded bg-muted hover:bg-accent text-[12px] text-foreground transition-colors",
107391
+ children: t2("common.close")
107392
+ }
107393
+ ),
107394
+ /* @__PURE__ */ jsxRuntime.jsx(
107395
+ "button",
107396
+ {
107397
+ type: "button",
107398
+ disabled: !canStart,
107399
+ onClick: handleStartBroadcast,
107400
+ 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",
107401
+ children: t2("pptx.broadcast.startBroadcast")
107402
+ }
107403
+ )
107404
+ ] })
107405
+ ]
107406
+ }
107407
+ ) })
107408
+ ] });
107409
+ }
107410
+ function StartBroadcastForm({
107411
+ roomId,
107412
+ userName,
107413
+ serverUrl,
107414
+ onRoomIdChange,
107415
+ onUserNameChange,
107416
+ onServerUrlChange
107417
+ }) {
107418
+ const { t: t2 } = reactI18next.useTranslation();
107419
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
107420
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[13px] text-muted-foreground leading-relaxed", children: t2("pptx.broadcast.description") }),
107421
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107422
+ /* @__PURE__ */ jsxRuntime.jsx(
107423
+ "label",
107424
+ {
107425
+ htmlFor: "broadcast-room-id",
107426
+ className: "block text-[12px] font-medium text-foreground",
107427
+ children: t2("pptx.broadcast.sessionName")
107428
+ }
107429
+ ),
107430
+ /* @__PURE__ */ jsxRuntime.jsx(
107431
+ "input",
107432
+ {
107433
+ id: "broadcast-room-id",
107434
+ type: "text",
107435
+ value: roomId,
107436
+ onChange: (e2) => onRoomIdChange(e2.target.value),
107437
+ placeholder: "broadcast-abc123",
107438
+ 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"
107439
+ }
107440
+ )
107441
+ ] }),
107442
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107443
+ /* @__PURE__ */ jsxRuntime.jsx(
107444
+ "label",
107445
+ {
107446
+ htmlFor: "broadcast-user-name",
107447
+ className: "block text-[12px] font-medium text-foreground",
107448
+ children: t2("pptx.broadcast.displayName")
107449
+ }
107450
+ ),
107451
+ /* @__PURE__ */ jsxRuntime.jsx(
107452
+ "input",
107453
+ {
107454
+ id: "broadcast-user-name",
107455
+ type: "text",
107456
+ value: userName,
107457
+ onChange: (e2) => onUserNameChange(e2.target.value),
107458
+ placeholder: "Presenter",
107459
+ 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"
107460
+ }
107461
+ )
107462
+ ] }),
107463
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107464
+ /* @__PURE__ */ jsxRuntime.jsx(
107465
+ "label",
107466
+ {
107467
+ htmlFor: "broadcast-server-url",
107468
+ className: "block text-[12px] font-medium text-foreground",
107469
+ children: t2("pptx.broadcast.serverLabel")
107470
+ }
107471
+ ),
107472
+ /* @__PURE__ */ jsxRuntime.jsx(
107473
+ "input",
107474
+ {
107475
+ id: "broadcast-server-url",
107476
+ type: "text",
107477
+ value: serverUrl,
107478
+ onChange: (e2) => onServerUrlChange(e2.target.value),
107479
+ placeholder: "ws://localhost:1234",
107480
+ 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"
107481
+ }
107482
+ )
107483
+ ] }),
107484
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground/70 leading-relaxed", children: t2("pptx.broadcast.hint") })
107485
+ ] });
107486
+ }
107487
+ function ActiveBroadcastView({
107488
+ collab,
107489
+ broadcastUrl,
107490
+ copied,
107491
+ onCopyUrl,
107492
+ onStopBroadcast
107493
+ }) {
107494
+ const { t: t2 } = reactI18next.useTranslation();
107495
+ const statusColor = collab.status === "connected" ? "text-green-400" : collab.status === "connecting" ? "text-yellow-400" : "text-red-400";
107496
+ 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" });
107497
+ const viewerCount = collab.remoteUsers.filter((u2) => u2.role === "viewer").length;
107498
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
107499
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
107500
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: statusColor, children: statusIcon }),
107501
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[13px] font-medium text-foreground capitalize", children: collab.status }),
107502
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[12px] text-muted-foreground ml-auto flex items-center gap-1", children: [
107503
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuUsers, { className: "w-3.5 h-3.5" }),
107504
+ t2("pptx.broadcast.viewerCount", { count: viewerCount })
107505
+ ] })
107506
+ ] }),
107507
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107508
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t2("pptx.broadcast.viewerLink") }),
107509
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
107510
+ /* @__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 }),
107094
107511
  /* @__PURE__ */ jsxRuntime.jsx(
107095
107512
  "button",
107096
107513
  {
107097
107514
  type: "button",
107098
- onClick: onClose,
107099
- className: "text-muted-foreground hover:text-foreground text-lg leading-none",
107100
- "aria-label": "Close",
107101
- children: "\xD7"
107515
+ onClick: onCopyUrl,
107516
+ 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",
107517
+ title: t2("pptx.broadcast.copyLink"),
107518
+ children: copied ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
107519
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCheck, { className: "w-3.5 h-3.5 text-green-400" }),
107520
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t2("pptx.share.copied") })
107521
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
107522
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCopy, { className: "w-3.5 h-3.5" }),
107523
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t2("pptx.share.copyUrl") })
107524
+ ] })
107102
107525
  }
107103
107526
  )
107104
107527
  ] }),
107105
- /* @__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") }) }),
107106
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2 px-5 py-3 border-t border-border", children: [
107528
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("pptx.broadcast.shareHint") })
107529
+ ] }),
107530
+ collab.remoteUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107531
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t2("pptx.broadcast.viewers") }),
107532
+ /* @__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: [
107107
107533
  /* @__PURE__ */ jsxRuntime.jsx(
107108
- "button",
107534
+ "div",
107109
107535
  {
107110
- type: "button",
107111
- onClick: onClose,
107112
- className: "px-3 py-1.5 rounded bg-muted hover:bg-accent text-[12px] text-foreground transition-colors",
107113
- children: t2("common.close")
107536
+ className: "w-5 h-5 rounded-full flex items-center justify-center text-[8px] font-semibold text-white shrink-0",
107537
+ style: { backgroundColor: user.userColor },
107538
+ children: user.userName.slice(0, 2).toUpperCase()
107114
107539
  }
107115
107540
  ),
107116
- /* @__PURE__ */ jsxRuntime.jsx(
107117
- "button",
107118
- {
107119
- type: "button",
107120
- disabled: true,
107121
- className: "px-3 py-1.5 rounded bg-primary/40 text-[12px] text-white/50 cursor-not-allowed",
107122
- children: t2("pptx.broadcast.startBroadcast")
107123
- }
107124
- )
107125
- ] })
107126
- ] }) })
107541
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[12px] text-foreground truncate", children: user.userName }),
107542
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] text-muted-foreground ml-auto", children: [
107543
+ "Slide ",
107544
+ user.activeSlideIndex + 1
107545
+ ] })
107546
+ ] }, user.clientId)) })
107547
+ ] }),
107548
+ /* @__PURE__ */ jsxRuntime.jsx(
107549
+ "button",
107550
+ {
107551
+ type: "button",
107552
+ onClick: onStopBroadcast,
107553
+ 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",
107554
+ children: t2("pptx.broadcast.stopBroadcast")
107555
+ }
107556
+ )
107127
107557
  ] });
107128
107558
  }
107129
107559
  function getInitials2(name) {
@@ -108707,7 +109137,8 @@ function useDrawingOverlay({
108707
109137
  drawingWidth,
108708
109138
  isDrawingRef,
108709
109139
  onAddInkElement,
108710
- onAddFreeformShape
109140
+ onAddFreeformShape,
109141
+ onEraseInkElement
108711
109142
  }) {
108712
109143
  const isDrawing = activeTool !== "select";
108713
109144
  const [currentStrokePoints, setCurrentStrokePoints] = React10.useState(
@@ -108756,6 +109187,7 @@ function useDrawingOverlay({
108756
109187
  continue;
108757
109188
  }
108758
109189
  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) {
109190
+ onEraseInkElement?.(el.id);
108759
109191
  break;
108760
109192
  }
108761
109193
  }
@@ -108774,7 +109206,7 @@ function useDrawingOverlay({
108774
109206
  isDrawingRef.current = true;
108775
109207
  }
108776
109208
  },
108777
- [activeTool, activeSlide, pointerToCanvasCoords, isDrawingRef]
109209
+ [activeTool, activeSlide, pointerToCanvasCoords, isDrawingRef, onEraseInkElement]
108778
109210
  );
108779
109211
  const handleDrawPointerMove = React10.useCallback(
108780
109212
  (e2) => {
@@ -108844,6 +109276,9 @@ function useDrawingOverlay({
108844
109276
  i3 === 0 ? { type: "moveTo", pt: scaledPt } : { type: "lineTo", pt: scaledPt }
108845
109277
  );
108846
109278
  }
109279
+ if (segments.length > 2) {
109280
+ segments.push({ type: "close" });
109281
+ }
108847
109282
  const freeformShape = {
108848
109283
  id: `shape-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
108849
109284
  type: "shape",
@@ -108853,7 +109288,7 @@ function useDrawingOverlay({
108853
109288
  height: h2,
108854
109289
  shapeType: "custom",
108855
109290
  shapeStyle: {
108856
- fillColor: drawingColor,
109291
+ fillColor: "transparent",
108857
109292
  strokeColor: drawingColor,
108858
109293
  strokeWidth: drawingWidth
108859
109294
  },
@@ -109003,6 +109438,7 @@ function SlideCanvas({
109003
109438
  isDrawingRef,
109004
109439
  onAddInkElement,
109005
109440
  onAddFreeformShape,
109441
+ onEraseInkElement,
109006
109442
  onActionClick,
109007
109443
  onHyperlinkClick,
109008
109444
  comments,
@@ -109088,7 +109524,8 @@ function SlideCanvas({
109088
109524
  drawingWidth,
109089
109525
  isDrawingRef,
109090
109526
  onAddInkElement,
109091
- onAddFreeformShape
109527
+ onAddFreeformShape,
109528
+ onEraseInkElement
109092
109529
  });
109093
109530
  const rulerOffset = showRulers ? RULER_THICKNESS : 0;
109094
109531
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -109584,6 +110021,10 @@ function ViewerToolbarSection(props) {
109584
110021
  onSetMode,
109585
110022
  onToggleSidebar: () => s.setIsSlidesPaneOpen((p3) => !p3),
109586
110023
  onToggleInspector: () => s.setIsInspectorPaneOpen((p3) => !p3),
110024
+ onOpenAnimationPanel: () => {
110025
+ s.setIsInspectorPaneOpen(true);
110026
+ s.setSidebarPanelMode("properties");
110027
+ },
109587
110028
  onToggleCompactToolbar: () => s.setIsCompactToolbarOpen((p3) => !p3),
109588
110029
  onSetToolbarSection: s.setToolbarSection,
109589
110030
  onZoomIn: zoom.handleZoomIn,
@@ -111255,13 +111696,6 @@ function ViewerDialogGroup(props) {
111255
111696
  slideCount: slides.length
111256
111697
  }
111257
111698
  ),
111258
- /* @__PURE__ */ jsxRuntime.jsx(
111259
- BroadcastDialog,
111260
- {
111261
- open: dialogs.isBroadcastDialogOpen,
111262
- onClose: () => dialogs.setIsBroadcastDialogOpen(false)
111263
- }
111264
- ),
111265
111699
  /* @__PURE__ */ jsxRuntime.jsx(
111266
111700
  PrintDialog,
111267
111701
  {
@@ -111504,6 +111938,7 @@ function ViewerCanvasArea(props) {
111504
111938
  isDrawingRef: s.isDrawingRef,
111505
111939
  onAddInkElement: insertHandlers.handleAddInkElement,
111506
111940
  onAddFreeformShape: insertHandlers.handleAddFreeformShape,
111941
+ onEraseInkElement: insertHandlers.handleEraseInkElement,
111507
111942
  onActionClick: handleActionClick,
111508
111943
  onHyperlinkClick: handleHyperlinkClick,
111509
111944
  allSlides: mode === "present" ? slides : void 0,
@@ -111511,6 +111946,24 @@ function ViewerCanvasArea(props) {
111511
111946
  sourceSlideIndex: mode === "present" ? activeSlideIndex : void 0,
111512
111947
  fieldContext,
111513
111948
  tableStyleContext,
111949
+ collaborationOverlay: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
111950
+ /* @__PURE__ */ jsxRuntime.jsx(
111951
+ RemoteSelectionOverlay,
111952
+ {
111953
+ elements: effectiveSlide?.elements ?? [],
111954
+ activeSlideIndex
111955
+ }
111956
+ ),
111957
+ /* @__PURE__ */ jsxRuntime.jsx(
111958
+ CollaborationCursorOverlay,
111959
+ {
111960
+ activeSlideIndex,
111961
+ canvasWidth: canvasSize.width,
111962
+ canvasHeight: canvasSize.height,
111963
+ selectedElementId: s.selectedElementId
111964
+ }
111965
+ )
111966
+ ] }),
111514
111967
  comments: activeSlide?.comments,
111515
111968
  showCommentMarkers: s.sidebarPanelMode === "comments",
111516
111969
  onCommentMarkerClick: () => s.setSidebarPanelMode("comments"),
@@ -113121,6 +113574,37 @@ function useYjsDocumentSync({
113121
113574
  };
113122
113575
  }, [doc2, isConnected, getDocMap, setSlides]);
113123
113576
  }
113577
+ function useBroadcastFollower({
113578
+ collab,
113579
+ activeSlideIndex,
113580
+ setActiveSlideIndex,
113581
+ slideCount
113582
+ }) {
113583
+ const lastBroadcasterSlide = React10.useRef(-1);
113584
+ React10.useEffect(() => {
113585
+ if (!collab) {
113586
+ return;
113587
+ }
113588
+ if (collab.config.role !== "viewer") {
113589
+ return;
113590
+ }
113591
+ const broadcaster = collab.remoteUsers.find((u2) => u2.role === "broadcaster");
113592
+ if (!broadcaster) {
113593
+ return;
113594
+ }
113595
+ const targetSlide = broadcaster.activeSlideIndex;
113596
+ if (targetSlide < 0 || targetSlide >= slideCount) {
113597
+ return;
113598
+ }
113599
+ if (targetSlide === lastBroadcasterSlide.current) {
113600
+ return;
113601
+ }
113602
+ lastBroadcasterSlide.current = targetSlide;
113603
+ if (targetSlide !== activeSlideIndex) {
113604
+ setActiveSlideIndex(targetSlide);
113605
+ }
113606
+ }, [collab, activeSlideIndex, setActiveSlideIndex, slideCount]);
113607
+ }
113124
113608
  function computeGridSpacingPx(presentationGridSpacing) {
113125
113609
  if (presentationGridSpacing) {
113126
113610
  const px2 = Math.round(presentationGridSpacing.cx / EMU_PER_PX);
@@ -115461,6 +115945,17 @@ function useInsertElements(input) {
115461
115945
  }
115462
115946
  addElement(shape);
115463
115947
  };
115948
+ const handleEraseInkElement = (elementId) => {
115949
+ if (!activeSlide) {
115950
+ return;
115951
+ }
115952
+ ops.updateSlides(
115953
+ (prev) => prev.map(
115954
+ (s, i3) => i3 === activeSlideIndex ? { ...s, elements: s.elements.filter((el) => el.id !== elementId) } : s
115955
+ )
115956
+ );
115957
+ history.markDirty();
115958
+ };
115464
115959
  return {
115465
115960
  handleAddTextBox,
115466
115961
  handleAddShape,
@@ -115468,6 +115963,7 @@ function useInsertElements(input) {
115468
115963
  ...structured,
115469
115964
  handleAddInkElement,
115470
115965
  handleAddFreeformShape,
115966
+ handleEraseInkElement,
115471
115967
  ...fileHandlers
115472
115968
  };
115473
115969
  }
@@ -116297,7 +116793,8 @@ function useEditorOperations(input) {
116297
116793
  selectedElementIds,
116298
116794
  canvasSize,
116299
116795
  dialogs,
116300
- presentation
116796
+ presentation,
116797
+ userName
116301
116798
  } = input;
116302
116799
  const ops = useElementOperations({
116303
116800
  activeSlide,
@@ -116331,6 +116828,7 @@ function useEditorOperations(input) {
116331
116828
  const comments = useComments({
116332
116829
  slides,
116333
116830
  canEdit,
116831
+ userName,
116334
116832
  selectedElementId: state2.selectedElementId,
116335
116833
  onUpdateSlides: ops.updateSlides,
116336
116834
  onMarkDirty: history.markDirty
@@ -123104,6 +123602,7 @@ var PowerPointViewer = React10.forwardRef(
123104
123602
  onDirtyChange,
123105
123603
  onActiveSlideChange,
123106
123604
  theme,
123605
+ authorName,
123107
123606
  collaboration,
123108
123607
  onStartCollaboration,
123109
123608
  onStopCollaboration,
@@ -123261,7 +123760,8 @@ var PowerPointViewer = React10.forwardRef(
123261
123760
  selectedElementIds,
123262
123761
  canvasSize,
123263
123762
  dialogs,
123264
- presentation
123763
+ presentation,
123764
+ userName: authorName ?? collaboration?.userName
123265
123765
  });
123266
123766
  const {
123267
123767
  exportHandlers,
@@ -123464,6 +123964,19 @@ var PowerPointViewer = React10.forwardRef(
123464
123964
  defaultServerUrl: shareDefaults?.serverUrl
123465
123965
  }
123466
123966
  ),
123967
+ /* @__PURE__ */ jsxRuntime.jsx(
123968
+ BroadcastDialog,
123969
+ {
123970
+ open: dialogs.isBroadcastDialogOpen,
123971
+ onClose: () => dialogs.setIsBroadcastDialogOpen(false),
123972
+ onStartBroadcast: onStartCollaboration,
123973
+ onStopBroadcast: onStopCollaboration,
123974
+ onStartPresenting: () => handleSetMode("present"),
123975
+ defaultRoomId: shareDefaults?.roomId,
123976
+ defaultUserName: shareDefaults?.userName,
123977
+ defaultServerUrl: shareDefaults?.serverUrl
123978
+ }
123979
+ ),
123467
123980
  /* @__PURE__ */ jsxRuntime.jsx(
123468
123981
  ViewerOverlays,
123469
123982
  {
@@ -123513,6 +124026,14 @@ var PowerPointViewer = React10.forwardRef(
123513
124026
  canvasHeight: canvasSize.height,
123514
124027
  children: [
123515
124028
  /* @__PURE__ */ jsxRuntime.jsx(CollaborationDocumentSync, { slides, setSlides: state2.setSlides }),
124029
+ /* @__PURE__ */ jsxRuntime.jsx(
124030
+ BroadcastFollowerSync,
124031
+ {
124032
+ activeSlideIndex,
124033
+ setActiveSlideIndex: state2.setActiveSlideIndex,
124034
+ slideCount: slides.length
124035
+ }
124036
+ ),
123516
124037
  viewerContent
123517
124038
  ]
123518
124039
  }
@@ -123540,6 +124061,20 @@ function CollaborationDocumentSync({
123540
124061
  });
123541
124062
  return null;
123542
124063
  }
124064
+ function BroadcastFollowerSync({
124065
+ activeSlideIndex,
124066
+ setActiveSlideIndex,
124067
+ slideCount
124068
+ }) {
124069
+ const collab = useCollaboration();
124070
+ useBroadcastFollower({
124071
+ collab,
124072
+ activeSlideIndex,
124073
+ setActiveSlideIndex,
124074
+ slideCount
124075
+ });
124076
+ return null;
124077
+ }
123543
124078
  function colorSchemesMatch(a2, b2) {
123544
124079
  if (!a2) {
123545
124080
  return false;