spacedeck_board_artifacts.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. /*
  2. SpacedeckBoardArtifacts
  3. This module contains functions dealing with absolute positioned Board Section Artifacts.
  4. */
  5. var SpacedeckBoardArtifacts = {
  6. update_board_artifact_viewmodel: function(a) {
  7. var mt = this.artifact_major_type(a);
  8. a.view = {
  9. _id: a._id,
  10. classes: this.artifact_classes(a),
  11. style: this.artifact_style(a),
  12. grid_style: this.artifact_style(a, true),
  13. inner_style: this.artifact_inner_style(a),
  14. text_cell_style: this.artifact_text_cell_style(a),
  15. vector_svg: this.artifact_vector_svg(a),
  16. payload_uri: a.payload_uri,
  17. thumbnail_uri: this.artifact_thumbnail_uri(a),
  18. major_type: mt,
  19. text_blank: this.artifact_is_text_blank(a),
  20. payload_alternatives: a.payload_alternatives,
  21. filename: this.artifact_filename(a),
  22. oembed_html: this.artifact_oembed_html(a),
  23. link: this.artifact_link(a),
  24. link_caption: this.artifact_link_caption(a),
  25. interactive: 0
  26. };
  27. if ((mt == "audio" || mt == "video") && !a.player_view) {
  28. a.player_view = {
  29. state: "stop",
  30. current_time_string: "",
  31. total_time_string: "",
  32. current_time_float: 0.0,
  33. inpoint_float: 0.0,
  34. outpoint_float: 0.0
  35. };
  36. }
  37. if ("medium_for_object" in this) {
  38. var medium = this.medium_for_object[a._id];
  39. if (medium && a._id != this.editing_artifact_id) {
  40. medium.value(a.description.toString());
  41. }
  42. }
  43. },
  44. is_artifact_audio: function(a) {
  45. if (a) {
  46. return a.mime.match("audio");
  47. } else return false;
  48. },
  49. artifact_filename: function(a) {
  50. if (a.payload_uri) {
  51. return a.payload_uri.replace(/.*\//g,"");
  52. } else {
  53. return "";
  54. }
  55. },
  56. artifact_link: function(a) {
  57. if (a.link_uri) {
  58. return a.link_uri;
  59. } else {
  60. return "";
  61. }
  62. },
  63. artifact_link_caption: function(a) {
  64. if (a.link_uri) {
  65. var parts = a.link_uri.split("/");
  66. // scheme://domain.foo/...
  67. // 0 1 2
  68. if (parts.length>2) {
  69. return parts[2];
  70. }
  71. return "Link";
  72. } else {
  73. return "";
  74. }
  75. },
  76. artifact_is_selected: function(a) {
  77. if (!a) return false;
  78. return !!this.selected_artifacts_dict[a._id];
  79. },
  80. artifact_is_text_blank: function(a) {
  81. if (a.description) {
  82. desc = a.description.toString();
  83. var filtered = desc.replace(/<[^>]+>/g,"").replace(/\s/g,"");
  84. return (filtered.length<1);
  85. } else {
  86. return false;
  87. }
  88. },
  89. artifact_classes: function(a) {
  90. clzs = ["artifact", "artifact-"+this.artifact_major_type(a), a.mime.replace("/","-")];
  91. if (this.artifact_is_selected(a) && this.editing_artifact_id!=a._id) clzs.push("selected");
  92. if (!a._id) clzs.push("creating");
  93. if (a.align) clzs.push("align-"+a.align);
  94. if (a.valign) clzs.push("align-"+a.valign);
  95. clzs.push("state-"+a.state);
  96. if (this.artifact_is_text_blank(a)) {
  97. clzs.push("text-blank");
  98. }
  99. if (a.locked) {
  100. clzs.push("locked");
  101. }
  102. return clzs.join(" ");
  103. },
  104. artifact_inner_style: function(a) {
  105. var styles = [];
  106. //if (a.style) {
  107. var svg_style = ((a.mime.match("vector") || a.mime.match("shape")) && a.shape!="square");
  108. if (!svg_style) {
  109. if (a.stroke) {
  110. styles.push("border-width:"+a.stroke+"px");
  111. styles.push("border-style:"+(a.stroke_style||"solid"));
  112. }
  113. if (a.stroke_color) {
  114. styles.push("border-color:"+a.stroke_color);
  115. }
  116. if (a.border_radius) {
  117. styles.push("border-radius:"+a.border_radius+"px");
  118. }
  119. }
  120. if (a.fill_color && !svg_style) {
  121. styles.push("background-color:"+a.fill_color);
  122. }
  123. if (a.text_color) {
  124. styles.push("color:"+a.text_color);
  125. }
  126. var filters = [];
  127. if (!isNaN(a.brightness) && a.brightness != 100) {
  128. filters.push("brightness("+a.brightness+"%)");
  129. }
  130. if (!isNaN(a.contrast) && a.contrast != 100) {
  131. filters.push("contrast("+a.contrast+"%)");
  132. }
  133. if (!isNaN(a.opacity) && a.opacity != 100) {
  134. filters.push("opacity("+a.opacity+"%)");
  135. }
  136. if (!isNaN(a.hue) && a.hue) {
  137. filters.push("hue-rotate("+a.hue+"deg)");
  138. }
  139. if (!isNaN(a.saturation) && a.saturation != 100) {
  140. filters.push("saturate("+a.saturation+"%)");
  141. }
  142. if (!isNaN(a.blur) && a.blur) {
  143. filters.push("blur("+a.blur+"px)");
  144. }
  145. if (filters.length) {
  146. styles.push("-webkit-filter:"+filters.join(" "));
  147. styles.push("filter:"+filters.join(" "));
  148. }
  149. //}
  150. return styles.join(";");
  151. },
  152. artifact_text_cell_style: function(a, for_text_editor) {
  153. var styles = [];
  154. if (a.padding_left) styles.push("padding-left:"+a.padding_left+"px");
  155. if (a.padding_right) styles.push("padding-right:"+a.padding_right+"px");
  156. if (a.padding_top) styles.push("padding-top:"+a.padding_top+"px");
  157. if (a.padding_bottom) styles.push("padding-bottom:"+a.padding_bottom+"px");
  158. return styles.join(";");
  159. },
  160. artifact_style: function(a, for_grid) {
  161. var styles = [];
  162. var z = 0;
  163. z = a.z;
  164. if (z<0) z=0; // fix negative z-index
  165. styles = [
  166. "left:" +a.x+"px",
  167. "top:" +a.y+"px",
  168. "width:" +a.w+"px",
  169. "height:"+a.h+"px",
  170. "z-index:"+z
  171. ];
  172. if (a.margin_left) styles.push("margin-left:"+a.margin_left+"px");
  173. if (a.margin_right) styles.push("margin-right:"+a.margin_right+"px");
  174. if (a.margin_top) styles.push("margin-top:"+a.margin_top+"px");
  175. if (a.margin_bottom) styles.push("margin-bottom:"+a.margin_bottom+"px");
  176. // FIXME: via class logic?
  177. if (a.mime.match("vector")) {
  178. styles.push("overflow:visible");
  179. }
  180. return styles.join(";");
  181. },
  182. artifact_major_type: function(a) {
  183. if (a.mime.match("oembed")) return "oembed";
  184. if (a.mime.match("zone")) return "zone";
  185. if (a.mime.match("svg")) return "svg";
  186. if (a.mime.match("image")) return "image";
  187. if (a.mime.match("pdf")) return "image";
  188. if (a.mime.match("video")) return "video";
  189. if (a.mime.match("audio")) return "audio";
  190. if (a.mime.match("website")) return "website";
  191. if (a.mime.match("vector")) return "vector";
  192. if (a.mime.match("shape")) return "shape";
  193. if (a.mime.match("placeholder")) return "placeholder";
  194. if (a.mime.match("text") || a.mime.match("note")) return "text";
  195. return "file";
  196. },
  197. artifact_thumbnail_uri: function(a) {
  198. if (a.payload_thumbnail_big_uri && a.board) {
  199. if (a.w>800) {
  200. return a.payload_thumbnail_big_uri;
  201. }
  202. }
  203. return a.payload_thumbnail_medium_uri || a.payload_thumbnail_big_uri || a.payload_thumbnail_web_uri || "";
  204. },
  205. artifact_oembed_html: function(a) {
  206. if (this.artifact_major_type(a) != "oembed") return "";
  207. var parts = a.mime.split("/")[1].split("-");
  208. var type = parts[0];
  209. var provider = parts[1];
  210. if (!a.link_uri) {
  211. console.log("missing meta / link_uri: ",a);
  212. console.log("type/provider: ",type,provider);
  213. return ("missing metadata: "+a._id);
  214. }
  215. if (provider=="youtube") {
  216. var vid = a.link_uri.match(/(v=|\/)([a-zA-Z0-9\-_]{11})/);
  217. if (vid && vid.length>2) {
  218. var uri = "https://youtube.com/embed/"+vid[2];
  219. return "<iframe frameborder=0 allowfullscreen src=\""+uri+"?showinfo=0&rel=0&controls=0\"></iframe>";
  220. } else return "Can't resolve: "+a.payload_uri;
  221. } else if (provider=="dailymotion") {
  222. var match = a.link_uri.match(/dailymotion.com\/video\/([^<]*)/);
  223. if (match && match.length>1) {
  224. var uri = "https://www.dailymotion.com/embed/video/"+match[1];
  225. return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>";
  226. } else return "Can't resolve: "+a.payload_uri;
  227. } else if (provider=="vimeo") {
  228. var match = a.link_uri.match(/https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/);
  229. if (match) {
  230. var uri = "https://player.vimeo.com/video/"+match[2];
  231. return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>";
  232. } else return "Can't resolve: "+a.payload_uri;
  233. } else if (provider=="soundcloud") {
  234. return '<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url='+a.link_uri.replace(":", "%3A")+'"></iframe>';
  235. } else if (provider=="spacedeck") {
  236. return ""; //<iframe frameborder=0 allowfullscreen src=\""+ a.meta.link_uri+"\"></iframe>
  237. } else {
  238. return "Don't know how to embed "+a.mime+".";
  239. }
  240. },
  241. artifact_vector_svg: function(a) {
  242. var mtype = this.artifact_major_type(a);
  243. if (mtype != "vector" && mtype != "shape") return "";
  244. var shape = a.shape || "";
  245. var padding = 32 + a.stroke*2;
  246. var path_svg;
  247. var fill = "";
  248. if (mtype == "vector") {
  249. path_svg = render_vector_drawing(a, padding);
  250. fill = "fill:none";
  251. } else {
  252. path_svg = render_vector_shape(a, padding);
  253. fill = "fill:"+a.fill_color+";";
  254. padding = 0;
  255. }
  256. var margin = padding;
  257. var svg = "<svg xmlns='http://www.w3.org/2000/svg' width='"+(a.w+2*padding)+"' height='"+(a.h+2*padding)+"' ";
  258. svg += "style='margin-left:"+(-margin)+"px;margin-top:"+(-margin)+"px;stroke-width:"+a.stroke+";stroke:"+a.stroke_color+";"+fill+"'>";
  259. svg += path_svg;
  260. svg += "</svg>";
  261. return svg;
  262. },
  263. /* whiteboard layouting functions */
  264. artifact_enclosing_rect: function(arts) {
  265. if (arts.length==0) return null;
  266. r = {
  267. x1: parseInt(_.min(arts.map(function(a){return a.x}))),
  268. y1: parseInt(_.min(arts.map(function(a){return a.y}))),
  269. x2: parseInt(_.max(arts.map(function(a){return a.x+a.w}))),
  270. y2: parseInt(_.max(arts.map(function(a){return a.y+a.h})))
  271. };
  272. r.x=r.x1;
  273. r.y=r.y1;
  274. r.w=r.x2-r.x1;
  275. r.h=r.y2-r.y1;
  276. return r;
  277. },
  278. artifact_selection_rect: function() {
  279. return this.artifact_enclosing_rect(this.selected_artifacts());
  280. },
  281. rects_intersecting: function(r1,r2) {
  282. if ( (r1.x+r1.w < r2.x)
  283. || (r1.x > r2.x+r2.w)
  284. || (r1.y+r1.h < r2.y)
  285. || (r1.y > r2.y+r2.h) ) return false;
  286. return true;
  287. },
  288. artifacts_in_rect: function(rect) {
  289. return _.filter(this.active_space_artifacts, function(a) {
  290. return this.rects_intersecting(a, rect);
  291. }.bind(this));
  292. },
  293. layout_stack_top: function() {
  294. this.begin_transaction();
  295. var rect = this.artifact_selection_rect();
  296. var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a)}.bind(this));
  297. var max_z = _.max(overlapping,function(a){ return a.z; });
  298. if (max_z.board) {
  299. max_z = max_z.z + 1;
  300. } else {
  301. max_z = 1;
  302. }
  303. this.update_selected_artifacts(function(a) {
  304. return { z: max_z };
  305. });
  306. },
  307. layout_stack_bottom: function() {
  308. this.begin_transaction();
  309. var rect = this.artifact_selection_rect();
  310. var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a);}.bind(this));
  311. var min_z = _.min(overlapping,function(a){ return a.z; });
  312. if (min_z.board) {
  313. min_z = min_z.z - 1;
  314. } else {
  315. min_z = 0;
  316. }
  317. var my_z = _.max(this.selected_artifacts(),function(a){ return a.z; });
  318. if (my_z.board) {
  319. my_z = my_z.z - 1;
  320. } else {
  321. my_z = 0;
  322. }
  323. // TODO: move all other items up in this case?
  324. if (min_z < 0) {
  325. this.update_artifacts(overlapping, function(a) {
  326. return { z: (my_z + a.z + 1) };
  327. });
  328. return;
  329. }
  330. this.update_selected_artifacts(function(a) {
  331. return { z: min_z };
  332. });
  333. },
  334. layout_align_left:function() {
  335. this.begin_transaction();
  336. var rect = this.artifact_selection_rect();
  337. this.update_selected_artifacts(function(a) {
  338. return { x: rect.x1 };
  339. });
  340. },
  341. layout_align_top: function() {
  342. this.begin_transaction();
  343. var rect = this.artifact_selection_rect();
  344. this.update_selected_artifacts(function(a) {
  345. return { y: rect.y1 };
  346. });
  347. },
  348. layout_align_right: function() {
  349. this.begin_transaction();
  350. var rect = this.artifact_selection_rect();
  351. this.update_selected_artifacts(function(a) {
  352. return { x: rect.x2 - a.w };
  353. });
  354. },
  355. layout_align_bottom: function() {
  356. this.begin_transaction();
  357. var rect = this.artifact_selection_rect();
  358. this.update_selected_artifacts(function(a) {
  359. return { y: rect.y2 - a.h };
  360. });
  361. },
  362. layout_align_center: function() {
  363. this.begin_transaction();
  364. var rect = this.artifact_selection_rect();
  365. var cx = rect.x1 + (rect.x2-rect.x1)/2;
  366. this.update_selected_artifacts(function(a) {
  367. return { x: cx - a.w/2 };
  368. });
  369. },
  370. layout_align_middle: function() {
  371. this.begin_transaction();
  372. var rect = this.artifact_selection_rect();
  373. var cy = rect.y1 + (rect.y2-rect.y1)/2;
  374. this.update_selected_artifacts(function(a) {
  375. return { y: cy - a.h/2 };
  376. });
  377. },
  378. layout_match_size_horiz:function() {
  379. this.begin_transaction();
  380. var arts = this.selected_artifacts();
  381. if (arts.length<2) return;
  382. var totalw = _.reduce(arts, function(sum, a) { return sum + a.w }, 0);
  383. var avgw = totalw / arts.length;
  384. this.update_selected_artifacts(function(a) {
  385. return { w: avgw };
  386. });
  387. },
  388. layout_match_size_vert:function() {
  389. this.begin_transaction();
  390. var arts = this.selected_artifacts();
  391. if (arts.length<2) return;
  392. var totalh = _.reduce(arts, function(sum, a) { return sum + a.h }, 0);
  393. var avgh = totalh / arts.length;
  394. this.update_selected_artifacts(function(a) {
  395. return { h: avgh };
  396. });
  397. },
  398. layout_match_size_both:function() {
  399. this.layout_match_size_horiz();
  400. this.layout_match_size_vert();
  401. },
  402. layout_distribute_horizontal: function() {
  403. this.begin_transaction();
  404. var selected = this.selected_artifacts();
  405. if (selected.length<3) return;
  406. var sorted = _.sortBy(selected, function(a) { return a.x });
  407. var startx = sorted[0].x + sorted[0].w/2;
  408. var stopx = _.last(sorted).x + _.last(sorted).w/2;
  409. var step = (stopx-startx)/(sorted.length-1);
  410. for (var i=1; i<sorted.length-1; i++) {
  411. var a = sorted[i];
  412. var x = startx + step*i - a.w/2;
  413. this.update_artifacts([a],function(a) {
  414. return { x: x }
  415. });
  416. }
  417. },
  418. layout_distribute_vertical: function() {
  419. this.begin_transaction();
  420. var selected = this.selected_artifacts();
  421. if (selected.length<3) return;
  422. var sorted = _.sortBy(selected, function(a) { return a.y });
  423. var starty = sorted[0].y + sorted[0].h/2;
  424. var stopy = _.last(sorted).y + _.last(sorted).h/2;
  425. var step = (stopy-starty)/(sorted.length-1);
  426. for (var i=1; i<sorted.length-1; i++) {
  427. var a = sorted[i];
  428. var y = starty + step*i - a.h/2;
  429. this.update_artifacts([a],function(a) {
  430. return { y: y }
  431. });
  432. }
  433. },
  434. layout_distribute_horizontal_spacing: function() {
  435. this.begin_transaction();
  436. var selected = this.selected_artifacts();
  437. if (selected.length<3) return;
  438. var sorted = _.sortBy(selected, function(a) { return a.x });
  439. var startx = sorted[0].x;
  440. var stopx = _.last(sorted).x + _.last(sorted).w;
  441. var range = stopx - startx;
  442. var totalw = _.reduce(sorted, function(sum, a) { return sum + a.w }, 0);
  443. var avgs = (range - totalw) / (sorted.length-1);
  444. var prevend = startx + sorted[0].w;
  445. for (var i=1; i<sorted.length-1; i++) {
  446. var a = sorted[i];
  447. var x = prevend + avgs;
  448. this.update_artifacts([a],function(a) {
  449. return { x: x }
  450. });
  451. prevend = x+a.w;
  452. }
  453. },
  454. layout_distribute_vertical_spacing: function() {
  455. this.begin_transaction();
  456. var selected = this.selected_artifacts();
  457. if (selected.length<3) return;
  458. var sorted = _.sortBy(selected, function(a) { return a.y });
  459. var starty = sorted[0].y;
  460. var stopy = _.last(sorted).y + _.last(sorted).h;
  461. var range = stopy - starty;
  462. var totalh = _.reduce(sorted, function(sum, a) { return sum + a.h }, 0);
  463. var avgs = (range - totalh) / (sorted.length-1);
  464. var prevend = starty + sorted[0].h;
  465. for (var i=1; i<sorted.length-1; i++) {
  466. var a = sorted[i];
  467. var y = prevend + avgs;
  468. this.update_artifacts([a],function(a) {
  469. return { y: y }
  470. });
  471. prevend = y+a.h;
  472. }
  473. },
  474. layout_auto: function() {
  475. this.begin_transaction();
  476. var selected = this.selected_artifacts();
  477. if (selected.length<2) return;
  478. var sorted = _.sortBy(selected, function(a) { return a.x+a.y*this.active_space.width }.bind(this));
  479. var minx = sorted[0].x;
  480. var miny = sorted[0].y;
  481. var sorted = _.sortBy(selected, function(a) { return -Math.max(a.w,a.h) }.bind(this));
  482. var blocks = [];
  483. var padding = 10;
  484. for (var i=0; i<sorted.length; i++) {
  485. var a = sorted[i];
  486. blocks.push({
  487. w: a.w+padding,
  488. h: a.h+padding,
  489. a: a
  490. });
  491. }
  492. var packer = new GrowingPacker();
  493. packer.fit(blocks);
  494. for (var i=0; i<blocks.length; i++) {
  495. var block = blocks[i];
  496. if (block.fit) {
  497. var a = block.a;
  498. this.update_artifacts([a],function(a) {
  499. return {
  500. x: minx+block.fit.x,
  501. y: miny+block.fit.y
  502. }
  503. });
  504. }
  505. }
  506. },
  507. show_artifact_comments: function(evt) {
  508. evt.preventDefault();
  509. evt.stopPropagation();
  510. var artifact = this.selected_artifacts()[0];
  511. this.selected_artifact = artifact;
  512. this.activate_modal('artifact');
  513. },
  514. create_artifact_comment: function(artifact, comment) {
  515. var data = {
  516. artifact_id: artifact._id,
  517. space_id: this.active_space._id,
  518. message: comment,
  519. user: this.user
  520. };
  521. save_comment(this.active_space._id, data, function(comment) {
  522. this.active_space_messages.push(comment);
  523. this.artifact_comment = "";
  524. }.bind(this), function(xhr){
  525. console.error(xhr);
  526. }.bind(this));
  527. },
  528. remove_artifact_comment: function(comment) {
  529. delete_comment(this.active_space._id, comment._id, function(comment) {
  530. this.active_space_messages.pop(comment);
  531. }.bind(this), function(xhr){
  532. console.error(xhr);
  533. }.bind(this));
  534. }
  535. }
  536. if (typeof(window) == 'undefined') {
  537. exports.SpacedeckBoardArtifacts = SpacedeckBoardArtifacts;
  538. }