editor.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * JavaScript code in this page
  4. *
  5. * Copyright 2022 Mozilla Foundation
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. *
  19. * @licend The above is the entire license notice for the
  20. * JavaScript code in this page
  21. */
  22. "use strict";
  23. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.AnnotationEditor = void 0;
  27. var _tools = require("./tools.js");
  28. var _util = require("../../shared/util.js");
  29. class AnnotationEditor {
  30. #boundFocusin = this.focusin.bind(this);
  31. #boundFocusout = this.focusout.bind(this);
  32. #hasBeenSelected = false;
  33. #isEditing = false;
  34. #isInEditMode = false;
  35. _uiManager = null;
  36. #zIndex = AnnotationEditor._zIndex++;
  37. static _colorManager = new _tools.ColorManager();
  38. static _zIndex = 1;
  39. constructor(parameters) {
  40. if (this.constructor === AnnotationEditor) {
  41. (0, _util.unreachable)("Cannot initialize AnnotationEditor.");
  42. }
  43. this.parent = parameters.parent;
  44. this.id = parameters.id;
  45. this.width = this.height = null;
  46. this.pageIndex = parameters.parent.pageIndex;
  47. this.name = parameters.name;
  48. this.div = null;
  49. this._uiManager = parameters.uiManager;
  50. const {
  51. rotation,
  52. rawDims: {
  53. pageWidth,
  54. pageHeight,
  55. pageX,
  56. pageY
  57. }
  58. } = this.parent.viewport;
  59. this.rotation = rotation;
  60. this.pageDimensions = [pageWidth, pageHeight];
  61. this.pageTranslation = [pageX, pageY];
  62. const [width, height] = this.parentDimensions;
  63. this.x = parameters.x / width;
  64. this.y = parameters.y / height;
  65. this.isAttachedToDOM = false;
  66. }
  67. static get _defaultLineColor() {
  68. return (0, _util.shadow)(this, "_defaultLineColor", this._colorManager.getHexCode("CanvasText"));
  69. }
  70. addCommands(params) {
  71. this._uiManager.addCommands(params);
  72. }
  73. get currentLayer() {
  74. return this._uiManager.currentLayer;
  75. }
  76. setInBackground() {
  77. this.div.style.zIndex = 0;
  78. }
  79. setInForeground() {
  80. this.div.style.zIndex = this.#zIndex;
  81. }
  82. setParent(parent) {
  83. if (parent !== null) {
  84. this.pageIndex = parent.pageIndex;
  85. this.pageDimensions = parent.pageDimensions;
  86. }
  87. this.parent = parent;
  88. }
  89. focusin(event) {
  90. if (!this.#hasBeenSelected) {
  91. this.parent.setSelected(this);
  92. } else {
  93. this.#hasBeenSelected = false;
  94. }
  95. }
  96. focusout(event) {
  97. if (!this.isAttachedToDOM) {
  98. return;
  99. }
  100. const target = event.relatedTarget;
  101. if (target?.closest(`#${this.id}`)) {
  102. return;
  103. }
  104. event.preventDefault();
  105. if (!this.parent?.isMultipleSelection) {
  106. this.commitOrRemove();
  107. }
  108. }
  109. commitOrRemove() {
  110. if (this.isEmpty()) {
  111. this.remove();
  112. } else {
  113. this.commit();
  114. }
  115. }
  116. commit() {
  117. this.addToAnnotationStorage();
  118. }
  119. addToAnnotationStorage() {
  120. this._uiManager.addToAnnotationStorage(this);
  121. }
  122. dragstart(event) {
  123. const rect = this.parent.div.getBoundingClientRect();
  124. this.startX = event.clientX - rect.x;
  125. this.startY = event.clientY - rect.y;
  126. event.dataTransfer.setData("text/plain", this.id);
  127. event.dataTransfer.effectAllowed = "move";
  128. }
  129. setAt(x, y, tx, ty) {
  130. const [width, height] = this.parentDimensions;
  131. [tx, ty] = this.screenToPageTranslation(tx, ty);
  132. this.x = (x + tx) / width;
  133. this.y = (y + ty) / height;
  134. this.div.style.left = `${100 * this.x}%`;
  135. this.div.style.top = `${100 * this.y}%`;
  136. }
  137. translate(x, y) {
  138. const [width, height] = this.parentDimensions;
  139. [x, y] = this.screenToPageTranslation(x, y);
  140. this.x += x / width;
  141. this.y += y / height;
  142. this.div.style.left = `${100 * this.x}%`;
  143. this.div.style.top = `${100 * this.y}%`;
  144. }
  145. screenToPageTranslation(x, y) {
  146. switch (this.parentRotation) {
  147. case 90:
  148. return [y, -x];
  149. case 180:
  150. return [-x, -y];
  151. case 270:
  152. return [-y, x];
  153. default:
  154. return [x, y];
  155. }
  156. }
  157. get parentScale() {
  158. return this._uiManager.viewParameters.realScale;
  159. }
  160. get parentRotation() {
  161. return this._uiManager.viewParameters.rotation;
  162. }
  163. get parentDimensions() {
  164. const {
  165. realScale
  166. } = this._uiManager.viewParameters;
  167. const [pageWidth, pageHeight] = this.pageDimensions;
  168. return [pageWidth * realScale, pageHeight * realScale];
  169. }
  170. setDims(width, height) {
  171. const [parentWidth, parentHeight] = this.parentDimensions;
  172. this.div.style.width = `${100 * width / parentWidth}%`;
  173. this.div.style.height = `${100 * height / parentHeight}%`;
  174. }
  175. fixDims() {
  176. const {
  177. style
  178. } = this.div;
  179. const {
  180. height,
  181. width
  182. } = style;
  183. const widthPercent = width.endsWith("%");
  184. const heightPercent = height.endsWith("%");
  185. if (widthPercent && heightPercent) {
  186. return;
  187. }
  188. const [parentWidth, parentHeight] = this.parentDimensions;
  189. if (!widthPercent) {
  190. style.width = `${100 * parseFloat(width) / parentWidth}%`;
  191. }
  192. if (!heightPercent) {
  193. style.height = `${100 * parseFloat(height) / parentHeight}%`;
  194. }
  195. }
  196. getInitialTranslation() {
  197. return [0, 0];
  198. }
  199. render() {
  200. this.div = document.createElement("div");
  201. this.div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360);
  202. this.div.className = this.name;
  203. this.div.setAttribute("id", this.id);
  204. this.div.setAttribute("tabIndex", 0);
  205. this.setInForeground();
  206. this.div.addEventListener("focusin", this.#boundFocusin);
  207. this.div.addEventListener("focusout", this.#boundFocusout);
  208. const [tx, ty] = this.getInitialTranslation();
  209. this.translate(tx, ty);
  210. (0, _tools.bindEvents)(this, this.div, ["dragstart", "pointerdown"]);
  211. return this.div;
  212. }
  213. pointerdown(event) {
  214. const {
  215. isMac
  216. } = _util.FeatureTest.platform;
  217. if (event.button !== 0 || event.ctrlKey && isMac) {
  218. event.preventDefault();
  219. return;
  220. }
  221. if (event.ctrlKey && !isMac || event.shiftKey || event.metaKey && isMac) {
  222. this.parent.toggleSelected(this);
  223. } else {
  224. this.parent.setSelected(this);
  225. }
  226. this.#hasBeenSelected = true;
  227. }
  228. getRect(tx, ty) {
  229. const scale = this.parentScale;
  230. const [pageWidth, pageHeight] = this.pageDimensions;
  231. const [pageX, pageY] = this.pageTranslation;
  232. const shiftX = tx / scale;
  233. const shiftY = ty / scale;
  234. const x = this.x * pageWidth;
  235. const y = this.y * pageHeight;
  236. const width = this.width * pageWidth;
  237. const height = this.height * pageHeight;
  238. switch (this.rotation) {
  239. case 0:
  240. return [x + shiftX + pageX, pageHeight - y - shiftY - height + pageY, x + shiftX + width + pageX, pageHeight - y - shiftY + pageY];
  241. case 90:
  242. return [x + shiftY + pageX, pageHeight - y + shiftX + pageY, x + shiftY + height + pageX, pageHeight - y + shiftX + width + pageY];
  243. case 180:
  244. return [x - shiftX - width + pageX, pageHeight - y + shiftY + pageY, x - shiftX + pageX, pageHeight - y + shiftY + height + pageY];
  245. case 270:
  246. return [x - shiftY - height + pageX, pageHeight - y - shiftX - width + pageY, x - shiftY + pageX, pageHeight - y - shiftX + pageY];
  247. default:
  248. throw new Error("Invalid rotation");
  249. }
  250. }
  251. getRectInCurrentCoords(rect, pageHeight) {
  252. const [x1, y1, x2, y2] = rect;
  253. const width = x2 - x1;
  254. const height = y2 - y1;
  255. switch (this.rotation) {
  256. case 0:
  257. return [x1, pageHeight - y2, width, height];
  258. case 90:
  259. return [x1, pageHeight - y1, height, width];
  260. case 180:
  261. return [x2, pageHeight - y1, width, height];
  262. case 270:
  263. return [x2, pageHeight - y2, height, width];
  264. default:
  265. throw new Error("Invalid rotation");
  266. }
  267. }
  268. onceAdded() {}
  269. isEmpty() {
  270. return false;
  271. }
  272. enableEditMode() {
  273. this.#isInEditMode = true;
  274. }
  275. disableEditMode() {
  276. this.#isInEditMode = false;
  277. }
  278. isInEditMode() {
  279. return this.#isInEditMode;
  280. }
  281. shouldGetKeyboardEvents() {
  282. return false;
  283. }
  284. needsToBeRebuilt() {
  285. return this.div && !this.isAttachedToDOM;
  286. }
  287. rebuild() {
  288. this.div?.addEventListener("focusin", this.#boundFocusin);
  289. }
  290. serialize() {
  291. (0, _util.unreachable)("An editor must be serializable");
  292. }
  293. static deserialize(data, parent, uiManager) {
  294. const editor = new this.prototype.constructor({
  295. parent,
  296. id: parent.getNextId(),
  297. uiManager
  298. });
  299. editor.rotation = data.rotation;
  300. const [pageWidth, pageHeight] = editor.pageDimensions;
  301. const [x, y, width, height] = editor.getRectInCurrentCoords(data.rect, pageHeight);
  302. editor.x = x / pageWidth;
  303. editor.y = y / pageHeight;
  304. editor.width = width / pageWidth;
  305. editor.height = height / pageHeight;
  306. return editor;
  307. }
  308. remove() {
  309. this.div.removeEventListener("focusin", this.#boundFocusin);
  310. this.div.removeEventListener("focusout", this.#boundFocusout);
  311. if (!this.isEmpty()) {
  312. this.commit();
  313. }
  314. this.parent.remove(this);
  315. }
  316. select() {
  317. this.div?.classList.add("selectedEditor");
  318. }
  319. unselect() {
  320. this.div?.classList.remove("selectedEditor");
  321. }
  322. updateParams(type, value) {}
  323. disableEditing() {}
  324. enableEditing() {}
  325. get propertiesToUpdate() {
  326. return {};
  327. }
  328. get contentDiv() {
  329. return this.div;
  330. }
  331. get isEditing() {
  332. return this.#isEditing;
  333. }
  334. set isEditing(value) {
  335. this.#isEditing = value;
  336. if (value) {
  337. this.parent.setSelected(this);
  338. this.parent.setActiveEditor(this);
  339. } else {
  340. this.parent.setActiveEditor(null);
  341. }
  342. }
  343. }
  344. exports.AnnotationEditor = AnnotationEditor;