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