jsmind.draggable.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. /*
  2. * Released under BSD License
  3. * Copyright (c) 2014-2021 hizzgdev@163.com
  4. *
  5. * Project Home:
  6. * https://github.com/hizzgdev/jsmind/
  7. */
  8. (function ($w) {
  9. 'use strict';
  10. var $d = $w.document;
  11. var __name__ = 'jsMind';
  12. var jsMind = $w[__name__];
  13. if (!jsMind) { return; }
  14. if (typeof jsMind.draggable != 'undefined') { return; }
  15. var jdom = jsMind.util.dom;
  16. var clear_selection = 'getSelection' in $w ? function () {
  17. $w.getSelection().removeAllRanges();
  18. } : function () {
  19. $d.selection.empty();
  20. };
  21. var options = {
  22. line_width: 5,
  23. lookup_delay: 500,
  24. lookup_interval: 80
  25. };
  26. jsMind.draggable = function (jm) {
  27. this.jm = jm;
  28. this.e_canvas = null;
  29. this.canvas_ctx = null;
  30. this.shadow = null;
  31. this.shadow_w = 0;
  32. this.shadow_h = 0;
  33. this.active_node = null;
  34. this.target_node = null;
  35. this.target_direct = null;
  36. this.client_w = 0;
  37. this.client_h = 0;
  38. this.offset_x = 0;
  39. this.offset_y = 0;
  40. this.hlookup_delay = 0;
  41. this.hlookup_timer = 0;
  42. this.capture = false;
  43. this.moved = false;
  44. };
  45. jsMind.draggable.prototype = {
  46. init: function () {
  47. this._create_canvas();
  48. this._create_shadow();
  49. this._event_bind();
  50. },
  51. resize: function () {
  52. this.jm.view.e_nodes.appendChild(this.shadow);
  53. this.e_canvas.width = this.jm.view.size.w;
  54. this.e_canvas.height = this.jm.view.size.h;
  55. },
  56. _create_canvas: function () {
  57. var c = $d.createElement('canvas');
  58. this.jm.view.e_panel.appendChild(c);
  59. var ctx = c.getContext('2d');
  60. this.e_canvas = c;
  61. this.canvas_ctx = ctx;
  62. },
  63. _create_shadow: function () {
  64. var s = $d.createElement('jmnode');
  65. s.style.visibility = 'hidden';
  66. s.style.zIndex = '3';
  67. s.style.cursor = 'move';
  68. s.style.opacity = '0.7';
  69. this.shadow = s;
  70. },
  71. reset_shadow: function (el) {
  72. var s = this.shadow.style;
  73. this.shadow.innerHTML = el.innerHTML;
  74. s.left = el.style.left;
  75. s.top = el.style.top;
  76. s.width = el.style.width;
  77. s.height = el.style.height;
  78. s.backgroundImage = el.style.backgroundImage;
  79. s.backgroundSize = el.style.backgroundSize;
  80. s.transform = el.style.transform;
  81. this.shadow_w = this.shadow.clientWidth;
  82. this.shadow_h = this.shadow.clientHeight;
  83. },
  84. show_shadow: function () {
  85. if (!this.moved) {
  86. this.shadow.style.visibility = 'visible';
  87. }
  88. },
  89. hide_shadow: function () {
  90. this.shadow.style.visibility = 'hidden';
  91. },
  92. _magnet_shadow: function (node) {
  93. if (!!node) {
  94. this.canvas_ctx.lineWidth = options.line_width;
  95. this.canvas_ctx.strokeStyle = 'rgba(0,0,0,0.3)';
  96. this.canvas_ctx.lineCap = 'round';
  97. this._clear_lines();
  98. this._canvas_lineto(node.sp.x, node.sp.y, node.np.x, node.np.y);
  99. }
  100. },
  101. _clear_lines: function () {
  102. this.canvas_ctx.clearRect(0, 0, this.jm.view.size.w, this.jm.view.size.h);
  103. },
  104. _canvas_lineto: function (x1, y1, x2, y2) {
  105. this.canvas_ctx.beginPath();
  106. this.canvas_ctx.moveTo(x1, y1);
  107. this.canvas_ctx.lineTo(x2, y2);
  108. this.canvas_ctx.stroke();
  109. },
  110. _lookup_close_node: function () {
  111. var root = this.jm.get_root();
  112. var root_location = root.get_location();
  113. var root_size = root.get_size();
  114. var root_x = root_location.x + root_size.w / 2;
  115. var sw = this.shadow_w;
  116. var sh = this.shadow_h;
  117. var sx = this.shadow.offsetLeft;
  118. var sy = this.shadow.offsetTop;
  119. var ns, nl;
  120. var direct = (sx + sw / 2) >= root_x ?
  121. jsMind.direction.right : jsMind.direction.left;
  122. var nodes = this.jm.mind.nodes;
  123. var node = null;
  124. var layout = this.jm.layout;
  125. var min_distance = Number.MAX_VALUE;
  126. var distance = 0;
  127. var closest_node = null;
  128. var closest_p = null;
  129. var shadow_p = null;
  130. for (var nodeid in nodes) {
  131. var np, sp;
  132. node = nodes[nodeid];
  133. if (node.isroot || node.direction == direct) {
  134. if (node.id == this.active_node.id) {
  135. continue;
  136. }
  137. if (!layout.is_visible(node)) {
  138. continue;
  139. }
  140. ns = node.get_size();
  141. nl = node.get_location();
  142. if (direct == jsMind.direction.right) {
  143. if (sx - nl.x - ns.w <= 0) { continue; }
  144. distance = Math.abs(sx - nl.x - ns.w) + Math.abs(sy + sh / 2 - nl.y - ns.h / 2);
  145. np = { x: nl.x + ns.w - options.line_width, y: nl.y + ns.h / 2 };
  146. sp = { x: sx + options.line_width, y: sy + sh / 2 };
  147. } else {
  148. if (nl.x - sx - sw <= 0) { continue; }
  149. distance = Math.abs(sx + sw - nl.x) + Math.abs(sy + sh / 2 - nl.y - ns.h / 2);
  150. np = { x: nl.x + options.line_width, y: nl.y + ns.h / 2 };
  151. sp = { x: sx + sw - options.line_width, y: sy + sh / 2 };
  152. }
  153. if (distance < min_distance) {
  154. closest_node = node;
  155. closest_p = np;
  156. shadow_p = sp;
  157. min_distance = distance;
  158. }
  159. }
  160. }
  161. var result_node = null;
  162. if (!!closest_node) {
  163. result_node = {
  164. node: closest_node,
  165. direction: direct,
  166. sp: shadow_p,
  167. np: closest_p
  168. };
  169. }
  170. return result_node;
  171. },
  172. lookup_close_node: function () {
  173. var node_data = this._lookup_close_node();
  174. if (!!node_data) {
  175. this._magnet_shadow(node_data);
  176. this.target_node = node_data.node;
  177. this.target_direct = node_data.direction;
  178. }
  179. },
  180. _event_bind: function () {
  181. var jd = this;
  182. var container = this.jm.view.container;
  183. jdom.add_event(container, 'mousedown', function (e) {
  184. var evt = e || event;
  185. jd.dragstart.call(jd, evt);
  186. });
  187. jdom.add_event(container, 'mousemove', function (e) {
  188. var evt = e || event;
  189. jd.drag.call(jd, evt);
  190. });
  191. jdom.add_event(container, 'mouseup', function (e) {
  192. var evt = e || event;
  193. jd.dragend.call(jd, evt);
  194. });
  195. jdom.add_event(container, 'touchstart', function (e) {
  196. var evt = e || event;
  197. jd.dragstart.call(jd, evt);
  198. });
  199. jdom.add_event(container, 'touchmove', function (e) {
  200. var evt = e || event;
  201. jd.drag.call(jd, evt);
  202. });
  203. jdom.add_event(container, 'touchend', function (e) {
  204. var evt = e || event;
  205. jd.dragend.call(jd, evt);
  206. });
  207. },
  208. dragstart: function (e) {
  209. if (!this.jm.get_editable()) { return; }
  210. if (this.capture) { return; }
  211. this.active_node = null;
  212. var jview = this.jm.view;
  213. var el = e.target || event.srcElement;
  214. if (el.tagName.toLowerCase() != 'jmnode') { return; }
  215. var nodeid = jview.get_binded_nodeid(el);
  216. if (!!nodeid) {
  217. var node = this.jm.get_node(nodeid);
  218. if (!node.isroot) {
  219. this.reset_shadow(el);
  220. this.active_node = node;
  221. this.offset_x = (e.clientX || e.touches[0].clientX) / jview.actualZoom - el.offsetLeft;
  222. this.offset_y = (e.clientY || e.touches[0].clientY) / jview.actualZoom - el.offsetTop;
  223. this.client_hw = Math.floor(el.clientWidth / 2);
  224. this.client_hh = Math.floor(el.clientHeight / 2);
  225. if (this.hlookup_delay != 0) {
  226. $w.clearTimeout(this.hlookup_delay);
  227. }
  228. if (this.hlookup_timer != 0) {
  229. $w.clearInterval(this.hlookup_timer);
  230. }
  231. var jd = this;
  232. this.hlookup_delay = $w.setTimeout(function () {
  233. jd.hlookup_delay = 0;
  234. jd.hlookup_timer = $w.setInterval(function () {
  235. jd.lookup_close_node.call(jd);
  236. }, options.lookup_interval);
  237. }, options.lookup_delay);
  238. this.capture = true;
  239. }
  240. }
  241. },
  242. drag: function (e) {
  243. if (!this.jm.get_editable()) { return; }
  244. if (this.capture) {
  245. e.preventDefault();
  246. this.show_shadow();
  247. this.moved = true;
  248. clear_selection();
  249. var jview = this.jm.view;
  250. var px = (e.clientX || e.touches[0].clientX) / jview.actualZoom - this.offset_x;
  251. var py = (e.clientY || e.touches[0].clientY) / jview.actualZoom - this.offset_y;
  252. this.shadow.style.left = px + 'px';
  253. this.shadow.style.top = py + 'px';
  254. clear_selection();
  255. }
  256. },
  257. dragend: function (e) {
  258. if (!this.jm.get_editable()) { return; }
  259. if (this.capture) {
  260. if (this.hlookup_delay != 0) {
  261. $w.clearTimeout(this.hlookup_delay);
  262. this.hlookup_delay = 0;
  263. this._clear_lines();
  264. }
  265. if (this.hlookup_timer != 0) {
  266. $w.clearInterval(this.hlookup_timer);
  267. this.hlookup_timer = 0;
  268. this._clear_lines();
  269. }
  270. if (this.moved) {
  271. var src_node = this.active_node;
  272. var target_node = this.target_node;
  273. var target_direct = this.target_direct;
  274. this.move_node(src_node, target_node, target_direct);
  275. }
  276. this.hide_shadow();
  277. }
  278. this.moved = false;
  279. this.capture = false;
  280. },
  281. move_node: function (src_node, target_node, target_direct) {
  282. var shadow_h = this.shadow.offsetTop;
  283. if (!!target_node && !!src_node && !jsMind.node.inherited(src_node, target_node)) {
  284. // lookup before_node
  285. var sibling_nodes = target_node.children;
  286. var sc = sibling_nodes.length;
  287. var node = null;
  288. var delta_y = Number.MAX_VALUE;
  289. var node_before = null;
  290. var beforeid = '_last_';
  291. while (sc--) {
  292. node = sibling_nodes[sc];
  293. if (node.direction == target_direct && node.id != src_node.id) {
  294. var dy = node.get_location().y - shadow_h;
  295. if (dy > 0 && dy < delta_y) {
  296. delta_y = dy;
  297. node_before = node;
  298. beforeid = '_first_';
  299. }
  300. }
  301. }
  302. if (!!node_before) { beforeid = node_before.id; }
  303. this.jm.move_node(src_node.id, beforeid, target_node.id, target_direct);
  304. }
  305. this.active_node = null;
  306. this.target_node = null;
  307. this.target_direct = null;
  308. },
  309. jm_event_handle: function (type, data) {
  310. if (type === jsMind.event_type.resize) {
  311. this.resize();
  312. }
  313. }
  314. };
  315. var draggable_plugin = new jsMind.plugin('draggable', function (jm) {
  316. var jd = new jsMind.draggable(jm);
  317. jd.init();
  318. jm.add_event_listener(function (type, data) {
  319. jd.jm_event_handle.call(jd, type, data);
  320. });
  321. });
  322. jsMind.register_plugin(draggable_plugin);
  323. })(window);