1 /* widgetsDefinition of an ancestor for the Widget classes */ |
1 /* widgetsDefinition of an ancestor for the Widget classes */ |
2 |
2 import Mustache from "mustache"; |
3 if (typeof IriSP.Widgets === "undefined") { |
3 import jQuery from "jquery"; |
4 IriSP.Widgets = {}; |
|
5 } |
|
6 |
4 |
7 /** |
5 /** |
8 * @class IriSP.Widget is an "abstract" class. It's mostly used to define some properties common to every widget. |
6 * @class IriSP.Widget is an "abstract" class. It's mostly used to define some properties common to every widget. |
9 * |
7 * |
10 * Note that widget constructors are never called directly by the user. Instead, the widgets are instantiated by functions |
8 * Note that widget constructors are never called directly by the user. Instead, the widgets are instantiated by functions |
11 * defined in init.js |
9 * defined in init.js |
12 * |
10 * |
13 * @constructor |
11 * @constructor |
14 * @param player - a reference to the player widget |
12 * @param player - a reference to the player widget |
15 * @param config - configuration options for the widget |
13 * @param config - configuration options for the widget |
|
14 * |
16 */ |
15 */ |
17 |
16 |
18 |
17 import _ from "lodash"; |
19 IriSP.Widgets.Widget = function(player, config) { |
18 |
20 |
19 export default function (IriSP) { |
21 if( typeof player === "undefined") { |
20 const Widgets = {}; |
|
21 |
|
22 Widgets.Widget = class { |
|
23 |
|
24 static defaults = {}; |
|
25 |
|
26 static template = ""; |
|
27 |
|
28 static messages = { en: {} }; |
|
29 |
|
30 constructor(player, config) { |
|
31 if (typeof player === "undefined") { |
22 /* Probably an abstract call of the class when |
32 /* Probably an abstract call of the class when |
23 * individual widgets set their prototype */ |
33 * individual widgets set their prototype */ |
24 return; |
34 return; |
25 } |
35 } |
26 |
36 |
27 this.__subwidgets = []; |
37 this.__subwidgets = []; |
28 |
38 |
29 /* Setting all the configuration options */ |
39 /* Setting all the configuration options */ |
30 var _type = config.type || "(unknown)", |
40 var _type = config.type || "(unknown)", |
31 _config = IriSP._.defaults({}, config, (player && player.config ? player.config.default_options : {}), this.defaults), |
41 _config = _.defaults( |
|
42 {}, |
|
43 config, |
|
44 player && player.config ? player.config.default_options : {}, |
|
45 this.constructor.defaults |
|
46 ), |
32 _this = this; |
47 _this = this; |
33 |
48 |
34 IriSP._(_config).forEach(function(_value, _key) { |
49 _(_config).forEach(function (_value, _key) { |
35 _this[_key] = _value; |
50 _this[_key] = _value; |
36 }); |
51 }); |
37 |
52 |
38 this.$ = IriSP.jQuery('#' + this.container); |
53 this.$ = jQuery("#" + this.container); |
39 |
54 |
40 if (typeof this.width === "undefined") { |
55 if (typeof this.width === "undefined") { |
41 this.width = this.$.width(); |
56 this.width = this.$.width(); |
42 } else { |
57 } else { |
43 this.$.css("width", this.width); |
58 this.$.css("width", this.width); |
44 } |
59 } |
45 |
60 |
46 if (typeof this.height !== "undefined") { |
61 if (typeof this.height !== "undefined") { |
47 this.$.css("height", this.height); |
62 this.$.css("height", this.height); |
48 } |
63 } |
49 |
64 |
50 /* Setting this.player at the end in case it's been overriden |
65 /* Setting this.player at the end in case it's been overriden |
51 * by a configuration option of the same name :-( |
66 * by a configuration option of the same name :-( |
52 */ |
67 */ |
53 this.player = player || new IriSP.FakeClass(["on","trigger","off","loadWidget","loadMetadata"]); |
68 this.player = |
54 |
69 player || |
55 /* Adding classes and html attributes */ |
70 new IriSP.FakeClass([ |
56 this.$.addClass("Ldt-TraceMe Ldt-Widget").attr("widget-type", _type); |
71 "on", |
57 |
72 "trigger", |
58 this.l10n = ( |
73 "off", |
59 typeof this.messages[IriSP.language] !== "undefined" |
74 "loadWidget", |
60 ? this.messages[IriSP.language] |
75 "loadMetadata", |
61 : ( |
76 ]); |
62 IriSP.language.length > 2 && typeof this.messages[IriSP.language.substr(0,2)] !== "undefined" |
77 |
63 ? this.messages[IriSP.language.substr(0,2)] |
78 /* Adding classes and html attributes */ |
64 : this.messages["en"] |
79 this.$.addClass("Ldt-TraceMe Ldt-Widget").attr("widget-type", _type); |
65 ) |
80 |
66 ); |
81 this.l10n = |
67 |
82 typeof this.constructor.messages[IriSP.language] !== "undefined" |
68 /* Loading Metadata if required */ |
83 ? this.constructor.messages[IriSP.language] |
69 |
84 : IriSP.language.length > 2 && |
70 function onsourceloaded() { |
85 typeof this.constructor.messages[IriSP.language.substr(0, 2)] !== "undefined" |
|
86 ? this.constructor.messages[IriSP.language.substr(0, 2)] |
|
87 : this.constructor.messages["en"]; |
|
88 |
|
89 /* Loading Metadata if required */ |
|
90 |
|
91 function onsourceloaded() { |
71 if (_this.localannotations) { |
92 if (_this.localannotations) { |
72 _this.localsource = player.loadLocalAnnotations(_this.localannotations); |
93 _this.localsource = player.loadLocalAnnotations( |
73 _this.source.merge(_this.localsource); |
94 _this.localannotations |
|
95 ); |
|
96 _this.source.merge(_this.localsource); |
74 } |
97 } |
75 if (_this.media_id) { |
98 if (_this.media_id) { |
76 _this.media = this.getElement(_this.media_id); |
99 _this.media = this.getElement(_this.media_id); |
77 } else { |
100 } else { |
78 var _mediaopts = { |
101 var _mediaopts = { |
79 is_mashup: _this.is_mashup || false |
102 is_mashup: _this.is_mashup || false, |
80 }; |
103 }; |
81 _this.media = _this.source.getCurrentMedia(_mediaopts); |
104 _this.media = _this.source.getCurrentMedia(_mediaopts); |
82 } |
105 } |
83 if (_this.pre_draw_callback){ |
106 if (_this.pre_draw_callback) { |
84 IriSP.jQuery.when(_this.pre_draw_callback()).done(_this.draw()); |
107 jQuery.when(_this.pre_draw_callback()).done(_this.draw()); |
85 } |
108 } else { |
86 else { |
109 _this.draw(); |
87 _this.draw(); |
|
88 } |
110 } |
89 _this.player.trigger("widget-loaded"); |
111 _this.player.trigger("widget-loaded"); |
90 } |
112 } |
91 |
113 |
92 if (this.metadata) { |
114 if (this.metadata) { |
93 /* Getting metadata */ |
115 /* Getting metadata */ |
94 this.source = player.loadMetadata(this.metadata); |
116 this.source = player.loadMetadata(this.metadata); |
95 /* Call draw when loaded */ |
117 /* Call draw when loaded */ |
96 this.source.onLoad(onsourceloaded); |
118 this.source.onLoad(onsourceloaded); |
97 } else { |
119 } else { |
98 if (this.source) { |
120 if (this.source) { |
99 onsourceloaded(); |
121 onsourceloaded(); |
100 } |
122 } |
101 } |
123 } |
102 |
124 } |
103 |
125 |
104 }; |
126 toString() { |
105 |
127 return "Widget " + this.type; |
106 IriSP.Widgets.Widget.prototype.defaults = {}; |
128 } |
107 |
129 |
108 IriSP.Widgets.Widget.prototype.template = ''; |
130 templateToHtml(_template) { |
109 |
131 return Mustache.render(_template, this); |
110 IriSP.Widgets.Widget.prototype.messages = {"en":{}}; |
132 } |
111 |
133 |
112 IriSP.Widgets.Widget.prototype.toString = function() { |
134 renderTemplate() { |
113 return "Widget " + this.type; |
135 this.$.append(this.templateToHtml(this.constructor.template)); |
114 }; |
136 } |
115 |
137 |
116 IriSP.Widgets.Widget.prototype.templateToHtml = function(_template) { |
138 functionWrapper(_name) { |
117 return Mustache.to_html(_template, this); |
139 var _this = this, |
118 }; |
|
119 |
|
120 IriSP.Widgets.Widget.prototype.renderTemplate = function() { |
|
121 this.$.append(this.templateToHtml(this.template)); |
|
122 }; |
|
123 |
|
124 IriSP.Widgets.Widget.prototype.functionWrapper = function(_name) { |
|
125 var _this = this, |
|
126 _function = this[_name]; |
140 _function = this[_name]; |
127 if (typeof _function !== "undefined") { |
141 if (typeof _function !== "undefined") { |
128 return function() { |
142 return function () { |
129 return _function.apply(_this, Array.prototype.slice.call(arguments, 0)); |
143 return _function.apply( |
|
144 _this, |
|
145 Array.prototype.slice.call(arguments, 0) |
|
146 ); |
130 }; |
147 }; |
131 } else { |
148 } else { |
132 console.log("Error, Unknown function IriSP.Widgets." + this.type + "." + _name); |
149 console.log( |
133 } |
150 "Error, Unknown function IriSP.Widgets." + this.type + "." + _name |
134 }; |
151 ); |
135 |
152 } |
136 IriSP.Widgets.Widget.prototype.getFunctionOrName = function(_functionOrName) { |
153 } |
137 switch (typeof _functionOrName) { |
154 |
|
155 getFunctionOrName(_functionOrName) { |
|
156 switch (typeof _functionOrName) { |
138 case "function": |
157 case "function": |
139 return _functionOrName; |
158 return _functionOrName; |
140 case "string": |
159 case "string": |
141 return this.functionWrapper(_functionOrName); |
160 return this.functionWrapper(_functionOrName); |
142 default: |
161 default: |
143 return undefined; |
162 return undefined; |
144 } |
163 } |
145 }; |
164 } |
146 |
165 |
147 IriSP.Widgets.Widget.prototype.onMdpEvent = function(_eventName, _functionOrName) { |
166 onMdpEvent(_eventName, _functionOrName) { |
148 this.player.on(_eventName, this.getFunctionOrName(_functionOrName)); |
167 this.player.on(_eventName, this.getFunctionOrName(_functionOrName)); |
149 }; |
168 } |
150 |
169 |
151 IriSP.Widgets.Widget.prototype.onMediaEvent = function(_eventName, _functionOrName) { |
170 onMediaEvent(_eventName, _functionOrName) { |
152 this.media.on(_eventName, this.getFunctionOrName(_functionOrName)); |
171 this.media.on(_eventName, this.getFunctionOrName(_functionOrName)); |
153 }; |
172 } |
154 |
173 |
155 IriSP.Widgets.Widget.prototype.getWidgetAnnotations = function() { |
174 getWidgetAnnotations() { |
156 var result = null; |
175 var result = null; |
157 if (typeof this.annotation_type === "undefined") { |
176 if (typeof this.annotation_type === "undefined") { |
158 result = this.media.getAnnotations(); |
177 result = this.media.getAnnotations(); |
159 } else if (this.annotation_type.elementType === "annotationType") { |
178 } else if (this.annotation_type.elementType === "annotationType") { |
160 result = this.annotation_type.getAnnotations(); |
179 result = this.annotation_type.getAnnotations(); |
161 } else { |
180 } else { |
162 result = this.media.getAnnotationsByTypeTitle(this.annotation_type); |
181 result = this.media.getAnnotationsByTypeTitle(this.annotation_type); |
163 } |
182 } |
164 if (typeof this.annotation_filter !== "undefined") { |
183 if (typeof this.annotation_filter !== "undefined") { |
165 return this.annotation_filter(result); |
184 return this.annotation_filter(result); |
166 } else { |
185 } else { |
167 return result; |
186 return result; |
168 } |
187 } |
169 }; |
188 } |
170 |
189 |
171 IriSP.Widgets.Widget.prototype.getWidgetAnnotationsAtTime = function() { |
190 getWidgetAnnotationsAtTime() { |
172 var _time = this.media.getCurrentTime(); |
191 var _time = this.media.getCurrentTime(); |
173 return this.getWidgetAnnotations().filter(function(_annotation) { |
192 return this.getWidgetAnnotations().filter(function (_annotation) { |
174 return _annotation.begin <= _time && _annotation.end > _time; |
193 return _annotation.begin <= _time && _annotation.end > _time; |
175 }); |
194 }); |
176 }; |
195 } |
177 |
196 |
178 IriSP.Widgets.Widget.prototype.isLoaded = function() { |
197 isLoaded() { |
179 var isloaded = !IriSP._(this.__subwidgets).any(function(w) { |
198 var isloaded = !_(this.__subwidgets).some(function (w) { |
180 return !(w && w.isLoaded()); |
199 return !(w && w.isLoaded()); |
181 }); |
200 }); |
182 return isloaded; |
201 return isloaded; |
183 }; |
202 } |
184 |
203 |
185 IriSP.Widgets.Widget.prototype.insertSubwidget = function(_selector, _widgetoptions, _propname) { |
204 insertSubwidget(_selector, _widgetoptions, _propname) { |
186 var _id = _selector.attr("id"), |
205 var _id = _selector.attr("id"), |
187 _this = this, |
206 _this = this, |
188 _type = _widgetoptions.type, |
|
189 $L = $LAB, |
|
190 key = this.__subwidgets.length; |
207 key = this.__subwidgets.length; |
191 this.__subwidgets.push(null); |
208 this.__subwidgets.push(null); |
192 if (typeof _id == "undefined") { |
209 if (typeof _id == "undefined") { |
193 _id = IriSP._.uniqueId(this.container + '_sub_widget_' + _widgetoptions.type); |
210 _id = _.uniqueId(this.container + "_sub_widget_" + _widgetoptions.type); |
194 _selector.attr("id", _id); |
211 _selector.attr("id", _id); |
195 } |
212 } |
196 _widgetoptions.container = _id; |
213 _widgetoptions.container = _id; |
197 if (typeof IriSP.widgetsRequirements[_type] !== "undefined" && typeof IriSP.widgetsRequirements[_type].requires !== "undefined" ) { |
214 _this.player.loadWidget(_widgetoptions, function (_widget) { |
198 for (var _j = 0; _j < IriSP.widgetsRequirements[_type].requires.length; _j++) { |
215 if (_propname) { |
199 $L.script(IriSP.getLib(IriSP.widgetsRequirements[_type].requires[_j])); |
216 _this[_propname] = _widget; |
200 } |
217 } |
201 } |
218 _this.__subwidgets[key] = _widget; |
202 $L.wait(function() { |
219 }); |
203 _this.player.loadWidget(_widgetoptions, function(_widget) { |
220 } |
204 if (_propname) { |
221 |
205 _this[_propname] = _widget; |
222 /* |
|
223 * Position the player to the next/previous annotations based on current player position |
|
224 * |
|
225 * Parameter: offset: -1 for previous annotation, +1 for next annotation |
|
226 */ |
|
227 navigate(offset) { |
|
228 // offset is normally either -1 (previous slide) or +1 (next slide) |
|
229 var _this = this; |
|
230 var currentTime = _this.media.getCurrentTime(); |
|
231 var annotations = _this |
|
232 .getWidgetAnnotations() |
|
233 .sortBy(function (_annotation) { |
|
234 return _annotation.begin; |
|
235 }); |
|
236 for (var i = 0; i < annotations.length; i++) { |
|
237 if ( |
|
238 annotations[i].begin <= currentTime && |
|
239 currentTime < annotations[i].end |
|
240 ) { |
|
241 // Found a current annotation - clamp i+offset value to [0, length - 1] |
|
242 i = Math.min(annotations.length - 1, Math.max(0, i + offset)); |
|
243 _this.media.setCurrentTime(annotations[i].begin); |
|
244 break; |
|
245 } |
|
246 } |
|
247 } |
|
248 |
|
249 /* |
|
250 * Propose an export of the widget's annotations |
|
251 * |
|
252 * Parameter: a list of annotations. If not specified, the widget's annotations will be exported. |
|
253 */ |
|
254 exportAnnotations(annotations) { |
|
255 var widget = this; |
|
256 if (annotations === undefined) annotations = this.getWidgetAnnotations(); |
|
257 var $ = jQuery; |
|
258 |
|
259 // FIXME: this should belong to a proper serialize/deserialize component? |
|
260 var content = |
|
261 Mustache.render("[video:{{url}}]\n", { url: widget.media.url }) + |
|
262 annotations |
|
263 .map(function (a) { |
|
264 return Mustache.render( |
|
265 "[{{ a.begin }}]{{ a.title }} {{ a.description }}[{{ a.end }}]", |
|
266 { a: a } |
|
267 ); |
|
268 }) |
|
269 .join("\n"); |
|
270 |
|
271 var el = $("<pre>") |
|
272 .addClass("exportContainer") |
|
273 .text(content) |
|
274 .dialog({ |
|
275 title: "Annotation export", |
|
276 open: function (event, ui) { |
|
277 // Select text |
|
278 var range; |
|
279 if (document.selection) { |
|
280 range = document.body.createTextRange(); |
|
281 range.moveToElementText(this[0]); |
|
282 range.select(); |
|
283 } else if (window.getSelection) { |
|
284 range = document.createRange(); |
|
285 range.selectNode(this[0]); |
|
286 window.getSelection().addRange(range); |
206 } |
287 } |
207 _this.__subwidgets[key] = _widget; |
288 }, |
|
289 autoOpen: true, |
|
290 width: "80%", |
|
291 minHeight: "400", |
|
292 height: 400, |
|
293 buttons: [ |
|
294 { |
|
295 text: "Close", |
|
296 click: function () { |
|
297 $(this).dialog("close"); |
|
298 }, |
|
299 }, |
|
300 { |
|
301 text: "Download", |
|
302 click: function () { |
|
303 a = document.createElement("a"); |
|
304 a.setAttribute( |
|
305 "href", |
|
306 "data:text/plain;base64," + btoa(content) |
|
307 ); |
|
308 a.setAttribute( |
|
309 "download", |
|
310 "Annotations - " + |
|
311 widget.media.title.replace(/[^ \w]/g, "") + |
|
312 ".txt" |
|
313 ); |
|
314 a.click(); |
|
315 }, |
|
316 }, |
|
317 ], |
208 }); |
318 }); |
209 }); |
319 } |
210 }; |
320 |
211 |
321 /** |
212 /* |
322 * This method responsible of drawing a widget on screen. |
213 * Position the player to the next/previous annotations based on current player position |
323 */ |
214 * |
324 draw() { |
215 * Parameter: offset: -1 for previous annotation, +1 for next annotation |
325 /* implemented by "sub-classes" */ |
216 */ |
326 } |
217 IriSP.Widgets.Widget.prototype.navigate = function(offset) { |
327 |
218 // offset is normally either -1 (previous slide) or +1 (next slide) |
328 //Generates uid |
219 var _this = this; |
329 //source : http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript |
220 var currentTime = _this.media.getCurrentTime(); |
330 generateUid() { |
221 var annotations = _this.getWidgetAnnotations().sortBy(function(_annotation) { |
331 return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( |
222 return _annotation.begin; |
332 /[xy]/g, |
223 }); |
333 function (c) { |
224 for (var i = 0; i < annotations.length; i++) { |
334 var r = (Math.random() * 16) | 0, |
225 if (annotations[i].begin <= currentTime && currentTime < annotations[i].end) { |
335 v = c === "x" ? r : (r & 0x3) | 0x8; |
226 // Found a current annotation - clamp i+offset value to [0, length - 1] |
336 return v.toString(16); |
227 i = Math.min(annotations.length - 1, Math.max(0, i + offset)); |
337 } |
228 _this.media.setCurrentTime(annotations[i].begin); |
338 ); |
229 break; |
339 } |
230 } |
340 }; |
231 }; |
341 |
232 }; |
342 return Widgets; |
233 |
343 } |
234 /* |
|
235 * Propose an export of the widget's annotations |
|
236 * |
|
237 * Parameter: a list of annotations. If not specified, the widget's annotations will be exported. |
|
238 */ |
|
239 IriSP.Widgets.Widget.prototype.exportAnnotations = function(annotations) { |
|
240 var widget = this; |
|
241 if (annotations === undefined) |
|
242 annotations = this.getWidgetAnnotations(); |
|
243 var $ = IriSP.jQuery; |
|
244 |
|
245 // FIXME: this should belong to a proper serialize/deserialize component? |
|
246 var content = Mustache.to_html("[video:{{url}}]\n", {url: widget.media.url}) + annotations.map( function(a) { return Mustache.to_html("[{{ a.begin }}]{{ a.title }} {{ a.description }}[{{ a.end }}]", { a: a }); }).join("\n"); |
|
247 |
|
248 var el = $("<pre>") |
|
249 .addClass("exportContainer") |
|
250 .text(content) |
|
251 .dialog({ |
|
252 title: "Annotation export", |
|
253 open: function( event, ui ) { |
|
254 // Select text |
|
255 var range; |
|
256 if (document.selection) { |
|
257 range = document.body.createTextRange(); |
|
258 range.moveToElementText(this[0]); |
|
259 range.select(); |
|
260 } else if (window.getSelection) { |
|
261 range = document.createRange(); |
|
262 range.selectNode(this[0]); |
|
263 window.getSelection().addRange(range); |
|
264 } |
|
265 }, |
|
266 autoOpen: true, |
|
267 width: '80%', |
|
268 minHeight: '400', |
|
269 height: 400, |
|
270 buttons: [ { text: "Close", click: function() { $( this ).dialog( "close" ); } }, |
|
271 { text: "Download", click: function () { |
|
272 a = document.createElement('a'); |
|
273 a.setAttribute('href', 'data:text/plain;base64,' + btoa(content)); |
|
274 a.setAttribute('download', 'Annotations - ' + widget.media.title.replace(/[^ \w]/g, '') + '.txt'); |
|
275 a.click(); |
|
276 } } ] |
|
277 }); |
|
278 }; |
|
279 |
|
280 /** |
|
281 * This method responsible of drawing a widget on screen. |
|
282 */ |
|
283 IriSP.Widgets.Widget.prototype.draw = function() { |
|
284 /* implemented by "sub-classes" */ |
|
285 }; |
|