spacedeck-open/public/javascripts/spacedeck_directives.js

569 lines
15 KiB
JavaScript

/*
Spacedeck Directives
This module registers custom Vue directives for Spacedeck.
*/
function setup_directives() {
Vue.directive('clipboard', {
bind: function () {
this.clipboard = new Clipboard(".clipboard-btn");
},
update: function (value) {
},
unbind: function () {
this.clipboard.destroy()
}
});
Vue.directive('t', {
update: function (value, key) {
this.el.innerHTML = key;
}
});
if ('ontouchstart' in window) {
var edown = "touchstart";
var emove = "touchmove";
var eup = "touchend";
} else {
var edown = "mousedown";
var emove = "mousemove";
var eup = "mouseup";
}
Vue.directive('videoplayer', {
update: function (a) {
var el = this.el;
var scope = this.vm.$root;
var video = el.querySelectorAll("video")[0];
var play_button = el.querySelectorAll(".play")[0];
var pause_button = el.querySelectorAll(".pause")[0];
var stop_button = el.querySelectorAll(".stop")[0];
var player_state = "stop";
var update_view = function() {
try {
if (!a.player_view) { a.player_view = {} };
a.player_view.state = player_state;
} catch (e) {
// catch InvalidStateError
}
}
var play_func = function() {
video.play();
player_state = "playing";
update_view();
}
var pause_func = function() {
try {
video.pause();
player_state = "paused";
update_view();
} catch (e) {
// catch InvalidStateError
}
}
var stop_func = function() {
try {
player_state = "stop";
video.pause();
video.currentTime = 0;
update_view();
} catch (e) {
// catch InvalidStateError
}
}
el.addEventListener("remote_play",play_func);
el.addEventListener("remote_pause",pause_func);
el.addEventListener("remote_stop",stop_func);
play_button.addEventListener(edown, function(evt) {
try {
play_func();
spacedeck.presenter_send_media_action(a._id,"video","play",video.currentTime);
} catch (e) {
// catch InvalidStateError
}
}, false);
pause_button.addEventListener(edown, function(evt) {
pause_func();
spacedeck.presenter_send_media_action(a._id,"video","pause",video.currentTime);
}, false);
stop_button.addEventListener(edown, function(evt) {
stop_func();
spacedeck.presenter_send_media_action(a._id,"video","stop",0);
}, false);
}
});
Vue.directive('audioplayer', {
update: function (a) {
var el = this.el;
var scope = this.vm.$root;
var play_button = el.querySelectorAll(".play")[0];
var pause_button = el.querySelectorAll(".pause")[0];
var stop_button = el.querySelectorAll(".stop")[0];
var timeline = el.querySelectorAll(".timeline")[0];
var set_inpoint = el.querySelectorAll(".set-inpoint")[0];
var set_outpoint = el.querySelectorAll(".set-outpoint")[0];
var reset_points = el.querySelectorAll(".reset-points")[0];
var player_state = "stop";
var play_from = 0.0;
var play_to = 0.0;
var audio = el.querySelectorAll("audio")[0];
var update_markers = function() {
try {
if (a.meta) {
if (!a.meta.play_to) a.meta.play_to = audio.duration;
play_from = parseFloat(a.meta.play_from) || 0.0;
play_to = parseFloat(a.meta.play_to) || 0.0;
} else {
play_from = 0.0;
play_to = parseFloat(audio.duration) || 0.0;
a.meta = {};
}
} catch (e) {
// catch InvalidStateError
}
}
var update_view = function() {
try {
if (!a.player_view) { a.player_view = {} };
a.player_view.state = player_state;
a.player_view.total_time_string = format_time(audio.duration);
a.player_view.current_time_string = format_time(audio.currentTime);
a.player_view.current_time_float = audio.currentTime/audio.duration;
a.player_view.inpoint_float = play_from/audio.duration;
a.player_view.outpoint_float = play_to/audio.duration;
a.player_view.duration = audio.duration;
} catch (e) {
// catch InvalidStateError
}
}
var pause_audio = function() {
try {
audio.pause();
player_state = "paused";
} catch (e) {
// catch InvalidStateError
}
update_view();
}
var stop_audio = function() {
try {
audio.currentTime = play_from;
audio.pause();
player_state = "stop";
} catch (e) {
// catch InvalidStateError
}
update_view();
}
update_view();
audio.addEventListener("loadedmetadata", function(evt) {
update_markers();
update_view();
}, false);
audio.addEventListener("timeupdate", function(evt) {
try {
update_markers();
if (audio.currentTime >= play_to && player_state == "playing") stop_audio();
update_view();
} catch (e) {
// catch InvalidStateError
}
}, false);
var play_func = function() {
if (player_state == "stop") {
audio.currentTime = play_from;
}
player_state = "playing";
update_markers();
audio.play();
update_view();
}
var pause_func = function() {
pause_audio();
update_view();
}
var stop_func = function() {
stop_audio();
update_view();
}
el.addEventListener("remote_play",play_func);
el.addEventListener("remote_pause",pause_func);
el.addEventListener("remote_stop",stop_func);
play_button.addEventListener(edown, function(evt) {
try {
play_func();
spacedeck.presenter_send_media_action(a._id,"audio","play",audio.currentTime);
} catch (e) {
// catch InvalidStateError
}
}, false);
pause_button.addEventListener(edown, function(evt) {
pause_func();
spacedeck.presenter_send_media_action(a._id,"audio","pause",audio.currentTime);
}, false);
stop_button.addEventListener(edown, function(evt) {
stop_func();
spacedeck.presenter_send_media_action(a._id,"audio","stop",0);
}, false);
timeline.addEventListener(edown, function(evt) {
var clicked_time = (parseFloat(evt.offsetX)/evt.currentTarget.offsetWidth)*audio.duration;
if (isNaN(clicked_time)) {
clicked_time = 0.0;
}
try {
audio.currentTime = clicked_time;
} catch (e) {
// catch InvalidStateErrors
}
}, false);
set_inpoint.addEventListener(edown, function(evt) {
if (!a.meta) a.meta = {};
a.meta.play_from = audio.currentTime;
if (a.meta.play_to<a.meta.play_from) a.meta.play_to = audio.duration;
update_markers();
stop_audio();
update_view();
scope.save_artifact(a);
}, false);
set_outpoint.addEventListener(edown, function(evt) {
if (!a.meta) a.meta = {};
a.meta.play_to = audio.currentTime;
if (a.meta.play_to<a.meta.play_from) a.meta.play_from = 0.0;
update_markers();
stop_audio();
update_view();
scope.save_artifact(a);
}, false);
reset_points.addEventListener(edown, function(evt) {
if (!a.meta) a.meta = {};
a.meta.play_from = 0.0;
a.meta.play_to = audio.duration;
update_markers();
stop_audio();
update_view();
scope.save_artifact(a);
}, false);
}
});
Vue.directive('sd-richtext', {
twoWay: true,
update: function(obj) {
this.mode = 'rich';
$(this.el).addClass("text-editing");
this.medium = new Medium({
element: this.el,
mode: Medium.richMode,
attributes: {
remove: ['class','href','onclick','onmousedown','onmouseup']
},
});
this.medium.value(obj.description);
this.medium.element.addEventListener('keyup', function() {
obj.description = this.medium.value();
spacedeck.queue_artifact_for_save(obj);
}.bind(this));
spacedeck.medium_for_object[obj._id] = this.medium;
}
});
Vue.directive('focus', {
bind: function () {
var el = this.el;
window.setTimeout(function() {
if (el.contentEditable && el.contentEditable!="inherit") {
var range = document.createRange();
range.selectNodeContents(el);
} else {
el.focus();
el.select();
}
}, 500);
},
});
Vue.directive('sd-draggable', {
update: function(data) {
var el = this.el;
el.addEventListener(
'dragstart',
function(evt) {
if ($(el).find(".text-editing").length) {
// FIXME: technical debt
evt.stopPropagation();
evt.preventDefault();
return;
}
evt.dataTransfer.setData('application/json', JSON.stringify(data));
$(el).addClass("dragging");
},
false
);
}
});
Vue.directive('sd-droppable', {
isFn: true,
bind: function() {
var el = this.el;
var expression = this.expression;
var parts = expression.split(";");
var func_key = parts[0];
var data_key = parts[1];
el.addEventListener(
'dragover',
function(e) {
e.dataTransfer.dropEffect = 'copy';
// allows us to drop
if (e.preventDefault) e.preventDefault();
el.classList.add('over');
return false;
}.bind(this),
false
);
el.addEventListener(
'dragenter',
function(e) {
el.classList.add('over');
return false;
}.bind(this),
false
);
el.addEventListener(
'dragleave',
function(e) {
el.classList.remove('over');
return false;
},
false
);
el.addEventListener(
'drop',
function(e) {
e.stopPropagation();
e.preventDefault();
$(e.currentTarget).find(".over").removeClass('over');
$(e.currentTarget).find(".dragging").removeClass('dragging');
var func = this.vm.$root[func_key].bind(this.vm.$root);
if (this._scope) {
var obj = this._scope[data_key];
} else {
var obj = this.vm[data_key];
}
func(e, obj);
return false;
}.bind(this),
false
);
}
});
Vue.directive('sd-fader', {
bind: function (section) {
function clamp(v, mn, mx) {
return Math.max(mn,Math.min(mx,v));
}
var scope = this.vm.$root;
this.fader_state = "idle";
this.fader_mx = 0;
this.fader_my = 0;
var $el = $(this.el);
var handle = $el.find(".fader-selector");
var indicator = $el.find(".fader-indicator");
var constraint = $el.find(".fader-constraint");
if (!constraint.length) constraint = $el;
var fader_var_x = $el.attr("sd-fader-var-x");
var fader_var_y = $el.attr("sd-fader-var-y");
var knob_size = 0;
var minx = 0;
var miny = 0;
var maxx = 0;
var maxy = 0;
var nx = 0;
if (xfader) nx = scope.$get(fader_var_x);
var ny = 0;
if (yfader) ny = scope.$get(fader_var_y);
var xfader = !!fader_var_x;
var yfader = !!fader_var_y;
var encoder = !handle[0];
var step = parseFloat($el.attr("sd-fader-step"))||1;
var sensitivity = parseFloat($el.attr("sd-fader-sens"))||1;
var discover_minmax = function() {
minx = (parseInt($el.attr("sd-fader-min-x"))||0);
miny = (parseInt($el.attr("sd-fader-min-y"))||0);
maxx = parseInt($el.attr("sd-fader-max-x"))||(constraint.width() - 1);
maxy = parseInt($el.attr("sd-fader-max-y"))||(constraint.height() - 1);
}
var position_handle = function() {
discover_minmax();
if (!nx || isNaN(nx)) nx = 0;
if (!ny || isNaN(ny)) ny = 0;
if (handle[0]) {
if (xfader) handle[0].style.left = nx+"px";
if (yfader) handle[0].style.top = (maxy-ny)+"px";
}
if (indicator[0]) {
indicator[0].style.height = ny+"px";
}
}.bind(this);
var move_handle = function(dx,dy) {
discover_minmax();
if (xfader) {
nx = clamp(dx, minx, maxx);
scope.$set(fader_var_x, nx);
}
if (yfader) {
ny = clamp(dy, miny, maxy);
if (step<1) ny = ny.toFixed(1); // float precision hack
scope.$set(fader_var_y, ny);
}
}.bind(this);
var handle_move = function(evt) {
evt = fixup_touches(evt);
var dx = parseInt((evt.pageX - this.fader_mx) * sensitivity);
var dy = parseInt((evt.pageY - this.fader_my) * sensitivity);
dx *= step;
dy *= step;
move_handle(this.fader_oldx+dx,this.fader_oldy-dy);
}.bind(this);
var handle_up = function(evt) {
this.fader_state = "idle";
$("body").off(emove, handle_move);
$("body").off("mouseleave "+eup+" blur", handle_up);
window._sd_fader_moving = false; // signal for other event systems
}.bind(this);
function prevent_event(evt) {
evt.preventDefault();
evt.stopPropagation();
};
$el.on(edown,function(evt) {
evt.preventDefault();
evt.stopPropagation();
evt = fixup_touches(evt);
var offset = $(evt.target).offset();
this.fader_state = "drag";
if (!encoder) {
move_handle(evt.pageX-offset.left, maxy - (evt.pageY - offset.top) + knob_size/2);
}
if (yfader) {
ny = scope.$get(fader_var_y);
}
$("body").on(emove, handle_move);
$("body").on("mouseleave "+eup+" blur", handle_up);
this.fader_mx = evt.pageX;
this.fader_my = evt.pageY;
this.fader_oldx = nx||0;
this.fader_oldy = ny||0;
window._sd_fader_moving = true; // signal for other event systems
}.bind(this));
// initial state
position_handle();
if (xfader) {
scope.$watch(fader_var_x, function(a) {
nx = parseInt(scope.$get(fader_var_x));
position_handle();
});
}
if (yfader) {
scope.$watch(fader_var_y, function(a) {
ny = parseInt(scope.$get(fader_var_y));
position_handle();
});
}
},
unbind: function() {
var scope = this.vm.$root;
var $el = $(this.el);
var fader_var_x = $el.attr("sd-fader-var-x");
var fader_var_y = $el.attr("sd-fader-var-y");
}
});
}