@netless/forge-whiteboard 1.2.0 → 1.2.1

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.
@@ -25804,6 +25804,12 @@ function ae(e, t = {}) {
25804
25804
  // src/model/renderable/ElementModel.ts
25805
25805
  var Y = __toESM(require("yjs"), 1);
25806
25806
  var import_forge_room = require("@netless/forge-room");
25807
+
25808
+ // src/utils/constant.ts
25809
+ var elementsUndoOrigin = "elementsUndoOrigin";
25810
+ var backgroundElementsUndoOrigin = "backgroundElementsUndoOrigin";
25811
+
25812
+ // src/model/renderable/ElementModel.ts
25807
25813
  function _defineProperty(e, r, t) {
25808
25814
  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: true, configurable: true, writable: true }) : e[r] = t, e;
25809
25815
  }
@@ -25833,6 +25839,12 @@ var ElementModel = class _ElementModel {
25833
25839
  get type() {
25834
25840
  return this.root.get("type");
25835
25841
  }
25842
+ get role() {
25843
+ return this.root.get("role") ?? "normal";
25844
+ }
25845
+ get isBackground() {
25846
+ return this.role === "background";
25847
+ }
25836
25848
  get strokeWidth() {
25837
25849
  return this.root.get(_ElementModel.KEYS.strokeWidth);
25838
25850
  }
@@ -25891,6 +25903,9 @@ var ElementModel = class _ElementModel {
25891
25903
  get isPerformanceEnvironment() {
25892
25904
  return this.isPerformanceMode() && this.shouldUseLocalPoints;
25893
25905
  }
25906
+ isAttached() {
25907
+ return this.root.has(_ElementModel.KEYS.uuid);
25908
+ }
25894
25909
  constructor(root, scope, liveCursor, isPerformanceMode) {
25895
25910
  _defineProperty(this, "shadowEmitter", null);
25896
25911
  _defineProperty(this, "root", void 0);
@@ -25976,16 +25991,12 @@ var ElementModel = class _ElementModel {
25976
25991
  }
25977
25992
  }
25978
25993
  bindObserver() {
25979
- const beforeL = this.root._eH?.l?.length ?? -1;
25980
- const beforeDL = this.root._dEH?.l?.length ?? -1;
25981
25994
  (0, import_forge_room.removeDeepObserver)(this.root, this.handlePropChange);
25982
25995
  this.subBindObserver();
25983
- const afterRemoveL = this.root._eH?.l?.length ?? -1;
25984
- const afterRemoveDL = this.root._dEH?.l?.length ?? -1;
25985
25996
  this.root.observeDeep(this.handlePropChange);
25986
- const afterAddL = this.root._eH?.l?.length ?? -1;
25987
- const afterAddDL = this.root._dEH?.l?.length ?? -1;
25988
- console.log(`[][][] bindObserver uuid=${this.uuid} doc=${!!this.root.doc} _eH: ${beforeL}->${afterRemoveL}->${afterAddL} _dEH: ${beforeDL}->${afterRemoveDL}->${afterAddDL}`);
25997
+ }
25998
+ mutationOrigin() {
25999
+ return elementsUndoOrigin;
25989
26000
  }
25990
26001
  subBindObserver() {
25991
26002
  }
@@ -26024,62 +26035,81 @@ var ElementModel = class _ElementModel {
26024
26035
  this.item.data.uuid = this.uuid;
26025
26036
  this.item.data.index = this.index;
26026
26037
  this.item.data.type = this.root.get("type");
26038
+ this.item.data.role = this.role;
26027
26039
  this.item.data.ownerId = this.ownerId;
26028
26040
  this.item.applyMatrix = false;
26029
26041
  }
26030
26042
  }
26031
26043
  appendPoints(points) {
26032
26044
  if (this.isPerformanceEnvironment) {
26033
- this.appendPointsPerformance(points);
26045
+ return this.appendPointsPerformance(points);
26034
26046
  } else {
26035
- this.appendPointsDirect(points);
26047
+ return this.appendPointsDirect(points);
26036
26048
  }
26037
26049
  }
26050
+ getPointsArray() {
26051
+ const yArray = this.root.get(_ElementModel.KEYS.points);
26052
+ return yArray instanceof Y.Array ? yArray : null;
26053
+ }
26038
26054
  appendPointsDirect(points) {
26039
- this.root.get(_ElementModel.KEYS.points).push(points);
26055
+ const yArray = this.getPointsArray();
26056
+ if (!yArray) {
26057
+ return false;
26058
+ }
26059
+ yArray.push(points);
26060
+ return true;
26040
26061
  }
26041
26062
  appendPointsPerformance(points) {
26063
+ const yArray = this.getPointsArray();
26064
+ if (!yArray) {
26065
+ return false;
26066
+ }
26042
26067
  this.localPoints = this.localPoints.concat(points);
26043
26068
  this.onVectorUpdate();
26044
26069
  if (this.appendPointsTimer) {
26045
26070
  window.clearTimeout(this.appendPointsTimer);
26046
26071
  }
26047
26072
  if (this.localPoints.length % 80 === 0) {
26048
- const yArray = this.root.get(_ElementModel.KEYS.points);
26049
26073
  yArray?.push(this.localPoints.slice(yArray.length));
26050
26074
  }
26051
26075
  this.appendPointsTimer = window.setTimeout(() => {
26052
26076
  this.appendPointsTimer = null;
26053
26077
  if (this.localPoints.length > 0) {
26054
- const yArray = this.root.get(_ElementModel.KEYS.points);
26055
- yArray?.push(this.localPoints.slice(yArray.length));
26078
+ const nextArray = this.getPointsArray();
26079
+ nextArray?.push(this.localPoints.slice(nextArray.length));
26056
26080
  }
26057
26081
  }, 100);
26082
+ return true;
26058
26083
  }
26059
26084
  setPoints(points) {
26060
26085
  if (this.isPerformanceEnvironment) {
26061
- this.setPointsPerformance(points);
26086
+ return this.setPointsPerformance(points);
26062
26087
  } else {
26063
- this.setPointsDirect(points);
26088
+ return this.setPointsDirect(points);
26064
26089
  }
26065
26090
  }
26066
26091
  setPointsDirect(points) {
26067
26092
  if (this.root.doc) {
26093
+ const yArray = this.getPointsArray();
26094
+ if (!yArray) {
26095
+ return false;
26096
+ }
26068
26097
  this.root.doc.transact(() => {
26069
- const yArray = this.root.get(_ElementModel.KEYS.points);
26070
- if (yArray) {
26071
- yArray.delete(0, yArray.length);
26072
- yArray.push(points);
26073
- }
26074
- });
26098
+ yArray.delete(0, yArray.length);
26099
+ yArray.push(points);
26100
+ }, this.mutationOrigin());
26075
26101
  } else {
26076
- const yArray = this.root.get(_ElementModel.KEYS.points) || new Y.Array();
26102
+ const yArray = this.getPointsArray() || new Y.Array();
26077
26103
  yArray.delete(0, yArray.length);
26078
26104
  yArray.push(points);
26079
26105
  this.root.set(_ElementModel.KEYS.points, yArray);
26080
26106
  }
26107
+ return true;
26081
26108
  }
26082
26109
  setPointsPerformance(points) {
26110
+ if (this.root.doc && !this.getPointsArray()) {
26111
+ return false;
26112
+ }
26083
26113
  this.localPoints = points;
26084
26114
  this.onVectorUpdate();
26085
26115
  if (this.setPointsTimer) {
@@ -26087,25 +26117,33 @@ var ElementModel = class _ElementModel {
26087
26117
  }
26088
26118
  this.setPointsTimer = window.setTimeout(() => {
26089
26119
  if (this.root.doc) {
26120
+ const yArray = this.getPointsArray();
26121
+ if (!yArray) {
26122
+ return;
26123
+ }
26090
26124
  this.root.doc.transact(() => {
26091
- const yArray = this.root.get(_ElementModel.KEYS.points);
26092
- if (yArray) {
26093
- yArray.delete(0, yArray.length);
26094
- yArray.push(points);
26095
- }
26096
- });
26125
+ yArray.delete(0, yArray.length);
26126
+ yArray.push(points);
26127
+ }, this.mutationOrigin());
26097
26128
  } else {
26098
- const yArray = this.root.get(_ElementModel.KEYS.points) || new Y.Array();
26129
+ const yArray = this.getPointsArray() || new Y.Array();
26099
26130
  yArray.delete(0, yArray.length);
26100
26131
  yArray.push(points);
26101
26132
  this.root.set(_ElementModel.KEYS.points, yArray);
26102
26133
  }
26103
26134
  }, 100);
26135
+ return true;
26104
26136
  }
26105
26137
  appendPointsMatrix(matrix) {
26106
26138
  const current = new this.scope.Matrix(this.pointsMatrix);
26107
26139
  const next = matrix.appended(current);
26108
- this.root.set(_ElementModel.KEYS.pointsMatrix, [next.a, next.b, next.c, next.d, next.tx, next.ty]);
26140
+ if (this.root.doc) {
26141
+ this.root.doc.transact(() => {
26142
+ this.root.set(_ElementModel.KEYS.pointsMatrix, [next.a, next.b, next.c, next.d, next.tx, next.ty]);
26143
+ }, this.mutationOrigin());
26144
+ } else {
26145
+ this.root.set(_ElementModel.KEYS.pointsMatrix, [next.a, next.b, next.c, next.d, next.tx, next.ty]);
26146
+ }
26109
26147
  }
26110
26148
  rotateByOffset(angle, centerX, centerY) {
26111
26149
  const current = new this.scope.Matrix(this.pointsMatrix);
@@ -26119,7 +26157,13 @@ var ElementModel = class _ElementModel {
26119
26157
  matrix2.rotate(delta, centerX, centerY);
26120
26158
  next = matrix2.appended(next);
26121
26159
  }
26122
- this.root.set(_ElementModel.KEYS.pointsMatrix, [next.a, next.b, next.c, next.d, next.tx, next.ty]);
26160
+ if (this.root.doc) {
26161
+ this.root.doc.transact(() => {
26162
+ this.root.set(_ElementModel.KEYS.pointsMatrix, [next.a, next.b, next.c, next.d, next.tx, next.ty]);
26163
+ }, this.mutationOrigin());
26164
+ } else {
26165
+ this.root.set(_ElementModel.KEYS.pointsMatrix, [next.a, next.b, next.c, next.d, next.tx, next.ty]);
26166
+ }
26123
26167
  return next.rotation;
26124
26168
  }
26125
26169
  getStyleKeys() {
@@ -26130,7 +26174,6 @@ var ElementModel = class _ElementModel {
26130
26174
  this.subDispose();
26131
26175
  }
26132
26176
  disposeObserver() {
26133
- console.log(`[][][] disposeObserver uuid=${this.uuid}`);
26134
26177
  (0, import_forge_room.removeDeepObserver)(this.root, this.handlePropChange);
26135
26178
  }
26136
26179
  };
@@ -26171,6 +26214,7 @@ var EditorConfig = class _EditorConfig {
26171
26214
  constructor() {
26172
26215
  _defineProperty2(this, "resizeModel", () => "eight");
26173
26216
  _defineProperty2(this, "uniformScale", () => false);
26217
+ _defineProperty2(this, "translatable", () => true);
26174
26218
  _defineProperty2(this, "controlPoints", []);
26175
26219
  }
26176
26220
  merge(other) {
@@ -26179,6 +26223,7 @@ var EditorConfig = class _EditorConfig {
26179
26223
  const j = RESIZE_MODEL_LEVEL.findIndex((v) => v === other.resizeModel());
26180
26224
  next.resizeModel = () => RESIZE_MODEL_LEVEL[Math.max(i, j)];
26181
26225
  next.uniformScale = this.uniformScale || other.uniformScale;
26226
+ next.translatable = () => this.translatable() && other.translatable();
26182
26227
  next.controlPoints = [];
26183
26228
  return next;
26184
26229
  }
@@ -26647,7 +26692,14 @@ var WhiteboardTool = class {
26647
26692
  }
26648
26693
  this.pendingDragEvent = null;
26649
26694
  this.shadowEmitter.setActive(true);
26650
- this.onMouseDown(event);
26695
+ try {
26696
+ this.onMouseDown(event);
26697
+ } catch (error) {
26698
+ this.cancelCurrentAction();
26699
+ this.eventAvailable = false;
26700
+ this.shadowEmitter.setActive(false);
26701
+ throw error;
26702
+ }
26651
26703
  });
26652
26704
  _defineProperty7(this, "flushPendingDrag", () => {
26653
26705
  this.dragRafId = 0;
@@ -26655,7 +26707,14 @@ var WhiteboardTool = class {
26655
26707
  this.lastDragTime = performance.now();
26656
26708
  const event = this.pendingDragEvent;
26657
26709
  this.pendingDragEvent = null;
26658
- this.onMouseDrag(event);
26710
+ try {
26711
+ this.onMouseDrag(event);
26712
+ } catch (error) {
26713
+ this.cancelCurrentAction();
26714
+ this.eventAvailable = false;
26715
+ this.shadowEmitter.setActive(false);
26716
+ throw error;
26717
+ }
26659
26718
  }
26660
26719
  });
26661
26720
  _defineProperty7(this, "onMouseDragSelf", (event) => {
@@ -26666,7 +26725,14 @@ var WhiteboardTool = class {
26666
26725
  if (now - this.lastDragTime >= DRAG_FRAME_MS) {
26667
26726
  this.lastDragTime = now;
26668
26727
  this.pendingDragEvent = null;
26669
- this.onMouseDrag(event);
26728
+ try {
26729
+ this.onMouseDrag(event);
26730
+ } catch (error) {
26731
+ this.cancelCurrentAction();
26732
+ this.eventAvailable = false;
26733
+ this.shadowEmitter.setActive(false);
26734
+ throw error;
26735
+ }
26670
26736
  } else {
26671
26737
  this.pendingDragEvent = event;
26672
26738
  if (!this.dragRafId) {
@@ -26683,11 +26749,26 @@ var WhiteboardTool = class {
26683
26749
  this.dragRafId = 0;
26684
26750
  }
26685
26751
  if (this.pendingDragEvent) {
26686
- this.onMouseDrag(this.pendingDragEvent);
26687
- this.pendingDragEvent = null;
26752
+ try {
26753
+ this.onMouseDrag(this.pendingDragEvent);
26754
+ this.pendingDragEvent = null;
26755
+ } catch (error) {
26756
+ this.pendingDragEvent = null;
26757
+ this.cancelCurrentAction();
26758
+ this.eventAvailable = false;
26759
+ this.shadowEmitter.setActive(false);
26760
+ throw error;
26761
+ }
26762
+ }
26763
+ try {
26764
+ this.onMouseUp(event);
26765
+ } catch (error) {
26766
+ this.cancelCurrentAction();
26767
+ this.eventAvailable = false;
26768
+ throw error;
26769
+ } finally {
26770
+ this.shadowEmitter.setActive(false);
26688
26771
  }
26689
- this.onMouseUp(event);
26690
- this.shadowEmitter.setActive(false);
26691
26772
  });
26692
26773
  this.modelGetter = modelGetter;
26693
26774
  this.enableToolEvent = enableToolEvent;
@@ -26698,6 +26779,8 @@ var WhiteboardTool = class {
26698
26779
  this.tool.onMouseDrag = this.onMouseDragSelf;
26699
26780
  this.tool.onMouseUp = this.onMouseUpSelf;
26700
26781
  }
26782
+ cancelCurrentAction() {
26783
+ }
26701
26784
  };
26702
26785
 
26703
26786
  // src/tool/LineTool.ts
@@ -27078,7 +27161,6 @@ var PointTextModel = class extends ElementModel {
27078
27161
  if (!this.item) {
27079
27162
  return null;
27080
27163
  }
27081
- console.log("[][][] drawPoints", this.drawPoints);
27082
27164
  const bounds = this.item.internalBounds;
27083
27165
  const matrix = new this.scope.Matrix(this.pointsMatrix);
27084
27166
  const topLeft = new this.scope.Point(this.drawPoints[0], this.drawPoints[1]).transform(matrix);
@@ -27449,9 +27531,6 @@ var RectangleModel = class extends ElementModel {
27449
27531
  }
27450
27532
  };
27451
27533
 
27452
- // src/utils/constant.ts
27453
- var elementsUndoOrigin = "elementsUndoOrigin";
27454
-
27455
27534
  // src/model/renderable/EraserModel.ts
27456
27535
  var Y9 = __toESM(require("yjs"), 1);
27457
27536
  var import_lodash5 = __toESM(require_lodash(), 1);
@@ -27732,7 +27811,6 @@ var LaserPointerModel = class extends ElementModel {
27732
27811
  matrixedPoints() {
27733
27812
  const matrix = new this.scope.Matrix(this.pointsMatrix);
27734
27813
  const points = this.cachedPoints || this.points;
27735
- console.log("[][][] ,", this.points.length, this.cachedPoints?.length, this.localPoints.length);
27736
27814
  const groupPoints = (0, import_lodash6.chunk)(points, 2).slice(this.sliceBegin);
27737
27815
  return groupPoints.map((_ref) => {
27738
27816
  let [x, y] = _ref;
@@ -28002,6 +28080,7 @@ var ImageModel = class extends ElementModel {
28002
28080
  this.item = new this.scope.Raster(this.uuid);
28003
28081
  const matrix = new this.scope.Matrix(this.pointsMatrix);
28004
28082
  this.item.matrix = matrix;
28083
+ this.item.data.role = this.role;
28005
28084
  }
28006
28085
  onVectorUpdate() {
28007
28086
  const matrix = new this.scope.Matrix(this.pointsMatrix);
@@ -28030,8 +28109,12 @@ var ImageModel = class extends ElementModel {
28030
28109
  const cfg = new EditorConfig();
28031
28110
  cfg.resizeModel = () => "four-corner";
28032
28111
  cfg.uniformScale = () => true;
28112
+ cfg.translatable = () => !this.isBackground;
28033
28113
  return cfg;
28034
28114
  }
28115
+ mutationOrigin() {
28116
+ return this.isBackground ? backgroundElementsUndoOrigin : elementsUndoOrigin;
28117
+ }
28035
28118
  liveCursorPoint() {
28036
28119
  return null;
28037
28120
  }
@@ -28114,6 +28197,7 @@ var RenderableModel = class extends import_eventemitter3.default {
28114
28197
  flushRenderables() {
28115
28198
  this.emit("elementClear");
28116
28199
  const elements = Array.from(this.elements.values()).map((v) => this.convertToModel(v)).filter((v) => !!v);
28200
+ elements.sort((a2, b2) => this.compareElements(a2, b2));
28117
28201
  this.maxIndex = elements.reduce((r, n) => Math.max(r, n.index), -1);
28118
28202
  const clearIds = [];
28119
28203
  elements.forEach((model) => {
@@ -28175,7 +28259,32 @@ var RenderableModel = class extends import_eventemitter3.default {
28175
28259
  initElement(element) {
28176
28260
  element.shadowEmitter = this.shadowEmitter;
28177
28261
  }
28262
+ compareElementRole(roleA, roleB) {
28263
+ const a2 = roleA === "background" ? 0 : 1;
28264
+ const b2 = roleB === "background" ? 0 : 1;
28265
+ return a2 - b2;
28266
+ }
28267
+ compareElements(a2, b2) {
28268
+ const roleCompare = this.compareElementRole(a2.role, b2.role);
28269
+ if (roleCompare !== 0) {
28270
+ return roleCompare;
28271
+ }
28272
+ if (a2.index !== b2.index) {
28273
+ return a2.index - b2.index;
28274
+ }
28275
+ return a2.uuid.localeCompare(b2.uuid);
28276
+ }
28277
+ isBackgroundElement(uuid) {
28278
+ const element = this.elements.get(uuid);
28279
+ if (!element) {
28280
+ return false;
28281
+ }
28282
+ return (element.get("role") ?? "normal") === "background";
28283
+ }
28178
28284
  removeElementItem(uuid) {
28285
+ if (this.isBackgroundElement(uuid)) {
28286
+ return;
28287
+ }
28179
28288
  this.elements.delete(uuid);
28180
28289
  }
28181
28290
  confirmPermission() {
@@ -28186,15 +28295,21 @@ var RenderableModel = class extends import_eventemitter3.default {
28186
28295
  return hasPermission;
28187
28296
  }
28188
28297
  createImage(src) {
28298
+ let fit = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "legacy";
28299
+ let role = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : "normal";
28189
28300
  if (!this.confirmPermission()) {
28190
28301
  return;
28191
28302
  }
28303
+ if (role === "background") {
28304
+ return this.upsertBackgroundImage(src, fit);
28305
+ }
28192
28306
  const yMap = new Y12.Map();
28193
28307
  this.elements.doc?.transact(() => {
28194
28308
  const uuid = this.uuid;
28195
28309
  yMap.set(ElementModel.KEYS.index, ++this.maxIndex);
28196
28310
  yMap.set(ElementModel.KEYS.uuid, uuid);
28197
28311
  yMap.set("type", "image");
28312
+ yMap.set("role", role);
28198
28313
  yMap.set(ElementModel.KEYS.ownerId, this.userManager.selfId);
28199
28314
  this.elements.set(uuid, yMap);
28200
28315
  }, elementsUndoOrigin);
@@ -28202,9 +28317,47 @@ var RenderableModel = class extends import_eventemitter3.default {
28202
28317
  model.bindObserver();
28203
28318
  model.root.set("src", src);
28204
28319
  model.ownerId = this.userManager.selfId;
28205
- this.fitImageToViewport(src, model);
28320
+ this.fitImageToViewport(src, model, fit);
28206
28321
  }
28207
- fitImageToViewport(src, model) {
28322
+ upsertBackgroundImage(src, fit) {
28323
+ const backgroundEntries = Array.from(this.elements.entries()).filter((_ref) => {
28324
+ let [, elementMap] = _ref;
28325
+ return elementMap.get("type") === "image" && (elementMap.get("role") ?? "normal") === "background";
28326
+ });
28327
+ let targetMap = backgroundEntries[0]?.[1] ?? null;
28328
+ if (this.elements.doc) {
28329
+ this.elements.doc.transact(() => {
28330
+ backgroundEntries.slice(1).forEach((_ref2) => {
28331
+ let [key] = _ref2;
28332
+ return this.elements.delete(key);
28333
+ });
28334
+ if (!targetMap) {
28335
+ targetMap = new Y12.Map();
28336
+ const uuid = this.uuid;
28337
+ targetMap.set(ElementModel.KEYS.index, -1);
28338
+ targetMap.set(ElementModel.KEYS.uuid, uuid);
28339
+ targetMap.set("type", "image");
28340
+ targetMap.set("role", "background");
28341
+ targetMap.set(ElementModel.KEYS.ownerId, this.userManager.selfId);
28342
+ this.elements.set(uuid, targetMap);
28343
+ } else {
28344
+ targetMap.set("role", "background");
28345
+ targetMap.set(ElementModel.KEYS.ownerId, this.userManager.selfId);
28346
+ }
28347
+ }, backgroundElementsUndoOrigin);
28348
+ }
28349
+ if (!targetMap) {
28350
+ return;
28351
+ }
28352
+ const model = this.convertToModel(targetMap);
28353
+ if (!model || !(model instanceof ImageModel)) {
28354
+ return;
28355
+ }
28356
+ model.root.set("src", src);
28357
+ model.ownerId = this.userManager.selfId;
28358
+ this.fitImageToViewport(src, model, fit);
28359
+ }
28360
+ fitImageToViewport(src, model, fit) {
28208
28361
  const center = this.scope.project.view.center;
28209
28362
  const fallbackMatrix = new this.scope.Matrix();
28210
28363
  fallbackMatrix.translate({
@@ -28221,9 +28374,9 @@ var RenderableModel = class extends import_eventemitter3.default {
28221
28374
  return;
28222
28375
  }
28223
28376
  const viewportBounds = this.scope.project.view.bounds;
28224
- const maxWidth = viewportBounds.width * 2 / 3;
28225
- const maxHeight = viewportBounds.height * 2 / 3;
28226
- const fitScale = Math.min(maxWidth / naturalWidth, maxHeight / naturalHeight, 1);
28377
+ const maxWidth = fit === "contain" ? viewportBounds.width : viewportBounds.width * 2 / 3;
28378
+ const maxHeight = fit === "contain" ? viewportBounds.height : viewportBounds.height * 2 / 3;
28379
+ const fitScale = fit === "contain" ? Math.min(maxWidth / naturalWidth, maxHeight / naturalHeight) : Math.min(maxWidth / naturalWidth, maxHeight / naturalHeight, 1);
28227
28380
  const nextMatrix = new this.scope.Matrix();
28228
28381
  nextMatrix.translate({
28229
28382
  x: center.x,
@@ -28246,7 +28399,7 @@ var RenderableModel = class extends import_eventemitter3.default {
28246
28399
  if (model.root.doc) {
28247
28400
  model.root.doc.transact(() => {
28248
28401
  model.root.set(ElementModel.KEYS.pointsMatrix, values);
28249
- }, elementsUndoOrigin);
28402
+ }, model.isBackground ? backgroundElementsUndoOrigin : elementsUndoOrigin);
28250
28403
  } else {
28251
28404
  model.root.set(ElementModel.KEYS.pointsMatrix, values);
28252
28405
  }
@@ -28844,22 +28997,33 @@ var CurveTool = class extends WhiteboardTool {
28844
28997
  _defineProperty19(this, "flushPendingPoints", () => {
28845
28998
  this.flushRafId = 0;
28846
28999
  if (this.elementModel && this.pendingPoints.length > 0) {
28847
- this.elementModel.appendPoints(this.pendingPoints);
29000
+ const appended = this.elementModel.appendPoints(this.pendingPoints);
29001
+ if (!appended) {
29002
+ this.resetStrokeState();
29003
+ return;
29004
+ }
28848
29005
  this.pendingPoints = [];
28849
29006
  }
28850
29007
  });
28851
29008
  }
28852
- onMouseDown(_event) {
29009
+ resetStrokeState() {
28853
29010
  this.pointCount = 0;
28854
29011
  this.pendingPoints = [];
28855
29012
  if (this.flushRafId) {
28856
29013
  cancelAnimationFrame(this.flushRafId);
28857
29014
  this.flushRafId = 0;
28858
29015
  }
28859
- if (this.elementModel) {
28860
- this.elementModel.dispose();
28861
- }
28862
29016
  this.elementModel = null;
29017
+ }
29018
+ cancelCurrentAction() {
29019
+ this.resetStrokeState();
29020
+ }
29021
+ onMouseDown(_event) {
29022
+ const previousElement = this.elementModel;
29023
+ this.resetStrokeState();
29024
+ if (previousElement) {
29025
+ previousElement.dispose();
29026
+ }
28863
29027
  this.modelGetter().then((model) => {
28864
29028
  if (model) {
28865
29029
  this.elementModel = model.createCurve(true);
@@ -28872,6 +29036,10 @@ var CurveTool = class extends WhiteboardTool {
28872
29036
  }
28873
29037
  const MIN_DISTANCE = 2;
28874
29038
  if (this.elementModel) {
29039
+ if (!this.elementModel.isAttached()) {
29040
+ this.resetStrokeState();
29041
+ return;
29042
+ }
28875
29043
  let lastX = 0;
28876
29044
  let lastY = 0;
28877
29045
  if (this.pendingPoints.length >= 2) {
@@ -28900,22 +29068,27 @@ var CurveTool = class extends WhiteboardTool {
28900
29068
  this.flushRafId = 0;
28901
29069
  }
28902
29070
  this.flushPendingPoints();
29071
+ if (!this.elementModel || !this.elementModel.isAttached()) {
29072
+ this.resetStrokeState();
29073
+ return;
29074
+ }
29075
+ const currentElement = this.elementModel;
29076
+ const currentUuid = currentElement.uuid;
29077
+ const currentPointCount = this.pointCount;
28903
29078
  this.modelGetter().then((model) => {
28904
29079
  if (!model) {
28905
29080
  return;
28906
29081
  }
28907
- if (this.pointCount < 3 && this.elementModel) {
28908
- if (this.elementModel) {
28909
- model.removeElementItem(this.elementModel.uuid);
28910
- }
29082
+ if (currentPointCount < 3) {
29083
+ model.removeElementItem(currentUuid);
28911
29084
  }
28912
- if (this.elementModel) {
28913
- this.elementModel.shadow = "";
29085
+ if (currentElement.isAttached()) {
29086
+ currentElement.shadow = "";
28914
29087
  }
28915
- if (this.elementModel && event.event.metaKey) {
28916
- const result = this.recognizer.recognize(this.elementModel.points);
29088
+ if (currentElement.isAttached() && event.event.metaKey) {
29089
+ const result = this.recognizer.recognize(currentElement.points);
28917
29090
  if (result) {
28918
- model.removeElementItem(this.elementModel.uuid);
29091
+ model.removeElementItem(currentUuid);
28919
29092
  if (/^rectangle/.test(result.shape)) {
28920
29093
  const rect = model.createRectangle(false);
28921
29094
  rect?.setPoints([result.minX, result.minY, result.maxX, result.maxY]);
@@ -28934,6 +29107,9 @@ var CurveTool = class extends WhiteboardTool {
28934
29107
  }
28935
29108
  }
28936
29109
  }
29110
+ if (this.elementModel === currentElement) {
29111
+ this.resetStrokeState();
29112
+ }
28937
29113
  });
28938
29114
  }
28939
29115
  };
@@ -29131,6 +29307,9 @@ var SelectorTool = class extends WhiteboardTool {
29131
29307
  _defineProperty22(this, "showLiveCursor", false);
29132
29308
  this.selectElementsModel = selectElementsModel;
29133
29309
  }
29310
+ isSelectableItem(item) {
29311
+ return !!item.data.type && ["selector", "eraser", "laser"].indexOf(item.data.type) < 0 && item.data.role !== "background";
29312
+ }
29134
29313
  onMouseDown(event) {
29135
29314
  this.from = null;
29136
29315
  this.to = null;
@@ -29156,7 +29335,7 @@ var SelectorTool = class extends WhiteboardTool {
29156
29335
  this.elementModel.setPoints([rect.topLeft.x, rect.topLeft.y, rect.size.width, rect.size.height]);
29157
29336
  this.selectElementsModel.clearSelectElementForSelf();
29158
29337
  this.scope.project.activeLayer.children.forEach((item) => {
29159
- if (item.data.type && ["selector", "eraser", "laser"].indexOf(item.data.type) < 0) {
29338
+ if (this.isSelectableItem(item)) {
29160
29339
  if (rect.contains(item.bounds)) {
29161
29340
  this.selectElements.set(item.data.uuid, item.data.ownerId);
29162
29341
  } else {
@@ -29182,7 +29361,7 @@ var SelectorTool = class extends WhiteboardTool {
29182
29361
  return result;
29183
29362
  }, []);
29184
29363
  const hitResult = this.scope.project.hitTest(event.point);
29185
- if (hitResult && hitResult.item.data.uuid) {
29364
+ if (hitResult && hitResult.item.data.uuid && this.isSelectableItem(hitResult.item)) {
29186
29365
  elements.push({
29187
29366
  elementId: hitResult.item.data.uuid,
29188
29367
  ownerId: hitResult.item.data.ownerId
@@ -29726,7 +29905,7 @@ var Editor = class extends import_eventemitter35.default {
29726
29905
  };
29727
29906
  this.rootView.style.opacity = "0";
29728
29907
  }
29729
- if (target === this.frame) {
29908
+ if (target === this.frame && this.editorConfig?.translatable()) {
29730
29909
  evt.preventDefault();
29731
29910
  this.editMode = "translate";
29732
29911
  this.lastEditPoint = {
@@ -30031,7 +30210,6 @@ var Editor = class extends import_eventemitter35.default {
30031
30210
  this.shadowContainer.remove();
30032
30211
  this.shadowScope.project.activeLayer.addChild(this.shadowContainer);
30033
30212
  this.targets.forEach((model) => {
30034
- console.log("[][][] translateShadow model", model.root._dEH);
30035
30213
  model.shadow = this.shadowContainer.data.uuid;
30036
30214
  });
30037
30215
  }
@@ -30912,6 +31090,9 @@ var EraserTool = class extends WhiteboardTool {
30912
31090
  this.elementModel.appendPoints([event.point.x, event.point.y]);
30913
31091
  }
30914
31092
  this.scope.project.activeLayer.children.forEach((item) => {
31093
+ if (item.data.role === "background") {
31094
+ return;
31095
+ }
30915
31096
  if (item.data.type && ["selector", "eraser", "laser"].indexOf(item.data.type) < 0 && item.hitTest(event.point, {
30916
31097
  segments: true,
30917
31098
  stroke: true,
@@ -30922,6 +31103,9 @@ var EraserTool = class extends WhiteboardTool {
30922
31103
  }
30923
31104
  });
30924
31105
  this.shadowScope.project.activeLayer.children.forEach((item) => {
31106
+ if (item.data.role === "background") {
31107
+ return;
31108
+ }
30925
31109
  if (item.data.type && ["selector", "eraser", "laser"].indexOf(item.data.type) < 0 && item.hitTest(event.point, {
30926
31110
  segments: true,
30927
31111
  stroke: true,
@@ -31381,16 +31565,7 @@ var IndexedNavigation = class extends import_eventemitter311.default {
31381
31565
  return this.pageModel.pageList().filter((id) => /^_i_/.test(id));
31382
31566
  }
31383
31567
  get head() {
31384
- const headId = Object.keys(this.list).find((key) => {
31385
- return this.list[key] && this.list[key].prev === "";
31386
- });
31387
- if (!headId) {
31388
- (0, import_forge_room12.log)("indexed navigation confusion", {
31389
- list: JSON.stringify(this.list)
31390
- }, "error");
31391
- throw new Error("indexed navigation confusion");
31392
- }
31393
- return headId;
31568
+ return this.ensureValidList().head;
31394
31569
  }
31395
31570
  get lastNodeId() {
31396
31571
  let currentId = this.head;
@@ -31410,7 +31585,8 @@ var IndexedNavigation = class extends import_eventemitter311.default {
31410
31585
  _defineProperty36(this, "list", {});
31411
31586
  _defineProperty36(this, "hasPermission", void 0);
31412
31587
  _defineProperty36(this, "handleIndexedPageMapUpdate", (_evt) => {
31413
- this.list = this.indexedPageMap.get("list");
31588
+ this.list = this.normalizeList(this.indexedPageMap.get("list")) ?? {};
31589
+ this.ensureValidList("update");
31414
31590
  const needRemoveList = this.pageModel.pageList().filter((v) => /^_i_/.test(v) && Object.keys(this.list).indexOf(v) < 0);
31415
31591
  const needAddList = Object.keys(this.list).filter((v) => this.pageModel.pageList().indexOf(v) < 0);
31416
31592
  this.indexedPageMap.doc.transact(() => {
@@ -31443,11 +31619,156 @@ var IndexedNavigation = class extends import_eventemitter311.default {
31443
31619
  this.indexedPageMap.set("list", this.list);
31444
31620
  this.idPool.add("_i_");
31445
31621
  } else {
31446
- this.list = this.indexedPageMap.get("list");
31622
+ this.list = this.normalizeList(this.indexedPageMap.get("list")) ?? {};
31447
31623
  Object.keys(this.list).forEach((id) => this.idPool.add(id));
31624
+ this.ensureValidList("constructor");
31448
31625
  }
31449
31626
  this.indexedPageMap.observe(this.handleIndexedPageMapUpdate);
31450
31627
  }
31628
+ normalizeList(list) {
31629
+ if (!list || typeof list !== "object" || Array.isArray(list)) {
31630
+ return null;
31631
+ }
31632
+ const nextList = {};
31633
+ for (const [id, node] of Object.entries(list)) {
31634
+ if (!/^_i_/.test(id) || !node || typeof node !== "object") {
31635
+ return null;
31636
+ }
31637
+ const {
31638
+ prev,
31639
+ next
31640
+ } = node;
31641
+ if (typeof prev !== "string" || typeof next !== "string") {
31642
+ return null;
31643
+ }
31644
+ nextList[id] = {
31645
+ prev,
31646
+ next
31647
+ };
31648
+ }
31649
+ return nextList;
31650
+ }
31651
+ validateList(list) {
31652
+ const ids = Object.keys(list);
31653
+ if (ids.length === 0) {
31654
+ return {
31655
+ valid: false,
31656
+ reason: "empty list"
31657
+ };
31658
+ }
31659
+ const heads = ids.filter((id) => list[id].prev === "");
31660
+ if (heads.length !== 1) {
31661
+ return {
31662
+ valid: false,
31663
+ reason: `expected one head, got ${heads.length}`
31664
+ };
31665
+ }
31666
+ for (const id of ids) {
31667
+ const node = list[id];
31668
+ if (node.prev && !list[node.prev]) {
31669
+ return {
31670
+ valid: false,
31671
+ reason: `missing prev node ${node.prev}`
31672
+ };
31673
+ }
31674
+ if (node.next && !list[node.next]) {
31675
+ return {
31676
+ valid: false,
31677
+ reason: `missing next node ${node.next}`
31678
+ };
31679
+ }
31680
+ if (node.prev && list[node.prev].next !== id) {
31681
+ return {
31682
+ valid: false,
31683
+ reason: `prev link mismatch for ${id}`
31684
+ };
31685
+ }
31686
+ if (node.next && list[node.next].prev !== id) {
31687
+ return {
31688
+ valid: false,
31689
+ reason: `next link mismatch for ${id}`
31690
+ };
31691
+ }
31692
+ }
31693
+ const visited = /* @__PURE__ */ new Set();
31694
+ let currentId = heads[0];
31695
+ while (currentId) {
31696
+ if (visited.has(currentId)) {
31697
+ return {
31698
+ valid: false,
31699
+ reason: `cycle at ${currentId}`
31700
+ };
31701
+ }
31702
+ visited.add(currentId);
31703
+ currentId = list[currentId].next;
31704
+ }
31705
+ if (visited.size !== ids.length) {
31706
+ return {
31707
+ valid: false,
31708
+ reason: "detached nodes"
31709
+ };
31710
+ }
31711
+ return {
31712
+ valid: true,
31713
+ head: heads[0]
31714
+ };
31715
+ }
31716
+ createList(ids) {
31717
+ return ids.reduce((list, id, index) => {
31718
+ list[id] = {
31719
+ prev: ids[index - 1] ?? "",
31720
+ next: ids[index + 1] ?? ""
31721
+ };
31722
+ return list;
31723
+ }, {});
31724
+ }
31725
+ repairList(reason) {
31726
+ const pageIds = this.idList;
31727
+ const ids = pageIds.length > 0 ? pageIds : Object.keys(this.list).filter((id) => /^_i_/.test(id));
31728
+ const nextIds = ids.length > 0 ? ids : ["_i_"];
31729
+ const nextList = this.createList(nextIds);
31730
+ (0, import_forge_room12.log)("indexed navigation recovered", {
31731
+ reason,
31732
+ pageList: JSON.stringify(this.pageModel.pageList()),
31733
+ list: JSON.stringify(this.list),
31734
+ nextList: JSON.stringify(nextList)
31735
+ }, "warn");
31736
+ const apply = () => {
31737
+ for (const id of nextIds) {
31738
+ if (this.pageModel.pageList().indexOf(id) < 0) {
31739
+ this.pageModel.addPage(id);
31740
+ }
31741
+ }
31742
+ this.list = nextList;
31743
+ this.indexedPageMap.set("list", nextList);
31744
+ };
31745
+ if (this.indexedPageMap.doc) {
31746
+ this.indexedPageMap.doc.transact(apply);
31747
+ } else {
31748
+ apply();
31749
+ }
31750
+ nextIds.forEach((id) => this.idPool.add(id));
31751
+ return nextIds[0];
31752
+ }
31753
+ ensureValidList() {
31754
+ let context = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : "read";
31755
+ const normalized = this.normalizeList(this.list);
31756
+ if (!normalized) {
31757
+ return {
31758
+ head: this.repairList(`${context}: invalid list`)
31759
+ };
31760
+ }
31761
+ this.list = normalized;
31762
+ const validation = this.validateList(this.list);
31763
+ if (!validation.valid) {
31764
+ return {
31765
+ head: this.repairList(`${context}: ${validation.reason}`)
31766
+ };
31767
+ }
31768
+ return {
31769
+ head: validation.head
31770
+ };
31771
+ }
31451
31772
  getNextId() {
31452
31773
  const cache = Array.from(this.idPool).filter((id) => this.idList.indexOf(id) < 0);
31453
31774
  if (cache.length > 0) {
@@ -32397,7 +32718,6 @@ var WhiteboardApplication = class _WhiteboardApplication extends import_forge_ro
32397
32718
  this.emitter.emit("elementDeselected", userId);
32398
32719
  return;
32399
32720
  }
32400
- editor.show();
32401
32721
  const targetLayerId = this.userMap(userId).get(WhiteboardKeys.currentPage);
32402
32722
  const selfLayerId = this.userMap(this.userId).get(WhiteboardKeys.currentPage);
32403
32723
  if (targetLayerId !== selfLayerId || !this.layers.has(targetLayerId)) {
@@ -32405,7 +32725,13 @@ var WhiteboardApplication = class _WhiteboardApplication extends import_forge_ro
32405
32725
  }
32406
32726
  const elementModels = elements.map((id) => {
32407
32727
  return this.layers.get(targetLayerId).elementModels.get(id);
32408
- }).filter((v) => !!v);
32728
+ }).filter((model) => !!model && !model.isBackground);
32729
+ if (elementModels.length === 0) {
32730
+ editor.hidden();
32731
+ this.emitter.emit("elementDeselected", userId);
32732
+ return;
32733
+ }
32734
+ editor.show();
32409
32735
  editor.setTargets(elementModels);
32410
32736
  if (elementModels.length === 1) {
32411
32737
  const model = elementModels[0];
@@ -32597,14 +32923,17 @@ var WhiteboardApplication = class _WhiteboardApplication extends import_forge_ro
32597
32923
  this.camera.resetViewMatrixToMain();
32598
32924
  }
32599
32925
  };
32600
- this.emitter.insertImage = (src, pageId) => {
32926
+ this.emitter.insertImage = (src, options) => {
32601
32927
  if (!/https/.test(src)) {
32602
32928
  (0, import_forge_room14.log)("[@netless/forge-whiteboard] invalid image url, src needs to be in the HTTPS protocol.", {
32603
32929
  src
32604
32930
  }, "warn");
32605
32931
  return;
32606
32932
  }
32607
- let targetPageId = pageId;
32933
+ const normalizedOptions = typeof options === "string" ? {
32934
+ pageId: options
32935
+ } : options ?? {};
32936
+ let targetPageId = normalizedOptions.pageId;
32608
32937
  if (!targetPageId) {
32609
32938
  targetPageId = this.pageModel.getCurrentPage(this.userManager.selfId);
32610
32939
  }
@@ -32612,13 +32941,16 @@ var WhiteboardApplication = class _WhiteboardApplication extends import_forge_ro
32612
32941
  (0, import_forge_room14.log)("[@netless/forge-whiteboard] page not found", {}, "warn");
32613
32942
  return;
32614
32943
  }
32615
- this.layers.get(targetPageId)?.createImage(src);
32944
+ this.layers.get(targetPageId)?.createImage(src, normalizedOptions.fit ?? "legacy", normalizedOptions.role ?? "normal");
32616
32945
  };
32617
32946
  this.emitter.removeElement = (pageId, elementId) => {
32618
32947
  if (!this.layers.has(pageId)) {
32619
32948
  (0, import_forge_room14.log)("[@netless/forge-whiteboard] page not found", {}, "warn");
32620
32949
  return;
32621
32950
  }
32951
+ if (this.layers.get(pageId)?.isBackgroundElement(elementId)) {
32952
+ return;
32953
+ }
32622
32954
  this.layers.get(pageId)?.removeElementItem(elementId);
32623
32955
  };
32624
32956
  this.emitter.getViewModel = (userId) => {
@@ -32669,7 +33001,11 @@ var WhiteboardApplication = class _WhiteboardApplication extends import_forge_ro
32669
33001
  if (model) {
32670
33002
  if (model.elements.doc) {
32671
33003
  model.elements.doc.transact(() => {
32672
- model.elements.clear();
33004
+ for (const key of Array.from(model.elements.keys())) {
33005
+ if (!model.isBackgroundElement(key)) {
33006
+ model.elements.delete(key);
33007
+ }
33008
+ }
32673
33009
  }, elementsUndoOrigin);
32674
33010
  } else {
32675
33011
  model.elementModels.clear();
@@ -33081,7 +33417,9 @@ var WhiteboardApplication = class _WhiteboardApplication extends import_forge_ro
33081
33417
  const at = this.findElementIndex(element.data.index, parent);
33082
33418
  for (let i = at; i >= 0; i--) {
33083
33419
  const child = children[i - 1];
33084
- if (!child || child.data.index < element.data.index || child.data.uuid < element.data.uuid) {
33420
+ const childRole = child?.data.role === "background" ? 0 : 1;
33421
+ const elementRole = element.data.role === "background" ? 0 : 1;
33422
+ if (!child || childRole < elementRole || childRole === elementRole && child.data.index < element.data.index || childRole === elementRole && child.data.index === element.data.index && child.data.uuid < element.data.uuid) {
33085
33423
  parent.insertChild(i, element);
33086
33424
  break;
33087
33425
  }