@dtducas/wh-forge-viewer 2.0.0-beta.1 → 2.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -2550,6 +2550,14 @@ var MarkupConverterService = /*#__PURE__*/function () {
2550
2550
  if (shape === null || shape === void 0 ? void 0 : shape.text) {
2551
2551
  attrs += " text=\"".concat(this.escapeXml(shape.text), "\"");
2552
2552
  }
2553
+ // Add text-data for stamp/image types.
2554
+ // Forge's MarkupStamp.loadSvgData() reads style['text-data'] as the image SVG source.
2555
+ // Without this, Forge creates a blank (invisible) stamp when loadMarkups() + enterEditMode()
2556
+ // is used to restore stamps into the edit canvas (used by reloadEditCanvasWithMergedMarkups).
2557
+ if (forgeType === 'stamp' && (shape === null || shape === void 0 ? void 0 : shape.imageData)) {
2558
+ var svgData = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1 1\" width=\"1\" height=\"1\"><image width=\"1\" height=\"1\" href=\"".concat(shape.imageData, "\"/></svg>");
2559
+ attrs += " text-data=\"".concat(this.escapeXml(svgData), "\"");
2560
+ }
2553
2561
  // Add font attributes.
2554
2562
  // CRITICAL: The <metadata><markup_element font-size="..."/> is parsed by Forge's
2555
2563
  // loadMarkups() as a COORDINATE-SPACE value (model units), NOT SVG pixel units.
@@ -2775,7 +2783,12 @@ var MarkupConverterService = /*#__PURE__*/function () {
2775
2783
  var _h3 = size[1];
2776
2784
  var x = -_w2 / 2;
2777
2785
  var y = -_h3 / 2;
2778
- return "<image href=\"".concat(imgData, "\" x=\"").concat(x, "\" y=\"").concat(y, "\" width=\"").concat(_w2, "\" height=\"").concat(_h3, "\" transform=\"").concat(transform, "\"/>") + "<rect id=\"hitarea\" fill=\"transparent\" stroke=\"transparent\" x=\"".concat(x, "\" y=\"").concat(y, "\" width=\"").concat(_w2, "\" height=\"").concat(_h3, "\" transform=\"").concat(transform, "\"/>");
2786
+ // Forge's markup SVG canvas uses a Y-up coordinate system.
2787
+ // Raster <image> pixels are always Y-down (screen space), so they appear
2788
+ // upside-down without a counter-flip. Append scale(1,-1) to flip the image
2789
+ // content back to the correct screen orientation for User B's overlay.
2790
+ // The hitarea rect does not need a flip — it is transparent and just tracks hits.
2791
+ return "<image href=\"".concat(imgData, "\" x=\"").concat(x, "\" y=\"").concat(y, "\" width=\"").concat(_w2, "\" height=\"").concat(_h3, "\" transform=\"").concat(transform, " scale(1,-1)\"/>") + "<rect id=\"hitarea\" fill=\"transparent\" stroke=\"transparent\" x=\"".concat(x, "\" y=\"").concat(y, "\" width=\"").concat(_w2, "\" height=\"").concat(_h3, "\" transform=\"").concat(transform, "\"/>");
2779
2792
  }
2780
2793
  if (imgData === '__pending__') {
2781
2794
  // Render a placeholder rectangle while image chunks are still arriving
@@ -34104,33 +34117,81 @@ function updateShapeAttributes(svgElement, element) {
34104
34117
  }
34105
34118
  // ========== Shape Creation Functions ==========
34106
34119
  /**
34107
- * Create arrow SVG element
34120
+ * Create arrow SVG element matching Forge Viewer's native arrow format.
34121
+ *
34122
+ * Forge Viewer renders arrows as a single filled <path> (shaft + arrowhead merged)
34123
+ * centered at the midpoint of the two endpoints and rotated to the correct angle.
34124
+ * This matches the native SVG:
34125
+ * <path d="M -hl -hh l (2hl-al) 0 l -ni -(ahh-hh) l (al+ni) ahh l -(al+ni) ahh l ni -(ahh-hh) l -(2hl-al) 0 z"
34126
+ * transform="translate(cx, cy) rotate(angleDeg)" stroke-width="sw" fill="strokeColor" stroke="strokeColor"/>
34127
+ *
34128
+ * Geometry constants (in Forge coordinate units):
34129
+ * sw = stroke-width
34130
+ * hl = totalLength / 2 (half the arrow length, placed symmetrically)
34131
+ * hh = sw / 2 (shaft half-height)
34132
+ * ahh = 1.5 * sw (arrowhead half-height)
34133
+ * al = 3 * sw (arrowhead length along x-axis)
34134
+ * ni = 0.3 * sw (neck inset — small notch where shaft meets arrowhead)
34135
+ *
34108
34136
  * @private
34109
34137
  */
34110
34138
  function createArrowElement(element, ns) {
34111
34139
  var _a;
34112
34140
  var group = document.createElementNS(ns, 'g');
34113
34141
  group.setAttribute('class', 'markup-arrow');
34114
- // Create line
34115
- var line = document.createElementNS(ns, 'line');
34142
+ group.setAttribute('cursor', 'inherit');
34143
+ group.setAttribute('pointer-events', 'painted');
34144
+ // Endpoint coordinates
34145
+ var x1 = 0,
34146
+ y1 = 0,
34147
+ x2 = 1,
34148
+ y2 = 0;
34116
34149
  if (((_a = element.shape) === null || _a === void 0 ? void 0 : _a.points) && element.shape.points.length >= 4) {
34117
- line.setAttribute('x1', String(element.shape.points[0]));
34118
- line.setAttribute('y1', String(element.shape.points[1]));
34119
- line.setAttribute('x2', String(element.shape.points[2]));
34120
- line.setAttribute('y2', String(element.shape.points[3]));
34121
- } else {
34122
- // Use size as fallback
34123
- line.setAttribute('x1', '0');
34124
- line.setAttribute('y1', '0');
34125
- line.setAttribute('x2', String(element.size[0]));
34126
- line.setAttribute('y2', String(element.size[1]));
34127
- }
34128
- line.setAttribute('stroke', element.style.sc || '#000000');
34129
- line.setAttribute('stroke-width', String(element.style.sw || 2));
34130
- line.setAttribute('marker-end', 'url(#arrowhead)');
34131
- group.appendChild(line);
34132
- // Create arrowhead marker if not exists
34133
- ensureArrowMarker(ns);
34150
+ x1 = element.shape.points[0];
34151
+ y1 = element.shape.points[1];
34152
+ x2 = element.shape.points[2];
34153
+ y2 = element.shape.points[3];
34154
+ } else if (element.size) {
34155
+ x2 = element.size[0];
34156
+ y2 = element.size[1];
34157
+ }
34158
+ var sw = element.style.sw || 0.025;
34159
+ var cx = (x1 + x2) / 2;
34160
+ var cy = (y1 + y2) / 2;
34161
+ var dx = x2 - x1;
34162
+ var dy = y2 - y1;
34163
+ var totalLength = Math.sqrt(dx * dx + dy * dy);
34164
+ var angleDeg = Math.atan2(dy, dx) * (180 / Math.PI);
34165
+ var hl = totalLength / 2;
34166
+ var hh = sw / 2;
34167
+ var ahh = 1.5 * sw;
34168
+ var al = 3 * sw;
34169
+ var ni = 0.3 * sw;
34170
+ // Shaft length must be positive; guard against arrows shorter than the arrowhead
34171
+ var shaftLen = Math.max(0, 2 * hl - al);
34172
+ // Arrow path: horizontal, pointing right (+x), centered at origin.
34173
+ // Apply transform="translate(cx,cy) rotate(angleDeg)" to position and orient.
34174
+ var d = ["M ".concat(-hl, " ").concat(-hh), "l ".concat(shaftLen, " 0"), "l ".concat(-ni, " ").concat(-(ahh - hh)), "l ".concat(al + ni, " ").concat(ahh), "l ".concat(-(al + ni), " ").concat(ahh), "l ".concat(ni, " ").concat(-(ahh - hh)), "l ".concat(-shaftLen, " 0"), 'z'].join(' ');
34175
+ var transform = "translate(".concat(cx, ", ").concat(cy, ") rotate(").concat(angleDeg, ")");
34176
+ var strokeColor = element.style.sc || 'rgba(0,0,0,1)';
34177
+ // Main arrow path (filled + stroked like native Forge Viewer arrow)
34178
+ var markupPath = document.createElementNS(ns, 'path');
34179
+ markupPath.setAttribute('id', 'markup');
34180
+ markupPath.setAttribute('d', d);
34181
+ markupPath.setAttribute('stroke-width', String(sw));
34182
+ markupPath.setAttribute('stroke', strokeColor);
34183
+ markupPath.setAttribute('fill', strokeColor);
34184
+ markupPath.setAttribute('transform', transform);
34185
+ group.appendChild(markupPath);
34186
+ // Transparent hit area with larger stroke-width for easier selection
34187
+ var hitPath = document.createElementNS(ns, 'path');
34188
+ hitPath.setAttribute('id', 'hitarea');
34189
+ hitPath.setAttribute('d', d);
34190
+ hitPath.setAttribute('fill', 'transparent');
34191
+ hitPath.setAttribute('stroke', 'transparent');
34192
+ hitPath.setAttribute('stroke-width', String(sw * 6.86));
34193
+ hitPath.setAttribute('transform', transform);
34194
+ group.appendChild(hitPath);
34134
34195
  return group;
34135
34196
  }
34136
34197
  /**
@@ -34305,23 +34366,37 @@ function createCalloutElement(element, ns) {
34305
34366
  return group;
34306
34367
  }
34307
34368
  /**
34308
- * Create stamp/image SVG element
34309
- * Position is CENTER, so offset image
34369
+ * Create stamp/image SVG element for collaborative rendering (User B).
34370
+ *
34371
+ * imageData is raw base64 (data URL) — no counter-transform needed here.
34372
+ * Forge's markup canvas is Y-up, so a plain <image> would appear upside-down.
34373
+ * We wrap it in a <g> and apply scale(1,-1) on the image to flip it into
34374
+ * correct screen orientation, matching what MarkupStamp renders locally.
34375
+ *
34376
+ * Position is CENTER, so offset the image by -w/2, -h/2.
34310
34377
  * @private
34311
34378
  */
34312
34379
  function createStampElement(element, ns) {
34313
34380
  var _a;
34381
+ var group = document.createElementNS(ns, 'g');
34382
+ group.setAttribute('class', 'markup-stamp');
34383
+ var w = element.size[0];
34384
+ var h = element.size[1];
34314
34385
  var image = document.createElementNS(ns, 'image');
34315
- image.setAttribute('class', 'markup-stamp');
34316
- // Position is center, so offset by -w/2, -h/2
34317
- image.setAttribute('x', String(-element.size[0] / 2));
34318
- image.setAttribute('y', String(-element.size[1] / 2));
34319
- image.setAttribute('width', String(element.size[0]));
34320
- image.setAttribute('height', String(element.size[1]));
34386
+ image.setAttribute('x', String(-w / 2));
34387
+ image.setAttribute('y', String(-h / 2));
34388
+ image.setAttribute('width', String(w));
34389
+ image.setAttribute('height', String(h));
34390
+ // scale(1,-1) flips the image content vertically in-place (the image is centered at
34391
+ // the origin so scale(1,-1) keeps the bounding box in the same position while reversing
34392
+ // the pixel rows). This is needed because SVG <image> pixels are Y-down but Forge's
34393
+ // markup canvas is Y-up — without the flip the image appears upside-down.
34394
+ image.setAttribute('transform', "scale(1,-1)");
34321
34395
  if ((_a = element.shape) === null || _a === void 0 ? void 0 : _a.imageData) {
34322
34396
  image.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', element.shape.imageData);
34323
34397
  }
34324
- return image;
34398
+ group.appendChild(image);
34399
+ return group;
34325
34400
  }
34326
34401
  // ========== Helper Functions ==========
34327
34402
  /**
@@ -34408,36 +34483,6 @@ function generateCloudPath(width, height, arcCount) {
34408
34483
  d += ' Z';
34409
34484
  return d;
34410
34485
  }
34411
- /**
34412
- * Ensure arrowhead marker exists in SVG defs
34413
- * @private
34414
- */
34415
- var arrowMarkerCreated = false;
34416
- function ensureArrowMarker(ns) {
34417
- if (arrowMarkerCreated) return;
34418
- var svg = document.querySelector('svg.markups-svg');
34419
- if (!svg) return;
34420
- var defs = svg.querySelector('defs');
34421
- if (!defs) {
34422
- defs = document.createElementNS(ns, 'defs');
34423
- svg.insertBefore(defs, svg.firstChild);
34424
- }
34425
- if (!defs.querySelector('#arrowhead')) {
34426
- var marker = document.createElementNS(ns, 'marker');
34427
- marker.setAttribute('id', 'arrowhead');
34428
- marker.setAttribute('markerWidth', '10');
34429
- marker.setAttribute('markerHeight', '7');
34430
- marker.setAttribute('refX', '9');
34431
- marker.setAttribute('refY', '3.5');
34432
- marker.setAttribute('orient', 'auto');
34433
- var polygon = document.createElementNS(ns, 'polygon');
34434
- polygon.setAttribute('points', '0 0, 10 3.5, 0 7');
34435
- polygon.setAttribute('fill', 'inherit');
34436
- marker.appendChild(polygon);
34437
- defs.appendChild(marker);
34438
- arrowMarkerCreated = true;
34439
- }
34440
- }
34441
34486
 
34442
34487
  /**
34443
34488
  * SVG Parser Utilities
@@ -36835,6 +36880,12 @@ var EditModeManager = /*#__PURE__*/function () {
36835
36880
  this.onEditModeChange = null;
36836
36881
  this.onClearCurrentTool = null;
36837
36882
  this.onExtensionReloaded = null;
36883
+ // Tracks explicit user intent to be in edit mode.
36884
+ // Immune to markupsCore.editMode being temporarily null during changeEditMode() transitions.
36885
+ this.userIntendedEditMode = false;
36886
+ // Set to true by refreshExtensionReferences() when MarkupsCore was fully reloaded.
36887
+ // Consumed in enterEditMode() to restore savedMarkupsSvg into the fresh edit canvas.
36888
+ this.extensionWasReloaded = false;
36838
36889
  // Track if page changed since last markup mode
36839
36890
  // Used to force extension reload when entering markup mode after page change
36840
36891
  this.pageChangedSinceLastMarkup = false;
@@ -36934,7 +36985,14 @@ var EditModeManager = /*#__PURE__*/function () {
36934
36985
  value: function handleEditModeChanged(event) {
36935
36986
  var _a;
36936
36987
  var wasInEditMode = this.editMode;
36937
- // Ensure editMode is always a boolean
36988
+ // CRITICAL FIX: When switching tools, markupsCore.changeEditMode() fires
36989
+ // EVENT_EDITMODE_CHANGED with active:false while markupsCore.editMode is temporarily null.
36990
+ // Using userIntendedEditMode (set explicitly by enterEditMode/leaveEditMode) instead of
36991
+ // markupsCore.editMode avoids the race condition where the guard fails during transitions.
36992
+ if (event.active === false && this.userIntendedEditMode) {
36993
+ console.log('[EditModeManager] Ignoring editMode=false: user intentionally in edit mode (tool-switch transition)');
36994
+ return;
36995
+ }
36938
36996
  var isActive = event.active === true || event.active !== false && !!((_a = this.context.markupsCore) === null || _a === void 0 ? void 0 : _a.editMode);
36939
36997
  this.editMode = isActive;
36940
36998
  console.log('[EditModeManager] handleEditModeChanged:', {
@@ -36963,10 +37021,16 @@ var EditModeManager = /*#__PURE__*/function () {
36963
37021
  key: "enterEditMode",
36964
37022
  value: function enterEditMode() {
36965
37023
  return __awaiter(this, void 0, void 0, /*#__PURE__*/_regenerator().m(function _callee2() {
36966
- var _t;
37024
+ var savedSvg, layerId, editLayerId, _yield$import$catch, patchForgeMarkupStampRestore, loaded, _t, _t2;
36967
37025
  return _regenerator().w(function (_context2) {
36968
37026
  while (1) switch (_context2.p = _context2.n) {
36969
37027
  case 0:
37028
+ // Mark explicit intent BEFORE any async work so the guard in handleEditModeChanged
37029
+ // can filter out spurious active:false events fired during enterEditMode() itself.
37030
+ this.userIntendedEditMode = true;
37031
+ // CRITICAL: Re-fetch MarkupsCore extension reference before entering edit mode
37032
+ // After page change, the extension might have been reset and we need fresh reference
37033
+ // This is async because extension may need to be reloaded via loadExtension()
36970
37034
  _context2.n = 1;
36971
37035
  return this.refreshExtensionReferences();
36972
37036
  case 1:
@@ -36981,15 +37045,65 @@ var EditModeManager = /*#__PURE__*/function () {
36981
37045
  _context2.p = 3;
36982
37046
  // Unload existing markups from view-only layer before entering edit mode
36983
37047
  this.markupOpsManager.unloadMarkupsBeforeEditMode();
36984
- // Enter edit mode
37048
+ // CRITICAL: Pre-load saved markups into a named layer BEFORE calling
37049
+ // enterEditMode(). Forge's loadMarkups() explicitly rejects calls while
37050
+ // duringEditMode===true, so loading must happen in view mode. By passing
37051
+ // the same layerId to enterEditMode(layerId) the pre-loaded markups become
37052
+ // interactive in the edit canvas.
37053
+ //
37054
+ // Additionally, apply the Forge MarkupStamp patch so that when Forge's
37055
+ // createMarkupFromSVG creates a MarkupStamp from the loaded SVG it can
37056
+ // fall back to style['text-data'] (which we populate in setMetadata()) when
37057
+ // scriptSvgData is a DOM element that DOMParser cannot parse.
37058
+ savedSvg = this.markupOpsManager.getSavedMarkupsSvg();
37059
+ layerId = this.markupOpsManager.getLayerId();
37060
+ if (!(savedSvg && savedSvg.trim().length > 0)) {
37061
+ _context2.n = 7;
37062
+ break;
37063
+ }
37064
+ _context2.p = 4;
37065
+ _context2.n = 5;
37066
+ return Promise.resolve().then(function () { return StampMarkup; })["catch"](function () {
37067
+ return {
37068
+ patchForgeMarkupStampRestore: null
37069
+ };
37070
+ });
37071
+ case 5:
37072
+ _yield$import$catch = _context2.v;
37073
+ patchForgeMarkupStampRestore = _yield$import$catch.patchForgeMarkupStampRestore;
37074
+ patchForgeMarkupStampRestore === null || patchForgeMarkupStampRestore === void 0 ? void 0 : patchForgeMarkupStampRestore();
37075
+ // Ensure view mode is active (loadMarkups requires duringViewMode===true)
37076
+ if (!this.context.markupsCore.duringViewMode) {
37077
+ try {
37078
+ this.context.markupsCore.show();
37079
+ } catch (_e) {
37080
+ /* ignore — enterEditMode() will call show() internally */
37081
+ }
37082
+ }
37083
+ loaded = this.context.markupsCore.loadMarkups(savedSvg, layerId);
37084
+ if (loaded) {
37085
+ editLayerId = layerId;
37086
+ console.log('[EditModeManager] Pre-loaded saved markups into layer:', layerId);
37087
+ } else {
37088
+ console.warn('[EditModeManager] loadMarkups returned false — will enter clean edit mode');
37089
+ }
37090
+ _context2.n = 7;
37091
+ break;
37092
+ case 6:
37093
+ _context2.p = 6;
37094
+ _t = _context2.v;
37095
+ console.warn('[EditModeManager] Could not pre-load markups before edit mode:', _t);
37096
+ case 7:
37097
+ // Enter edit mode — with layerId when we pre-loaded (restores markups),
37098
+ // or without to start a clean empty canvas.
36985
37099
  console.log('[EditModeManager] Calling markupsCore.enterEditMode()...');
36986
- _context2.n = 4;
36987
- return this.context.markupsCore.enterEditMode();
36988
- case 4:
37100
+ _context2.n = 8;
37101
+ return this.context.markupsCore.enterEditMode(editLayerId);
37102
+ case 8:
36989
37103
  console.log('[EditModeManager] markupsCore.enterEditMode() completed, svg:', !!this.context.markupsCore.svg);
36990
37104
  // Verify that svg layer was created (can fail after page change)
36991
37105
  if (this.context.markupsCore.svg) {
36992
- _context2.n = 7;
37106
+ _context2.n = 11;
36993
37107
  break;
36994
37108
  }
36995
37109
  console.warn('[EditModeManager] svg layer is null after enterEditMode, retrying with delay...');
@@ -37000,29 +37114,32 @@ var EditModeManager = /*#__PURE__*/function () {
37000
37114
  console.warn('[EditModeManager] Error leaving edit mode for retry:', e);
37001
37115
  }
37002
37116
  // Wait a bit for viewer to stabilize after page change
37003
- _context2.n = 5;
37117
+ _context2.n = 9;
37004
37118
  return new Promise(function (resolve) {
37005
37119
  return setTimeout(resolve, 100);
37006
37120
  });
37007
- case 5:
37008
- // Re-enter edit mode
37121
+ case 9:
37122
+ // Re-enter edit mode (use layerId again if we pre-loaded — the layer
37123
+ // survives leaveEditMode() since it only removes the edit canvas node)
37009
37124
  console.log('[EditModeManager] Retrying enterEditMode after delay...');
37010
- _context2.n = 6;
37011
- return this.context.markupsCore.enterEditMode();
37012
- case 6:
37125
+ _context2.n = 10;
37126
+ return this.context.markupsCore.enterEditMode(editLayerId);
37127
+ case 10:
37013
37128
  console.log('[EditModeManager] Retry completed, svg:', !!this.context.markupsCore.svg);
37014
37129
  if (this.context.markupsCore.svg) {
37015
- _context2.n = 7;
37130
+ _context2.n = 11;
37016
37131
  break;
37017
37132
  }
37018
37133
  console.error('[EditModeManager] Failed to create svg layer after retry');
37019
37134
  this.editMode = false;
37135
+ this.userIntendedEditMode = false;
37020
37136
  return _context2.a(2);
37021
- case 7:
37137
+ case 11:
37022
37138
  // Set editMode flag explicitly to true
37023
37139
  this.editMode = true;
37024
37140
  console.log('[EditModeManager] editMode flag set to true explicitly');
37025
37141
  console.log('[EditModeManager] svg layer exists:', !!this.context.markupsCore.svg);
37142
+ this.extensionWasReloaded = false;
37026
37143
  // Notify presence manager of edit mode state
37027
37144
  if (this.onEditModeChange) {
37028
37145
  this.onEditModeChange(true);
@@ -37031,17 +37148,18 @@ var EditModeManager = /*#__PURE__*/function () {
37031
37148
  this.toolbarVisManager.hideDefaultToolbar();
37032
37149
  this.eventBus.emit('drawing:editModeEntered');
37033
37150
  console.log('[EditModeManager] Entered edit mode');
37034
- _context2.n = 9;
37151
+ _context2.n = 13;
37035
37152
  break;
37036
- case 8:
37037
- _context2.p = 8;
37038
- _t = _context2.v;
37039
- console.error('[EditModeManager] Failed to enter edit mode:', _t);
37153
+ case 12:
37154
+ _context2.p = 12;
37155
+ _t2 = _context2.v;
37156
+ console.error('[EditModeManager] Failed to enter edit mode:', _t2);
37040
37157
  this.editMode = false;
37041
- case 9:
37158
+ this.userIntendedEditMode = false;
37159
+ case 13:
37042
37160
  return _context2.a(2);
37043
37161
  }
37044
- }, _callee2, this, [[3, 8]]);
37162
+ }, _callee2, this, [[4, 6], [3, 12]]);
37045
37163
  }));
37046
37164
  }
37047
37165
  /**
@@ -37056,7 +37174,7 @@ var EditModeManager = /*#__PURE__*/function () {
37056
37174
  key: "refreshExtensionReferences",
37057
37175
  value: function refreshExtensionReferences() {
37058
37176
  return __awaiter(this, void 0, void 0, /*#__PURE__*/_regenerator().m(function _callee3() {
37059
- var viewer, extensionWasReloaded, markupsCore, markupsGui, _t2, _t3, _t4, _t5, _t6;
37177
+ var viewer, extensionWasReloaded, markupsCore, markupsGui, _t3, _t4, _t5, _t6, _t7;
37060
37178
  return _regenerator().w(function (_context3) {
37061
37179
  while (1) switch (_context3.p = _context3.n) {
37062
37180
  case 0:
@@ -37087,8 +37205,8 @@ var EditModeManager = /*#__PURE__*/function () {
37087
37205
  break;
37088
37206
  case 4:
37089
37207
  _context3.p = 4;
37090
- _t2 = _context3.v;
37091
- console.warn('[EditModeManager] Failed to unload stale MarkupsCore:', _t2);
37208
+ _t3 = _context3.v;
37209
+ console.warn('[EditModeManager] Failed to unload stale MarkupsCore:', _t3);
37092
37210
  case 5:
37093
37211
  if (markupsCore) {
37094
37212
  _context3.n = 9;
@@ -37107,8 +37225,8 @@ var EditModeManager = /*#__PURE__*/function () {
37107
37225
  break;
37108
37226
  case 8:
37109
37227
  _context3.p = 8;
37110
- _t3 = _context3.v;
37111
- console.error('[EditModeManager] Failed to reload MarkupsCore extension:', _t3);
37228
+ _t4 = _context3.v;
37229
+ console.error('[EditModeManager] Failed to reload MarkupsCore extension:', _t4);
37112
37230
  case 9:
37113
37231
  if (markupsCore) {
37114
37232
  this.context.markupsCore = markupsCore;
@@ -37131,8 +37249,8 @@ var EditModeManager = /*#__PURE__*/function () {
37131
37249
  break;
37132
37250
  case 12:
37133
37251
  _context3.p = 12;
37134
- _t4 = _context3.v;
37135
- console.warn('[EditModeManager] Failed to unload stale MarkupsGui:', _t4);
37252
+ _t5 = _context3.v;
37253
+ console.warn('[EditModeManager] Failed to unload stale MarkupsGui:', _t5);
37136
37254
  case 13:
37137
37255
  if (markupsGui) {
37138
37256
  _context3.n = 17;
@@ -37150,8 +37268,8 @@ var EditModeManager = /*#__PURE__*/function () {
37150
37268
  break;
37151
37269
  case 16:
37152
37270
  _context3.p = 16;
37153
- _t5 = _context3.v;
37154
- console.error('[EditModeManager] Failed to reload MarkupsGui extension:', _t5);
37271
+ _t6 = _context3.v;
37272
+ console.error('[EditModeManager] Failed to reload MarkupsGui extension:', _t6);
37155
37273
  case 17:
37156
37274
  if (markupsGui) {
37157
37275
  this.context.markupsGui = markupsGui;
@@ -37175,16 +37293,20 @@ var EditModeManager = /*#__PURE__*/function () {
37175
37293
  case 18:
37176
37294
  // Notify listeners that extension was reloaded
37177
37295
  // This allows ToolManager to reinitialize its ToolFactory with the new MarkupsCore
37178
- if (extensionWasReloaded && this.onExtensionReloaded) {
37179
- console.log('[EditModeManager] Notifying listeners of extension reload');
37180
- this.onExtensionReloaded();
37296
+ if (extensionWasReloaded) {
37297
+ // Persist flag so enterEditMode() can reload savedMarkupsSvg into the fresh canvas
37298
+ this.extensionWasReloaded = true;
37299
+ if (this.onExtensionReloaded) {
37300
+ console.log('[EditModeManager] Notifying listeners of extension reload');
37301
+ this.onExtensionReloaded();
37302
+ }
37181
37303
  }
37182
37304
  _context3.n = 20;
37183
37305
  break;
37184
37306
  case 19:
37185
37307
  _context3.p = 19;
37186
- _t6 = _context3.v;
37187
- console.error('[EditModeManager] Failed to refresh extension references:', _t6);
37308
+ _t7 = _context3.v;
37309
+ console.error('[EditModeManager] Failed to refresh extension references:', _t7);
37188
37310
  case 20:
37189
37311
  return _context3.a(2);
37190
37312
  }
@@ -37198,19 +37320,24 @@ var EditModeManager = /*#__PURE__*/function () {
37198
37320
  key: "leaveEditMode",
37199
37321
  value: function leaveEditMode() {
37200
37322
  var _this3 = this;
37323
+ this.userIntendedEditMode = false;
37201
37324
  if (!this.context.markupsCore) {
37202
37325
  console.warn('[EditModeManager] Cannot leave edit mode: MarkupsCore not available');
37203
37326
  return;
37204
37327
  }
37205
37328
  try {
37206
- // Save markups before leaving edit mode
37207
- this.markupOpsManager.saveAndReloadMarkupsOnLeave(function () {
37329
+ // Step 1: Save current edit-mode markups while still in edit mode
37330
+ this.markupOpsManager.saveMarkupsOnLeave(function () {
37208
37331
  var _a;
37209
37332
  return ((_a = _this3.context.markupsCore) === null || _a === void 0 ? void 0 : _a.generateData()) || '';
37210
37333
  });
37211
- // Leave edit mode (this clears all markups)
37334
+ // Step 2: Leave edit mode (Forge clears its edit canvas)
37212
37335
  this.context.markupsCore.leaveEditMode();
37213
37336
  this.editMode = false;
37337
+ // Step 3: Load saved markups into view-only layer AFTER leaving edit mode.
37338
+ // CRITICAL: doing this BEFORE leaveEditMode() caused Forge to clear the
37339
+ // loaded layer during the leaveEditMode() call, making markups disappear.
37340
+ this.markupOpsManager.reloadMarkupsToViewLayer();
37214
37341
  // Notify presence manager of edit mode state
37215
37342
  if (this.onEditModeChange) {
37216
37343
  this.onEditModeChange(false);
@@ -37443,6 +37570,22 @@ var MarkupOperationsManager = /*#__PURE__*/function () {
37443
37570
  console.error('[MarkupOperationsManager] Failed to redo:', error);
37444
37571
  }
37445
37572
  }
37573
+ /**
37574
+ * Load savedMarkupsSvg into the view-only layer so markups are visible
37575
+ * when NOT in edit mode. Must be called AFTER markupsCore.leaveEditMode().
37576
+ */
37577
+ }, {
37578
+ key: "reloadMarkupsToViewLayer",
37579
+ value: function reloadMarkupsToViewLayer() {
37580
+ if (!this.context.markupsCore) return;
37581
+ if (!this.savedMarkupsSvg || this.savedMarkupsSvg.trim().length === 0) return;
37582
+ try {
37583
+ this.context.markupsCore.loadMarkups(this.savedMarkupsSvg, this.layerId);
37584
+ console.log('[MarkupOperationsManager] Markups reloaded to view layer after leaveEditMode');
37585
+ } catch (error) {
37586
+ console.warn('[MarkupOperationsManager] Failed to reload markups to view layer:', error);
37587
+ }
37588
+ }
37446
37589
  /**
37447
37590
  * Unload markups from layer before entering edit mode
37448
37591
  */
@@ -37460,29 +37603,24 @@ var MarkupOperationsManager = /*#__PURE__*/function () {
37460
37603
  }
37461
37604
  }
37462
37605
  /**
37463
- * Save and reload markups when leaving edit mode
37606
+ * Save markup SVG from edit mode into savedMarkupsSvg.
37607
+ * Does NOT call loadMarkups — that must happen AFTER markupsCore.leaveEditMode().
37608
+ * See reloadMarkupsToViewLayer().
37464
37609
  *
37465
- * @param generateData - Function to generate SVG data from edit mode
37610
+ * @param generateData - Function that returns SVG from current edit mode state
37466
37611
  */
37467
37612
  }, {
37468
- key: "saveAndReloadMarkupsOnLeave",
37469
- value: function saveAndReloadMarkupsOnLeave(generateData) {
37613
+ key: "saveMarkupsOnLeave",
37614
+ value: function saveMarkupsOnLeave(generateData) {
37470
37615
  if (!this.context.markupsCore) return;
37471
- // Check if markupsCore is in a valid state (svg layer exists)
37472
- // This can be null if page changed or extension was reset
37473
37616
  if (!this.context.markupsCore.svg) {
37474
37617
  console.warn('[MarkupOperationsManager] MarkupsCore.svg is null, skipping save');
37475
37618
  return;
37476
37619
  }
37477
37620
  try {
37478
- // Save markups before leaving edit mode
37479
37621
  var svgString = generateData();
37480
37622
  this.savedMarkupsSvg = svgString || '';
37481
- // Load the saved markups back to display them in view-only mode
37482
- if (this.savedMarkupsSvg && this.savedMarkupsSvg.trim().length > 0) {
37483
- this.context.markupsCore.loadMarkups(this.savedMarkupsSvg, this.layerId);
37484
- console.log('[MarkupOperationsManager] Markups saved and reloaded to preserve them');
37485
- }
37623
+ console.log('[MarkupOperationsManager] Markups saved on leave:', this.savedMarkupsSvg.length, 'chars');
37486
37624
  } catch (error) {
37487
37625
  console.warn('[MarkupOperationsManager] Error saving markups on leave:', error);
37488
37626
  }
@@ -45559,6 +45697,8 @@ function createMarkupStampClass() {
45559
45697
  _classCallCheck(this, MarkupStamp);
45560
45698
  _this = _callSuper(this, MarkupStamp, [id, editor, ['stroke-width', 'stroke-color', 'stroke-opacity', 'fill-color', 'fill-opacity']]);
45561
45699
  _this.type = 'stamp';
45700
+ /** Raw base64 image data (data URL) for collaborative serialization */
45701
+ _this.imageData = '';
45562
45702
  _this.type = 'stamp';
45563
45703
  _this.customSVG = customSVG;
45564
45704
  _this.addMarkupMetadata = avemcu.addMarkupMetadata.bind(_this);
@@ -45569,8 +45709,12 @@ function createMarkupStampClass() {
45569
45709
  _this$getDimensions2 = _slicedToArray(_this$getDimensions, 2),
45570
45710
  width = _this$getDimensions2[0],
45571
45711
  height = _this$getDimensions2[1];
45572
- _this.originalWidth = width;
45573
- _this.originalHeight = height;
45712
+ // Extract raw base64 image data for collaborative serialization.
45713
+ // MarkupsCore serializers read markup.imageData to pass via compact JSON
45714
+ // to remote users (User B). Storing raw base64 (not the SVG wrapper)
45715
+ // means createStampElement can render it directly without any transform issues.
45716
+ var imgEl = customSVG.querySelector('image');
45717
+ _this.imageData = (imgEl === null || imgEl === void 0 ? void 0 : imgEl.getAttribute('href')) || (imgEl === null || imgEl === void 0 ? void 0 : imgEl.getAttributeNS('http://www.w3.org/1999/xlink', 'href')) || '';
45574
45718
  // Copy SVG content
45575
45719
  _this.group.innerHTML = customSVG.innerHTML;
45576
45720
  // Create hit area for selection
@@ -45661,6 +45805,16 @@ function createMarkupStampClass() {
45661
45805
  metadata.position = [this.position.x, this.position.y].join(' ');
45662
45806
  metadata.size = [this.size.x, this.size.y].join(' ');
45663
45807
  metadata.rotation = String(this.rotation);
45808
+ // Store image SVG as text-data so Forge's createMarkupFromSVG can restore
45809
+ // the stamp image when loadMarkups() is called (e.g. on re-entry to edit mode).
45810
+ // Forge's MarkupStamp.loadSvgData() reads style['text-data'] as fallback.
45811
+ if (this.imageData) {
45812
+ var _this$getDimensions3 = this.getDimensions(this.customSVG),
45813
+ _this$getDimensions4 = _slicedToArray(_this$getDimensions3, 2),
45814
+ w = _this$getDimensions4[0],
45815
+ h = _this$getDimensions4[1];
45816
+ metadata['text-data'] = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ".concat(w, " ").concat(h, "\" width=\"").concat(w, "\" height=\"").concat(h, "\"><image width=\"").concat(w, "\" height=\"").concat(h, "\" href=\"").concat(this.imageData, "\"/></svg>");
45817
+ }
45664
45818
  return this.addMarkupMetadata(this.shape, metadata);
45665
45819
  }
45666
45820
  }]);
@@ -45968,11 +46122,66 @@ function parseSVGString(svgString) {
45968
46122
  }
45969
46123
  /**
45970
46124
  * Create SVG element with embedded image (base64)
45971
- * This format is compatible with MarkupsCore
46125
+ * This format is compatible with MarkupsCore.
46126
+ *
46127
+ * NOTE: Do NOT apply a Y-flip transform to the <image> here.
46128
+ * MarkupStamp.group already applies scale(1/W, -1/H) which handles the Y-axis
46129
+ * flip for the Forge Y-up coordinate system. Adding a pre-flip would double-flip
46130
+ * the image causing it to appear upside-down for User A.
46131
+ * The collaborative overlay (User B) applies its own scale(1,-1) separately
46132
+ * via generateVisualElement() in MarkupConverterService.
45972
46133
  */
45973
46134
  function createImageSVG(base64Data, width, height) {
45974
46135
  return "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"".concat(width, "\" height=\"").concat(height, "\" viewBox=\"0 0 ").concat(width, " ").concat(height, "\">\n <image width=\"").concat(width, "\" height=\"").concat(height, "\" href=\"").concat(base64Data, "\"/>\n</svg>");
45975
46136
  }
46137
+ /**
46138
+ * Patch Forge's built-in MarkupStamp.prototype.loadSvgData so that when
46139
+ * createMarkupFromSVG passes the full child DOM element as svgData (which
46140
+ * cannot be parsed by DOMParser), the method falls back to style['text-data']
46141
+ * instead. Our setMetadata() stores the image SVG there so Forge can
46142
+ * properly restore stamp images when loadMarkups() → enterEditMode() is used.
46143
+ *
46144
+ * Safe to call multiple times — guarded by a _patched flag.
46145
+ */
46146
+ function patchForgeMarkupStampRestore() {
46147
+ var _a, _b, _c;
46148
+ var avemc = getMarkupsCore();
46149
+ if (!avemc) return;
46150
+ // The class may be exposed directly on the Core namespace
46151
+ var ForgeMarkupStamp = avemc.MarkupStamp || ((_a = avemc.Classes) === null || _a === void 0 ? void 0 : _a.MarkupStamp) || ((_b = avemc.Markups) === null || _b === void 0 ? void 0 : _b.MarkupStamp);
46152
+ if (!ForgeMarkupStamp || typeof ((_c = ForgeMarkupStamp.prototype) === null || _c === void 0 ? void 0 : _c.loadSvgData) !== 'function') return;
46153
+ if (ForgeMarkupStamp.prototype.loadSvgData._patched) return;
46154
+ var originalLoadSvgData = ForgeMarkupStamp.prototype.loadSvgData;
46155
+ ForgeMarkupStamp.prototype.loadSvgData = function patchedLoadSvgData() {
46156
+ var _a;
46157
+ // When scriptSvgData is a DOM element (not a string), DOMParser cannot parse
46158
+ // it — fall back to style['text-data'] which contains the real image SVG.
46159
+ if (this.scriptSvgData && typeof this.scriptSvgData !== 'string') {
46160
+ var textData = (_a = this.style) === null || _a === void 0 ? void 0 : _a['text-data'];
46161
+ if (textData && typeof textData === 'string') {
46162
+ var saved = this.scriptSvgData;
46163
+ this.scriptSvgData = null; // force the fallback branch
46164
+ originalLoadSvgData.call(this);
46165
+ this.scriptSvgData = saved; // restore so other code (e.g. undo) still works
46166
+ return;
46167
+ }
46168
+ }
46169
+ originalLoadSvgData.call(this);
46170
+ };
46171
+ ForgeMarkupStamp.prototype.loadSvgData._patched = true;
46172
+ }
46173
+
46174
+ var StampMarkup = /*#__PURE__*/Object.freeze({
46175
+ __proto__: null,
46176
+ createImageSVG: createImageSVG,
46177
+ createMarkupStampClass: createMarkupStampClass,
46178
+ getEditModeStampClass: getEditModeStampClass,
46179
+ getStampCreateActionClass: getStampCreateActionClass,
46180
+ getStampDeleteActionClass: getStampDeleteActionClass,
46181
+ getStampUpdateActionClass: getStampUpdateActionClass,
46182
+ parseSVGString: parseSVGString,
46183
+ patchForgeMarkupStampRestore: patchForgeMarkupStampRestore
46184
+ });
45976
46185
 
45977
46186
  /**
45978
46187
  * Image tool for inserting images as markups
@@ -47339,7 +47548,6 @@ var DrawingManager = /*#__PURE__*/function () {
47339
47548
  var _this = this;
47340
47549
  var config = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
47341
47550
  _classCallCheck(this, DrawingManager);
47342
- this.eventBus = eventBus;
47343
47551
  // Initialize context
47344
47552
  this.context = {
47345
47553
  viewer: viewer,
@@ -47418,7 +47626,10 @@ var DrawingManager = /*#__PURE__*/function () {
47418
47626
  }, {
47419
47627
  key: "setupEventListeners",
47420
47628
  value: function setupEventListeners() {
47421
- this.eventBus.on('drawing:toggle', this.editModeManager.handleToggle.bind(this.editModeManager));
47629
+ // NOTE: 'drawing:toggle' is handled exclusively by ViewerForgePDF (React layer)
47630
+ // which orchestrates: enterEditMode() → setShowMarkupToolbar(true).
47631
+ // Subscribing here caused double-call to enterEditMode(), resetting Forge Viewer
47632
+ // to its default Arrow tool on every markup mode entry.
47422
47633
  if (this.context.markupsCore) {
47423
47634
  this.context.markupsCore.addEventListener('EVENT_EDITMODE_CHANGED', this.editModeManager.handleEditModeChanged.bind(this.editModeManager));
47424
47635
  this.context.markupsCore.addEventListener('EVENT_MARKUP_SELECTED', this.propertyManager.handleMarkupSelected.bind(this.propertyManager));
@@ -48041,7 +48252,6 @@ var DrawingManager = /*#__PURE__*/function () {
48041
48252
  this.collaborativeManager.destroy();
48042
48253
  this.presenceManager.destroy();
48043
48254
  // Remove event listeners
48044
- this.eventBus.off('drawing:toggle', this.editModeManager.handleToggle.bind(this.editModeManager));
48045
48255
  if (this.context.markupsCore) {
48046
48256
  this.context.markupsCore.removeEventListener('EVENT_EDITMODE_CHANGED', this.editModeManager.handleEditModeChanged.bind(this.editModeManager));
48047
48257
  this.context.markupsCore.removeEventListener('EVENT_MARKUP_SELECTED', this.propertyManager.handleMarkupSelected.bind(this.propertyManager));
@@ -48350,21 +48560,26 @@ var MarkupToolbar = function MarkupToolbar(_ref) {
48350
48560
  setActiveTool = _useState2[1];
48351
48561
  // Refs
48352
48562
  var toolbarRef = useRef(null);
48353
- var initializedRef = useRef(false);
48354
- // Auto-activate Pen tool when toolbar mounts
48355
- // Note: Edit mode is already entered by ViewerForgePDF before showing toolbar
48563
+ // Activate Pen tool immediately on mount.
48564
+ // Edit mode is guaranteed active — ViewerForgePDF awaits enterEditMode() before
48565
+ // setting showMarkupToolbar=true, so no delay needed.
48356
48566
  useEffect(function () {
48357
- if (initializedRef.current) return;
48358
- initializedRef.current = true;
48359
- // Small delay to ensure edit mode is fully initialized
48360
- var timer = setTimeout(function () {
48361
- drawingManager.setActiveTool(MARKUP_TOOLS.PEN);
48362
- console.log('[MarkupToolbar] Auto-activated Pen tool on mount');
48363
- }, 50);
48567
+ drawingManager.setActiveTool(MARKUP_TOOLS.PEN);
48568
+ console.log('[MarkupToolbar] Activated Pen tool on mount');
48569
+ }, [drawingManager]);
48570
+ // Sync activeTool React state from Forge Viewer events.
48571
+ // Keeps the toolbar highlight correct if the active tool changes externally.
48572
+ useEffect(function () {
48573
+ var eventBus = EventBus.getInstance();
48574
+ var handleToolChanged = function handleToolChanged(_ref2) {
48575
+ var tool = _ref2.tool;
48576
+ setActiveTool(tool);
48577
+ };
48578
+ eventBus.on('drawing:toolChanged', handleToolChanged);
48364
48579
  return function () {
48365
- return clearTimeout(timer);
48580
+ eventBus.off('drawing:toolChanged', handleToolChanged);
48366
48581
  };
48367
- }, [drawingManager]);
48582
+ }, []);
48368
48583
  /**
48369
48584
  * Handle tool selection
48370
48585
  * Note: Edit mode is already managed by ViewerForgePDF, no need to check/enter here