spacedeck_whiteboard.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  1. /*
  2. Spacedeck Whiteboard Directive
  3. This module registers a custom Vue directive that handles Whiteboard sections.
  4. */
  5. function setup_whiteboard_directives() {
  6. if ('ontouchstart' in window) {
  7. var edown = "touchstart";
  8. var emove = "touchmove";
  9. var eup = "touchend";
  10. } else {
  11. var edown = "mousedown";
  12. var emove = "mousemove";
  13. var eup = "mouseup";
  14. }
  15. Vue.directive('sd-whiteboard', {
  16. bind: function () {
  17. var el = this.el;
  18. $(el).on(edown, ".artifact", this.handle_mouse_down_artifact.bind(this));
  19. $(el).on("dblclick", ".artifact", this.handle_double_click_artifact.bind(this));
  20. $(el).on("keyup", ".artifact", this.handle_key_up_artifact.bind(this));
  21. $(el).on("keydown", ".artifact", this.handle_key_down_artifact.bind(this));
  22. $(el).bind(edown, this.handle_mouse_down_space.bind(this));
  23. $(el).bind(emove, this.handle_mouse_move.bind(this));
  24. $(el).bind(eup, this.handle_mouse_up_space.bind(this));
  25. $(el).bind("wheel", this.handle_wheel_space.bind(this));
  26. $(document.body).bind("mouseleave", this.handle_mouse_leave.bind(this));
  27. $(el).find(".handle.resize-nw").bind(edown, function(e){this.handle_transform_mouse_down(e,1,1)}.bind(this));
  28. $(el).find(".handle.resize-n").bind(edown, function(e){this.handle_transform_mouse_down(e,0.5,1)}.bind(this));
  29. $(el).find(".handle.resize-ne").bind(edown, function(e){this.handle_transform_mouse_down(e,0,1)}.bind(this));
  30. $(el).find(".handle.resize-e").bind(edown, function(e){this.handle_transform_mouse_down(e,0,0.5)}.bind(this));
  31. $(el).find(".handle.resize-se").bind(edown, function(e){this.handle_transform_mouse_down(e,0,0)}.bind(this));
  32. $(el).find(".handle.resize-s").bind(edown, function(e){this.handle_transform_mouse_down(e,0.5,0)}.bind(this));
  33. $(el).find(".handle.resize-sw").bind(edown, function(e){this.handle_transform_mouse_down(e,1,0)}.bind(this));
  34. $(el).find(".handle.resize-w").bind(edown, function(e){this.handle_transform_mouse_down(e,1,0.5)}.bind(this));
  35. $(el).find(".edge-handle.resize-n").bind(edown, function(e){this.handle_transform_mouse_down(e,0.5,1)}.bind(this));
  36. $(el).find(".edge-handle.resize-s").bind(edown, function(e){this.handle_transform_mouse_down(e,0.5,0)}.bind(this));
  37. $(el).find(".edge-handle.resize-e").bind(edown, function(e){this.handle_transform_mouse_down(e,0,0.5)}.bind(this));
  38. $(el).find(".edge-handle.resize-w").bind(edown, function(e){this.handle_transform_mouse_down(e,1,0.5)}.bind(this));
  39. $(el).on(edown, ".vector-handle", function(e){this.handle_vector_transform_mouse_down(e)}.bind(this));
  40. var $scope = this.vm.$root;
  41. this.space_zoom = 1;
  42. this.artifacts_before_transaction = [];
  43. $scope.active_tool = "pointer";
  44. },
  45. update: function () {
  46. },
  47. unbind: function () {
  48. // do clean up work
  49. // e.g. remove event listeners added in bind()
  50. var el = this.el;
  51. $(el).off(edown+" "+emove+" "+eup+" "+"keyup keydown mouseleave");
  52. $(document.body).unbind("mouseleave");
  53. },
  54. handle_key_down_artifact: function(evt) {
  55. var $scope = this.vm.$root;
  56. },
  57. handle_key_up_artifact: function(evt) {
  58. var $scope = this.vm.$root;
  59. },
  60. handle_mouse_down_artifact: function(evt) {
  61. var $scope = this.vm.$root;
  62. if (!$scope.editing_artifact_id) {
  63. evt.preventDefault();
  64. evt.stopPropagation();
  65. }
  66. var a = $scope.find_artifact_by_id(evt.currentTarget.id.replace("artifact-",""));
  67. if ($scope.active_tool == "zoom") return;
  68. if ($scope.active_tool == "eyedrop") {
  69. var arts = $scope.selected_artifacts();
  70. if (!$scope.is_selected(a) && arts.length > 0) {
  71. // copy style from clicked artifact to selected artifacts
  72. $scope.begin_transaction();
  73. $scope.update_selected_artifacts(function(selected_artifact) {
  74. selected_artifact.style = _.clone(a.style);
  75. });
  76. $scope.active_tool = "pointer";
  77. return;
  78. }
  79. }
  80. if ($scope.active_tool == "pan") {
  81. this.start_pan(evt);
  82. return;
  83. }
  84. if ($scope.active_tool == "pointer") {
  85. if (!$scope.is_selected(a) || evt.shiftKey) {
  86. this.select(evt,a);
  87. }
  88. // copy via alt+move
  89. if (evt.altKey) {
  90. a = $scope.clone_artifact(a);
  91. this.select(evt,a);
  92. }
  93. }
  94. $scope.begin_transaction();
  95. var cursor = this.cursor_point_to_space(evt);
  96. $scope.mouse_ox = cursor.x;
  97. $scope.mouse_oy = cursor.y;
  98. $scope.mouse_moved = false;
  99. this.mouse_state = "move";
  100. evt.stopPropagation();
  101. },
  102. handle_double_click_artifact: function(evt) {
  103. var $scope = this.vm.$root;
  104. var a = $scope.find_artifact_by_id(evt.currentTarget.id.replace("artifact-",""));
  105. if (!a) return;
  106. if (a.payload_uri) {
  107. $scope.download_selected_artifacts();
  108. }
  109. $scope.toggle_selected_artifact_editing(true);
  110. },
  111. handle_transform_mouse_down: function(evt,origin_x,origin_y) {
  112. evt.stopPropagation();
  113. evt.preventDefault();
  114. var $scope = this.vm.$root;
  115. $scope.begin_transaction();
  116. var cursor = this.cursor_point_to_space(evt);
  117. this.mouse_state = "transform";
  118. $scope.mouse_ox = cursor.x;
  119. $scope.mouse_oy = cursor.y;
  120. $scope.transform_ox = origin_x;
  121. $scope.transform_oy = origin_y;
  122. },
  123. handle_vector_transform_mouse_down: function(evt) {
  124. evt.stopPropagation();
  125. evt.preventDefault();
  126. var $scope = this.vm.$root;
  127. var idx = parseInt($(evt.currentTarget).attr("data-idx"));
  128. $scope.selected_control_point_idx = idx;
  129. $scope.begin_transaction();
  130. var cursor = this.cursor_point_to_space(evt);
  131. this.mouse_state = "vector_transform";
  132. $scope.mouse_ox = cursor.x;
  133. $scope.mouse_oy = cursor.y;
  134. //$scope.transform_ox = origin_x;
  135. //$scope.transform_oy = origin_y;
  136. },
  137. handle_wheel_space: function(evt) {
  138. var $scope = this.vm.$root;
  139. if (!evt.ctrlKey && !evt.shiftKey) return;
  140. evt.preventDefault();
  141. evt.stopPropagation();
  142. var amount = 1;
  143. var dy = evt.originalEvent.deltaY;
  144. if (dy>0) {
  145. amount=1.2;
  146. if ($scope.viewport_zoom<=0.05) return false;
  147. } else if (dy<0) {
  148. amount=0.9;
  149. if ($scope.viewport_zoom>=2) return false;
  150. } else {
  151. return false;
  152. }
  153. $scope.zoom_to_cursor(evt,amount);
  154. },
  155. handle_mouse_down_space: function(evt) {
  156. if (evt.target != evt.currentTarget && !_.include(["wrapper"],evt.target.className)) return;
  157. var $scope = this.vm.$root;
  158. $scope.opened_dialog="none";
  159. var cursor = this.cursor_point_to_space(evt);
  160. $scope.mouse_ox = cursor.x;
  161. $scope.mouse_oy = cursor.y;
  162. if (evt.which == 2 || evt.buttons == 4) {
  163. $scope.active_tool = "pan";
  164. }
  165. if ($scope.active_tool=="note") {
  166. this.deselect();
  167. this.mouse_state = "transform";
  168. $scope.mouse_state = this.mouse_state;
  169. this.start_adding_note(evt);
  170. return;
  171. } else if ($scope.active_tool=="arrow") {
  172. this.deselect();
  173. this.mouse_state = "vector_transform";
  174. $scope.mouse_state = this.mouse_state;
  175. this.start_drawing_arrow(evt);
  176. return;
  177. } else if ($scope.active_tool=="line") {
  178. this.deselect();
  179. this.mouse_state = "vector_transform";
  180. $scope.mouse_state = this.mouse_state;
  181. this.start_drawing_line(evt);
  182. return;
  183. } else if ($scope.active_tool=="scribble") {
  184. this.deselect();
  185. this.mouse_state = "scribble";
  186. $scope.mouse_state = this.mouse_state;
  187. this.start_drawing_scribble(evt);
  188. return;
  189. } else if ($scope.active_tool=="zoom") {
  190. if (evt.altKey) {
  191. $scope.zoom_out();
  192. } else {
  193. $scope.zoom_in();
  194. }
  195. return;
  196. } else if ($scope.active_tool=="pointer") {
  197. this.mouse_state = "lasso";
  198. this.start_lasso(evt);
  199. } else if ($scope.active_tool=="zone") {
  200. this.deselect();
  201. this.mouse_state = "transform";
  202. $scope.start_adding_zone(evt);
  203. return;
  204. } else if ($scope.active_tool=="image") {
  205. this.deselect();
  206. this.mouse_state = "transform";
  207. $scope.start_adding_placeholder(evt);
  208. return;
  209. } else if ($scope.active_tool=="pan") {
  210. this.start_pan(evt);
  211. return;
  212. }
  213. if ($scope.selection_metrics.count>0) {
  214. this._no_artifact_toolbar_this_round = true;
  215. }
  216. this.deselect();
  217. },
  218. start_pan: function(evt) {
  219. var $scope = this.vm.$root;
  220. el = $("#space")[0];
  221. if (el) {
  222. this.mouse_state = "pan";
  223. this.old_panx = el.scrollLeft;
  224. this.old_pany = el.scrollTop;
  225. }
  226. var cursor = this.cursor_point_to_space(evt);
  227. $scope.mouse_ox = cursor.x;
  228. $scope.mouse_oy = cursor.y;
  229. $scope.mouse_moved = false;
  230. },
  231. deselect: function() {
  232. var $scope = this.vm.$root;
  233. $scope.deselect();
  234. },
  235. select: function(evt, a) {
  236. var $scope = this.vm.$root;
  237. $scope.select(evt, a);
  238. },
  239. multi_select: function(arts) {
  240. var $scope = this.vm.$root;
  241. $scope.multi_select(arts);
  242. },
  243. start_lasso: function(evt) {
  244. var point = this.cursor_point_to_space(evt);
  245. this.lasso = {
  246. x: point.x,
  247. y: point.y,
  248. w: 0,
  249. h: 0
  250. }
  251. },
  252. rects_intersecting: function(r1,r2) {
  253. if (!r1 || !r2) return false;
  254. if ( (r1.x+r1.w < r2.x)
  255. || (r1.x > r2.x+r2.w)
  256. || (r1.y+r1.h < r2.y)
  257. || (r1.y > r2.y+r2.h) ) return false;
  258. return true;
  259. },
  260. artifacts_in_rect: function(rect) {
  261. if (!rect) return [];
  262. var $scope = this.vm.$root;
  263. return _.filter($scope.active_space_artifacts, function(a) {
  264. return this.rects_intersecting(a, rect);
  265. }.bind(this));
  266. },
  267. abs_rect: function(rect) {
  268. var res = {
  269. x: rect.x,
  270. y: rect.y,
  271. w: Math.abs(rect.w),
  272. h: Math.abs(rect.h)
  273. }
  274. if (rect.w<0) res.x+=rect.w;
  275. if (rect.h<0) res.y+=rect.h;
  276. return res;
  277. },
  278. lasso_style: function() {
  279. var $scope = this.vm.$root;
  280. if (!this.lasso) return "";
  281. var lasso_scaled = {
  282. x:this.lasso.x,
  283. y:this.lasso.y,
  284. w:this.lasso.w*$scope.viewport_zoom,
  285. h:this.lasso.h*$scope.viewport_zoom
  286. }
  287. lasso_scaled = this.abs_rect(lasso_scaled);
  288. lasso_scaled.x += $scope.bounds_margin_horiz;
  289. lasso_scaled.y += $scope.bounds_margin_vert;
  290. var s = "left:" +lasso_scaled.x+"px;";
  291. s += "top:" +lasso_scaled.y+"px;";
  292. s += "width:" +lasso_scaled.w+"px;";
  293. s += "height:"+lasso_scaled.h+"px;";
  294. s += "opacity: 1;";
  295. return s;
  296. },
  297. render_lasso: function() {
  298. if (!this.lasso) {
  299. $("#lasso").hide();
  300. return;
  301. }
  302. $("#lasso").attr("style", this.lasso_style());
  303. $("#lasso").show();
  304. },
  305. cursor_point_to_space: function(evt) {
  306. var $scope = this.vm.$root;
  307. var offset = {left: 0, top: 0};
  308. evt = fixup_touches(evt);
  309. return {
  310. x: (parseInt(evt.pageX) - parseInt(offset.left) - $scope.bounds_margin_horiz) / this.space_zoom,
  311. y: (parseInt(evt.pageY) - parseInt(offset.top) - $scope.bounds_margin_vert) / this.space_zoom
  312. };
  313. },
  314. rect_to_points: function(rect) {
  315. return [
  316. {x:rect.x,y:rect.y},
  317. {x:rect.x+rect.w,y:rect.y},
  318. {x:rect.x,y:rect.y+rect.h},
  319. {x:rect.x+rect.w,y:rect.y+rect.h}
  320. ];
  321. },
  322. old_selection_rect: function() {
  323. var $scope = this.vm.$root;
  324. var selected = $scope.selected_artifacts().map(function(a){
  325. return $scope.find_artifact_before_transaction(a);
  326. }.bind(this));
  327. return $scope.enclosing_rect(selected);
  328. },
  329. snap_point: function(x,y,snap_middle) {
  330. var $scope = this.vm.$root;
  331. var TOL = 8;
  332. var dists = [];
  333. if (snap_middle) {
  334. dists.push([[x-window.innerWidth/2,Math.abs(y-window.innerHeight/2)],[x-window.innerWidth/2,Math.abs(y-window.innerHeight/2)]]);
  335. }
  336. if ($scope.grid_active) {
  337. // snap to grid
  338. var gw = $scope.grid.spacing/$scope.grid.subdivisions;
  339. var gh = $scope.grid.spacing/$scope.grid.subdivisions;
  340. var sx1 = parseInt(x/gw)*gw;
  341. var sy1 = parseInt(y/gh)*gh;
  342. var sx2 = (parseInt(x/gw)+1)*gw;
  343. var sy2 = (parseInt(y/gh)+1)*gh;
  344. dists = [[[Math.abs(sx1-x),sx1], [Math.abs(sy1-y),sy1]],
  345. [[Math.abs(sx2-x),sx2], [Math.abs(sy2-y),sy2]]];
  346. } else {
  347. // snap to other artifacts
  348. dists = $scope.unselected_artifacts().map(function(a){
  349. var r = this.rect_to_points(a);
  350. var xd1 = Math.abs(r[0].x-x);
  351. var xd2 = Math.abs(r[1].x-x);
  352. var xd3 = Math.abs(r[0].x+a.w/2 - x);
  353. var yd1 = Math.abs(r[0].y-y);
  354. var yd2 = Math.abs(r[2].y-y);
  355. var yd3 = Math.abs(r[0].y+a.h/2 - y);
  356. if (!snap_middle) {
  357. if (xd2<xd1) {
  358. var xd = xd2;
  359. var sx = r[1].x;
  360. } else {
  361. var xd = xd1;
  362. var sx = r[0].x;
  363. }
  364. if (yd2<yd1) {
  365. var yd = yd2;
  366. var sy = r[2].y;
  367. } else {
  368. var yd = yd1;
  369. var sy = r[0].y;
  370. }
  371. }
  372. if (snap_middle) {
  373. var xd = xd3;
  374. var sx = r[0].x+a.w/2;
  375. var yd = yd3;
  376. var sy = r[0].y+a.h/2;
  377. }
  378. return [[xd,sx],[yd,sy]];
  379. }.bind(this));
  380. }
  381. // snap to space edges
  382. dists.push([[Math.abs(x),0],[Math.abs(y),0]]);
  383. //dists.push([[Math.abs(dims.width-x),dims.width],[Math.abs(dims.height-y),dims.height]]);
  384. var unzipped = _.unzip(dists);
  385. var xdists = _.sortBy(unzipped[0], function(pair) {return pair[0];});
  386. var ydists = _.sortBy(unzipped[1], function(pair) {return pair[0];});
  387. var results = {snapx:xdists[0], snapy:ydists[0]};
  388. if (!xdists[0] || xdists[0][0]>TOL) {
  389. results.snapx = [0,x]; // distance, coordinate
  390. } else {
  391. //$scope.snap_ruler_x = xdists[0][1];
  392. }
  393. if (!ydists[0] || ydists[0][0]>TOL) {
  394. results.snapy = [0,y];
  395. } else {
  396. //$scope.snap_ruler_y = ydists[0][1];
  397. }
  398. return results;
  399. },
  400. offset_point_in_wrapper: function(point) {
  401. var $scope = this.vm.$root;
  402. var section_el = $(this.el)[0];
  403. var z = $scope.viewport_zoom;
  404. var pt = parseInt($("#space").css("padding-top"));
  405. point.y=(point.y+section_el.scrollTop-pt)/z;
  406. point.x=(point.x+section_el.scrollLeft)/z;
  407. return point;
  408. },
  409. start_drawing_scribble: function(evt) {
  410. evt.preventDefault();
  411. evt.stopPropagation();
  412. var $scope = this.vm.$root;
  413. var point = this.offset_point_in_wrapper(this.cursor_point_to_space(evt));
  414. var z = $scope.highest_z()+1;
  415. $scope.deselect();
  416. var a = {
  417. space_id: $scope.active_space._id,
  418. mime: "x-spacedeck/vector",
  419. description: "",
  420. control_points: [{dx:0,dy:0}],
  421. x: point.x,
  422. y: point.y,
  423. z: z,
  424. w: 64,
  425. h: 64,
  426. stroke_color: "#000000",
  427. stroke: 2,
  428. shape: "scribble"
  429. };
  430. $scope.save_artifact(a, function(saved_a) {
  431. $scope.update_board_artifact_viewmodel(saved_a);
  432. $scope.active_space_artifacts.push(saved_a);
  433. this.select(evt,saved_a);
  434. //$scope.tool_artifact = a;
  435. $scope.transform_ox = 0;
  436. $scope.transform_oy = 0;
  437. $scope.begin_transaction();
  438. }.bind(this));
  439. },
  440. start_drawing_arrow: function(evt) {
  441. evt.preventDefault();
  442. evt.stopPropagation();
  443. var $scope = this.vm.$root;
  444. var point = this.cursor_point_to_space(evt);
  445. this.offset_point_in_wrapper(point);
  446. var z = $scope.highest_z()+1;
  447. var a = {
  448. space_id: $scope.active_space._id,
  449. mime: "x-spacedeck/vector",
  450. description: "",
  451. control_points: [{dx:0,dy:0},{dx:0,dy:0},{dx:0,dy:0}],
  452. x: point.x,
  453. y: point.y,
  454. z: z,
  455. w: 64,
  456. h: 64,
  457. stroke_color: "#000000",
  458. stroke: 2,
  459. shape: "arrow"
  460. };
  461. $scope.save_artifact(a, function(saved_a) {
  462. $scope.update_board_artifact_viewmodel(saved_a);
  463. $scope.active_space_artifacts.push(saved_a);
  464. $scope.select(evt,a);
  465. $scope.selected_control_point_idx = 1;
  466. $scope.transform_ox = 0;
  467. $scope.transform_oy = 0;
  468. $scope.begin_transaction();
  469. }.bind(this));
  470. },
  471. // FIXME: consolidate with arrow drawing?
  472. start_drawing_line: function(evt) {
  473. evt.preventDefault();
  474. evt.stopPropagation();
  475. var $scope = this.vm.$root;
  476. var point = this.cursor_point_to_space(evt);
  477. this.offset_point_in_wrapper(point);
  478. var z = $scope.highest_z()+1;
  479. var a = {
  480. space_id: $scope.active_space._id,
  481. mime: "x-spacedeck/vector",
  482. description: "",
  483. control_points: [{dx:0,dy:0},{dx:0,dy:0}],
  484. x: point.x,
  485. y: point.y,
  486. z: z,
  487. w: 64,
  488. h: 64,
  489. stroke_color: "#000000",
  490. stroke: 2,
  491. shape: "line"
  492. };
  493. $scope.save_artifact(a, function(saved_a) {
  494. $scope.update_board_artifact_viewmodel(saved_a);
  495. $scope.active_space_artifacts.push(saved_a);
  496. $scope.select(evt,a);
  497. $scope.selected_control_point_idx = 1;
  498. $scope.transform_ox = 0;
  499. $scope.transform_oy = 0;
  500. $scope.begin_transaction();
  501. }.bind(this));
  502. },
  503. snap_point_simple: function(point) {
  504. var snapped = this.snap_point(point.x, point.y);
  505. return {
  506. x: snapped.snapx[1],
  507. y: snapped.snapy[1]
  508. }
  509. },
  510. handle_mouse_up_space: function(evt) {
  511. var $scope = this.vm.$root;
  512. evt.preventDefault();
  513. if (this.mouse_state == "lasso") {
  514. var lasso_rect = this.abs_rect(this.offset_point_in_wrapper(this.lasso));
  515. // convert to space coordinates
  516. if (lasso_rect.w>0 && lasso_rect.h>0) {
  517. var arts = this.artifacts_in_rect(lasso_rect);
  518. this.multi_select(arts);
  519. } else {
  520. if (this._no_artifact_toolbar_this_round) {
  521. this._no_artifact_toolbar_this_round = false;
  522. } else {
  523. $scope.start_adding_artifact(evt);
  524. }
  525. }
  526. this.lasso = null;
  527. this.render_lasso();
  528. }
  529. else if (_.include(["transform","move","vector_transform","scribble"],this.mouse_state)) {
  530. var ars = $scope.selected_artifacts();
  531. for (var i=0; i<ars.length; i++) {
  532. if (_.include(["text","placeholder"],$scope.artifact_major_type(ars[i]))) {
  533. // some types of artifact need a minimum size
  534. if (ars[i].w<10) {
  535. ars[i].w = 10;
  536. }
  537. if (ars[i].h<10) {
  538. ars[i].h = 10;
  539. }
  540. }
  541. //save_artifact(ars[i], null, $scope.display_saving_error);
  542. }
  543. }
  544. if (this.mouse_state == "text_editor") {
  545. return;
  546. }
  547. if (_.include(["zoom"], $scope.active_tool)) {
  548. // tools that stay active after use
  549. this.mouse_state = "idle";
  550. $scope.mouse_state = this.mouse_state;
  551. $scope.end_transaction();
  552. $scope.deselect();
  553. return;
  554. }
  555. this.mouse_state = "idle";
  556. $scope.mouse_state = this.mouse_state;
  557. this.lasso = null;
  558. $scope.active_tool = "pointer";
  559. $scope.end_transaction();
  560. $scope.show_toolbar_props();
  561. },
  562. handle_mouse_leave: function(evt) {
  563. var $scope = this.vm.$root;
  564. this.mouse_state = "idle";
  565. this.lasso = null;
  566. $scope.active_tool = "pointer";
  567. $scope.end_transaction();
  568. this.render_lasso();
  569. },
  570. handle_mouse_move: function(evt) {
  571. var $scope = this.vm.$root;
  572. if (!$scope.active_space) return;
  573. if (!$scope.editing_artifact_id) {
  574. evt.preventDefault();
  575. evt.stopPropagation();
  576. }
  577. $scope.handle_scroll();
  578. var cursor = this.cursor_point_to_space(evt);
  579. var dx = cursor.x - $scope.mouse_ox;
  580. var dy = cursor.y - $scope.mouse_oy;
  581. var dt = (new Date()).getTime() - this.last_mouse_move_time;
  582. this.last_mouse_move_time = (new Date()).getTime();
  583. var zoom = $scope.viewport_zoom||1;
  584. if (zoom) {
  585. dx/=zoom;
  586. dy/=zoom;
  587. }
  588. // send cursor
  589. if (dx>10 || dy>10 || dt>100) {
  590. var name = "anonymous";
  591. if ($scope.logged_in) {
  592. name = $scope.user.nickname || $scope.user.email;
  593. } else {
  594. name = $scope.guest_nickname || "anonymous";
  595. }
  596. var cursor_msg = {
  597. action: "cursor",
  598. x: cursor.x/zoom,
  599. y: cursor.y/zoom,
  600. name: name,
  601. id: $scope.user._id||name
  602. };
  603. $scope.websocket_send(cursor_msg);
  604. }
  605. // side effects ftw!
  606. $scope.snap_ruler_x = -1000;
  607. $scope.snap_ruler_y = -1000;
  608. $scope.mouse_moved = true;
  609. $scope.transform_lock = evt.shiftKey;
  610. if ($scope.transform_lock) {
  611. if (this.mouse_state == "transform") {
  612. // lock aspect is done in transform
  613. } else {
  614. // lock axis
  615. if (Math.abs(dy)>Math.abs(dx)) {
  616. dx = 0;
  617. } else {
  618. dy = 0;
  619. }
  620. }
  621. }
  622. if (this.mouse_state == "move") {
  623. $scope.hide_toolbar_props();
  624. var snap_dx = 0;
  625. var snap_dy = 0;
  626. var selected = $scope.selected_artifacts();
  627. var snap_edges = this.old_selection_rect();
  628. if (selected.length && selected[0]._id==$scope.editing_artifact_id) {
  629. // bail out of moving editable artifact
  630. return;
  631. }
  632. if (snap_edges) {
  633. var mx = snap_edges.x1 + (snap_edges.x2-snap_edges.x1)/2;
  634. var my = snap_edges.y1 + (snap_edges.y2-snap_edges.y1)/2;
  635. var snapped1 = this.snap_point(snap_edges.x1 + dx, snap_edges.y1 + dy, false);
  636. var snapped2 = this.snap_point(snap_edges.x2 + dx, snap_edges.y2 + dy, false);
  637. var snapped3 = this.snap_point(mx + dx, my + dy, true);
  638. if (snapped3.snapx[0]>0) {
  639. snap_dx = mx + dx - snapped3.snapx[1];
  640. } else if (snapped2.snapx[0]>0) {
  641. snap_dx = snap_edges.x2 + dx - snapped2.snapx[1];
  642. } else {
  643. snap_dx = snap_edges.x1 + dx - snapped1.snapx[1];
  644. }
  645. if (snapped3.snapy[0]>0) {
  646. snap_dy = my + dy - snapped3.snapy[1];
  647. } else if (snapped2.snapy[0]>0) {
  648. snap_dy = snap_edges.y2 + dy - snapped2.snapy[1];
  649. } else {
  650. snap_dy = snap_edges.y1 + dy - snapped1.snapy[1];
  651. }
  652. }
  653. $scope.update_selected_artifacts(function(a) {
  654. var old_a = $scope.find_artifact_before_transaction(a);
  655. if (old_a) {
  656. return {
  657. x: old_a.x + dx - snap_dx,
  658. y: old_a.y + dy - snap_dy
  659. };
  660. } else {
  661. // deleted?
  662. return {};
  663. }
  664. }.bind(this));
  665. } else if (this.mouse_state == "transform") {
  666. var selected = $scope.selected_artifacts();
  667. var edges = this.old_selection_rect();
  668. if (!edges) {
  669. this.mouse_state = "idle";
  670. return;
  671. }
  672. $scope.hide_toolbar_props();
  673. var ew = (edges.x2-edges.x1);
  674. var eh = (edges.y2-edges.y1);
  675. var origin_x = edges.x1 + ew * $scope.transform_ox;
  676. var origin_y = edges.y1 + eh * $scope.transform_oy;
  677. // "leading point"
  678. var lead_x = edges.x1 + ew * (1-$scope.transform_ox) - origin_x;
  679. var lead_y = edges.y1 + eh * (1-$scope.transform_oy) - origin_y;
  680. var lead_snapped = this.snap_point(origin_x + lead_x + dx, origin_y + lead_y + dy);
  681. var moved_x = (lead_snapped.snapx[1] - origin_x);
  682. var moved_y = (lead_snapped.snapy[1] - origin_y);
  683. var scale_x = lead_x ? (moved_x)/lead_x : 1;
  684. var scale_y = lead_y ? (moved_y)/lead_y : 1;
  685. if (!$scope.transform_lock) scale_y = scale_x;
  686. $scope.update_selected_artifacts(function(a) {
  687. var old_a = $scope.find_artifact_before_transaction(a);
  688. var x1 = origin_x + ((old_a.x - origin_x) * scale_x);
  689. var y1 = origin_y + ((old_a.y - origin_y) * scale_y);
  690. var x2 = origin_x + (((old_a.x + old_a.w) - origin_x) * scale_x);
  691. var y2 = origin_y + (((old_a.y + old_a.h) - origin_y) * scale_y);
  692. if (x1>x2) { var t = x1; x1 = x2; x2 = t; }
  693. if (y1>y2) { var t = y1; y1 = y2; y2 = t; }
  694. return {
  695. x: x1,
  696. y: y1,
  697. w: x2 - x1,
  698. h: y2 - y1
  699. };
  700. }.bind(this));
  701. } else if (this.mouse_state == "lasso") {
  702. this.lasso.w = dx;
  703. this.lasso.h = dy;
  704. this.render_lasso();
  705. } else if (this.mouse_state == "vector_transform") {
  706. $scope.hide_toolbar_props();
  707. var _this = this;
  708. $scope.update_selected_artifacts(function(a) {
  709. var old_a = $scope.find_artifact_before_transaction(a);
  710. var control_points = _.cloneDeep(old_a.control_points);
  711. var cp = control_points[$scope.selected_control_point_idx];
  712. var snapped = _this.snap_point(old_a.x+cp.dx+dx, old_a.y+cp.dy+dy);
  713. dx = snapped.snapx[1]-(old_a.x+cp.dx);
  714. dy = snapped.snapy[1]-(old_a.y+cp.dy);
  715. cp.dx += dx;
  716. cp.dy += dy;
  717. // special case for arrow's 3rd point
  718. if (a.shape == "arrow" && $scope.selected_control_point_idx!=2) {
  719. /*control_points[2].dx += dx/2;
  720. control_points[2].dy += dy/2; */
  721. control_points[2].dx = (control_points[0].dx+control_points[1].dx)/2;
  722. control_points[2].dy = (control_points[0].dy+control_points[1].dy)/2;
  723. }
  724. return _this.normalize_control_points(control_points, old_a);
  725. });
  726. } else if (this.mouse_state == "scribble") {
  727. $scope.update_selected_artifacts(function(a) {
  728. var old_a = a;
  729. var control_points = _.cloneDeep(old_a.control_points);
  730. var offset = this.offset_point_in_wrapper({x:cursor.x,y:cursor.y});
  731. control_points.push({
  732. dx: offset.x-old_a.x,
  733. dy: offset.y-old_a.y
  734. });
  735. return this.normalize_control_points(simplify_scribble_points(control_points), old_a);
  736. }.bind(this));
  737. var arts = $scope.selected_artifacts();
  738. if (arts.length) {
  739. $scope.update_board_artifact_viewmodel(arts[0]);
  740. }
  741. }
  742. else if (this.mouse_state == "pan") {
  743. if (!$("#space").length) return;
  744. el = $("#space")[0];
  745. el.scrollLeft = this.old_panx - dx*$scope.viewport_zoom;
  746. el.scrollTop = this.old_pany - dy*$scope.viewport_zoom;
  747. $scope.handle_scroll();
  748. }
  749. },
  750. normalize_control_points: function(control_points, artifact) {
  751. var x1 = _.min(control_points,"dx").dx;
  752. var y1 = _.min(control_points,"dy").dy;
  753. var x2 = _.max(control_points,"dx").dx;
  754. var y2 = _.max(control_points,"dy").dy;
  755. var shiftx = -x1;
  756. var shifty = -y1;
  757. var shifted_cps = control_points.map(function(cp) {
  758. return {
  759. dx: cp.dx + shiftx,
  760. dy: cp.dy + shifty
  761. };
  762. });
  763. var w = Math.abs(x2 - x1);
  764. var h = Math.abs(y2 - y1);
  765. var bshiftx = 0;
  766. var bshifty = 0;
  767. if (artifact.w < 0) bshiftx = -artifact.w;
  768. if (artifact.h < 0) bshifty = -artifact.h;
  769. return {
  770. x: artifact.x + bshiftx - shiftx,
  771. y: artifact.y + bshifty - shifty,
  772. w: w,
  773. h: h,
  774. z: artifact.z,
  775. control_points: shifted_cps
  776. };
  777. }
  778. });
  779. }