spacedeck_directives.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. /*
  2. Spacedeck Directives
  3. This module registers custom Vue directives for Spacedeck.
  4. */
  5. function setup_directives() {
  6. Vue.directive('clipboard', {
  7. bind: function () {
  8. this.clipboard = new Clipboard(".clipboard-btn");
  9. },
  10. update: function (value) {
  11. },
  12. unbind: function () {
  13. this.clipboard.destroy()
  14. }
  15. });
  16. Vue.directive('t', {
  17. update: function (value, key) {
  18. this.el.innerHTML = key;
  19. }
  20. });
  21. if ('ontouchstart' in window) {
  22. var edown = "touchstart";
  23. var emove = "touchmove";
  24. var eup = "touchend";
  25. } else {
  26. var edown = "mousedown";
  27. var emove = "mousemove";
  28. var eup = "mouseup";
  29. }
  30. Vue.directive('videoplayer', {
  31. update: function (a) {
  32. var el = this.el;
  33. var scope = this.vm.$root;
  34. var video = el.querySelectorAll("video")[0];
  35. var play_button = el.querySelectorAll(".play")[0];
  36. var pause_button = el.querySelectorAll(".pause")[0];
  37. var stop_button = el.querySelectorAll(".stop")[0];
  38. var player_state = "stop";
  39. var update_view = function() {
  40. try {
  41. if (!a.player_view) { a.player_view = {} };
  42. a.player_view.state = player_state;
  43. } catch (e) {
  44. // catch InvalidStateError
  45. }
  46. }
  47. var play_func = function() {
  48. video.play();
  49. player_state = "playing";
  50. update_view();
  51. }
  52. var pause_func = function() {
  53. try {
  54. video.pause();
  55. player_state = "paused";
  56. update_view();
  57. } catch (e) {
  58. // catch InvalidStateError
  59. }
  60. }
  61. var stop_func = function() {
  62. try {
  63. player_state = "stop";
  64. video.pause();
  65. video.currentTime = 0;
  66. update_view();
  67. } catch (e) {
  68. // catch InvalidStateError
  69. }
  70. }
  71. el.addEventListener("remote_play",play_func);
  72. el.addEventListener("remote_pause",pause_func);
  73. el.addEventListener("remote_stop",stop_func);
  74. play_button.addEventListener(edown, function(evt) {
  75. try {
  76. play_func();
  77. spacedeck.presenter_send_media_action(a._id,"video","play",video.currentTime);
  78. } catch (e) {
  79. // catch InvalidStateError
  80. }
  81. }, false);
  82. pause_button.addEventListener(edown, function(evt) {
  83. pause_func();
  84. spacedeck.presenter_send_media_action(a._id,"video","pause",video.currentTime);
  85. }, false);
  86. stop_button.addEventListener(edown, function(evt) {
  87. stop_func();
  88. spacedeck.presenter_send_media_action(a._id,"video","stop",0);
  89. }, false);
  90. }
  91. });
  92. Vue.directive('audioplayer', {
  93. update: function (a) {
  94. var el = this.el;
  95. var scope = this.vm.$root;
  96. var play_button = el.querySelectorAll(".play")[0];
  97. var pause_button = el.querySelectorAll(".pause")[0];
  98. var stop_button = el.querySelectorAll(".stop")[0];
  99. var timeline = el.querySelectorAll(".timeline")[0];
  100. var set_inpoint = el.querySelectorAll(".set-inpoint")[0];
  101. var set_outpoint = el.querySelectorAll(".set-outpoint")[0];
  102. var reset_points = el.querySelectorAll(".reset-points")[0];
  103. var player_state = "stop";
  104. var play_from = 0.0;
  105. var play_to = 0.0;
  106. var audio = el.querySelectorAll("audio")[0];
  107. var update_markers = function() {
  108. try {
  109. if (a.meta) {
  110. if (!a.meta.play_to) a.meta.play_to = audio.duration;
  111. play_from = parseFloat(a.meta.play_from) || 0.0;
  112. play_to = parseFloat(a.meta.play_to) || 0.0;
  113. } else {
  114. play_from = 0.0;
  115. play_to = parseFloat(audio.duration) || 0.0;
  116. a.meta = {};
  117. }
  118. } catch (e) {
  119. // catch InvalidStateError
  120. }
  121. }
  122. var update_view = function() {
  123. try {
  124. if (!a.player_view) { a.player_view = {} };
  125. a.player_view.state = player_state;
  126. a.player_view.total_time_string = format_time(audio.duration);
  127. a.player_view.current_time_string = format_time(audio.currentTime);
  128. a.player_view.current_time_float = audio.currentTime/audio.duration;
  129. a.player_view.inpoint_float = play_from/audio.duration;
  130. a.player_view.outpoint_float = play_to/audio.duration;
  131. a.player_view.duration = audio.duration;
  132. } catch (e) {
  133. // catch InvalidStateError
  134. }
  135. }
  136. var pause_audio = function() {
  137. try {
  138. audio.pause();
  139. player_state = "paused";
  140. } catch (e) {
  141. // catch InvalidStateError
  142. }
  143. update_view();
  144. }
  145. var stop_audio = function() {
  146. try {
  147. audio.currentTime = play_from;
  148. audio.pause();
  149. player_state = "stop";
  150. } catch (e) {
  151. // catch InvalidStateError
  152. }
  153. update_view();
  154. }
  155. update_view();
  156. audio.addEventListener("loadedmetadata", function(evt) {
  157. update_markers();
  158. update_view();
  159. }, false);
  160. audio.addEventListener("timeupdate", function(evt) {
  161. try {
  162. update_markers();
  163. if (audio.currentTime >= play_to && player_state == "playing") stop_audio();
  164. update_view();
  165. } catch (e) {
  166. // catch InvalidStateError
  167. }
  168. }, false);
  169. var play_func = function() {
  170. if (player_state == "stop") {
  171. audio.currentTime = play_from;
  172. }
  173. player_state = "playing";
  174. update_markers();
  175. audio.play();
  176. update_view();
  177. }
  178. var pause_func = function() {
  179. pause_audio();
  180. update_view();
  181. }
  182. var stop_func = function() {
  183. stop_audio();
  184. update_view();
  185. }
  186. el.addEventListener("remote_play",play_func);
  187. el.addEventListener("remote_pause",pause_func);
  188. el.addEventListener("remote_stop",stop_func);
  189. play_button.addEventListener(edown, function(evt) {
  190. try {
  191. play_func();
  192. spacedeck.presenter_send_media_action(a._id,"audio","play",audio.currentTime);
  193. } catch (e) {
  194. // catch InvalidStateError
  195. }
  196. }, false);
  197. pause_button.addEventListener(edown, function(evt) {
  198. pause_func();
  199. spacedeck.presenter_send_media_action(a._id,"audio","pause",audio.currentTime);
  200. }, false);
  201. stop_button.addEventListener(edown, function(evt) {
  202. stop_func();
  203. spacedeck.presenter_send_media_action(a._id,"audio","stop",0);
  204. }, false);
  205. timeline.addEventListener(edown, function(evt) {
  206. var clicked_time = (parseFloat(evt.offsetX)/evt.currentTarget.offsetWidth)*audio.duration;
  207. if (isNaN(clicked_time)) {
  208. clicked_time = 0.0;
  209. }
  210. try {
  211. audio.currentTime = clicked_time;
  212. } catch (e) {
  213. // catch InvalidStateErrors
  214. }
  215. }, false);
  216. set_inpoint.addEventListener(edown, function(evt) {
  217. if (!a.meta) a.meta = {};
  218. a.meta.play_from = audio.currentTime;
  219. if (a.meta.play_to<a.meta.play_from) a.meta.play_to = audio.duration;
  220. update_markers();
  221. stop_audio();
  222. update_view();
  223. scope.save_artifact(a);
  224. }, false);
  225. set_outpoint.addEventListener(edown, function(evt) {
  226. if (!a.meta) a.meta = {};
  227. a.meta.play_to = audio.currentTime;
  228. if (a.meta.play_to<a.meta.play_from) a.meta.play_from = 0.0;
  229. update_markers();
  230. stop_audio();
  231. update_view();
  232. scope.save_artifact(a);
  233. }, false);
  234. reset_points.addEventListener(edown, function(evt) {
  235. if (!a.meta) a.meta = {};
  236. a.meta.play_from = 0.0;
  237. a.meta.play_to = audio.duration;
  238. update_markers();
  239. stop_audio();
  240. update_view();
  241. scope.save_artifact(a);
  242. }, false);
  243. }
  244. });
  245. Vue.directive('sd-richtext', {
  246. twoWay: true,
  247. update: function(obj) {
  248. this.mode = 'rich';
  249. $(this.el).addClass("text-editing");
  250. this.medium = new Medium({
  251. element: this.el,
  252. mode: Medium.richMode,
  253. attributes: {
  254. remove: ['class','href','onclick','onmousedown','onmouseup']
  255. },
  256. });
  257. this.medium.value(obj.description);
  258. this.medium.element.addEventListener('keyup', function() {
  259. obj.description = this.medium.value();
  260. spacedeck.queue_artifact_for_save(obj);
  261. }.bind(this));
  262. spacedeck.medium_for_object[obj._id] = this.medium;
  263. }
  264. });
  265. Vue.directive('focus', {
  266. bind: function () {
  267. var el = this.el;
  268. window.setTimeout(function() {
  269. if (el.contentEditable && el.contentEditable!="inherit") {
  270. var range = document.createRange();
  271. range.selectNodeContents(el);
  272. } else {
  273. el.focus();
  274. el.select();
  275. }
  276. }, 500);
  277. },
  278. });
  279. Vue.directive('sd-draggable', {
  280. update: function(data) {
  281. var el = this.el;
  282. el.addEventListener(
  283. 'dragstart',
  284. function(evt) {
  285. if ($(el).find(".text-editing").length) {
  286. // FIXME: technical debt
  287. evt.stopPropagation();
  288. evt.preventDefault();
  289. return;
  290. }
  291. evt.dataTransfer.setData('application/json', JSON.stringify(data));
  292. $(el).addClass("dragging");
  293. },
  294. false
  295. );
  296. }
  297. });
  298. Vue.directive('sd-droppable', {
  299. isFn: true,
  300. bind: function() {
  301. var el = this.el;
  302. var expression = this.expression;
  303. var parts = expression.split(";");
  304. var func_key = parts[0];
  305. var data_key = parts[1];
  306. el.addEventListener(
  307. 'dragover',
  308. function(e) {
  309. e.dataTransfer.dropEffect = 'copy';
  310. // allows us to drop
  311. if (e.preventDefault) e.preventDefault();
  312. el.classList.add('over');
  313. return false;
  314. }.bind(this),
  315. false
  316. );
  317. el.addEventListener(
  318. 'dragenter',
  319. function(e) {
  320. el.classList.add('over');
  321. return false;
  322. }.bind(this),
  323. false
  324. );
  325. el.addEventListener(
  326. 'dragleave',
  327. function(e) {
  328. el.classList.remove('over');
  329. return false;
  330. },
  331. false
  332. );
  333. el.addEventListener(
  334. 'drop',
  335. function(e) {
  336. e.stopPropagation();
  337. e.preventDefault();
  338. $(e.currentTarget).find(".over").removeClass('over');
  339. $(e.currentTarget).find(".dragging").removeClass('dragging');
  340. var func = this.vm.$root[func_key].bind(this.vm.$root);
  341. if (this._scope) {
  342. var obj = this._scope[data_key];
  343. } else {
  344. var obj = this.vm[data_key];
  345. }
  346. func(e, obj);
  347. return false;
  348. }.bind(this),
  349. false
  350. );
  351. }
  352. });
  353. Vue.directive('sd-fader', {
  354. bind: function (section) {
  355. function clamp(v, mn, mx) {
  356. return Math.max(mn,Math.min(mx,v));
  357. }
  358. var scope = this.vm.$root;
  359. this.fader_state = "idle";
  360. this.fader_mx = 0;
  361. this.fader_my = 0;
  362. var $el = $(this.el);
  363. var handle = $el.find(".fader-selector");
  364. var indicator = $el.find(".fader-indicator");
  365. var constraint = $el.find(".fader-constraint");
  366. if (!constraint.length) constraint = $el;
  367. var fader_var_x = $el.attr("sd-fader-var-x");
  368. var fader_var_y = $el.attr("sd-fader-var-y");
  369. var knob_size = 0;
  370. var minx = 0;
  371. var miny = 0;
  372. var maxx = 0;
  373. var maxy = 0;
  374. var nx = 0;
  375. if (xfader) nx = scope.$get(fader_var_x);
  376. var ny = 0;
  377. if (yfader) ny = scope.$get(fader_var_y);
  378. var xfader = !!fader_var_x;
  379. var yfader = !!fader_var_y;
  380. var encoder = !handle[0];
  381. var step = parseFloat($el.attr("sd-fader-step"))||1;
  382. var sensitivity = parseFloat($el.attr("sd-fader-sens"))||1;
  383. var discover_minmax = function() {
  384. minx = (parseInt($el.attr("sd-fader-min-x"))||0);
  385. miny = (parseInt($el.attr("sd-fader-min-y"))||0);
  386. maxx = parseInt($el.attr("sd-fader-max-x"))||(constraint.width() - 1);
  387. maxy = parseInt($el.attr("sd-fader-max-y"))||(constraint.height() - 1);
  388. }
  389. var position_handle = function() {
  390. discover_minmax();
  391. if (!nx || isNaN(nx)) nx = 0;
  392. if (!ny || isNaN(ny)) ny = 0;
  393. if (handle[0]) {
  394. if (xfader) handle[0].style.left = nx+"px";
  395. if (yfader) handle[0].style.top = (maxy-ny)+"px";
  396. }
  397. if (indicator[0]) {
  398. indicator[0].style.height = ny+"px";
  399. }
  400. }.bind(this);
  401. var move_handle = function(dx,dy) {
  402. discover_minmax();
  403. if (xfader) {
  404. nx = clamp(dx, minx, maxx);
  405. scope.$set(fader_var_x, nx);
  406. }
  407. if (yfader) {
  408. ny = clamp(dy, miny, maxy);
  409. if (step<1) ny = ny.toFixed(1); // float precision hack
  410. scope.$set(fader_var_y, ny);
  411. }
  412. }.bind(this);
  413. var handle_move = function(evt) {
  414. evt = fixup_touches(evt);
  415. var dx = parseInt((evt.pageX - this.fader_mx) * sensitivity);
  416. var dy = parseInt((evt.pageY - this.fader_my) * sensitivity);
  417. dx *= step;
  418. dy *= step;
  419. move_handle(this.fader_oldx+dx,this.fader_oldy-dy);
  420. }.bind(this);
  421. var handle_up = function(evt) {
  422. this.fader_state = "idle";
  423. $("body").off(emove, handle_move);
  424. $("body").off("mouseleave "+eup+" blur", handle_up);
  425. window._sd_fader_moving = false; // signal for other event systems
  426. }.bind(this);
  427. function prevent_event(evt) {
  428. evt.preventDefault();
  429. evt.stopPropagation();
  430. };
  431. $el.on(edown,function(evt) {
  432. evt.preventDefault();
  433. evt.stopPropagation();
  434. evt = fixup_touches(evt);
  435. var offset = $(evt.target).offset();
  436. this.fader_state = "drag";
  437. if (!encoder) {
  438. move_handle(evt.pageX-offset.left, maxy - (evt.pageY - offset.top) + knob_size/2);
  439. }
  440. if (yfader) {
  441. ny = scope.$get(fader_var_y);
  442. }
  443. $("body").on(emove, handle_move);
  444. $("body").on("mouseleave "+eup+" blur", handle_up);
  445. this.fader_mx = evt.pageX;
  446. this.fader_my = evt.pageY;
  447. this.fader_oldx = nx||0;
  448. this.fader_oldy = ny||0;
  449. window._sd_fader_moving = true; // signal for other event systems
  450. }.bind(this));
  451. // initial state
  452. position_handle();
  453. if (xfader) {
  454. scope.$watch(fader_var_x, function(a) {
  455. nx = parseInt(scope.$get(fader_var_x));
  456. position_handle();
  457. });
  458. }
  459. if (yfader) {
  460. scope.$watch(fader_var_y, function(a) {
  461. ny = parseInt(scope.$get(fader_var_y));
  462. position_handle();
  463. });
  464. }
  465. },
  466. unbind: function() {
  467. var scope = this.vm.$root;
  468. var $el = $(this.el);
  469. var fader_var_x = $el.attr("sd-fader-var-x");
  470. var fader_var_y = $el.attr("sd-fader-var-y");
  471. }
  472. });
  473. }