@inweb/client 25.2.2 → 25.2.5

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.
Files changed (222) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +70 -70
  3. package/dist/client.js +12739 -12352
  4. package/dist/client.js.map +1 -1
  5. package/dist/client.min.js +1 -1
  6. package/dist/client.module.js +588 -157
  7. package/dist/client.module.js.map +1 -1
  8. package/lib/Api/Assembly.d.ts +338 -338
  9. package/lib/Api/ClashTest.d.ts +175 -175
  10. package/lib/Api/Client.d.ts +451 -451
  11. package/lib/Api/ClientEvents.d.ts +28 -28
  12. package/lib/Api/File.d.ts +567 -567
  13. package/lib/Api/HttpClient.d.ts +12 -12
  14. package/lib/Api/IAssembly.d.ts +13 -13
  15. package/lib/Api/IFile.d.ts +45 -45
  16. package/lib/Api/IHttpClient.d.ts +10 -10
  17. package/lib/Api/IUser.d.ts +10 -10
  18. package/lib/Api/Job.d.ts +141 -141
  19. package/lib/Api/Member.d.ts +84 -84
  20. package/lib/Api/Model.d.ts +146 -146
  21. package/lib/Api/Permission.d.ts +92 -92
  22. package/lib/Api/Project.d.ts +246 -246
  23. package/lib/Api/Role.d.ts +81 -81
  24. package/lib/Api/User.d.ts +196 -196
  25. package/lib/Api/impl/FetchError.d.ts +17 -17
  26. package/lib/Api/impl/Utils.d.ts +32 -32
  27. package/lib/Api/impl/http.d.ts +66 -66
  28. package/lib/ConvetMath.d.ts +28 -28
  29. package/lib/Viewer/CanvasEvents.d.ts +1 -1
  30. package/lib/Viewer/Commands/ApplyModelTransform.d.ts +1 -1
  31. package/lib/Viewer/Commands/ClearMarkup.d.ts +1 -1
  32. package/lib/Viewer/Commands/ClearSlices.d.ts +1 -1
  33. package/lib/Viewer/Commands/CreatePreview.d.ts +1 -1
  34. package/lib/Viewer/Commands/Explode.d.ts +1 -1
  35. package/lib/Viewer/Commands/GetDefaultViewPositions.d.ts +1 -1
  36. package/lib/Viewer/Commands/GetModels.d.ts +1 -1
  37. package/lib/Viewer/Commands/GetSelected.d.ts +1 -1
  38. package/lib/Viewer/Commands/HideSelected.d.ts +1 -1
  39. package/lib/Viewer/Commands/IsolateSelected.d.ts +1 -1
  40. package/lib/Viewer/Commands/RegenerateAll.d.ts +1 -1
  41. package/lib/Viewer/Commands/ResetView.d.ts +1 -1
  42. package/lib/Viewer/Commands/SelectModel.d.ts +1 -1
  43. package/lib/Viewer/Commands/SetActiveDragger.d.ts +1 -1
  44. package/lib/Viewer/Commands/SetDefaultViewPosition.d.ts +1 -1
  45. package/lib/Viewer/Commands/SetMarkupColor.d.ts +1 -1
  46. package/lib/Viewer/Commands/SetSelected.d.ts +1 -1
  47. package/lib/Viewer/Commands/ShowAll.d.ts +1 -1
  48. package/lib/Viewer/Commands/Unselect.d.ts +1 -1
  49. package/lib/Viewer/Commands/ZoomToExtents.d.ts +1 -1
  50. package/lib/Viewer/Commands/ZoomToObjects.d.ts +1 -1
  51. package/lib/Viewer/Commands/ZoomToSelected.d.ts +1 -1
  52. package/lib/Viewer/Commands.d.ts +2 -2
  53. package/lib/Viewer/Draggers/Actions/OrbitAction.d.ts +21 -21
  54. package/lib/Viewer/Draggers/Actions/PanAction.d.ts +17 -17
  55. package/lib/Viewer/Draggers/Actions/ZoomAction.d.ts +7 -7
  56. package/lib/Viewer/Draggers/Common/Geometry.d.ts +114 -114
  57. package/lib/Viewer/Draggers/Common/GestureManager.d.ts +40 -40
  58. package/lib/Viewer/Draggers/Common/OdBaseDragger.d.ts +53 -53
  59. package/lib/Viewer/Draggers/Common/OdaGeAction.d.ts +29 -29
  60. package/lib/Viewer/Draggers/MeasureLineDragger/MeasureLineItem.d.ts +30 -30
  61. package/lib/Viewer/Draggers/MeasureLineDragger/MeasureUtils.d.ts +19 -19
  62. package/lib/Viewer/Draggers/MeasureLineDragger/index.d.ts +23 -23
  63. package/lib/Viewer/Draggers/OdBaseCuttingPlaneDragger.d.ts +26 -26
  64. package/lib/Viewer/Draggers/OdCuttingPlaneXAxisDragger.d.ts +7 -7
  65. package/lib/Viewer/Draggers/OdCuttingPlaneYAxisDragger.d.ts +7 -7
  66. package/lib/Viewer/Draggers/OdCuttingPlaneZAxisDragger.d.ts +7 -7
  67. package/lib/Viewer/Draggers/OdOrbitDragger.d.ts +14 -14
  68. package/lib/Viewer/Draggers/OdPanDragger.d.ts +11 -11
  69. package/lib/Viewer/Draggers/OdZoomDragger.d.ts +11 -11
  70. package/lib/Viewer/Draggers/OdZoomWheelDragger.d.ts +10 -10
  71. package/lib/Viewer/Draggers/OdZoomWindowDragger/OdSelectionFrame.d.ts +18 -18
  72. package/lib/Viewer/Draggers/OdZoomWindowDragger/index.d.ts +13 -13
  73. package/lib/Viewer/Draggers/OdaLineDragger.d.ts +14 -14
  74. package/lib/Viewer/Draggers/OdaTextDragger.d.ts +15 -15
  75. package/lib/Viewer/Draggers/OdaWalkDragger.d.ts +29 -29
  76. package/lib/Viewer/Draggers/OrbitAroundBuildingDragger.d.ts +18 -18
  77. package/lib/Viewer/EventEmitter2.d.ts +53 -53
  78. package/lib/Viewer/ICommands.d.ts +24 -24
  79. package/lib/Viewer/IEventEmitter2.d.ts +12 -12
  80. package/lib/Viewer/IViewer.d.ts +26 -26
  81. package/lib/Viewer/Loaders/BaseLoader.d.ts +10 -10
  82. package/lib/Viewer/Loaders/LoaderFactory.d.ts +9 -9
  83. package/lib/Viewer/Loaders/TCSLoader.d.ts +4 -4
  84. package/lib/Viewer/Loaders/UpdaterController.d.ts +14 -14
  85. package/lib/Viewer/Loaders/VsfXLoader.d.ts +4 -4
  86. package/lib/Viewer/Loaders/VsfXPartialLoader.d.ts +4 -4
  87. package/lib/Viewer/Markup/Api/IMarkupArrow.d.ts +21 -11
  88. package/lib/Viewer/Markup/Api/IMarkupCloud.d.ts +15 -0
  89. package/lib/Viewer/Markup/Api/IMarkupColorable.d.ts +4 -4
  90. package/lib/Viewer/Markup/Api/IMarkupEllipse.d.ts +14 -14
  91. package/lib/Viewer/Markup/Api/IMarkupImage.d.ts +14 -14
  92. package/lib/Viewer/Markup/Api/IMarkupLine.d.ts +18 -10
  93. package/lib/Viewer/Markup/Api/IMarkupObject.d.ts +11 -11
  94. package/lib/Viewer/Markup/Api/IMarkupRectangle.d.ts +14 -14
  95. package/lib/Viewer/Markup/Api/IMarkupText.d.ts +12 -12
  96. package/lib/Viewer/Markup/Api/Impl/Konva/KonvaArrow.d.ts +46 -31
  97. package/lib/Viewer/Markup/Api/Impl/Konva/KonvaCloud.d.ts +36 -0
  98. package/lib/Viewer/Markup/Api/Impl/Konva/KonvaEllipse.d.ts +40 -39
  99. package/lib/Viewer/Markup/Api/Impl/Konva/KonvaImage.d.ts +36 -35
  100. package/lib/Viewer/Markup/Api/Impl/Konva/KonvaLine.d.ts +35 -31
  101. package/lib/Viewer/Markup/Api/Impl/Konva/KonvaRectangle.d.ts +38 -37
  102. package/lib/Viewer/Markup/Api/Impl/Konva/KonvaText.d.ts +37 -36
  103. package/lib/Viewer/Markup/IMarkup.d.ts +38 -37
  104. package/lib/Viewer/Markup/Impl/Konva/KonvaMarkup.d.ts +71 -62
  105. package/lib/Viewer/Markup/Impl/Konva/MarkupColor.d.ts +18 -18
  106. package/lib/Viewer/Markup/Impl/Visualize/VisualizeMarkup.d.ts +32 -32
  107. package/lib/Viewer/Markup/MarkupFactory.d.ts +6 -6
  108. package/lib/Viewer/Options.d.ts +227 -227
  109. package/lib/Viewer/OptionsEvents.d.ts +25 -25
  110. package/lib/Viewer/Viewer.d.ts +352 -352
  111. package/lib/Viewer/ViewerCommands.d.ts +22 -22
  112. package/lib/Viewer/ViewerEvents.d.ts +600 -600
  113. package/lib/Viewer/utils.d.ts +3 -3
  114. package/lib/index.d.ts +23 -23
  115. package/package.json +38 -32
  116. package/src/Api/Assembly.ts +678 -678
  117. package/src/Api/ClashTest.ts +290 -290
  118. package/src/Api/Client.ts +816 -816
  119. package/src/Api/ClientEvents.ts +31 -31
  120. package/src/Api/File.ts +962 -962
  121. package/src/Api/HttpClient.ts +73 -73
  122. package/src/Api/IAssembly.ts +37 -37
  123. package/src/Api/IFile.ts +74 -74
  124. package/src/Api/IHttpClient.ts +50 -50
  125. package/src/Api/IUser.ts +33 -33
  126. package/src/Api/Job.ts +253 -253
  127. package/src/Api/Member.ts +161 -161
  128. package/src/Api/Model.ts +259 -259
  129. package/src/Api/Permission.ts +173 -173
  130. package/src/Api/Project.ts +479 -479
  131. package/src/Api/Role.ts +158 -158
  132. package/src/Api/User.ts +357 -357
  133. package/src/Api/impl/FetchError.ts +48 -48
  134. package/src/Api/impl/Utils.ts +367 -367
  135. package/src/Api/impl/http.ts +92 -92
  136. package/src/ConvetMath.ts +372 -372
  137. package/src/Viewer/CanvasEvents.ts +41 -41
  138. package/src/Viewer/Commands/ApplyModelTransform.ts +70 -70
  139. package/src/Viewer/Commands/ClearMarkup.ts +28 -28
  140. package/src/Viewer/Commands/ClearSlices.ts +27 -27
  141. package/src/Viewer/Commands/CreatePreview.ts +33 -33
  142. package/src/Viewer/Commands/Explode.ts +38 -38
  143. package/src/Viewer/Commands/GetDefaultViewPositions.ts +36 -36
  144. package/src/Viewer/Commands/GetModels.ts +43 -43
  145. package/src/Viewer/Commands/GetSelected.ts +58 -58
  146. package/src/Viewer/Commands/HideSelected.ts +37 -37
  147. package/src/Viewer/Commands/IsolateSelected.ts +37 -37
  148. package/src/Viewer/Commands/RegenerateAll.ts +37 -37
  149. package/src/Viewer/Commands/ResetView.ts +43 -43
  150. package/src/Viewer/Commands/SelectModel.ts +52 -52
  151. package/src/Viewer/Commands/SetActiveDragger.ts +29 -29
  152. package/src/Viewer/Commands/SetDefaultViewPosition.ts +50 -50
  153. package/src/Viewer/Commands/SetMarkupColor.ts +29 -29
  154. package/src/Viewer/Commands/SetSelected.ts +47 -47
  155. package/src/Viewer/Commands/ShowAll.ts +37 -37
  156. package/src/Viewer/Commands/Unselect.ts +37 -37
  157. package/src/Viewer/Commands/ZoomToExtents.ts +43 -43
  158. package/src/Viewer/Commands/ZoomToObjects.ts +47 -47
  159. package/src/Viewer/Commands/ZoomToSelected.ts +39 -39
  160. package/src/Viewer/Commands.ts +81 -81
  161. package/src/Viewer/Draggers/Actions/OrbitAction.ts +250 -250
  162. package/src/Viewer/Draggers/Actions/PanAction.ts +102 -102
  163. package/src/Viewer/Draggers/Actions/ZoomAction.ts +45 -45
  164. package/src/Viewer/Draggers/Common/Geometry.ts +152 -152
  165. package/src/Viewer/Draggers/Common/GestureManager.ts +263 -263
  166. package/src/Viewer/Draggers/Common/OdBaseDragger.ts +270 -270
  167. package/src/Viewer/Draggers/Common/OdaGeAction.ts +146 -146
  168. package/src/Viewer/Draggers/MeasureLineDragger/MeasureLineItem.ts +248 -248
  169. package/src/Viewer/Draggers/MeasureLineDragger/MeasureUtils.ts +182 -182
  170. package/src/Viewer/Draggers/MeasureLineDragger/index.ts +166 -166
  171. package/src/Viewer/Draggers/OdBaseCuttingPlaneDragger.ts +182 -182
  172. package/src/Viewer/Draggers/OdCuttingPlaneXAxisDragger.ts +53 -53
  173. package/src/Viewer/Draggers/OdCuttingPlaneYAxisDragger.ts +53 -53
  174. package/src/Viewer/Draggers/OdCuttingPlaneZAxisDragger.ts +53 -53
  175. package/src/Viewer/Draggers/OdOrbitDragger.ts +70 -70
  176. package/src/Viewer/Draggers/OdPanDragger.ts +62 -62
  177. package/src/Viewer/Draggers/OdZoomDragger.ts +59 -59
  178. package/src/Viewer/Draggers/OdZoomWheelDragger.ts +103 -103
  179. package/src/Viewer/Draggers/OdZoomWindowDragger/OdSelectionFrame.ts +123 -123
  180. package/src/Viewer/Draggers/OdZoomWindowDragger/index.ts +75 -75
  181. package/src/Viewer/Draggers/OdaLineDragger.ts +80 -80
  182. package/src/Viewer/Draggers/OdaTextDragger.ts +118 -118
  183. package/src/Viewer/Draggers/OdaWalkDragger.ts +278 -278
  184. package/src/Viewer/Draggers/OrbitAroundBuildingDragger.ts +184 -184
  185. package/src/Viewer/EventEmitter2.ts +116 -116
  186. package/src/Viewer/ICommands.ts +53 -53
  187. package/src/Viewer/IEventEmitter2.ts +36 -36
  188. package/src/Viewer/IViewer.ts +55 -55
  189. package/src/Viewer/Loaders/BaseLoader.ts +40 -40
  190. package/src/Viewer/Loaders/LoaderFactory.ts +44 -44
  191. package/src/Viewer/Loaders/TCSLoader.ts +82 -82
  192. package/src/Viewer/Loaders/UpdaterController.ts +36 -36
  193. package/src/Viewer/Loaders/VsfXLoader.ts +87 -87
  194. package/src/Viewer/Loaders/VsfXPartialLoader.ts +208 -208
  195. package/src/Viewer/Markup/Api/IMarkupArrow.ts +12 -15
  196. package/src/Viewer/Markup/Api/IMarkupCloud.ts +15 -0
  197. package/src/Viewer/Markup/Api/IMarkupColorable.ts +4 -4
  198. package/src/Viewer/Markup/Api/IMarkupEllipse.ts +15 -15
  199. package/src/Viewer/Markup/Api/IMarkupImage.ts +15 -15
  200. package/src/Viewer/Markup/Api/IMarkupLine.ts +20 -10
  201. package/src/Viewer/Markup/Api/IMarkupObject.ts +15 -15
  202. package/src/Viewer/Markup/Api/IMarkupRectangle.ts +15 -15
  203. package/src/Viewer/Markup/Api/IMarkupText.ts +12 -12
  204. package/src/Viewer/Markup/Api/Impl/Konva/KonvaArrow.ts +117 -119
  205. package/src/Viewer/Markup/Api/Impl/Konva/KonvaCloud.ts +208 -0
  206. package/src/Viewer/Markup/Api/Impl/Konva/KonvaEllipse.ts +118 -113
  207. package/src/Viewer/Markup/Api/Impl/Konva/KonvaImage.ts +123 -121
  208. package/src/Viewer/Markup/Api/Impl/Konva/KonvaLine.ts +130 -99
  209. package/src/Viewer/Markup/Api/Impl/Konva/KonvaRectangle.ts +119 -113
  210. package/src/Viewer/Markup/Api/Impl/Konva/KonvaText.ts +110 -104
  211. package/src/Viewer/Markup/IMarkup.ts +40 -39
  212. package/src/Viewer/Markup/Impl/Konva/KonvaMarkup.ts +1208 -872
  213. package/src/Viewer/Markup/Impl/Konva/MarkupColor.ts +39 -39
  214. package/src/Viewer/Markup/Impl/Visualize/VisualizeMarkup.ts +273 -273
  215. package/src/Viewer/Markup/MarkupFactory.ts +32 -32
  216. package/src/Viewer/Options.ts +502 -502
  217. package/src/Viewer/OptionsEvents.ts +28 -28
  218. package/src/Viewer/Viewer.ts +1109 -1109
  219. package/src/Viewer/ViewerCommands.ts +45 -45
  220. package/src/Viewer/ViewerEvents.ts +700 -700
  221. package/src/Viewer/utils.ts +74 -74
  222. package/src/index.ts +48 -48
@@ -1,872 +1,1208 @@
1
- import Konva from "konva";
2
- import { IMarkup, MarkupMode } from "../../IMarkup";
3
- import { Viewer } from "../../../Viewer";
4
- import { ChangeActiveDraggerEvent, PanEvent } from "../../../ViewerEvents";
5
- import { MarkupColor } from "./MarkupColor";
6
- import * as utils from "../../../Draggers/MeasureLineDragger/MeasureUtils";
7
- import { OdBaseDragger } from "../../../Draggers/Common/OdBaseDragger";
8
- import { IMarkupObject } from "../../Api/IMarkupObject";
9
- import { KonvaLine } from "../../Api/Impl/Konva/KonvaLine";
10
- import { KonvaText } from "../../Api/Impl/Konva/KonvaText";
11
- import { KonvaRectangle } from "../../Api/Impl/Konva/KonvaRectangle";
12
- import { KonvaEllipse } from "../../Api/Impl/Konva/KonvaEllipse";
13
- import { KonvaArrow } from "../../Api/Impl/Konva/KonvaArrow";
14
- import { KonvaImage } from "../../Api/Impl/Konva/KonvaImage";
15
-
16
- class KonvaShape {
17
- name: string;
18
- initializer: (ref: any) => any;
19
- }
20
-
21
- // move to separate class and create factory with enum?
22
- const MarkupMode2Konva = new Map<MarkupMode, KonvaShape>([
23
- [
24
- MarkupMode.Line,
25
- {
26
- name: "Line",
27
- initializer: (ref) => {
28
- return new KonvaLine(null, ref);
29
- },
30
- },
31
- ],
32
- [
33
- MarkupMode.Text,
34
- {
35
- name: "Text",
36
- initializer: (ref) => {
37
- return new KonvaText(null, ref);
38
- },
39
- },
40
- ],
41
- [
42
- MarkupMode.Rectangle,
43
- {
44
- name: "Rect",
45
- initializer: (ref) => {
46
- return new KonvaRectangle(null, ref);
47
- },
48
- },
49
- ],
50
- [
51
- MarkupMode.Ellipse,
52
- {
53
- name: "Ellipse",
54
- initializer: (ref) => {
55
- return new KonvaEllipse(null, ref);
56
- },
57
- },
58
- ],
59
- [
60
- MarkupMode.Arrow,
61
- {
62
- name: "Arrow",
63
- initializer: (ref) => {
64
- return new KonvaArrow(null, ref);
65
- },
66
- },
67
- ],
68
- [
69
- MarkupMode.Image,
70
- {
71
- name: "Image",
72
- initializer: (ref) => {
73
- return new KonvaImage(null, ref);
74
- },
75
- },
76
- ],
77
- ]);
78
-
79
- export class KonvaMarkup implements IMarkup {
80
- private _isInitialized = false;
81
- private _viewer: Viewer;
82
- private _canvasOriginal: HTMLCanvasElement;
83
- private _canvasEvents: string[];
84
- private _markupIsActive: boolean;
85
- private _markupMode: MarkupMode;
86
- private _markupColor: MarkupColor;
87
- private _konvaStage: Konva.Stage;
88
- private _konvaLayer: Konva.Layer;
89
- private _konvaTransformer: Konva.Transformer;
90
-
91
- private _textInputRef: HTMLTextAreaElement;
92
- private _textInputPos: Konva.Vector2d;
93
- private _textInputAngle: number;
94
- private _markupContainer: HTMLDivElement;
95
- private _zIndex = 1;
96
-
97
- private readonly _markupContainerName = "markupContainer";
98
- private readonly TEXT_FONT_FAMILY = "Calibri";
99
-
100
- public lineWidth = 4;
101
-
102
- initialize(viewer: Viewer, canvas: HTMLCanvasElement, canvasEvents: string[] = []): void {
103
- if (!Konva)
104
- throw new Error(
105
- 'Konva Markup: Error during Markup Initialization. Konva is not initialized. Update node_modules or add to your page <script src="https://unpkg.com/konva@9/konva.min.js"></script>'
106
- );
107
-
108
- this._viewer = viewer;
109
-
110
- this._canvasOriginal = canvas;
111
- this._canvasEvents = canvasEvents;
112
-
113
- this._markupContainer = document.createElement("div");
114
- this._markupContainer.id = this._markupContainerName;
115
- this._markupContainer.style.position = "absolute";
116
- this._markupContainer.style.top = "0px";
117
- this._markupContainer.style.left = "0px";
118
- // to eliminate grey box during delete elements
119
- this._markupContainer.style.outline = "0px";
120
-
121
- const parentDiv = this._canvasOriginal.parentElement;
122
- parentDiv.appendChild(this._markupContainer);
123
-
124
- this._markupColor = new MarkupColor(255, 0, 0);
125
- this._markupIsActive = false;
126
-
127
- this.initializeKonva();
128
- this.resize();
129
-
130
- this._canvasEvents.forEach((x) => this._markupContainer.addEventListener(x, this.redirectToViewer));
131
-
132
- this._viewer.addEventListener("resize", this.resize);
133
- this._viewer.addEventListener("changeactivedragger", this.changeActiveDragger);
134
- this._viewer.addEventListener("pan", this.pan);
135
-
136
- this._isInitialized = true;
137
- }
138
-
139
- dispose(): void {
140
- if (!this._isInitialized) return;
141
-
142
- this._canvasEvents.forEach((x) => this._markupContainer.removeEventListener(x, this.redirectToViewer));
143
-
144
- this._viewer.removeEventListener("pan", this.pan);
145
- this._viewer.removeEventListener("changeactivedragger", this.changeActiveDragger);
146
- this._viewer.removeEventListener("resize", this.resize);
147
-
148
- this.destroyKonva();
149
-
150
- this._markupContainer.remove();
151
-
152
- this._markupContainer = undefined;
153
- this._canvasOriginal = undefined;
154
- this._viewer = undefined;
155
-
156
- this._isInitialized = false;
157
- }
158
-
159
- changeActiveDragger = (event: ChangeActiveDraggerEvent) => {
160
- const draggerName = event.data;
161
-
162
- this._markupContainer.className = this._canvasOriginal.className
163
- .split(" ")
164
- .filter((x) => !x.startsWith("oda-cursor-"))
165
- .filter((x) => x)
166
- .concat(`oda-cursor-${draggerName.toLowerCase()}`)
167
- .join(" ");
168
-
169
- this.removeTextInput();
170
-
171
- const markupMode = MarkupMode[draggerName];
172
- const konvaMode = MarkupMode2Konva.get(markupMode);
173
- if (konvaMode) {
174
- this._markupMode = markupMode;
175
- this._markupIsActive = true;
176
- } else {
177
- this._markupIsActive = false;
178
- this._konvaTransformer.nodes([]);
179
- }
180
- };
181
-
182
- resize = () => {
183
- this._konvaStage?.width(this._canvasOriginal.clientWidth);
184
- this._konvaStage?.height(this._canvasOriginal.clientHeight);
185
- };
186
-
187
- pan = (event: PanEvent) => {
188
- const dX = event.dX / window.devicePixelRatio;
189
- const dY = event.dY / window.devicePixelRatio;
190
-
191
- Object.values(MarkupMode).forEach((mode) => this.konvaLayerFind(mode).forEach((x) => x.move({ x: dX, y: dY })));
192
- };
193
-
194
- redirectToViewer = (event) => {
195
- this._viewer.emit(event);
196
- };
197
-
198
- getDraggers(): Map<string, typeof OdBaseDragger> {
199
- return null;
200
- }
201
-
202
- clearOverlay(): void {
203
- this.removeTextInput();
204
- this._konvaTransformer.nodes([]);
205
- Object.values(MarkupMode).forEach((mode) => this.konvaLayerFind(mode).forEach((x) => x.destroy()));
206
- }
207
-
208
- getMarkupColor(): { r: number; g: number; b: number } {
209
- return this._markupColor.RGB;
210
- }
211
-
212
- setMarkupColor(r: number, g: number, b: number): void {
213
- this._markupColor.setColor(r, g, b);
214
- }
215
-
216
- colorizeAllMarkup(r = 255, g = 0, b = 0): void {
217
- const hex = new MarkupColor(r, g, b).HexColor;
218
- Object.values(MarkupMode).forEach((mode) => {
219
- this.konvaLayerFind(mode).forEach((x) => {
220
- const konvaObj = MarkupMode2Konva.get(mode).initializer(x);
221
- if (konvaObj.setColor) konvaObj.setColor(hex);
222
- });
223
- });
224
-
225
- this._konvaLayer.draw();
226
- }
227
-
228
- drawViewpoint(viewpoint: any): void {
229
- function getLogicalPoint3dAsArray(point3d) {
230
- return [point3d.x, point3d.y, point3d.z];
231
- }
232
-
233
- if (!this._isInitialized) return;
234
- if (!this._viewer.visualizeJs) return;
235
-
236
- const visLib = this._viewer.visLib();
237
- const visViewer = visLib.getViewer();
238
- const activeView = visViewer.activeView;
239
-
240
- this._viewer.resetActiveDragger();
241
- this._viewer.clearSlices();
242
- this._viewer.clearOverlay();
243
-
244
- if (viewpoint.orthogonal_camera) {
245
- activeView.setView(
246
- getLogicalPoint3dAsArray(viewpoint.orthogonal_camera.view_point),
247
- getLogicalPoint3dAsArray(viewpoint.orthogonal_camera.direction),
248
- getLogicalPoint3dAsArray(viewpoint.orthogonal_camera.up_vector),
249
- viewpoint.orthogonal_camera.field_width,
250
- viewpoint.orthogonal_camera.field_height,
251
- true
252
- );
253
- }
254
-
255
- this._viewer.syncOverlay();
256
-
257
- const markupColor = viewpoint.custom_fields.markup_color || { r: 255, g: 0, b: 0 };
258
- this.setMarkupColor(markupColor.r, markupColor.g, markupColor.b);
259
-
260
- if (viewpoint.clipping_planes) {
261
- for (const plane of viewpoint.clipping_planes) {
262
- const cuttingPlane = new visLib.OdTvPlane();
263
- cuttingPlane.set(getLogicalPoint3dAsArray(plane.location), getLogicalPoint3dAsArray(plane.direction));
264
-
265
- activeView.addCuttingPlane(cuttingPlane);
266
- activeView.setEnableCuttingPlaneFill(true, 0x66, 0x66, 0x66);
267
- }
268
- }
269
-
270
- this.loadMarkup(viewpoint);
271
-
272
- this._viewer.update();
273
- }
274
-
275
- createViewpoint(): object {
276
- function getLogicalPoint3dFromArray(array) {
277
- return { x: array[0], y: array[1], z: array[2] };
278
- }
279
-
280
- if (!this._isInitialized) return {};
281
- if (!this._viewer.visualizeJs) return {};
282
-
283
- const visLib = this._viewer.visLib();
284
- const visViewer = visLib.getViewer();
285
- const activeView = visViewer.activeView;
286
-
287
- const viewpoint = {
288
- lines: [],
289
- texts: [],
290
- clipping_planes: [],
291
- } as any;
292
-
293
- viewpoint.orthogonal_camera = {
294
- view_point: getLogicalPoint3dFromArray(activeView.viewPosition),
295
- direction: getLogicalPoint3dFromArray(activeView.viewTarget),
296
- up_vector: getLogicalPoint3dFromArray(activeView.upVector),
297
- field_width: activeView.viewFieldWidth,
298
- field_height: activeView.viewFieldHeight,
299
- };
300
-
301
- for (let i = 0; i < activeView.numCuttingPlanes(); i++) {
302
- const cuttingPlane = activeView.getCuttingPlane(i);
303
-
304
- const plane = {
305
- location: getLogicalPoint3dFromArray(cuttingPlane.getOrigin()),
306
- direction: getLogicalPoint3dFromArray(cuttingPlane.normal()),
307
- };
308
-
309
- viewpoint.clipping_planes.push(plane);
310
- }
311
-
312
- viewpoint.snapshot = {
313
- data: this.combineMarkupWithDrawing(),
314
- };
315
-
316
- viewpoint.custom_fields = {
317
- markup_color: this.getMarkupColor(),
318
- };
319
-
320
- const markupLines = this.getMarkupLines();
321
- if (markupLines && markupLines.length > 0) {
322
- markupLines?.forEach((markupLine) => {
323
- const line = {
324
- points: [],
325
- color: markupLine.color || "ff0000",
326
- width: markupLine.width || 3,
327
- };
328
- for (const point of markupLine.points) {
329
- line.points.push(getLogicalPoint3dFromArray(point));
330
- }
331
- viewpoint.lines.push(line);
332
- });
333
- }
334
-
335
- const markupTexts = this.getMarkupTexts();
336
- if (markupTexts && markupTexts.length > 0) {
337
- markupTexts?.forEach((markupText) => {
338
- const text = {
339
- position: getLogicalPoint3dFromArray(markupText.position),
340
- text: markupText.text,
341
- angle: markupText.rotation ?? 0,
342
- text_size: markupText.size,
343
- color: markupText.color,
344
- };
345
- viewpoint.texts.push(text);
346
- });
347
- }
348
-
349
- viewpoint.description = new Date().toDateString();
350
- return viewpoint;
351
- }
352
-
353
- createObject(type: string, params: any): IMarkupObject {
354
- let object;
355
- let zIndex = this._zIndex;
356
-
357
- // TODO: factory?
358
- switch (type) {
359
- case "line":
360
- object = new KonvaLine(params);
361
- zIndex = 1;
362
- break;
363
- case "text":
364
- object = new KonvaText(params);
365
- break;
366
- case "rectangle":
367
- object = new KonvaRectangle(params);
368
- zIndex = 1;
369
- break;
370
- case "ellipse":
371
- object = new KonvaEllipse(params);
372
- zIndex = 1;
373
- break;
374
- case "arrow":
375
- object = new KonvaArrow(params);
376
- break;
377
- case "image":
378
- object = new KonvaImage(params);
379
- zIndex = 0;
380
- break;
381
- default:
382
- throw new Error("Markup CreateObject - unsupported type has been detected.");
383
- }
384
-
385
- this.addObject(object);
386
-
387
- // Set zIndex only when shape has been added to Layer else we will get "Konva warning: Node has no parent. zIndex parameter is ignored."
388
- object.setZIndex(zIndex);
389
- this._zIndex++;
390
- return object;
391
- }
392
-
393
- getObjects(): IMarkupObject[] {
394
- const objects = [];
395
- this.konvaLayerFind(MarkupMode.Line).forEach((line) => {
396
- objects.push(new KonvaLine(null, line));
397
- });
398
-
399
- this.konvaLayerFind(MarkupMode.Text).forEach((text) => {
400
- objects.push(new KonvaText(null, text));
401
- });
402
-
403
- this.konvaLayerFind(MarkupMode.Rectangle).forEach((rectangle) => {
404
- objects.push(new KonvaRectangle(null, rectangle));
405
- });
406
-
407
- this.konvaLayerFind(MarkupMode.Ellipse).forEach((ellipse) => {
408
- objects.push(new KonvaEllipse(null, ellipse));
409
- });
410
-
411
- this.konvaLayerFind(MarkupMode.Arrow).forEach((arrow) => {
412
- objects.push(new KonvaArrow(null, arrow));
413
- });
414
-
415
- this.konvaLayerFind(MarkupMode.Image).forEach((image) => {
416
- objects.push(new KonvaImage(null, image));
417
- });
418
-
419
- return objects;
420
- }
421
-
422
- getSelectedObjects(): IMarkupObject[] {
423
- const objects = [];
424
-
425
- this._konvaTransformer.nodes().forEach((obj) => {
426
- const konvaShapeName = obj.className;
427
- switch (konvaShapeName) {
428
- case "Line":
429
- objects.push(new KonvaLine(null, obj));
430
- break;
431
- case "Text":
432
- objects.push(new KonvaText(null, obj));
433
- break;
434
- case "Rect":
435
- objects.push(new KonvaRectangle(null, obj));
436
- break;
437
- case "Ellipse":
438
- objects.push(new KonvaEllipse(null, obj));
439
- break;
440
- case "Arrow":
441
- objects.push(new KonvaArrow(null, obj));
442
- break;
443
- case "Image":
444
- objects.push(new KonvaImage(null, obj));
445
- break;
446
- default:
447
- break;
448
- }
449
- });
450
-
451
- return objects;
452
- }
453
-
454
- selectObjects(objects: IMarkupObject[]) {
455
- const selectedObjs = this._konvaTransformer.nodes().concat(objects.map((x) => x.ref()));
456
- this._konvaTransformer.nodes(selectedObjs);
457
- }
458
-
459
- clearSelected(): void {
460
- this._konvaTransformer.nodes([]);
461
- }
462
-
463
- private addObject(object: IMarkupObject): void {
464
- this._konvaLayer.add(object.ref());
465
- }
466
-
467
- private konvaLayerFind(markupShape: MarkupMode): any {
468
- const konvaShape = MarkupMode2Konva.get(markupShape);
469
- if (konvaShape) {
470
- // for "draggable" Konva uses Rectangles in Transformer. We need only Shapes from Layer.
471
- const konvaShapes = this._konvaLayer.find(konvaShape.name).filter((x) => x.parent instanceof Konva.Layer);
472
- return konvaShapes;
473
- }
474
- }
475
-
476
- private initializeKonva(): any {
477
- // first we need Konva core things: stage and layer
478
- this._konvaStage = new Konva.Stage({
479
- container: this._markupContainerName,
480
- width: this._canvasOriginal.clientWidth,
481
- height: this._canvasOriginal.clientHeight,
482
- });
483
- const stage = this._konvaStage;
484
- const layer = new Konva.Layer({ pixelRation: window.devicePixelRatio });
485
- stage.add(layer);
486
- this._konvaLayer = layer;
487
-
488
- const transformer = new Konva.Transformer({
489
- shouldOverdrawWholeArea: false,
490
- });
491
-
492
- this._konvaTransformer = transformer;
493
- layer.add(transformer);
494
-
495
- let isPaint = false;
496
- let lastLine;
497
-
498
- stage.on("mousedown touchstart", (e) => {
499
- // do nothing if we mousedown on any shape
500
- if (!this._markupIsActive || e.target !== stage || this._markupMode === MarkupMode.Text) return;
501
-
502
- if (e.target === stage && transformer.nodes().length > 0) {
503
- transformer.nodes([]);
504
- return;
505
- }
506
-
507
- const pos = stage.getPointerPosition();
508
-
509
- if (this._markupMode === MarkupMode.Line) {
510
- // add point twice, so we have some drawings even on a simple click
511
- lastLine = this.addLine([pos.x, pos.y, pos.x, pos.y]);
512
-
513
- isPaint = true;
514
- } else if (this._markupMode === MarkupMode.Rectangle) {
515
- this.addRectangle({ x: pos.x, y: pos.y }, 50, 50);
516
- } else if (this._markupMode === MarkupMode.Ellipse) {
517
- this.addEllipse({ x: pos.x, y: pos.y }, { x: 50, y: 50 });
518
- } else if (this._markupMode === MarkupMode.Arrow) {
519
- this.addArrow([pos.x, pos.y, pos.x + 50, pos.y + 50], 5, 5);
520
- }
521
- });
522
-
523
- stage.on("mouseup touchend", (e) => {
524
- if (!this._markupIsActive) return;
525
- isPaint = false;
526
- });
527
-
528
- stage.on("mousemove touchmove", (e) => {
529
- if (!this._markupIsActive) return;
530
- if (!isPaint) {
531
- return;
532
- }
533
-
534
- // prevent scrolling on touch devices
535
- //e.evt.preventDefault();
536
-
537
- const pos = stage.getPointerPosition();
538
- const newPoints = lastLine.points().concat([pos.x, pos.y]);
539
- lastLine.points(newPoints);
540
- });
541
-
542
- // clicks should select/deselect shapes
543
- stage.on("click tap", (e) => {
544
- if (!this._markupIsActive) return;
545
-
546
- // if click on empty area - remove all selections
547
- if (e.target === stage) {
548
- if (this._markupMode === MarkupMode.Text) {
549
- if (this._textInputRef) this.addText(this._textInputRef.value, this._textInputPos, this._textInputAngle);
550
- else if (transformer.nodes().length === 0) {
551
- const pos = stage.getPointerPosition();
552
- this.createTextInput(pos, e.evt.pageX, e.evt.pageY, 0, null);
553
- }
554
- }
555
- transformer.nodes([]);
556
- return;
557
- }
558
-
559
- if (e.target.className === "Text" && transformer.nodes().length === 1 && transformer.nodes()[0] === e.target) {
560
- if (this._textInputRef) this.addText(this._textInputRef.value, this._textInputPos, this._textInputAngle);
561
- else
562
- this.createTextInput(
563
- { x: e.target.attrs.x, y: e.target.attrs.y },
564
- e.evt.pageX,
565
- e.evt.pageY,
566
- e.target.attrs.rotation,
567
- e.target.attrs.text
568
- );
569
- return;
570
- } else {
571
- this.removeTextInput();
572
- }
573
-
574
- // do we pressed shift or ctrl?
575
- const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey;
576
- const isSelected = transformer.nodes().indexOf(e.target) >= 0;
577
-
578
- if (!metaPressed && !isSelected) {
579
- // if no key pressed and the node is not selected
580
- // select just one
581
- transformer.nodes([e.target]);
582
- } else if (metaPressed && isSelected) {
583
- // if we pressed keys and node was selected
584
- // we need to remove it from selection:
585
- const nodes = transformer.nodes().slice(); // use slice to have new copy of array
586
- // remove node from array
587
- nodes.splice(nodes.indexOf(e.target), 1);
588
- transformer.nodes(nodes);
589
- } else if (metaPressed && !isSelected) {
590
- // add the node into selection
591
- const nodes = transformer.nodes().concat([e.target]);
592
- transformer.nodes(nodes);
593
- }
594
- });
595
-
596
- const container = stage.container();
597
- container.tabIndex = 1;
598
- // focus it
599
- // also stage will be in focus on its click
600
- container.focus();
601
-
602
- container.addEventListener("keydown", (e) => {
603
- if (!this._markupIsActive) return;
604
- if (e.code === "Delete") {
605
- const trNodes = this._konvaTransformer.nodes();
606
- if (trNodes.length > 0) {
607
- this._konvaTransformer.nodes().forEach((x) => x.destroy());
608
- this._konvaTransformer.nodes([]);
609
- }
610
- layer.draw();
611
- return;
612
- }
613
- e.preventDefault();
614
- });
615
- }
616
-
617
- private destroyKonva() {
618
- this.clearOverlay();
619
-
620
- this._konvaStage.destroy();
621
-
622
- this._konvaLayer = undefined;
623
- this._konvaTransformer = undefined;
624
- this._konvaStage = undefined;
625
- }
626
-
627
- private getMarkupLines() {
628
- const lines = [];
629
- this.konvaLayerFind(MarkupMode.Line).forEach((line) => {
630
- const linePoints = line.attrs.points;
631
- if (!linePoints) return;
632
- const worldPoints = [];
633
- const absoluteTransform = line.getAbsoluteTransform();
634
- for (let i = 0; i < linePoints.length; i += 2) {
635
- // https://stackoverflow.com/a/57641487 - check answer's comments
636
- const atPoint = absoluteTransform.point({ x: linePoints[i], y: linePoints[i + 1] });
637
- const worldPoint = this._viewer
638
- .visViewer()
639
- .screenToWorld(atPoint.x * window.devicePixelRatio, atPoint.y * window.devicePixelRatio);
640
- worldPoints.push(worldPoint);
641
- }
642
-
643
- const konvaLine = new KonvaLine(null, line);
644
- lines.push({
645
- points: worldPoints,
646
- color: konvaLine.getColor(),
647
- width: konvaLine.getLineWidth(),
648
- });
649
- });
650
-
651
- return lines;
652
- }
653
-
654
- private getMarkupTexts() {
655
- const texts = [];
656
-
657
- const textSize = 0.02;
658
- let textScale = 1.0;
659
-
660
- const projMtrx = this._viewer.visViewer().activeView.projectionMatrix;
661
- const mtrxNumber = projMtrx.get(1, 1);
662
- const tol = 1.0e-6;
663
- if (!(mtrxNumber < tol && mtrxNumber > -tol)) {
664
- textScale = 1 / mtrxNumber;
665
- }
666
-
667
- this.konvaLayerFind(MarkupMode.Text).forEach((text) => {
668
- if (!text) return;
669
- texts.push({
670
- position: this._viewer
671
- .visViewer()
672
- .screenToWorld(text.attrs.x * window.devicePixelRatio, text.attrs.y * window.devicePixelRatio),
673
- text: text.attrs.text,
674
- rotation: text.attrs.rotation && Math.abs(text.attrs.rotation) > tol ? text.attrs.rotation : 0,
675
- size: textSize * textScale,
676
- color: text.attrs.fill,
677
- });
678
- });
679
-
680
- return texts;
681
- }
682
-
683
- private loadMarkup(viewpoint) {
684
- const vpLines = viewpoint.lines;
685
- vpLines?.forEach((vpLine) => {
686
- const linePoints = [];
687
- vpLine.points.forEach((point) => {
688
- const screenPoint = utils.worldToScreen(
689
- [point.x, point.y, point.z],
690
- this._viewer.visualizeJs,
691
- this._viewer.visViewer()
692
- );
693
- linePoints.push(screenPoint.x);
694
- linePoints.push(screenPoint.y);
695
- });
696
-
697
- this.addLine(linePoints, vpLine.color, vpLine.width);
698
- });
699
-
700
- const vpTexts = viewpoint.texts;
701
- vpTexts?.forEach((vpText) => {
702
- const screenPoint = utils.worldToScreen(
703
- [vpText.position.x, vpText.position.y, vpText.position.z],
704
- this._viewer.visualizeJs,
705
- this._viewer.visViewer()
706
- );
707
- this.addText(vpText.text, screenPoint, vpText.angle, vpText.color);
708
- });
709
- }
710
-
711
- private combineMarkupWithDrawing() {
712
- const trNodes = this._konvaTransformer.nodes();
713
- if (trNodes.length > 0) {
714
- this._konvaTransformer.nodes([]);
715
- }
716
-
717
- const tempCanvas = document.createElement("canvas");
718
- tempCanvas.height = this._canvasOriginal.height;
719
- tempCanvas.width = this._canvasOriginal.width;
720
- const ctx = tempCanvas.getContext("2d");
721
- ctx.drawImage(this._canvasOriginal, 0, 0);
722
- ctx.drawImage(this._konvaStage.toCanvas({ pixelRatio: window.devicePixelRatio }), 0, 0);
723
- return tempCanvas.toDataURL("image/jpeg", 0.25);
724
- }
725
-
726
- private addLine(linePoints: number[], color?: string, width?: number): Konva.Line | void {
727
- if (!linePoints || linePoints.length === 0) return;
728
- const points: { x: number; y: number }[] = [];
729
- for (let i = 0; i < linePoints.length; i += 2) {
730
- points.push({ x: linePoints[i], y: linePoints[i + 1] });
731
- }
732
-
733
- const konvaLine = new KonvaLine({
734
- points,
735
- color: color || this._markupColor.HexColor,
736
- width: width || this.lineWidth,
737
- });
738
-
739
- const obj = konvaLine.ref();
740
- this._konvaLayer.add(obj);
741
- return obj;
742
- }
743
-
744
- private createTextInput(pos: Konva.Vector2d, inputX: number, inputY: number, angle: number, text: string): void {
745
- if (!this._textInputRef) {
746
- this._textInputPos = pos;
747
- this._textInputAngle = angle;
748
- this._textInputRef = document.createElement("textarea");
749
- this._textInputRef.style.zIndex = "9999";
750
- this._textInputRef.style.position = "absolute";
751
- this._textInputRef.style.display = "block";
752
- this._textInputRef.style.top = inputY + "px";
753
- this._textInputRef.style.left = inputX + "px";
754
- this._textInputRef.onkeydown = (event) => {
755
- if (event.key === "Enter") {
756
- event.preventDefault();
757
- this.addText(this._textInputRef.value, this._textInputPos, this._textInputAngle);
758
- }
759
- if (event.key === "Escape") {
760
- event.preventDefault();
761
- this.removeTextInput();
762
- }
763
- };
764
- if (text) this._textInputRef.value = text;
765
- document.body.appendChild(this._textInputRef);
766
-
767
- setTimeout(() => {
768
- this._textInputRef.focus();
769
- }, 50);
770
- } else {
771
- this.removeTextInput();
772
- }
773
- }
774
-
775
- private removeTextInput(): void {
776
- this._textInputRef?.remove();
777
- this._textInputRef = null;
778
- this._textInputPos = null;
779
- this._textInputAngle = 0;
780
- }
781
-
782
- private addText(specifiedText: string, pos: Konva.Vector2d, angle: number, color?: string): void {
783
- if (specifiedText) {
784
- const konvaText = new Konva.Text({
785
- x: pos.x,
786
- y: pos.y,
787
- text: specifiedText,
788
- fontSize: 34,
789
- fontFamily: this.TEXT_FONT_FAMILY,
790
- fill: color || this._markupColor.HexColor,
791
- align: "left",
792
- draggable: true,
793
- rotation: angle,
794
- });
795
-
796
- this._konvaLayer.add(konvaText);
797
- }
798
-
799
- const trNodes = this._konvaTransformer.nodes();
800
- if (trNodes.length > 0) {
801
- // in case of edit - remove old Konva.Text object
802
- trNodes[0].destroy();
803
- this._konvaTransformer.nodes([]);
804
- }
805
-
806
- this.removeTextInput();
807
-
808
- return;
809
- }
810
-
811
- private addRectangle(pos: Konva.Vector2d, width: number, height: number): Konva.Rect | void {
812
- if (!pos) return;
813
- const konvaRectangle = new Konva.Rect({
814
- stroke: this._markupColor.HexColor,
815
- strokeWidth: this.lineWidth,
816
- globalCompositeOperation: "source-over",
817
- lineCap: "round",
818
- lineJoin: "round",
819
- x: pos.x,
820
- y: pos.y,
821
- width,
822
- height,
823
- draggable: true,
824
- strokeScaleEnabled: false,
825
- });
826
-
827
- this._konvaLayer.add(konvaRectangle);
828
- return konvaRectangle;
829
- }
830
-
831
- private addEllipse(pos: { x: number; y: number }, radius: { x: number; y: number }): Konva.Ellipse | void {
832
- if (!pos) return;
833
-
834
- const konvaEllipse = new Konva.Ellipse({
835
- stroke: this._markupColor.HexColor,
836
- strokeWidth: this.lineWidth,
837
- globalCompositeOperation: "source-over",
838
- lineCap: "round",
839
- lineJoin: "round",
840
- x: pos.x,
841
- y: pos.y,
842
- radiusX: radius.x,
843
- radiusY: radius.y,
844
- draggable: true,
845
- strokeScaleEnabled: false,
846
- });
847
-
848
- this._konvaLayer.add(konvaEllipse);
849
- return konvaEllipse;
850
- }
851
-
852
- private addArrow(points: number[], pointerWidth: number, pointerLength: number): Konva.Arrow | void {
853
- if (!points) return;
854
-
855
- const konvaArrow = new Konva.Arrow({
856
- stroke: this._markupColor.HexColor,
857
- fill: this._markupColor.HexColor,
858
- strokeWidth: this.lineWidth,
859
- globalCompositeOperation: "source-over",
860
- lineCap: "round",
861
- lineJoin: "round",
862
- points,
863
- pointerWidth,
864
- pointerLength,
865
- draggable: true,
866
- strokeScaleEnabled: false,
867
- });
868
-
869
- this._konvaLayer.add(konvaArrow);
870
- return konvaArrow;
871
- }
872
- }
1
+ import Konva from "konva";
2
+ import { IMarkup, MarkupMode } from "../../IMarkup";
3
+ import { Viewer } from "../../../Viewer";
4
+ import { ChangeActiveDraggerEvent, PanEvent } from "../../../ViewerEvents";
5
+ import { MarkupColor } from "./MarkupColor";
6
+ import * as utils from "../../../Draggers/MeasureLineDragger/MeasureUtils";
7
+ import { OdBaseDragger } from "../../../Draggers/Common/OdBaseDragger";
8
+ import { IMarkupObject } from "../../Api/IMarkupObject";
9
+ import { KonvaLine } from "../../Api/Impl/Konva/KonvaLine";
10
+ import { LineType } from "../../Api/IMarkupLine";
11
+ import { KonvaText } from "../../Api/Impl/Konva/KonvaText";
12
+ import { KonvaRectangle } from "../../Api/Impl/Konva/KonvaRectangle";
13
+ import { KonvaEllipse } from "../../Api/Impl/Konva/KonvaEllipse";
14
+ import { KonvaArrow } from "../../Api/Impl/Konva/KonvaArrow";
15
+ import { KonvaImage } from "../../Api/Impl/Konva/KonvaImage";
16
+ import { KonvaCloud } from "../../Api/Impl/Konva/KonvaCloud";
17
+
18
+ class KonvaShape {
19
+ name: string;
20
+ initializer: (ref: any) => any;
21
+ }
22
+
23
+ // move to separate class and create factory with enum?
24
+ const MarkupMode2Konva = new Map<MarkupMode, KonvaShape>([
25
+ [
26
+ MarkupMode.Line,
27
+ {
28
+ name: "Line",
29
+ initializer: (ref) => {
30
+ return new KonvaLine(null, ref);
31
+ },
32
+ },
33
+ ],
34
+ [
35
+ MarkupMode.Text,
36
+ {
37
+ name: "Text",
38
+ initializer: (ref) => {
39
+ return new KonvaText(null, ref);
40
+ },
41
+ },
42
+ ],
43
+ [
44
+ MarkupMode.Rectangle,
45
+ {
46
+ name: "Rect",
47
+ initializer: (ref) => {
48
+ return new KonvaRectangle(null, ref);
49
+ },
50
+ },
51
+ ],
52
+ [
53
+ MarkupMode.Ellipse,
54
+ {
55
+ name: "Ellipse",
56
+ initializer: (ref) => {
57
+ return new KonvaEllipse(null, ref);
58
+ },
59
+ },
60
+ ],
61
+ [
62
+ MarkupMode.Arrow,
63
+ {
64
+ name: "Arrow",
65
+ initializer: (ref) => {
66
+ return new KonvaArrow(null, ref);
67
+ },
68
+ },
69
+ ],
70
+ [
71
+ MarkupMode.Image,
72
+ {
73
+ name: "Image",
74
+ initializer: (ref) => {
75
+ return new KonvaImage(null, ref);
76
+ },
77
+ },
78
+ ],
79
+ [
80
+ MarkupMode.Cloud,
81
+ {
82
+ name: "Cloud",
83
+ initializer: (ref) => {
84
+ return new KonvaCloud(null, ref);
85
+ },
86
+ },
87
+ ],
88
+ ]);
89
+
90
+ export class KonvaMarkup implements IMarkup {
91
+ private _isInitialized = false;
92
+ private _viewer: Viewer;
93
+ private _canvasOriginal: HTMLCanvasElement;
94
+ private _canvasEvents: string[];
95
+ private _markupIsActive: boolean;
96
+ private _markupMode: MarkupMode;
97
+ private _markupColor: MarkupColor;
98
+ private _konvaStage: Konva.Stage;
99
+ private _konvaLayer: Konva.Layer;
100
+ private _konvaTransformer: Konva.Transformer;
101
+
102
+ private _textInputRef: HTMLTextAreaElement;
103
+ private _textInputPos: Konva.Vector2d;
104
+ private _textInputAngle: number;
105
+ private _markupContainer: HTMLDivElement;
106
+ private _zIndex = 1;
107
+
108
+ private readonly _markupContainerName = "markupContainer";
109
+ private readonly TEXT_FONT_FAMILY = "Calibri";
110
+
111
+ public lineWidth = 4;
112
+
113
+ initialize(viewer: Viewer, canvas: HTMLCanvasElement, canvasEvents: string[] = []): void {
114
+ if (!Konva)
115
+ throw new Error(
116
+ 'Konva Markup: Error during Markup Initialization. Konva is not initialized. Update node_modules or add to your page <script src="https://unpkg.com/konva@9/konva.min.js"></script>'
117
+ );
118
+
119
+ this._viewer = viewer;
120
+
121
+ this._canvasOriginal = canvas;
122
+ this._canvasEvents = canvasEvents;
123
+
124
+ this._markupContainer = document.createElement("div");
125
+ this._markupContainer.id = this._markupContainerName;
126
+ this._markupContainer.style.position = "absolute";
127
+ this._markupContainer.style.top = "0px";
128
+ this._markupContainer.style.left = "0px";
129
+ // to eliminate grey box during delete elements
130
+ this._markupContainer.style.outline = "0px";
131
+
132
+ const parentDiv = this._canvasOriginal.parentElement;
133
+ parentDiv.appendChild(this._markupContainer);
134
+
135
+ this._markupColor = new MarkupColor(255, 0, 0);
136
+ this._markupIsActive = false;
137
+
138
+ this.initializeKonva();
139
+ this.resize();
140
+
141
+ this._canvasEvents.forEach((x) => this._markupContainer.addEventListener(x, this.redirectToViewer));
142
+
143
+ this._viewer.addEventListener("resize", this.resize);
144
+ this._viewer.addEventListener("changeactivedragger", this.changeActiveDragger);
145
+ this._viewer.addEventListener("pan", this.pan);
146
+
147
+ this._isInitialized = true;
148
+ }
149
+
150
+ dispose(): void {
151
+ if (!this._isInitialized) return;
152
+
153
+ this._canvasEvents.forEach((x) => this._markupContainer.removeEventListener(x, this.redirectToViewer));
154
+
155
+ this._viewer.removeEventListener("pan", this.pan);
156
+ this._viewer.removeEventListener("changeactivedragger", this.changeActiveDragger);
157
+ this._viewer.removeEventListener("resize", this.resize);
158
+
159
+ this.destroyKonva();
160
+
161
+ this._markupContainer.remove();
162
+
163
+ this._markupContainer = undefined;
164
+ this._canvasOriginal = undefined;
165
+ this._viewer = undefined;
166
+
167
+ this._isInitialized = false;
168
+ }
169
+
170
+ changeActiveDragger = (event: ChangeActiveDraggerEvent) => {
171
+ const draggerName = event.data;
172
+
173
+ this._markupContainer.className = this._canvasOriginal.className
174
+ .split(" ")
175
+ .filter((x) => !x.startsWith("oda-cursor-"))
176
+ .filter((x) => x)
177
+ .concat(`oda-cursor-${draggerName.toLowerCase()}`)
178
+ .join(" ");
179
+
180
+ this.removeTextInput();
181
+
182
+ const markupMode = MarkupMode[draggerName];
183
+ const konvaMode = MarkupMode2Konva.get(markupMode);
184
+ if (konvaMode) {
185
+ this._markupMode = markupMode;
186
+ this._markupIsActive = true;
187
+ } else {
188
+ this._markupIsActive = false;
189
+ this._konvaTransformer.nodes([]);
190
+ }
191
+ };
192
+
193
+ resize = () => {
194
+ this._konvaStage?.width(this._canvasOriginal.clientWidth);
195
+ this._konvaStage?.height(this._canvasOriginal.clientHeight);
196
+ };
197
+
198
+ pan = (event: PanEvent) => {
199
+ const dX = event.dX / window.devicePixelRatio;
200
+ const dY = event.dY / window.devicePixelRatio;
201
+
202
+ Object.values(MarkupMode).forEach((mode) => this.konvaLayerFind(mode).forEach((x) => x.move({ x: dX, y: dY })));
203
+ };
204
+
205
+ redirectToViewer = (event) => {
206
+ this._viewer.emit(event);
207
+ };
208
+
209
+ getDraggers(): Map<string, typeof OdBaseDragger> {
210
+ return null;
211
+ }
212
+
213
+ clearOverlay(): void {
214
+ this.removeTextInput();
215
+ this._konvaTransformer.nodes([]);
216
+ Object.values(MarkupMode).forEach((mode) => this.konvaLayerFind(mode).forEach((x) => x.destroy()));
217
+ }
218
+
219
+ getMarkupColor(): { r: number; g: number; b: number } {
220
+ return this._markupColor.RGB;
221
+ }
222
+
223
+ setMarkupColor(r: number, g: number, b: number): void {
224
+ this._markupColor.setColor(r, g, b);
225
+ }
226
+
227
+ colorizeAllMarkup(r = 255, g = 0, b = 0): void {
228
+ const hex = new MarkupColor(r, g, b).HexColor;
229
+ Object.values(MarkupMode).forEach((mode) => {
230
+ this.konvaLayerFind(mode).forEach((x) => {
231
+ const konvaObj = MarkupMode2Konva.get(mode).initializer(x);
232
+ if (konvaObj.setColor) konvaObj.setColor(hex);
233
+ });
234
+ });
235
+
236
+ this._konvaLayer.draw();
237
+ }
238
+
239
+ drawViewpoint(viewpoint: any): void {
240
+ function getLogicalPoint3dAsArray(point3d) {
241
+ return [point3d.x, point3d.y, point3d.z];
242
+ }
243
+
244
+ if (!this._isInitialized) return;
245
+ if (!this._viewer.visualizeJs) return;
246
+
247
+ const visLib = this._viewer.visLib();
248
+ const visViewer = visLib.getViewer();
249
+ const activeView = visViewer.activeView;
250
+
251
+ this._viewer.resetActiveDragger();
252
+ this._viewer.clearSlices();
253
+ this._viewer.clearOverlay();
254
+
255
+ if (viewpoint.orthogonal_camera) {
256
+ activeView.setView(
257
+ getLogicalPoint3dAsArray(viewpoint.orthogonal_camera.view_point),
258
+ getLogicalPoint3dAsArray(viewpoint.orthogonal_camera.direction),
259
+ getLogicalPoint3dAsArray(viewpoint.orthogonal_camera.up_vector),
260
+ viewpoint.orthogonal_camera.field_width,
261
+ viewpoint.orthogonal_camera.field_height,
262
+ true
263
+ );
264
+ }
265
+
266
+ this._viewer.syncOverlay();
267
+
268
+ const markupColor = viewpoint.custom_fields.markup_color || { r: 255, g: 0, b: 0 };
269
+ this.setMarkupColor(markupColor.r, markupColor.g, markupColor.b);
270
+
271
+ if (viewpoint.clipping_planes) {
272
+ for (const plane of viewpoint.clipping_planes) {
273
+ const cuttingPlane = new visLib.OdTvPlane();
274
+ cuttingPlane.set(getLogicalPoint3dAsArray(plane.location), getLogicalPoint3dAsArray(plane.direction));
275
+
276
+ activeView.addCuttingPlane(cuttingPlane);
277
+ activeView.setEnableCuttingPlaneFill(true, 0x66, 0x66, 0x66);
278
+ }
279
+ }
280
+
281
+ this.loadMarkup(viewpoint);
282
+
283
+ this._viewer.update();
284
+ }
285
+
286
+ createViewpoint(): object {
287
+ if (!this._viewer.visualizeJs) return {};
288
+
289
+ const visLib = this._viewer.visLib();
290
+ const visViewer = visLib.getViewer();
291
+ const activeView = visViewer.activeView;
292
+
293
+ const viewpoint = {
294
+ lines: [],
295
+ texts: [],
296
+ arrows: [],
297
+ clouds: [],
298
+ ellipses: [],
299
+ images: [],
300
+ rectangles: [],
301
+ clipping_planes: [],
302
+ } as any;
303
+
304
+ viewpoint.orthogonal_camera = {
305
+ view_point: this.getPoint3dFromArray(activeView.viewPosition),
306
+ direction: this.getPoint3dFromArray(activeView.viewTarget),
307
+ up_vector: this.getPoint3dFromArray(activeView.upVector),
308
+ field_width: activeView.viewFieldWidth,
309
+ field_height: activeView.viewFieldHeight,
310
+ };
311
+
312
+ for (let i = 0; i < activeView.numCuttingPlanes(); i++) {
313
+ const cuttingPlane = activeView.getCuttingPlane(i);
314
+
315
+ const plane = {
316
+ location: this.getPoint3dFromArray(cuttingPlane.getOrigin()),
317
+ direction: this.getPoint3dFromArray(cuttingPlane.normal()),
318
+ };
319
+
320
+ viewpoint.clipping_planes.push(plane);
321
+ }
322
+
323
+ viewpoint.snapshot = {
324
+ data: this.combineMarkupWithDrawing(),
325
+ };
326
+
327
+ viewpoint.custom_fields = {
328
+ markup_color: this.getMarkupColor(),
329
+ };
330
+
331
+ this.fillViewpointShapes(viewpoint);
332
+
333
+ viewpoint.description = new Date().toDateString();
334
+ return viewpoint;
335
+ }
336
+
337
+ createObject(type: string, params: any): IMarkupObject {
338
+ let object;
339
+ let zIndex = this._zIndex;
340
+
341
+ // TODO: factory?
342
+ switch (type) {
343
+ case "line":
344
+ object = new KonvaLine(params);
345
+ zIndex = 1;
346
+ break;
347
+ case "text":
348
+ object = new KonvaText(params);
349
+ break;
350
+ case "rectangle":
351
+ object = new KonvaRectangle(params);
352
+ zIndex = 1;
353
+ break;
354
+ case "ellipse":
355
+ object = new KonvaEllipse(params);
356
+ zIndex = 1;
357
+ break;
358
+ case "arrow":
359
+ object = new KonvaArrow(params);
360
+ break;
361
+ case "image":
362
+ object = new KonvaImage(params);
363
+ zIndex = 0;
364
+ break;
365
+ case "cloud":
366
+ object = new KonvaCloud(params);
367
+ zIndex = 1;
368
+ break;
369
+ default:
370
+ throw new Error("Markup CreateObject - unsupported type has been detected.");
371
+ }
372
+
373
+ this.addObject(object);
374
+
375
+ // Set zIndex only when shape has been added to Layer else we will get "Konva warning: Node has no parent. zIndex parameter is ignored."
376
+ object.setZIndex(zIndex);
377
+ this._zIndex++;
378
+ return object;
379
+ }
380
+
381
+ getObjects(): IMarkupObject[] {
382
+ const objects = [];
383
+ this.konvaLayerFind(MarkupMode.Line).forEach((line) => {
384
+ objects.push(new KonvaLine(null, line));
385
+ });
386
+
387
+ this.konvaLayerFind(MarkupMode.Text).forEach((text) => {
388
+ objects.push(new KonvaText(null, text));
389
+ });
390
+
391
+ this.konvaLayerFind(MarkupMode.Rectangle).forEach((rectangle) => {
392
+ objects.push(new KonvaRectangle(null, rectangle));
393
+ });
394
+
395
+ this.konvaLayerFind(MarkupMode.Ellipse).forEach((ellipse) => {
396
+ objects.push(new KonvaEllipse(null, ellipse));
397
+ });
398
+
399
+ this.konvaLayerFind(MarkupMode.Arrow).forEach((arrow) => {
400
+ objects.push(new KonvaArrow(null, arrow));
401
+ });
402
+
403
+ this.konvaLayerFind(MarkupMode.Image).forEach((image) => {
404
+ objects.push(new KonvaImage(null, image));
405
+ });
406
+
407
+ this.konvaLayerFind(MarkupMode.Cloud).forEach((cloud) => {
408
+ objects.push(new KonvaCloud(null, cloud));
409
+ });
410
+
411
+ return objects;
412
+ }
413
+
414
+ getSelectedObjects(): IMarkupObject[] {
415
+ const objects = [];
416
+
417
+ this._konvaTransformer.nodes().forEach((obj) => {
418
+ const konvaShapeName = obj.className;
419
+ switch (konvaShapeName) {
420
+ case "Line":
421
+ objects.push(new KonvaLine(null, obj));
422
+ break;
423
+ case "Text":
424
+ objects.push(new KonvaText(null, obj));
425
+ break;
426
+ case "Rect":
427
+ objects.push(new KonvaRectangle(null, obj));
428
+ break;
429
+ case "Ellipse":
430
+ objects.push(new KonvaEllipse(null, obj));
431
+ break;
432
+ case "Arrow":
433
+ objects.push(new KonvaArrow(null, obj));
434
+ break;
435
+ case "Image":
436
+ objects.push(new KonvaImage(null, obj));
437
+ break;
438
+ case "Cloud":
439
+ objects.push(new KonvaCloud(null, obj));
440
+ break;
441
+ default:
442
+ break;
443
+ }
444
+ });
445
+
446
+ return objects;
447
+ }
448
+
449
+ selectObjects(objects: IMarkupObject[]) {
450
+ const selectedObjs = this._konvaTransformer.nodes().concat(objects.map((x) => x.ref()));
451
+ this._konvaTransformer.nodes(selectedObjs);
452
+ }
453
+
454
+ clearSelected(): void {
455
+ this._konvaTransformer.nodes([]);
456
+ }
457
+
458
+ private getPoint3dFromArray(array) {
459
+ return { x: array[0], y: array[1], z: array[2] };
460
+ }
461
+
462
+ private fillViewpointShapes(viewpoint) {
463
+ const markupLines = this.getMarkupLines();
464
+ if (markupLines && markupLines.length > 0) {
465
+ markupLines?.forEach((line) => {
466
+ viewpoint.lines.push(line);
467
+ });
468
+ }
469
+
470
+ const markupTexts = this.getMarkupTexts();
471
+ if (markupTexts && markupTexts.length > 0) {
472
+ markupTexts?.forEach((text) => {
473
+ viewpoint.texts.push(text);
474
+ });
475
+ }
476
+
477
+ const markupRectangles = this.getMarkupRectangles();
478
+ if (markupRectangles && markupRectangles.length > 0) {
479
+ markupRectangles?.forEach((rectangle) => {
480
+ viewpoint.rectangles.push(rectangle);
481
+ });
482
+ }
483
+
484
+ const markupEllipses = this.getMarkupEllipses();
485
+ if (markupEllipses && markupEllipses.length > 0) {
486
+ markupEllipses?.forEach((ellipse) => {
487
+ viewpoint.ellipses.push(ellipse);
488
+ });
489
+ }
490
+
491
+ const markupArrows = this.getMarkupArrows();
492
+ if (markupArrows && markupArrows.length > 0) {
493
+ markupArrows?.forEach((arrow) => {
494
+ viewpoint.arrows.push(arrow);
495
+ });
496
+ }
497
+
498
+ const markupImages = this.getMarkupImages();
499
+ if (markupImages && markupImages.length > 0) {
500
+ markupImages?.forEach((image) => {
501
+ viewpoint.images.push(image);
502
+ });
503
+ }
504
+
505
+ const markupClouds = this.getMarkupClouds();
506
+ if (markupClouds && markupClouds.length > 0) {
507
+ markupClouds?.forEach((cloud) => {
508
+ viewpoint.clouds.push(cloud);
509
+ });
510
+ }
511
+ }
512
+
513
+ private addObject(object: IMarkupObject): void {
514
+ this._konvaLayer.add(object.ref());
515
+ }
516
+
517
+ private konvaLayerFind(markupShape: MarkupMode): any {
518
+ const konvaShape = MarkupMode2Konva.get(markupShape);
519
+ if (konvaShape) {
520
+ // for "draggable" Konva uses Rectangles in Transformer. We need only Shapes from Layer.
521
+ const konvaShapes = this._konvaLayer.find(konvaShape.name).filter((x) => x.parent instanceof Konva.Layer);
522
+ return konvaShapes;
523
+ }
524
+ }
525
+
526
+ private initializeKonva(): any {
527
+ // first we need Konva core things: stage and layer
528
+ this._konvaStage = new Konva.Stage({
529
+ container: this._markupContainerName,
530
+ width: this._canvasOriginal.clientWidth,
531
+ height: this._canvasOriginal.clientHeight,
532
+ });
533
+ const stage = this._konvaStage;
534
+ const layer = new Konva.Layer({ pixelRation: window.devicePixelRatio });
535
+ stage.add(layer);
536
+ this._konvaLayer = layer;
537
+
538
+ const transformer = new Konva.Transformer({
539
+ shouldOverdrawWholeArea: false,
540
+ });
541
+
542
+ this._konvaTransformer = transformer;
543
+ layer.add(transformer);
544
+
545
+ let isPaint = false;
546
+ let lastLine;
547
+
548
+ stage.on("mousedown touchstart", (e) => {
549
+ // do nothing if we mousedown on any shape
550
+ if (!this._markupIsActive || e.target !== stage || this._markupMode === MarkupMode.Text) return;
551
+
552
+ if (e.target === stage && transformer.nodes().length > 0) {
553
+ transformer.nodes([]);
554
+ return;
555
+ }
556
+
557
+ const pos = stage.getPointerPosition();
558
+
559
+ if (this._markupMode === MarkupMode.Line) {
560
+ // add point twice, so we have some drawings even on a simple click
561
+ lastLine = this.addLine([pos.x, pos.y, pos.x, pos.y]);
562
+
563
+ isPaint = true;
564
+ }
565
+ // 25.2 - currently only for debug purposes:
566
+ else if (this._markupMode === MarkupMode.Rectangle) {
567
+ this.addRectangle({ x: pos.x, y: pos.y }, 50, 50);
568
+ } else if (this._markupMode === MarkupMode.Ellipse) {
569
+ this.addEllipse({ x: pos.x, y: pos.y }, { x: 50, y: 50 });
570
+ } else if (this._markupMode === MarkupMode.Arrow) {
571
+ this.addArrow({ x: pos.x, y: pos.y }, { x: pos.x + 50, y: pos.y + 50 });
572
+ } else if (this._markupMode === MarkupMode.Cloud) {
573
+ this.addCloud({ x: pos.x, y: pos.y }, 200, 400);
574
+ }
575
+ });
576
+
577
+ stage.on("mouseup touchend", (e) => {
578
+ if (!this._markupIsActive) return;
579
+ isPaint = false;
580
+ });
581
+
582
+ stage.on("mousemove touchmove", (e) => {
583
+ if (!this._markupIsActive) return;
584
+ if (!isPaint) {
585
+ return;
586
+ }
587
+
588
+ // prevent scrolling on touch devices
589
+ //e.evt.preventDefault();
590
+
591
+ const pos = stage.getPointerPosition();
592
+ const newPoints = lastLine.points().concat([pos.x, pos.y]);
593
+ lastLine.points(newPoints);
594
+ });
595
+
596
+ // clicks should select/deselect shapes
597
+ stage.on("click tap", (e) => {
598
+ if (!this._markupIsActive) return;
599
+
600
+ // if click on empty area - remove all selections
601
+ if (e.target === stage) {
602
+ if (this._markupMode === MarkupMode.Text) {
603
+ if (this._textInputRef) this.addText(this._textInputRef.value, this._textInputPos, this._textInputAngle);
604
+ else if (transformer.nodes().length === 0) {
605
+ const pos = stage.getPointerPosition();
606
+ this.createTextInput(pos, e.evt.pageX, e.evt.pageY, 0, null);
607
+ }
608
+ }
609
+ transformer.nodes([]);
610
+ return;
611
+ }
612
+
613
+ if (e.target.className === "Text" && transformer.nodes().length === 1 && transformer.nodes()[0] === e.target) {
614
+ if (this._textInputRef) this.addText(this._textInputRef.value, this._textInputPos, this._textInputAngle);
615
+ else
616
+ this.createTextInput(
617
+ { x: e.target.attrs.x, y: e.target.attrs.y },
618
+ e.evt.pageX,
619
+ e.evt.pageY,
620
+ e.target.attrs.rotation,
621
+ e.target.attrs.text
622
+ );
623
+ return;
624
+ } else {
625
+ this.removeTextInput();
626
+ }
627
+
628
+ if (transformer.nodes().filter((x) => x.className === "Cloud").length > 0 || e.target.className === "Cloud") {
629
+ transformer.rotateEnabled(false);
630
+ } else {
631
+ transformer.rotateEnabled(true);
632
+ }
633
+
634
+ // do we pressed shift or ctrl?
635
+ const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey;
636
+ const isSelected = transformer.nodes().indexOf(e.target) >= 0;
637
+
638
+ if (!metaPressed && !isSelected) {
639
+ // if no key pressed and the node is not selected
640
+ // select just one
641
+ transformer.nodes([e.target]);
642
+ } else if (metaPressed && isSelected) {
643
+ // if we pressed keys and node was selected
644
+ // we need to remove it from selection:
645
+ const nodes = transformer.nodes().slice(); // use slice to have new copy of array
646
+ // remove node from array
647
+ nodes.splice(nodes.indexOf(e.target), 1);
648
+ transformer.nodes(nodes);
649
+ } else if (metaPressed && !isSelected) {
650
+ // add the node into selection
651
+ const nodes = transformer.nodes().concat([e.target]);
652
+ transformer.nodes(nodes);
653
+ }
654
+ });
655
+
656
+ const container = stage.container();
657
+ container.tabIndex = 1;
658
+ // focus it
659
+ // also stage will be in focus on its click
660
+ container.focus();
661
+
662
+ container.addEventListener("keydown", (e) => {
663
+ if (!this._markupIsActive) return;
664
+ if (e.code === "Delete") {
665
+ const trNodes = this._konvaTransformer.nodes();
666
+ if (trNodes.length > 0) {
667
+ this._konvaTransformer.nodes().forEach((x) => x.destroy());
668
+ this._konvaTransformer.nodes([]);
669
+ }
670
+ layer.draw();
671
+ return;
672
+ }
673
+ e.preventDefault();
674
+ });
675
+ }
676
+
677
+ private destroyKonva() {
678
+ this.clearOverlay();
679
+
680
+ this._konvaStage.destroy();
681
+
682
+ this._konvaLayer = undefined;
683
+ this._konvaTransformer = undefined;
684
+ this._konvaStage = undefined;
685
+ }
686
+
687
+ private getMarkupLines() {
688
+ const lines = [];
689
+ this.konvaLayerFind(MarkupMode.Line).forEach((line) => {
690
+ const linePoints = line.points();
691
+ if (!linePoints) return;
692
+ const worldPoints = [];
693
+ const absoluteTransform = line.getAbsoluteTransform();
694
+ for (let i = 0; i < linePoints.length; i += 2) {
695
+ // we need getAbsoluteTransform because inside Konva position starts from {0, 0}
696
+ // https://stackoverflow.com/a/57641487 - check answer's comments
697
+ const atPoint = absoluteTransform.point({ x: linePoints[i], y: linePoints[i + 1] });
698
+ const worldPoint = this._viewer
699
+ .visViewer()
700
+ .screenToWorld(atPoint.x * window.devicePixelRatio, atPoint.y * window.devicePixelRatio);
701
+ worldPoints.push(worldPoint);
702
+ }
703
+
704
+ const konvaLine = new KonvaLine(null, line);
705
+ lines.push({
706
+ id: konvaLine.id(),
707
+ points: worldPoints.map((p) => this.getPoint3dFromArray(p)),
708
+ color: konvaLine.getColor() || "ff0000",
709
+ type: konvaLine.getLineType() || "solid",
710
+ width: konvaLine.getLineWidth() || 3,
711
+ });
712
+ });
713
+
714
+ return lines;
715
+ }
716
+
717
+ private getMarkupTexts() {
718
+ const texts = [];
719
+
720
+ const textSize = 0.02;
721
+ let textScale = 1.0;
722
+
723
+ const projMtrx = this._viewer.visViewer().activeView.projectionMatrix;
724
+ const mtrxNumber = projMtrx.get(1, 1);
725
+ const tol = 1.0e-6;
726
+ if (mtrxNumber > tol || mtrxNumber < -tol) {
727
+ textScale = 1 / mtrxNumber;
728
+ }
729
+
730
+ this.konvaLayerFind(MarkupMode.Text).forEach((text) => {
731
+ if (!text) return;
732
+
733
+ const position = this._viewer
734
+ .visViewer()
735
+ .screenToWorld(text.x() * window.devicePixelRatio, text.y() * window.devicePixelRatio);
736
+
737
+ const shape = new KonvaText(null, text);
738
+ texts.push({
739
+ id: shape.id(),
740
+ position: this.getPoint3dFromArray(position),
741
+ text: shape.getText(),
742
+ text_size: textSize * textScale,
743
+ angle: shape.getRotation(),
744
+ color: shape.getColor(),
745
+ font_size: shape.getFontSize(),
746
+ });
747
+ });
748
+
749
+ return texts;
750
+ }
751
+
752
+ private getMarkupRectangles() {
753
+ const rectangles = [];
754
+ this.konvaLayerFind(MarkupMode.Rectangle).forEach((rect) => {
755
+ const position = rect.position();
756
+ const worldPoint = this._viewer
757
+ .visViewer()
758
+ .screenToWorld(position.x * window.devicePixelRatio, position.y * window.devicePixelRatio);
759
+
760
+ const shape = new KonvaRectangle(null, rect);
761
+ rectangles.push({
762
+ id: shape.id(),
763
+ position: this.getPoint3dFromArray(worldPoint),
764
+ width: shape.getWidth(),
765
+ height: shape.getHeigth(),
766
+ line_width: shape.getLineWidth(),
767
+ color: shape.getColor(),
768
+ });
769
+ });
770
+
771
+ return rectangles;
772
+ }
773
+
774
+ private getMarkupEllipses() {
775
+ const ellipses = [];
776
+ this.konvaLayerFind(MarkupMode.Ellipse).forEach((ellipse) => {
777
+ const position = ellipse.position();
778
+ const worldPoint = this._viewer
779
+ .visViewer()
780
+ .screenToWorld(position.x * window.devicePixelRatio, position.y * window.devicePixelRatio);
781
+
782
+ const shape = new KonvaEllipse(null, ellipse);
783
+ ellipses.push({
784
+ id: shape.id(),
785
+ position: this.getPoint3dFromArray(worldPoint),
786
+ radius: { x: ellipse.getRadiusX(), y: ellipse.getRadiusY() },
787
+ line_width: shape.getLineWidth(),
788
+ color: shape.getColor(),
789
+ });
790
+ });
791
+
792
+ return ellipses;
793
+ }
794
+
795
+ private getMarkupArrows() {
796
+ const arrows = [];
797
+ this.konvaLayerFind(MarkupMode.Arrow).forEach((arrow) => {
798
+ // we need getAbsoluteTransform because inside Konva position starts from {0, 0}
799
+ const absoluteTransform = arrow.getAbsoluteTransform();
800
+
801
+ const atStartPoint = absoluteTransform.point({ x: arrow.points()[0], y: arrow.points()[1] });
802
+ const worldStartPoint = this._viewer
803
+ .visViewer()
804
+ .screenToWorld(atStartPoint.x * window.devicePixelRatio, atStartPoint.y * window.devicePixelRatio);
805
+
806
+ const atEndPoint = absoluteTransform.point({ x: arrow.points()[2], y: arrow.points()[3] });
807
+ const worldEndPoint = this._viewer
808
+ .visViewer()
809
+ .screenToWorld(atEndPoint.x * window.devicePixelRatio, atEndPoint.y * window.devicePixelRatio);
810
+
811
+ const shape = new KonvaArrow(null, arrow);
812
+ arrows.push({
813
+ id: shape.id(),
814
+ start: this.getPoint3dFromArray(worldStartPoint),
815
+ end: this.getPoint3dFromArray(worldEndPoint),
816
+ color: shape.getColor(),
817
+ });
818
+ });
819
+
820
+ return arrows;
821
+ }
822
+
823
+ private getMarkupImages() {
824
+ const images = [];
825
+ this.konvaLayerFind(MarkupMode.Image).forEach((image) => {
826
+ const position = image.position();
827
+ const worldPoint = this._viewer
828
+ .visViewer()
829
+ .screenToWorld(position.x * window.devicePixelRatio, position.y * window.devicePixelRatio);
830
+
831
+ const shape = new KonvaImage(null, image);
832
+ images.push({
833
+ id: shape.id(),
834
+ position: this.getPoint3dFromArray(worldPoint),
835
+ src: shape.getSrc(),
836
+ width: shape.getWidth(),
837
+ height: shape.getHeight(),
838
+ });
839
+ });
840
+
841
+ return images;
842
+ }
843
+
844
+ private getMarkupClouds() {
845
+ const clouds = [];
846
+ this.konvaLayerFind(MarkupMode.Cloud).forEach((cloud) => {
847
+ const position = cloud.position();
848
+ const worldPoint = this._viewer
849
+ .visViewer()
850
+ .screenToWorld(position.x * window.devicePixelRatio, position.y * window.devicePixelRatio);
851
+
852
+ const shape = new KonvaCloud(null, cloud);
853
+ clouds.push({
854
+ id: shape.id(),
855
+ position: this.getPoint3dFromArray(worldPoint),
856
+ width: shape.getWidth(),
857
+ height: shape.getHeigth(),
858
+ line_width: shape.getLineWidth(),
859
+ color: shape.getColor(),
860
+ });
861
+ });
862
+
863
+ return clouds;
864
+ }
865
+
866
+ private loadMarkup(viewpoint) {
867
+ viewpoint.lines?.forEach((vpLine) => {
868
+ const linePoints = [];
869
+ vpLine.points.forEach((point) => {
870
+ const screenPoint = utils.worldToScreen(
871
+ [point.x, point.y, point.z],
872
+ this._viewer.visualizeJs,
873
+ this._viewer.visViewer()
874
+ );
875
+ linePoints.push(screenPoint.x);
876
+ linePoints.push(screenPoint.y);
877
+ });
878
+
879
+ this.addLine(linePoints, vpLine.color, vpLine.type, vpLine.width, vpLine.id);
880
+ });
881
+
882
+ viewpoint.texts?.forEach((vpText) => {
883
+ const screenPoint = utils.worldToScreen(
884
+ [vpText.position.x, vpText.position.y, vpText.position.z],
885
+ this._viewer.visualizeJs,
886
+ this._viewer.visViewer()
887
+ );
888
+ this.addText(vpText.text, screenPoint, vpText.angle, vpText.color, vpText.text_size, vpText.font_size, vpText.id);
889
+ });
890
+
891
+ viewpoint.rectangles?.forEach((vpRect) => {
892
+ const screenPoint = utils.worldToScreen(
893
+ [vpRect.position.x, vpRect.position.y, vpRect.position.z],
894
+ this._viewer.visualizeJs,
895
+ this._viewer.visViewer()
896
+ );
897
+
898
+ this.addRectangle(
899
+ { x: screenPoint.x, y: screenPoint.y },
900
+ vpRect.width,
901
+ vpRect.height,
902
+ vpRect.line_width,
903
+ vpRect.color,
904
+ vpRect.id
905
+ );
906
+ });
907
+
908
+ viewpoint.ellipses?.forEach((vpEllipse) => {
909
+ const screenPoint = utils.worldToScreen(
910
+ [vpEllipse.position.x, vpEllipse.position.y, vpEllipse.position.z],
911
+ this._viewer.visualizeJs,
912
+ this._viewer.visViewer()
913
+ );
914
+
915
+ this.addEllipse(
916
+ { x: screenPoint.x, y: screenPoint.y },
917
+ { x: vpEllipse.radius.x, y: vpEllipse.radius.y },
918
+ vpEllipse.line_width,
919
+ vpEllipse.color,
920
+ vpEllipse.id
921
+ );
922
+ });
923
+
924
+ viewpoint.arrows?.forEach((vpArrow) => {
925
+ const startPoint = utils.worldToScreen(
926
+ [vpArrow.start.x, vpArrow.start.y, vpArrow.start.z],
927
+ this._viewer.visualizeJs,
928
+ this._viewer.visViewer()
929
+ );
930
+
931
+ const endPoint = utils.worldToScreen(
932
+ [vpArrow.end.x, vpArrow.end.y, vpArrow.end.z],
933
+ this._viewer.visualizeJs,
934
+ this._viewer.visViewer()
935
+ );
936
+
937
+ this.addArrow({ x: startPoint.x, y: startPoint.y }, { x: endPoint.x, y: endPoint.y }, vpArrow.color, vpArrow.id);
938
+ });
939
+
940
+ viewpoint.clouds?.forEach((vpCloud) => {
941
+ const screenPoint = utils.worldToScreen(
942
+ [vpCloud.position.x, vpCloud.position.y, vpCloud.position.z],
943
+ this._viewer.visualizeJs,
944
+ this._viewer.visViewer()
945
+ );
946
+
947
+ this.addCloud(
948
+ { x: screenPoint.x, y: screenPoint.y },
949
+ vpCloud.width,
950
+ vpCloud.height,
951
+ vpCloud.line_width,
952
+ vpCloud.color,
953
+ vpCloud.id
954
+ );
955
+ });
956
+
957
+ viewpoint.images?.forEach((vpImage) => {
958
+ const screenPoint = utils.worldToScreen(
959
+ [vpImage.position.x, vpImage.position.y, vpImage.position.z],
960
+ this._viewer.visualizeJs,
961
+ this._viewer.visViewer()
962
+ );
963
+
964
+ this.addImage({ x: screenPoint.x, y: screenPoint.y }, vpImage.src, vpImage.width, vpImage.height, vpImage.id);
965
+ });
966
+ }
967
+
968
+ private combineMarkupWithDrawing() {
969
+ const trNodes = this._konvaTransformer.nodes();
970
+ if (trNodes.length > 0) {
971
+ this._konvaTransformer.nodes([]);
972
+ }
973
+
974
+ const tempCanvas = document.createElement("canvas");
975
+ tempCanvas.height = this._canvasOriginal.height;
976
+ tempCanvas.width = this._canvasOriginal.width;
977
+ const ctx = tempCanvas.getContext("2d");
978
+ ctx.drawImage(this._canvasOriginal, 0, 0);
979
+ ctx.drawImage(this._konvaStage.toCanvas({ pixelRatio: window.devicePixelRatio }), 0, 0);
980
+ return tempCanvas.toDataURL("image/jpeg", 0.25);
981
+ }
982
+
983
+ private addLine(
984
+ linePoints: number[],
985
+ color?: string,
986
+ type?: LineType,
987
+ width?: number,
988
+ id?: string
989
+ ): Konva.Line | void {
990
+ if (!linePoints || linePoints.length === 0) return;
991
+ const points: { x: number; y: number }[] = [];
992
+ for (let i = 0; i < linePoints.length; i += 2) {
993
+ points.push({ x: linePoints[i], y: linePoints[i + 1] });
994
+ }
995
+
996
+ const konvaLine = new KonvaLine({
997
+ points,
998
+ color: color || this._markupColor.HexColor,
999
+ type: type || LineType.Solid,
1000
+ width: width || this.lineWidth,
1001
+ id,
1002
+ });
1003
+
1004
+ const obj = konvaLine.ref();
1005
+ this._konvaLayer.add(obj);
1006
+ return obj;
1007
+ }
1008
+
1009
+ private createTextInput(pos: Konva.Vector2d, inputX: number, inputY: number, angle: number, text: string): void {
1010
+ if (!this._textInputRef) {
1011
+ this._textInputPos = pos;
1012
+ this._textInputAngle = angle;
1013
+ this._textInputRef = document.createElement("textarea");
1014
+ this._textInputRef.style.zIndex = "9999";
1015
+ this._textInputRef.style.position = "absolute";
1016
+ this._textInputRef.style.display = "block";
1017
+ this._textInputRef.style.top = inputY + "px";
1018
+ this._textInputRef.style.left = inputX + "px";
1019
+ this._textInputRef.onkeydown = (event) => {
1020
+ if (event.key === "Enter") {
1021
+ event.preventDefault();
1022
+ this.addText(this._textInputRef.value, this._textInputPos, this._textInputAngle);
1023
+ }
1024
+ if (event.key === "Escape") {
1025
+ event.preventDefault();
1026
+ this.removeTextInput();
1027
+ }
1028
+ };
1029
+ if (text) this._textInputRef.value = text;
1030
+ document.body.appendChild(this._textInputRef);
1031
+
1032
+ setTimeout(() => {
1033
+ this._textInputRef.focus();
1034
+ }, 50);
1035
+ } else {
1036
+ this.removeTextInput();
1037
+ }
1038
+ }
1039
+
1040
+ private removeTextInput(): void {
1041
+ this._textInputRef?.remove();
1042
+ this._textInputRef = null;
1043
+ this._textInputPos = null;
1044
+ this._textInputAngle = 0;
1045
+ }
1046
+
1047
+ private addText(
1048
+ specifiedText: string,
1049
+ position: Konva.Vector2d,
1050
+ angle?: number,
1051
+ color?: string,
1052
+ textSize?: number,
1053
+ fontSize?: number,
1054
+ id?: string
1055
+ ): void {
1056
+ if (specifiedText) {
1057
+ const tol = 1.0e-6;
1058
+
1059
+ // in case we have old viewpoint without font_size
1060
+ if (textSize && textSize > tol && (!fontSize || fontSize < tol)) {
1061
+ const size = 0.02;
1062
+ let scale = 1.0;
1063
+
1064
+ const projMtrx = this._viewer.visViewer().activeView.projectionMatrix;
1065
+ const mtrxNumber = projMtrx.get(1, 1);
1066
+
1067
+ if (mtrxNumber > tol || mtrxNumber < -tol) {
1068
+ scale = 1 / mtrxNumber;
1069
+ }
1070
+ fontSize = textSize / (scale / size) / 34;
1071
+ }
1072
+
1073
+ const konvaText = new KonvaText({
1074
+ position: { x: position.x, y: position.y },
1075
+ text: specifiedText,
1076
+ fontSize,
1077
+ color: color || this._markupColor.HexColor,
1078
+ rotation: angle,
1079
+ id,
1080
+ });
1081
+
1082
+ this._konvaLayer.add(konvaText.ref());
1083
+ }
1084
+
1085
+ const trNodes = this._konvaTransformer.nodes();
1086
+ if (trNodes.length > 0) {
1087
+ // in case of edit - remove old Konva.Text object
1088
+ trNodes[0].destroy();
1089
+ this._konvaTransformer.nodes([]);
1090
+ }
1091
+
1092
+ this.removeTextInput();
1093
+
1094
+ return;
1095
+ }
1096
+
1097
+ private addRectangle(
1098
+ position: Konva.Vector2d,
1099
+ width: number,
1100
+ height: number,
1101
+ lineWidth?: number,
1102
+ color?: string,
1103
+ id?: string
1104
+ ): Konva.Rect | void {
1105
+ if (!position) return;
1106
+
1107
+ const konvaRectangle = new KonvaRectangle({
1108
+ position,
1109
+ color: color || this._markupColor.HexColor,
1110
+ lineWidth: lineWidth || this.lineWidth,
1111
+ width,
1112
+ height,
1113
+ id,
1114
+ });
1115
+
1116
+ const obj = konvaRectangle.ref();
1117
+ this._konvaLayer.add(obj);
1118
+ return obj;
1119
+ }
1120
+
1121
+ private addEllipse(
1122
+ position: { x: number; y: number },
1123
+ radius: { x: number; y: number },
1124
+ lineWidth?: number,
1125
+ color?: string,
1126
+ id?: string
1127
+ ): Konva.Ellipse | void {
1128
+ if (!position) return;
1129
+
1130
+ const konvaEllipse = new KonvaEllipse({
1131
+ position,
1132
+ radius,
1133
+ lineWidth,
1134
+ color: color || this._markupColor.HexColor,
1135
+ id,
1136
+ });
1137
+
1138
+ const obj = konvaEllipse.ref();
1139
+ this._konvaLayer.add(obj);
1140
+ return obj;
1141
+ }
1142
+
1143
+ private addArrow(
1144
+ start: { x: number; y: number },
1145
+ end: { x: number; y: number },
1146
+ color?: string,
1147
+ id?: string
1148
+ ): Konva.Arrow | void {
1149
+ if (!start || !end) return;
1150
+
1151
+ const konvaArrow = new KonvaArrow({
1152
+ start,
1153
+ end,
1154
+ color: color || this._markupColor.HexColor,
1155
+ id,
1156
+ });
1157
+
1158
+ const obj = konvaArrow.ref();
1159
+ this._konvaLayer.add(obj);
1160
+ return obj;
1161
+ }
1162
+
1163
+ private addCloud(
1164
+ position: { x: number; y: number },
1165
+ width: number,
1166
+ height: number,
1167
+ lineWidth?: number,
1168
+ color?: string,
1169
+ id?: string
1170
+ ): Konva.Shape | void {
1171
+ if (!position || !width || !height) return;
1172
+
1173
+ const konvaCloud = new KonvaCloud({
1174
+ position,
1175
+ width,
1176
+ height,
1177
+ color: color || this._markupColor.HexColor,
1178
+ lineWidth: lineWidth || this.lineWidth,
1179
+ id,
1180
+ });
1181
+
1182
+ const obj = konvaCloud.ref();
1183
+ this._konvaLayer.add(obj);
1184
+ return obj;
1185
+ }
1186
+
1187
+ private addImage(
1188
+ position: { x: number; y: number },
1189
+ src: string,
1190
+ width: number,
1191
+ height: number,
1192
+ id?: string
1193
+ ): Konva.Image | void {
1194
+ if (!position || !width || !height) return;
1195
+
1196
+ const konvaImage = new KonvaImage({
1197
+ position,
1198
+ src,
1199
+ width,
1200
+ height,
1201
+ id,
1202
+ });
1203
+
1204
+ const obj = konvaImage.ref();
1205
+ this._konvaLayer.add(obj);
1206
+ return obj;
1207
+ }
1208
+ }