@netless/forge-whiteboard 1.2.0-beta.3 → 1.3.0-beta.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.
@@ -25794,6 +25794,12 @@ function ae(e, t = {}) {
25794
25794
  // src/model/renderable/ElementModel.ts
25795
25795
  import * as Y from "yjs";
25796
25796
  import { removeDeepObserver } from "@netless/forge-room";
25797
+
25798
+ // src/utils/constant.ts
25799
+ var elementsUndoOrigin = "elementsUndoOrigin";
25800
+ var backgroundElementsUndoOrigin = "backgroundElementsUndoOrigin";
25801
+
25802
+ // src/model/renderable/ElementModel.ts
25797
25803
  function _defineProperty(e, r, t) {
25798
25804
  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: true, configurable: true, writable: true }) : e[r] = t, e;
25799
25805
  }
@@ -25823,6 +25829,12 @@ var ElementModel = class _ElementModel {
25823
25829
  get type() {
25824
25830
  return this.root.get("type");
25825
25831
  }
25832
+ get role() {
25833
+ return this.root.get("role") ?? "normal";
25834
+ }
25835
+ get isBackground() {
25836
+ return this.role === "background";
25837
+ }
25826
25838
  get strokeWidth() {
25827
25839
  return this.root.get(_ElementModel.KEYS.strokeWidth);
25828
25840
  }
@@ -25881,6 +25893,9 @@ var ElementModel = class _ElementModel {
25881
25893
  get isPerformanceEnvironment() {
25882
25894
  return this.isPerformanceMode() && this.shouldUseLocalPoints;
25883
25895
  }
25896
+ isAttached() {
25897
+ return this.root.has(_ElementModel.KEYS.uuid);
25898
+ }
25884
25899
  constructor(root, scope, liveCursor, isPerformanceMode) {
25885
25900
  _defineProperty(this, "shadowEmitter", null);
25886
25901
  _defineProperty(this, "root", void 0);
@@ -25966,16 +25981,12 @@ var ElementModel = class _ElementModel {
25966
25981
  }
25967
25982
  }
25968
25983
  bindObserver() {
25969
- const beforeL = this.root._eH?.l?.length ?? -1;
25970
- const beforeDL = this.root._dEH?.l?.length ?? -1;
25971
25984
  removeDeepObserver(this.root, this.handlePropChange);
25972
25985
  this.subBindObserver();
25973
- const afterRemoveL = this.root._eH?.l?.length ?? -1;
25974
- const afterRemoveDL = this.root._dEH?.l?.length ?? -1;
25975
25986
  this.root.observeDeep(this.handlePropChange);
25976
- const afterAddL = this.root._eH?.l?.length ?? -1;
25977
- const afterAddDL = this.root._dEH?.l?.length ?? -1;
25978
- console.log(`[][][] bindObserver uuid=${this.uuid} doc=${!!this.root.doc} _eH: ${beforeL}->${afterRemoveL}->${afterAddL} _dEH: ${beforeDL}->${afterRemoveDL}->${afterAddDL}`);
25987
+ }
25988
+ mutationOrigin() {
25989
+ return elementsUndoOrigin;
25979
25990
  }
25980
25991
  subBindObserver() {
25981
25992
  }
@@ -26014,62 +26025,81 @@ var ElementModel = class _ElementModel {
26014
26025
  this.item.data.uuid = this.uuid;
26015
26026
  this.item.data.index = this.index;
26016
26027
  this.item.data.type = this.root.get("type");
26028
+ this.item.data.role = this.role;
26017
26029
  this.item.data.ownerId = this.ownerId;
26018
26030
  this.item.applyMatrix = false;
26019
26031
  }
26020
26032
  }
26021
26033
  appendPoints(points) {
26022
26034
  if (this.isPerformanceEnvironment) {
26023
- this.appendPointsPerformance(points);
26035
+ return this.appendPointsPerformance(points);
26024
26036
  } else {
26025
- this.appendPointsDirect(points);
26037
+ return this.appendPointsDirect(points);
26026
26038
  }
26027
26039
  }
26040
+ getPointsArray() {
26041
+ const yArray = this.root.get(_ElementModel.KEYS.points);
26042
+ return yArray instanceof Y.Array ? yArray : null;
26043
+ }
26028
26044
  appendPointsDirect(points) {
26029
- this.root.get(_ElementModel.KEYS.points).push(points);
26045
+ const yArray = this.getPointsArray();
26046
+ if (!yArray) {
26047
+ return false;
26048
+ }
26049
+ yArray.push(points);
26050
+ return true;
26030
26051
  }
26031
26052
  appendPointsPerformance(points) {
26053
+ const yArray = this.getPointsArray();
26054
+ if (!yArray) {
26055
+ return false;
26056
+ }
26032
26057
  this.localPoints = this.localPoints.concat(points);
26033
26058
  this.onVectorUpdate();
26034
26059
  if (this.appendPointsTimer) {
26035
26060
  window.clearTimeout(this.appendPointsTimer);
26036
26061
  }
26037
26062
  if (this.localPoints.length % 80 === 0) {
26038
- const yArray = this.root.get(_ElementModel.KEYS.points);
26039
26063
  yArray?.push(this.localPoints.slice(yArray.length));
26040
26064
  }
26041
26065
  this.appendPointsTimer = window.setTimeout(() => {
26042
26066
  this.appendPointsTimer = null;
26043
26067
  if (this.localPoints.length > 0) {
26044
- const yArray = this.root.get(_ElementModel.KEYS.points);
26045
- yArray?.push(this.localPoints.slice(yArray.length));
26068
+ const nextArray = this.getPointsArray();
26069
+ nextArray?.push(this.localPoints.slice(nextArray.length));
26046
26070
  }
26047
26071
  }, 100);
26072
+ return true;
26048
26073
  }
26049
26074
  setPoints(points) {
26050
26075
  if (this.isPerformanceEnvironment) {
26051
- this.setPointsPerformance(points);
26076
+ return this.setPointsPerformance(points);
26052
26077
  } else {
26053
- this.setPointsDirect(points);
26078
+ return this.setPointsDirect(points);
26054
26079
  }
26055
26080
  }
26056
26081
  setPointsDirect(points) {
26057
26082
  if (this.root.doc) {
26083
+ const yArray = this.getPointsArray();
26084
+ if (!yArray) {
26085
+ return false;
26086
+ }
26058
26087
  this.root.doc.transact(() => {
26059
- const yArray = this.root.get(_ElementModel.KEYS.points);
26060
- if (yArray) {
26061
- yArray.delete(0, yArray.length);
26062
- yArray.push(points);
26063
- }
26064
- });
26088
+ yArray.delete(0, yArray.length);
26089
+ yArray.push(points);
26090
+ }, this.mutationOrigin());
26065
26091
  } else {
26066
- const yArray = this.root.get(_ElementModel.KEYS.points) || new Y.Array();
26092
+ const yArray = this.getPointsArray() || new Y.Array();
26067
26093
  yArray.delete(0, yArray.length);
26068
26094
  yArray.push(points);
26069
26095
  this.root.set(_ElementModel.KEYS.points, yArray);
26070
26096
  }
26097
+ return true;
26071
26098
  }
26072
26099
  setPointsPerformance(points) {
26100
+ if (this.root.doc && !this.getPointsArray()) {
26101
+ return false;
26102
+ }
26073
26103
  this.localPoints = points;
26074
26104
  this.onVectorUpdate();
26075
26105
  if (this.setPointsTimer) {
@@ -26077,25 +26107,33 @@ var ElementModel = class _ElementModel {
26077
26107
  }
26078
26108
  this.setPointsTimer = window.setTimeout(() => {
26079
26109
  if (this.root.doc) {
26110
+ const yArray = this.getPointsArray();
26111
+ if (!yArray) {
26112
+ return;
26113
+ }
26080
26114
  this.root.doc.transact(() => {
26081
- const yArray = this.root.get(_ElementModel.KEYS.points);
26082
- if (yArray) {
26083
- yArray.delete(0, yArray.length);
26084
- yArray.push(points);
26085
- }
26086
- });
26115
+ yArray.delete(0, yArray.length);
26116
+ yArray.push(points);
26117
+ }, this.mutationOrigin());
26087
26118
  } else {
26088
- const yArray = this.root.get(_ElementModel.KEYS.points) || new Y.Array();
26119
+ const yArray = this.getPointsArray() || new Y.Array();
26089
26120
  yArray.delete(0, yArray.length);
26090
26121
  yArray.push(points);
26091
26122
  this.root.set(_ElementModel.KEYS.points, yArray);
26092
26123
  }
26093
26124
  }, 100);
26125
+ return true;
26094
26126
  }
26095
26127
  appendPointsMatrix(matrix) {
26096
26128
  const current = new this.scope.Matrix(this.pointsMatrix);
26097
26129
  const next = matrix.appended(current);
26098
- this.root.set(_ElementModel.KEYS.pointsMatrix, [next.a, next.b, next.c, next.d, next.tx, next.ty]);
26130
+ if (this.root.doc) {
26131
+ this.root.doc.transact(() => {
26132
+ this.root.set(_ElementModel.KEYS.pointsMatrix, [next.a, next.b, next.c, next.d, next.tx, next.ty]);
26133
+ }, this.mutationOrigin());
26134
+ } else {
26135
+ this.root.set(_ElementModel.KEYS.pointsMatrix, [next.a, next.b, next.c, next.d, next.tx, next.ty]);
26136
+ }
26099
26137
  }
26100
26138
  rotateByOffset(angle, centerX, centerY) {
26101
26139
  const current = new this.scope.Matrix(this.pointsMatrix);
@@ -26109,7 +26147,13 @@ var ElementModel = class _ElementModel {
26109
26147
  matrix2.rotate(delta, centerX, centerY);
26110
26148
  next = matrix2.appended(next);
26111
26149
  }
26112
- this.root.set(_ElementModel.KEYS.pointsMatrix, [next.a, next.b, next.c, next.d, next.tx, next.ty]);
26150
+ if (this.root.doc) {
26151
+ this.root.doc.transact(() => {
26152
+ this.root.set(_ElementModel.KEYS.pointsMatrix, [next.a, next.b, next.c, next.d, next.tx, next.ty]);
26153
+ }, this.mutationOrigin());
26154
+ } else {
26155
+ this.root.set(_ElementModel.KEYS.pointsMatrix, [next.a, next.b, next.c, next.d, next.tx, next.ty]);
26156
+ }
26113
26157
  return next.rotation;
26114
26158
  }
26115
26159
  getStyleKeys() {
@@ -26120,7 +26164,6 @@ var ElementModel = class _ElementModel {
26120
26164
  this.subDispose();
26121
26165
  }
26122
26166
  disposeObserver() {
26123
- console.log(`[][][] disposeObserver uuid=${this.uuid}`);
26124
26167
  removeDeepObserver(this.root, this.handlePropChange);
26125
26168
  }
26126
26169
  };
@@ -26161,6 +26204,7 @@ var EditorConfig = class _EditorConfig {
26161
26204
  constructor() {
26162
26205
  _defineProperty2(this, "resizeModel", () => "eight");
26163
26206
  _defineProperty2(this, "uniformScale", () => false);
26207
+ _defineProperty2(this, "translatable", () => true);
26164
26208
  _defineProperty2(this, "controlPoints", []);
26165
26209
  }
26166
26210
  merge(other) {
@@ -26169,6 +26213,7 @@ var EditorConfig = class _EditorConfig {
26169
26213
  const j = RESIZE_MODEL_LEVEL.findIndex((v) => v === other.resizeModel());
26170
26214
  next.resizeModel = () => RESIZE_MODEL_LEVEL[Math.max(i, j)];
26171
26215
  next.uniformScale = this.uniformScale || other.uniformScale;
26216
+ next.translatable = () => this.translatable() && other.translatable();
26172
26217
  next.controlPoints = [];
26173
26218
  return next;
26174
26219
  }
@@ -26637,7 +26682,14 @@ var WhiteboardTool = class {
26637
26682
  }
26638
26683
  this.pendingDragEvent = null;
26639
26684
  this.shadowEmitter.setActive(true);
26640
- this.onMouseDown(event);
26685
+ try {
26686
+ this.onMouseDown(event);
26687
+ } catch (error) {
26688
+ this.cancelCurrentAction();
26689
+ this.eventAvailable = false;
26690
+ this.shadowEmitter.setActive(false);
26691
+ throw error;
26692
+ }
26641
26693
  });
26642
26694
  _defineProperty7(this, "flushPendingDrag", () => {
26643
26695
  this.dragRafId = 0;
@@ -26645,7 +26697,14 @@ var WhiteboardTool = class {
26645
26697
  this.lastDragTime = performance.now();
26646
26698
  const event = this.pendingDragEvent;
26647
26699
  this.pendingDragEvent = null;
26648
- this.onMouseDrag(event);
26700
+ try {
26701
+ this.onMouseDrag(event);
26702
+ } catch (error) {
26703
+ this.cancelCurrentAction();
26704
+ this.eventAvailable = false;
26705
+ this.shadowEmitter.setActive(false);
26706
+ throw error;
26707
+ }
26649
26708
  }
26650
26709
  });
26651
26710
  _defineProperty7(this, "onMouseDragSelf", (event) => {
@@ -26656,7 +26715,14 @@ var WhiteboardTool = class {
26656
26715
  if (now - this.lastDragTime >= DRAG_FRAME_MS) {
26657
26716
  this.lastDragTime = now;
26658
26717
  this.pendingDragEvent = null;
26659
- this.onMouseDrag(event);
26718
+ try {
26719
+ this.onMouseDrag(event);
26720
+ } catch (error) {
26721
+ this.cancelCurrentAction();
26722
+ this.eventAvailable = false;
26723
+ this.shadowEmitter.setActive(false);
26724
+ throw error;
26725
+ }
26660
26726
  } else {
26661
26727
  this.pendingDragEvent = event;
26662
26728
  if (!this.dragRafId) {
@@ -26673,11 +26739,26 @@ var WhiteboardTool = class {
26673
26739
  this.dragRafId = 0;
26674
26740
  }
26675
26741
  if (this.pendingDragEvent) {
26676
- this.onMouseDrag(this.pendingDragEvent);
26677
- this.pendingDragEvent = null;
26742
+ try {
26743
+ this.onMouseDrag(this.pendingDragEvent);
26744
+ this.pendingDragEvent = null;
26745
+ } catch (error) {
26746
+ this.pendingDragEvent = null;
26747
+ this.cancelCurrentAction();
26748
+ this.eventAvailable = false;
26749
+ this.shadowEmitter.setActive(false);
26750
+ throw error;
26751
+ }
26752
+ }
26753
+ try {
26754
+ this.onMouseUp(event);
26755
+ } catch (error) {
26756
+ this.cancelCurrentAction();
26757
+ this.eventAvailable = false;
26758
+ throw error;
26759
+ } finally {
26760
+ this.shadowEmitter.setActive(false);
26678
26761
  }
26679
- this.onMouseUp(event);
26680
- this.shadowEmitter.setActive(false);
26681
26762
  });
26682
26763
  this.modelGetter = modelGetter;
26683
26764
  this.enableToolEvent = enableToolEvent;
@@ -26688,6 +26769,8 @@ var WhiteboardTool = class {
26688
26769
  this.tool.onMouseDrag = this.onMouseDragSelf;
26689
26770
  this.tool.onMouseUp = this.onMouseUpSelf;
26690
26771
  }
26772
+ cancelCurrentAction() {
26773
+ }
26691
26774
  };
26692
26775
 
26693
26776
  // src/tool/LineTool.ts
@@ -27068,7 +27151,6 @@ var PointTextModel = class extends ElementModel {
27068
27151
  if (!this.item) {
27069
27152
  return null;
27070
27153
  }
27071
- console.log("[][][] drawPoints", this.drawPoints);
27072
27154
  const bounds = this.item.internalBounds;
27073
27155
  const matrix = new this.scope.Matrix(this.pointsMatrix);
27074
27156
  const topLeft = new this.scope.Point(this.drawPoints[0], this.drawPoints[1]).transform(matrix);
@@ -27439,9 +27521,6 @@ var RectangleModel = class extends ElementModel {
27439
27521
  }
27440
27522
  };
27441
27523
 
27442
- // src/utils/constant.ts
27443
- var elementsUndoOrigin = "elementsUndoOrigin";
27444
-
27445
27524
  // src/model/renderable/EraserModel.ts
27446
27525
  var import_lodash5 = __toESM(require_lodash(), 1);
27447
27526
  import * as Y9 from "yjs";
@@ -27722,7 +27801,6 @@ var LaserPointerModel = class extends ElementModel {
27722
27801
  matrixedPoints() {
27723
27802
  const matrix = new this.scope.Matrix(this.pointsMatrix);
27724
27803
  const points = this.cachedPoints || this.points;
27725
- console.log("[][][] ,", this.points.length, this.cachedPoints?.length, this.localPoints.length);
27726
27804
  const groupPoints = (0, import_lodash6.chunk)(points, 2).slice(this.sliceBegin);
27727
27805
  return groupPoints.map((_ref) => {
27728
27806
  let [x, y] = _ref;
@@ -27992,6 +28070,7 @@ var ImageModel = class extends ElementModel {
27992
28070
  this.item = new this.scope.Raster(this.uuid);
27993
28071
  const matrix = new this.scope.Matrix(this.pointsMatrix);
27994
28072
  this.item.matrix = matrix;
28073
+ this.item.data.role = this.role;
27995
28074
  }
27996
28075
  onVectorUpdate() {
27997
28076
  const matrix = new this.scope.Matrix(this.pointsMatrix);
@@ -28020,8 +28099,12 @@ var ImageModel = class extends ElementModel {
28020
28099
  const cfg = new EditorConfig();
28021
28100
  cfg.resizeModel = () => "four-corner";
28022
28101
  cfg.uniformScale = () => true;
28102
+ cfg.translatable = () => !this.isBackground;
28023
28103
  return cfg;
28024
28104
  }
28105
+ mutationOrigin() {
28106
+ return this.isBackground ? backgroundElementsUndoOrigin : elementsUndoOrigin;
28107
+ }
28025
28108
  liveCursorPoint() {
28026
28109
  return null;
28027
28110
  }
@@ -28104,6 +28187,7 @@ var RenderableModel = class extends EventEmitter {
28104
28187
  flushRenderables() {
28105
28188
  this.emit("elementClear");
28106
28189
  const elements = Array.from(this.elements.values()).map((v) => this.convertToModel(v)).filter((v) => !!v);
28190
+ elements.sort((a2, b2) => this.compareElements(a2, b2));
28107
28191
  this.maxIndex = elements.reduce((r, n) => Math.max(r, n.index), -1);
28108
28192
  const clearIds = [];
28109
28193
  elements.forEach((model) => {
@@ -28165,7 +28249,32 @@ var RenderableModel = class extends EventEmitter {
28165
28249
  initElement(element) {
28166
28250
  element.shadowEmitter = this.shadowEmitter;
28167
28251
  }
28252
+ compareElementRole(roleA, roleB) {
28253
+ const a2 = roleA === "background" ? 0 : 1;
28254
+ const b2 = roleB === "background" ? 0 : 1;
28255
+ return a2 - b2;
28256
+ }
28257
+ compareElements(a2, b2) {
28258
+ const roleCompare = this.compareElementRole(a2.role, b2.role);
28259
+ if (roleCompare !== 0) {
28260
+ return roleCompare;
28261
+ }
28262
+ if (a2.index !== b2.index) {
28263
+ return a2.index - b2.index;
28264
+ }
28265
+ return a2.uuid.localeCompare(b2.uuid);
28266
+ }
28267
+ isBackgroundElement(uuid) {
28268
+ const element = this.elements.get(uuid);
28269
+ if (!element) {
28270
+ return false;
28271
+ }
28272
+ return (element.get("role") ?? "normal") === "background";
28273
+ }
28168
28274
  removeElementItem(uuid) {
28275
+ if (this.isBackgroundElement(uuid)) {
28276
+ return;
28277
+ }
28169
28278
  this.elements.delete(uuid);
28170
28279
  }
28171
28280
  confirmPermission() {
@@ -28176,15 +28285,21 @@ var RenderableModel = class extends EventEmitter {
28176
28285
  return hasPermission;
28177
28286
  }
28178
28287
  createImage(src) {
28288
+ let fit = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "legacy";
28289
+ let role = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : "normal";
28179
28290
  if (!this.confirmPermission()) {
28180
28291
  return;
28181
28292
  }
28293
+ if (role === "background") {
28294
+ return this.upsertBackgroundImage(src, fit);
28295
+ }
28182
28296
  const yMap = new Y12.Map();
28183
28297
  this.elements.doc?.transact(() => {
28184
28298
  const uuid = this.uuid;
28185
28299
  yMap.set(ElementModel.KEYS.index, ++this.maxIndex);
28186
28300
  yMap.set(ElementModel.KEYS.uuid, uuid);
28187
28301
  yMap.set("type", "image");
28302
+ yMap.set("role", role);
28188
28303
  yMap.set(ElementModel.KEYS.ownerId, this.userManager.selfId);
28189
28304
  this.elements.set(uuid, yMap);
28190
28305
  }, elementsUndoOrigin);
@@ -28192,9 +28307,47 @@ var RenderableModel = class extends EventEmitter {
28192
28307
  model.bindObserver();
28193
28308
  model.root.set("src", src);
28194
28309
  model.ownerId = this.userManager.selfId;
28195
- this.fitImageToViewport(src, model);
28310
+ this.fitImageToViewport(src, model, fit);
28196
28311
  }
28197
- fitImageToViewport(src, model) {
28312
+ upsertBackgroundImage(src, fit) {
28313
+ const backgroundEntries = Array.from(this.elements.entries()).filter((_ref) => {
28314
+ let [, elementMap] = _ref;
28315
+ return elementMap.get("type") === "image" && (elementMap.get("role") ?? "normal") === "background";
28316
+ });
28317
+ let targetMap = backgroundEntries[0]?.[1] ?? null;
28318
+ if (this.elements.doc) {
28319
+ this.elements.doc.transact(() => {
28320
+ backgroundEntries.slice(1).forEach((_ref2) => {
28321
+ let [key] = _ref2;
28322
+ return this.elements.delete(key);
28323
+ });
28324
+ if (!targetMap) {
28325
+ targetMap = new Y12.Map();
28326
+ const uuid = this.uuid;
28327
+ targetMap.set(ElementModel.KEYS.index, -1);
28328
+ targetMap.set(ElementModel.KEYS.uuid, uuid);
28329
+ targetMap.set("type", "image");
28330
+ targetMap.set("role", "background");
28331
+ targetMap.set(ElementModel.KEYS.ownerId, this.userManager.selfId);
28332
+ this.elements.set(uuid, targetMap);
28333
+ } else {
28334
+ targetMap.set("role", "background");
28335
+ targetMap.set(ElementModel.KEYS.ownerId, this.userManager.selfId);
28336
+ }
28337
+ }, backgroundElementsUndoOrigin);
28338
+ }
28339
+ if (!targetMap) {
28340
+ return;
28341
+ }
28342
+ const model = this.convertToModel(targetMap);
28343
+ if (!model || !(model instanceof ImageModel)) {
28344
+ return;
28345
+ }
28346
+ model.root.set("src", src);
28347
+ model.ownerId = this.userManager.selfId;
28348
+ this.fitImageToViewport(src, model, fit);
28349
+ }
28350
+ fitImageToViewport(src, model, fit) {
28198
28351
  const center = this.scope.project.view.center;
28199
28352
  const fallbackMatrix = new this.scope.Matrix();
28200
28353
  fallbackMatrix.translate({
@@ -28211,9 +28364,9 @@ var RenderableModel = class extends EventEmitter {
28211
28364
  return;
28212
28365
  }
28213
28366
  const viewportBounds = this.scope.project.view.bounds;
28214
- const maxWidth = viewportBounds.width * 2 / 3;
28215
- const maxHeight = viewportBounds.height * 2 / 3;
28216
- const fitScale = Math.min(maxWidth / naturalWidth, maxHeight / naturalHeight, 1);
28367
+ const maxWidth = fit === "contain" ? viewportBounds.width : viewportBounds.width * 2 / 3;
28368
+ const maxHeight = fit === "contain" ? viewportBounds.height : viewportBounds.height * 2 / 3;
28369
+ const fitScale = fit === "contain" ? Math.min(maxWidth / naturalWidth, maxHeight / naturalHeight) : Math.min(maxWidth / naturalWidth, maxHeight / naturalHeight, 1);
28217
28370
  const nextMatrix = new this.scope.Matrix();
28218
28371
  nextMatrix.translate({
28219
28372
  x: center.x,
@@ -28236,7 +28389,7 @@ var RenderableModel = class extends EventEmitter {
28236
28389
  if (model.root.doc) {
28237
28390
  model.root.doc.transact(() => {
28238
28391
  model.root.set(ElementModel.KEYS.pointsMatrix, values);
28239
- }, elementsUndoOrigin);
28392
+ }, model.isBackground ? backgroundElementsUndoOrigin : elementsUndoOrigin);
28240
28393
  } else {
28241
28394
  model.root.set(ElementModel.KEYS.pointsMatrix, values);
28242
28395
  }
@@ -28834,22 +28987,33 @@ var CurveTool = class extends WhiteboardTool {
28834
28987
  _defineProperty19(this, "flushPendingPoints", () => {
28835
28988
  this.flushRafId = 0;
28836
28989
  if (this.elementModel && this.pendingPoints.length > 0) {
28837
- this.elementModel.appendPoints(this.pendingPoints);
28990
+ const appended = this.elementModel.appendPoints(this.pendingPoints);
28991
+ if (!appended) {
28992
+ this.resetStrokeState();
28993
+ return;
28994
+ }
28838
28995
  this.pendingPoints = [];
28839
28996
  }
28840
28997
  });
28841
28998
  }
28842
- onMouseDown(_event) {
28999
+ resetStrokeState() {
28843
29000
  this.pointCount = 0;
28844
29001
  this.pendingPoints = [];
28845
29002
  if (this.flushRafId) {
28846
29003
  cancelAnimationFrame(this.flushRafId);
28847
29004
  this.flushRafId = 0;
28848
29005
  }
28849
- if (this.elementModel) {
28850
- this.elementModel.dispose();
28851
- }
28852
29006
  this.elementModel = null;
29007
+ }
29008
+ cancelCurrentAction() {
29009
+ this.resetStrokeState();
29010
+ }
29011
+ onMouseDown(_event) {
29012
+ const previousElement = this.elementModel;
29013
+ this.resetStrokeState();
29014
+ if (previousElement) {
29015
+ previousElement.dispose();
29016
+ }
28853
29017
  this.modelGetter().then((model) => {
28854
29018
  if (model) {
28855
29019
  this.elementModel = model.createCurve(true);
@@ -28862,6 +29026,10 @@ var CurveTool = class extends WhiteboardTool {
28862
29026
  }
28863
29027
  const MIN_DISTANCE = 2;
28864
29028
  if (this.elementModel) {
29029
+ if (!this.elementModel.isAttached()) {
29030
+ this.resetStrokeState();
29031
+ return;
29032
+ }
28865
29033
  let lastX = 0;
28866
29034
  let lastY = 0;
28867
29035
  if (this.pendingPoints.length >= 2) {
@@ -28890,22 +29058,27 @@ var CurveTool = class extends WhiteboardTool {
28890
29058
  this.flushRafId = 0;
28891
29059
  }
28892
29060
  this.flushPendingPoints();
29061
+ if (!this.elementModel || !this.elementModel.isAttached()) {
29062
+ this.resetStrokeState();
29063
+ return;
29064
+ }
29065
+ const currentElement = this.elementModel;
29066
+ const currentUuid = currentElement.uuid;
29067
+ const currentPointCount = this.pointCount;
28893
29068
  this.modelGetter().then((model) => {
28894
29069
  if (!model) {
28895
29070
  return;
28896
29071
  }
28897
- if (this.pointCount < 3 && this.elementModel) {
28898
- if (this.elementModel) {
28899
- model.removeElementItem(this.elementModel.uuid);
28900
- }
29072
+ if (currentPointCount < 3) {
29073
+ model.removeElementItem(currentUuid);
28901
29074
  }
28902
- if (this.elementModel) {
28903
- this.elementModel.shadow = "";
29075
+ if (currentElement.isAttached()) {
29076
+ currentElement.shadow = "";
28904
29077
  }
28905
- if (this.elementModel && event.event.metaKey) {
28906
- const result = this.recognizer.recognize(this.elementModel.points);
29078
+ if (currentElement.isAttached() && event.event.metaKey) {
29079
+ const result = this.recognizer.recognize(currentElement.points);
28907
29080
  if (result) {
28908
- model.removeElementItem(this.elementModel.uuid);
29081
+ model.removeElementItem(currentUuid);
28909
29082
  if (/^rectangle/.test(result.shape)) {
28910
29083
  const rect = model.createRectangle(false);
28911
29084
  rect?.setPoints([result.minX, result.minY, result.maxX, result.maxY]);
@@ -28924,6 +29097,9 @@ var CurveTool = class extends WhiteboardTool {
28924
29097
  }
28925
29098
  }
28926
29099
  }
29100
+ if (this.elementModel === currentElement) {
29101
+ this.resetStrokeState();
29102
+ }
28927
29103
  });
28928
29104
  }
28929
29105
  };
@@ -29121,6 +29297,9 @@ var SelectorTool = class extends WhiteboardTool {
29121
29297
  _defineProperty22(this, "showLiveCursor", false);
29122
29298
  this.selectElementsModel = selectElementsModel;
29123
29299
  }
29300
+ isSelectableItem(item) {
29301
+ return !!item.data.type && ["selector", "eraser", "laser"].indexOf(item.data.type) < 0 && item.data.role !== "background";
29302
+ }
29124
29303
  onMouseDown(event) {
29125
29304
  this.from = null;
29126
29305
  this.to = null;
@@ -29146,7 +29325,7 @@ var SelectorTool = class extends WhiteboardTool {
29146
29325
  this.elementModel.setPoints([rect.topLeft.x, rect.topLeft.y, rect.size.width, rect.size.height]);
29147
29326
  this.selectElementsModel.clearSelectElementForSelf();
29148
29327
  this.scope.project.activeLayer.children.forEach((item) => {
29149
- if (item.data.type && ["selector", "eraser", "laser"].indexOf(item.data.type) < 0) {
29328
+ if (this.isSelectableItem(item)) {
29150
29329
  if (rect.contains(item.bounds)) {
29151
29330
  this.selectElements.set(item.data.uuid, item.data.ownerId);
29152
29331
  } else {
@@ -29172,7 +29351,7 @@ var SelectorTool = class extends WhiteboardTool {
29172
29351
  return result;
29173
29352
  }, []);
29174
29353
  const hitResult = this.scope.project.hitTest(event.point);
29175
- if (hitResult && hitResult.item.data.uuid) {
29354
+ if (hitResult && hitResult.item.data.uuid && this.isSelectableItem(hitResult.item)) {
29176
29355
  elements.push({
29177
29356
  elementId: hitResult.item.data.uuid,
29178
29357
  ownerId: hitResult.item.data.ownerId
@@ -29716,7 +29895,7 @@ var Editor = class extends EventEmitter5 {
29716
29895
  };
29717
29896
  this.rootView.style.opacity = "0";
29718
29897
  }
29719
- if (target === this.frame) {
29898
+ if (target === this.frame && this.editorConfig?.translatable()) {
29720
29899
  evt.preventDefault();
29721
29900
  this.editMode = "translate";
29722
29901
  this.lastEditPoint = {
@@ -30021,7 +30200,6 @@ var Editor = class extends EventEmitter5 {
30021
30200
  this.shadowContainer.remove();
30022
30201
  this.shadowScope.project.activeLayer.addChild(this.shadowContainer);
30023
30202
  this.targets.forEach((model) => {
30024
- console.log("[][][] translateShadow model", model.root._dEH);
30025
30203
  model.shadow = this.shadowContainer.data.uuid;
30026
30204
  });
30027
30205
  }
@@ -30902,6 +31080,9 @@ var EraserTool = class extends WhiteboardTool {
30902
31080
  this.elementModel.appendPoints([event.point.x, event.point.y]);
30903
31081
  }
30904
31082
  this.scope.project.activeLayer.children.forEach((item) => {
31083
+ if (item.data.role === "background") {
31084
+ return;
31085
+ }
30905
31086
  if (item.data.type && ["selector", "eraser", "laser"].indexOf(item.data.type) < 0 && item.hitTest(event.point, {
30906
31087
  segments: true,
30907
31088
  stroke: true,
@@ -30912,6 +31093,9 @@ var EraserTool = class extends WhiteboardTool {
30912
31093
  }
30913
31094
  });
30914
31095
  this.shadowScope.project.activeLayer.children.forEach((item) => {
31096
+ if (item.data.role === "background") {
31097
+ return;
31098
+ }
30915
31099
  if (item.data.type && ["selector", "eraser", "laser"].indexOf(item.data.type) < 0 && item.hitTest(event.point, {
30916
31100
  segments: true,
30917
31101
  stroke: true,
@@ -31371,16 +31555,7 @@ var IndexedNavigation = class extends EventEmitter11 {
31371
31555
  return this.pageModel.pageList().filter((id) => /^_i_/.test(id));
31372
31556
  }
31373
31557
  get head() {
31374
- const headId = Object.keys(this.list).find((key) => {
31375
- return this.list[key] && this.list[key].prev === "";
31376
- });
31377
- if (!headId) {
31378
- log3("indexed navigation confusion", {
31379
- list: JSON.stringify(this.list)
31380
- }, "error");
31381
- throw new Error("indexed navigation confusion");
31382
- }
31383
- return headId;
31558
+ return this.ensureValidList().head;
31384
31559
  }
31385
31560
  get lastNodeId() {
31386
31561
  let currentId = this.head;
@@ -31400,7 +31575,8 @@ var IndexedNavigation = class extends EventEmitter11 {
31400
31575
  _defineProperty36(this, "list", {});
31401
31576
  _defineProperty36(this, "hasPermission", void 0);
31402
31577
  _defineProperty36(this, "handleIndexedPageMapUpdate", (_evt) => {
31403
- this.list = this.indexedPageMap.get("list");
31578
+ this.list = this.normalizeList(this.indexedPageMap.get("list")) ?? {};
31579
+ this.ensureValidList("update");
31404
31580
  const needRemoveList = this.pageModel.pageList().filter((v) => /^_i_/.test(v) && Object.keys(this.list).indexOf(v) < 0);
31405
31581
  const needAddList = Object.keys(this.list).filter((v) => this.pageModel.pageList().indexOf(v) < 0);
31406
31582
  this.indexedPageMap.doc.transact(() => {
@@ -31433,11 +31609,156 @@ var IndexedNavigation = class extends EventEmitter11 {
31433
31609
  this.indexedPageMap.set("list", this.list);
31434
31610
  this.idPool.add("_i_");
31435
31611
  } else {
31436
- this.list = this.indexedPageMap.get("list");
31612
+ this.list = this.normalizeList(this.indexedPageMap.get("list")) ?? {};
31437
31613
  Object.keys(this.list).forEach((id) => this.idPool.add(id));
31614
+ this.ensureValidList("constructor");
31438
31615
  }
31439
31616
  this.indexedPageMap.observe(this.handleIndexedPageMapUpdate);
31440
31617
  }
31618
+ normalizeList(list) {
31619
+ if (!list || typeof list !== "object" || Array.isArray(list)) {
31620
+ return null;
31621
+ }
31622
+ const nextList = {};
31623
+ for (const [id, node] of Object.entries(list)) {
31624
+ if (!/^_i_/.test(id) || !node || typeof node !== "object") {
31625
+ return null;
31626
+ }
31627
+ const {
31628
+ prev,
31629
+ next
31630
+ } = node;
31631
+ if (typeof prev !== "string" || typeof next !== "string") {
31632
+ return null;
31633
+ }
31634
+ nextList[id] = {
31635
+ prev,
31636
+ next
31637
+ };
31638
+ }
31639
+ return nextList;
31640
+ }
31641
+ validateList(list) {
31642
+ const ids = Object.keys(list);
31643
+ if (ids.length === 0) {
31644
+ return {
31645
+ valid: false,
31646
+ reason: "empty list"
31647
+ };
31648
+ }
31649
+ const heads = ids.filter((id) => list[id].prev === "");
31650
+ if (heads.length !== 1) {
31651
+ return {
31652
+ valid: false,
31653
+ reason: `expected one head, got ${heads.length}`
31654
+ };
31655
+ }
31656
+ for (const id of ids) {
31657
+ const node = list[id];
31658
+ if (node.prev && !list[node.prev]) {
31659
+ return {
31660
+ valid: false,
31661
+ reason: `missing prev node ${node.prev}`
31662
+ };
31663
+ }
31664
+ if (node.next && !list[node.next]) {
31665
+ return {
31666
+ valid: false,
31667
+ reason: `missing next node ${node.next}`
31668
+ };
31669
+ }
31670
+ if (node.prev && list[node.prev].next !== id) {
31671
+ return {
31672
+ valid: false,
31673
+ reason: `prev link mismatch for ${id}`
31674
+ };
31675
+ }
31676
+ if (node.next && list[node.next].prev !== id) {
31677
+ return {
31678
+ valid: false,
31679
+ reason: `next link mismatch for ${id}`
31680
+ };
31681
+ }
31682
+ }
31683
+ const visited = /* @__PURE__ */ new Set();
31684
+ let currentId = heads[0];
31685
+ while (currentId) {
31686
+ if (visited.has(currentId)) {
31687
+ return {
31688
+ valid: false,
31689
+ reason: `cycle at ${currentId}`
31690
+ };
31691
+ }
31692
+ visited.add(currentId);
31693
+ currentId = list[currentId].next;
31694
+ }
31695
+ if (visited.size !== ids.length) {
31696
+ return {
31697
+ valid: false,
31698
+ reason: "detached nodes"
31699
+ };
31700
+ }
31701
+ return {
31702
+ valid: true,
31703
+ head: heads[0]
31704
+ };
31705
+ }
31706
+ createList(ids) {
31707
+ return ids.reduce((list, id, index) => {
31708
+ list[id] = {
31709
+ prev: ids[index - 1] ?? "",
31710
+ next: ids[index + 1] ?? ""
31711
+ };
31712
+ return list;
31713
+ }, {});
31714
+ }
31715
+ repairList(reason) {
31716
+ const pageIds = this.idList;
31717
+ const ids = pageIds.length > 0 ? pageIds : Object.keys(this.list).filter((id) => /^_i_/.test(id));
31718
+ const nextIds = ids.length > 0 ? ids : ["_i_"];
31719
+ const nextList = this.createList(nextIds);
31720
+ log3("indexed navigation recovered", {
31721
+ reason,
31722
+ pageList: JSON.stringify(this.pageModel.pageList()),
31723
+ list: JSON.stringify(this.list),
31724
+ nextList: JSON.stringify(nextList)
31725
+ }, "warn");
31726
+ const apply = () => {
31727
+ for (const id of nextIds) {
31728
+ if (this.pageModel.pageList().indexOf(id) < 0) {
31729
+ this.pageModel.addPage(id);
31730
+ }
31731
+ }
31732
+ this.list = nextList;
31733
+ this.indexedPageMap.set("list", nextList);
31734
+ };
31735
+ if (this.indexedPageMap.doc) {
31736
+ this.indexedPageMap.doc.transact(apply);
31737
+ } else {
31738
+ apply();
31739
+ }
31740
+ nextIds.forEach((id) => this.idPool.add(id));
31741
+ return nextIds[0];
31742
+ }
31743
+ ensureValidList() {
31744
+ let context = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : "read";
31745
+ const normalized = this.normalizeList(this.list);
31746
+ if (!normalized) {
31747
+ return {
31748
+ head: this.repairList(`${context}: invalid list`)
31749
+ };
31750
+ }
31751
+ this.list = normalized;
31752
+ const validation = this.validateList(this.list);
31753
+ if (!validation.valid) {
31754
+ return {
31755
+ head: this.repairList(`${context}: ${validation.reason}`)
31756
+ };
31757
+ }
31758
+ return {
31759
+ head: validation.head
31760
+ };
31761
+ }
31441
31762
  getNextId() {
31442
31763
  const cache = Array.from(this.idPool).filter((id) => this.idList.indexOf(id) < 0);
31443
31764
  if (cache.length > 0) {
@@ -32387,7 +32708,6 @@ var WhiteboardApplication = class _WhiteboardApplication extends AbstractApplica
32387
32708
  this.emitter.emit("elementDeselected", userId);
32388
32709
  return;
32389
32710
  }
32390
- editor.show();
32391
32711
  const targetLayerId = this.userMap(userId).get(WhiteboardKeys.currentPage);
32392
32712
  const selfLayerId = this.userMap(this.userId).get(WhiteboardKeys.currentPage);
32393
32713
  if (targetLayerId !== selfLayerId || !this.layers.has(targetLayerId)) {
@@ -32395,7 +32715,13 @@ var WhiteboardApplication = class _WhiteboardApplication extends AbstractApplica
32395
32715
  }
32396
32716
  const elementModels = elements.map((id) => {
32397
32717
  return this.layers.get(targetLayerId).elementModels.get(id);
32398
- }).filter((v) => !!v);
32718
+ }).filter((model) => !!model && !model.isBackground);
32719
+ if (elementModels.length === 0) {
32720
+ editor.hidden();
32721
+ this.emitter.emit("elementDeselected", userId);
32722
+ return;
32723
+ }
32724
+ editor.show();
32399
32725
  editor.setTargets(elementModels);
32400
32726
  if (elementModels.length === 1) {
32401
32727
  const model = elementModels[0];
@@ -32587,14 +32913,17 @@ var WhiteboardApplication = class _WhiteboardApplication extends AbstractApplica
32587
32913
  this.camera.resetViewMatrixToMain();
32588
32914
  }
32589
32915
  };
32590
- this.emitter.insertImage = (src, pageId) => {
32916
+ this.emitter.insertImage = (src, options) => {
32591
32917
  if (!/https/.test(src)) {
32592
32918
  log4("[@netless/forge-whiteboard] invalid image url, src needs to be in the HTTPS protocol.", {
32593
32919
  src
32594
32920
  }, "warn");
32595
32921
  return;
32596
32922
  }
32597
- let targetPageId = pageId;
32923
+ const normalizedOptions = typeof options === "string" ? {
32924
+ pageId: options
32925
+ } : options ?? {};
32926
+ let targetPageId = normalizedOptions.pageId;
32598
32927
  if (!targetPageId) {
32599
32928
  targetPageId = this.pageModel.getCurrentPage(this.userManager.selfId);
32600
32929
  }
@@ -32602,13 +32931,16 @@ var WhiteboardApplication = class _WhiteboardApplication extends AbstractApplica
32602
32931
  log4("[@netless/forge-whiteboard] page not found", {}, "warn");
32603
32932
  return;
32604
32933
  }
32605
- this.layers.get(targetPageId)?.createImage(src);
32934
+ this.layers.get(targetPageId)?.createImage(src, normalizedOptions.fit ?? "legacy", normalizedOptions.role ?? "normal");
32606
32935
  };
32607
32936
  this.emitter.removeElement = (pageId, elementId) => {
32608
32937
  if (!this.layers.has(pageId)) {
32609
32938
  log4("[@netless/forge-whiteboard] page not found", {}, "warn");
32610
32939
  return;
32611
32940
  }
32941
+ if (this.layers.get(pageId)?.isBackgroundElement(elementId)) {
32942
+ return;
32943
+ }
32612
32944
  this.layers.get(pageId)?.removeElementItem(elementId);
32613
32945
  };
32614
32946
  this.emitter.getViewModel = (userId) => {
@@ -32659,7 +32991,11 @@ var WhiteboardApplication = class _WhiteboardApplication extends AbstractApplica
32659
32991
  if (model) {
32660
32992
  if (model.elements.doc) {
32661
32993
  model.elements.doc.transact(() => {
32662
- model.elements.clear();
32994
+ for (const key of Array.from(model.elements.keys())) {
32995
+ if (!model.isBackgroundElement(key)) {
32996
+ model.elements.delete(key);
32997
+ }
32998
+ }
32663
32999
  }, elementsUndoOrigin);
32664
33000
  } else {
32665
33001
  model.elementModels.clear();
@@ -33071,7 +33407,9 @@ var WhiteboardApplication = class _WhiteboardApplication extends AbstractApplica
33071
33407
  const at = this.findElementIndex(element.data.index, parent);
33072
33408
  for (let i = at; i >= 0; i--) {
33073
33409
  const child = children[i - 1];
33074
- if (!child || child.data.index < element.data.index || child.data.uuid < element.data.uuid) {
33410
+ const childRole = child?.data.role === "background" ? 0 : 1;
33411
+ const elementRole = element.data.role === "background" ? 0 : 1;
33412
+ 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) {
33075
33413
  parent.insertChild(i, element);
33076
33414
  break;
33077
33415
  }