/* widgetsDefinition of an ancestor for the Widget classes */
import Mustache from "mustache";
import jQuery from "jquery";
/**
* @class IriSP.Widget is an "abstract" class. It's mostly used to define some properties common to every widget.
*
* Note that widget constructors are never called directly by the user. Instead, the widgets are instantiated by functions
* defined in init.js
*
* @constructor
* @param player - a reference to the player widget
* @param config - configuration options for the widget
*
*/
import _ from "lodash";
export default function (IriSP) {
const Widgets = {};
Widgets.Widget = class {
static defaults = {};
static template = "";
static messages = { en: {} };
constructor(player, config) {
if (typeof player === "undefined") {
/* Probably an abstract call of the class when
* individual widgets set their prototype */
return;
}
this.__subwidgets = [];
/* Setting all the configuration options */
var _type = config.type || "(unknown)",
_config = _.defaults(
{},
config,
player && player.config ? player.config.default_options : {},
this.constructor.defaults
),
_this = this;
_(_config).forEach(function (_value, _key) {
_this[_key] = _value;
});
this.$ = jQuery("#" + this.container);
if (typeof this.width === "undefined") {
this.width = this.$.width();
} else {
this.$.css("width", this.width);
}
if (typeof this.height !== "undefined") {
this.$.css("height", this.height);
}
/* Setting this.player at the end in case it's been overriden
* by a configuration option of the same name :-(
*/
this.player =
player ||
new IriSP.FakeClass([
"on",
"trigger",
"off",
"loadWidget",
"loadMetadata",
]);
/* Adding classes and html attributes */
this.$.addClass("Ldt-TraceMe Ldt-Widget").attr("widget-type", _type);
this.l10n =
typeof this.constructor.messages[IriSP.language] !== "undefined"
? this.constructor.messages[IriSP.language]
: IriSP.language.length > 2 &&
typeof this.constructor.messages[IriSP.language.substr(0, 2)] !== "undefined"
? this.constructor.messages[IriSP.language.substr(0, 2)]
: this.constructor.messages["en"];
/* Loading Metadata if required */
function onsourceloaded() {
if (_this.localannotations) {
_this.localsource = player.loadLocalAnnotations(
_this.localannotations
);
_this.source.merge(_this.localsource);
}
if (_this.media_id) {
_this.media = this.getElement(_this.media_id);
} else {
var _mediaopts = {
is_mashup: _this.is_mashup || false,
};
_this.media = _this.source.getCurrentMedia(_mediaopts);
}
if (_this.pre_draw_callback) {
jQuery.when(_this.pre_draw_callback()).done(_this.draw());
} else {
_this.draw();
}
_this.player.trigger("widget-loaded");
}
if (this.metadata) {
/* Getting metadata */
this.source = player.loadMetadata(this.metadata);
/* Call draw when loaded */
this.source.onLoad(onsourceloaded);
} else {
if (this.source) {
onsourceloaded();
}
}
}
toString() {
return "Widget " + this.type;
}
templateToHtml(_template) {
return Mustache.render(_template, this);
}
renderTemplate() {
this.$.append(this.templateToHtml(this.constructor.template));
}
functionWrapper(_name) {
var _this = this,
_function = this[_name];
if (typeof _function !== "undefined") {
return function () {
return _function.apply(
_this,
Array.prototype.slice.call(arguments, 0)
);
};
} else {
console.log(
"Error, Unknown function IriSP.Widgets." + this.type + "." + _name
);
}
}
getFunctionOrName(_functionOrName) {
switch (typeof _functionOrName) {
case "function":
return _functionOrName;
case "string":
return this.functionWrapper(_functionOrName);
default:
return undefined;
}
}
onMdpEvent(_eventName, _functionOrName) {
this.player.on(_eventName, this.getFunctionOrName(_functionOrName));
}
onMediaEvent(_eventName, _functionOrName) {
this.media.on(_eventName, this.getFunctionOrName(_functionOrName));
}
getWidgetAnnotations() {
var result = null;
if (typeof this.annotation_type === "undefined") {
result = this.media.getAnnotations();
} else if (this.annotation_type.elementType === "annotationType") {
result = this.annotation_type.getAnnotations();
} else {
result = this.media.getAnnotationsByTypeTitle(this.annotation_type);
}
if (typeof this.annotation_filter !== "undefined") {
return this.annotation_filter(result);
} else {
return result;
}
}
getWidgetAnnotationsAtTime() {
var _time = this.media.getCurrentTime();
return this.getWidgetAnnotations().filter(function (_annotation) {
return _annotation.begin <= _time && _annotation.end > _time;
});
}
isLoaded() {
var isloaded = !_(this.__subwidgets).some(function (w) {
return !(w && w.isLoaded());
});
return isloaded;
}
insertSubwidget(_selector, _widgetoptions, _propname) {
var _id = _selector.attr("id"),
_this = this,
key = this.__subwidgets.length;
this.__subwidgets.push(null);
if (typeof _id == "undefined") {
_id = _.uniqueId(this.container + "_sub_widget_" + _widgetoptions.type);
_selector.attr("id", _id);
}
_widgetoptions.container = _id;
_this.player.loadWidget(_widgetoptions, function (_widget) {
if (_propname) {
_this[_propname] = _widget;
}
_this.__subwidgets[key] = _widget;
});
}
/*
* Position the player to the next/previous annotations based on current player position
*
* Parameter: offset: -1 for previous annotation, +1 for next annotation
*/
navigate(offset) {
// offset is normally either -1 (previous slide) or +1 (next slide)
var _this = this;
var currentTime = _this.media.getCurrentTime();
var annotations = _this
.getWidgetAnnotations()
.sortBy(function (_annotation) {
return _annotation.begin;
});
for (var i = 0; i < annotations.length; i++) {
if (
annotations[i].begin <= currentTime &&
currentTime < annotations[i].end
) {
// Found a current annotation - clamp i+offset value to [0, length - 1]
i = Math.min(annotations.length - 1, Math.max(0, i + offset));
_this.media.setCurrentTime(annotations[i].begin);
break;
}
}
}
/*
* Propose an export of the widget's annotations
*
* Parameter: a list of annotations. If not specified, the widget's annotations will be exported.
*/
exportAnnotations(annotations) {
var widget = this;
if (annotations === undefined) annotations = this.getWidgetAnnotations();
var $ = jQuery;
// FIXME: this should belong to a proper serialize/deserialize component?
var content =
Mustache.render("[video:{{url}}]\n", { url: widget.media.url }) +
annotations
.map(function (a) {
return Mustache.render(
"[{{ a.begin }}]{{ a.title }} {{ a.description }}[{{ a.end }}]",
{ a: a }
);
})
.join("\n");
var el = $("<pre>")
.addClass("exportContainer")
.text(content)
.dialog({
title: "Annotation export",
open: function (event, ui) {
// Select text
var range;
if (document.selection) {
range = document.body.createTextRange();
range.moveToElementText(this[0]);
range.select();
} else if (window.getSelection) {
range = document.createRange();
range.selectNode(this[0]);
window.getSelection().addRange(range);
}
},
autoOpen: true,
width: "80%",
minHeight: "400",
height: 400,
buttons: [
{
text: "Close",
click: function () {
$(this).dialog("close");
},
},
{
text: "Download",
click: function () {
a = document.createElement("a");
a.setAttribute(
"href",
"data:text/plain;base64," + btoa(content)
);
a.setAttribute(
"download",
"Annotations - " +
widget.media.title.replace(/[^ \w]/g, "") +
".txt"
);
a.click();
},
},
],
});
}
/**
* This method responsible of drawing a widget on screen.
*/
draw() {
/* implemented by "sub-classes" */
}
//Generates uid
//source : http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
generateUid() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function (c) {
var r = (Math.random() * 16) | 0,
v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
}
);
}
};
return Widgets;
}