569 lines
15 KiB
JavaScript
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");
|
|
}
|
|
});
|
|
}
|