1 /* |
|
2 * |
|
3 * Copyright 2010-2012 Institut de recherche et d'innovation |
|
4 * contributor(s) : Karim Hamidou, Samuel Huron, Raphael Velt, Thibaut Cavalie |
|
5 * |
|
6 * contact@iri.centrepompidou.fr |
|
7 * http://www.iri.centrepompidou.fr |
|
8 * |
|
9 * This software is a computer program whose purpose is to show and add annotations on a video . |
|
10 * This software is governed by the CeCILL-C license under French law and |
|
11 * abiding by the rules of distribution of free software. You can use, |
|
12 * modify and/ or redistribute the software under the terms of the CeCILL-C |
|
13 * license as circulated by CEA, CNRS and INRIA at the following URL |
|
14 * "http://www.cecill.info". |
|
15 * |
|
16 * The fact that you are presently reading this means that you have had |
|
17 * knowledge of the CeCILL-C license and that you accept its terms. |
|
18 */ |
|
19 /*! LAB.js (LABjs :: Loading And Blocking JavaScript) |
|
20 v2.0.3 (c) Kyle Simpson |
|
21 MIT License |
|
22 */ |
|
23 |
|
24 (function(global){ |
|
25 var _$LAB = global.$LAB, |
|
26 |
|
27 // constants for the valid keys of the options object |
|
28 _UseLocalXHR = "UseLocalXHR", |
|
29 _AlwaysPreserveOrder = "AlwaysPreserveOrder", |
|
30 _AllowDuplicates = "AllowDuplicates", |
|
31 _CacheBust = "CacheBust", |
|
32 /*!START_DEBUG*/_Debug = "Debug",/*!END_DEBUG*/ |
|
33 _BasePath = "BasePath", |
|
34 |
|
35 // stateless variables used across all $LAB instances |
|
36 root_page = /^[^?#]*\//.exec(location.href)[0], |
|
37 root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0], |
|
38 append_to = document.head || document.getElementsByTagName("head"), |
|
39 |
|
40 // inferences... ick, but still necessary |
|
41 opera_or_gecko = (global.opera && Object.prototype.toString.call(global.opera) == "[object Opera]") || ("MozAppearance" in document.documentElement.style), |
|
42 |
|
43 /*!START_DEBUG*/ |
|
44 // console.log() and console.error() wrappers |
|
45 log_msg = function(){}, |
|
46 log_error = log_msg, |
|
47 /*!END_DEBUG*/ |
|
48 |
|
49 // feature sniffs (yay!) |
|
50 test_script_elem = document.createElement("script"), |
|
51 explicit_preloading = typeof test_script_elem.preload == "boolean", // http://wiki.whatwg.org/wiki/Script_Execution_Control#Proposal_1_.28Nicholas_Zakas.29 |
|
52 real_preloading = explicit_preloading || (test_script_elem.readyState && test_script_elem.readyState == "uninitialized"), // will a script preload with `src` set before DOM append? |
|
53 script_ordered_async = !real_preloading && test_script_elem.async === true, // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order |
|
54 |
|
55 // XHR preloading (same-domain) and cache-preloading (remote-domain) are the fallbacks (for some browsers) |
|
56 xhr_or_cache_preloading = !real_preloading && !script_ordered_async && !opera_or_gecko |
|
57 ; |
|
58 |
|
59 /*!START_DEBUG*/ |
|
60 // define console wrapper functions if applicable |
|
61 if (global.console && global.console.log) { |
|
62 if (!global.console.error) global.console.error = global.console.log; |
|
63 log_msg = function(msg) { global.console.log(msg); }; |
|
64 log_error = function(msg,err) { global.console.error(msg,err); }; |
|
65 } |
|
66 /*!END_DEBUG*/ |
|
67 |
|
68 // test for function |
|
69 function is_func(func) { return Object.prototype.toString.call(func) == "[object Function]"; } |
|
70 |
|
71 // test for array |
|
72 function is_array(arr) { return Object.prototype.toString.call(arr) == "[object Array]"; } |
|
73 |
|
74 // make script URL absolute/canonical |
|
75 function canonical_uri(src,base_path) { |
|
76 var absolute_regex = /^\w+\:\/\//; |
|
77 |
|
78 // is `src` is protocol-relative (begins with // or ///), prepend protocol |
|
79 if (/^\/\/\/?/.test(src)) { |
|
80 src = location.protocol + src; |
|
81 } |
|
82 // is `src` page-relative? (not an absolute URL, and not a domain-relative path, beginning with /) |
|
83 else if (!absolute_regex.test(src) && src.charAt(0) != "/") { |
|
84 // prepend `base_path`, if any |
|
85 src = (base_path || "") + src; |
|
86 } |
|
87 // make sure to return `src` as absolute |
|
88 return absolute_regex.test(src) ? src : ((src.charAt(0) == "/" ? root_domain : root_page) + src); |
|
89 } |
|
90 |
|
91 // merge `source` into `target` |
|
92 function merge_objs(source,target) { |
|
93 for (var k in source) { if (source.hasOwnProperty(k)) { |
|
94 target[k] = source[k]; // TODO: does this need to be recursive for our purposes? |
|
95 }} |
|
96 return target; |
|
97 } |
|
98 |
|
99 // does the chain group have any ready-to-execute scripts? |
|
100 function check_chain_group_scripts_ready(chain_group) { |
|
101 var any_scripts_ready = false; |
|
102 for (var i=0; i<chain_group.scripts.length; i++) { |
|
103 if (chain_group.scripts[i].ready && chain_group.scripts[i].exec_trigger) { |
|
104 any_scripts_ready = true; |
|
105 chain_group.scripts[i].exec_trigger(); |
|
106 chain_group.scripts[i].exec_trigger = null; |
|
107 } |
|
108 } |
|
109 return any_scripts_ready; |
|
110 } |
|
111 |
|
112 // creates a script load listener |
|
113 function create_script_load_listener(elem,registry_item,flag,onload) { |
|
114 elem.onload = elem.onreadystatechange = function() { |
|
115 if ((elem.readyState && elem.readyState != "complete" && elem.readyState != "loaded") || registry_item[flag]) return; |
|
116 elem.onload = elem.onreadystatechange = null; |
|
117 onload(); |
|
118 }; |
|
119 } |
|
120 |
|
121 // script executed handler |
|
122 function script_executed(registry_item) { |
|
123 registry_item.ready = registry_item.finished = true; |
|
124 for (var i=0; i<registry_item.finished_listeners.length; i++) { |
|
125 registry_item.finished_listeners[i](); |
|
126 } |
|
127 registry_item.ready_listeners = []; |
|
128 registry_item.finished_listeners = []; |
|
129 } |
|
130 |
|
131 // make the request for a scriptha |
|
132 function request_script(chain_opts,script_obj,registry_item,onload,preload_this_script) { |
|
133 // setTimeout() "yielding" prevents some weird race/crash conditions in older browsers |
|
134 setTimeout(function(){ |
|
135 var script, src = script_obj.real_src, xhr; |
|
136 |
|
137 // don't proceed until `append_to` is ready to append to |
|
138 if ("item" in append_to) { // check if `append_to` ref is still a live node list |
|
139 if (!append_to[0]) { // `append_to` node not yet ready |
|
140 // try again in a little bit -- note: will re-call the anonymous function in the outer setTimeout, not the parent `request_script()` |
|
141 setTimeout(arguments.callee,25); |
|
142 return; |
|
143 } |
|
144 // reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists |
|
145 append_to = append_to[0]; |
|
146 } |
|
147 script = document.createElement("script"); |
|
148 if (script_obj.type) script.type = script_obj.type; |
|
149 if (script_obj.charset) script.charset = script_obj.charset; |
|
150 |
|
151 // should preloading be used for this script? |
|
152 if (preload_this_script) { |
|
153 // real script preloading? |
|
154 if (real_preloading) { |
|
155 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload: "+src);/*!END_DEBUG*/ |
|
156 registry_item.elem = script; |
|
157 if (explicit_preloading) { // explicit preloading (aka, Zakas' proposal) |
|
158 script.preload = true; |
|
159 script.onpreload = onload; |
|
160 } |
|
161 else { |
|
162 script.onreadystatechange = function(){ |
|
163 if (script.readyState == "loaded") onload(); |
|
164 }; |
|
165 } |
|
166 script.src = src; |
|
167 // NOTE: no append to DOM yet, appending will happen when ready to execute |
|
168 } |
|
169 // same-domain and XHR allowed? use XHR preloading |
|
170 else if (preload_this_script && src.indexOf(root_domain) == 0 && chain_opts[_UseLocalXHR]) { |
|
171 xhr = new XMLHttpRequest(); // note: IE never uses XHR (it supports true preloading), so no more need for ActiveXObject fallback for IE <= 7 |
|
172 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (xhr): "+src);/*!END_DEBUG*/ |
|
173 xhr.onreadystatechange = function() { |
|
174 if (xhr.readyState == 4) { |
|
175 xhr.onreadystatechange = function(){}; // fix a memory leak in IE |
|
176 registry_item.text = xhr.responseText + "\n//@ sourceURL=" + src; // http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/ |
|
177 onload(); |
|
178 } |
|
179 }; |
|
180 xhr.open("GET",src); |
|
181 xhr.send(); |
|
182 } |
|
183 // as a last resort, use cache-preloading |
|
184 else { |
|
185 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (cache): "+src);/*!END_DEBUG*/ |
|
186 script.type = "text/cache-script"; |
|
187 create_script_load_listener(script,registry_item,"ready",function() { |
|
188 append_to.removeChild(script); |
|
189 onload(); |
|
190 }); |
|
191 script.src = src; |
|
192 append_to.insertBefore(script,append_to.firstChild); |
|
193 } |
|
194 } |
|
195 // use async=false for ordered async? parallel-load-serial-execute http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order |
|
196 else if (script_ordered_async) { |
|
197 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load (ordered async): "+src);/*!END_DEBUG*/ |
|
198 script.async = false; |
|
199 create_script_load_listener(script,registry_item,"finished",onload); |
|
200 script.src = src; |
|
201 append_to.insertBefore(script,append_to.firstChild); |
|
202 } |
|
203 // otherwise, just a normal script element |
|
204 else { |
|
205 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load: "+src);/*!END_DEBUG*/ |
|
206 create_script_load_listener(script,registry_item,"finished",onload); |
|
207 script.src = src; |
|
208 append_to.insertBefore(script,append_to.firstChild); |
|
209 } |
|
210 },0); |
|
211 } |
|
212 |
|
213 // create a clean instance of $LAB |
|
214 function create_sandbox() { |
|
215 var global_defaults = {}, |
|
216 can_use_preloading = real_preloading || xhr_or_cache_preloading, |
|
217 queue = [], |
|
218 registry = {}, |
|
219 instanceAPI |
|
220 ; |
|
221 |
|
222 // global defaults |
|
223 global_defaults[_UseLocalXHR] = true; |
|
224 global_defaults[_AlwaysPreserveOrder] = false; |
|
225 global_defaults[_AllowDuplicates] = false; |
|
226 global_defaults[_CacheBust] = false; |
|
227 /*!START_DEBUG*/global_defaults[_Debug] = false;/*!END_DEBUG*/ |
|
228 global_defaults[_BasePath] = ""; |
|
229 |
|
230 // execute a script that has been preloaded already |
|
231 function execute_preloaded_script(chain_opts,script_obj,registry_item) { |
|
232 var script; |
|
233 |
|
234 function preload_execute_finished() { |
|
235 if (script != null) { // make sure this only ever fires once |
|
236 script = null; |
|
237 script_executed(registry_item); |
|
238 } |
|
239 } |
|
240 |
|
241 if (registry[script_obj.src].finished) return; |
|
242 if (!chain_opts[_AllowDuplicates]) registry[script_obj.src].finished = true; |
|
243 |
|
244 script = registry_item.elem || document.createElement("script"); |
|
245 if (script_obj.type) script.type = script_obj.type; |
|
246 if (script_obj.charset) script.charset = script_obj.charset; |
|
247 create_script_load_listener(script,registry_item,"finished",preload_execute_finished); |
|
248 |
|
249 // script elem was real-preloaded |
|
250 if (registry_item.elem) { |
|
251 registry_item.elem = null; |
|
252 } |
|
253 // script was XHR preloaded |
|
254 else if (registry_item.text) { |
|
255 script.onload = script.onreadystatechange = null; // script injection doesn't fire these events |
|
256 script.text = registry_item.text; |
|
257 } |
|
258 // script was cache-preloaded |
|
259 else { |
|
260 script.src = script_obj.real_src; |
|
261 } |
|
262 append_to.insertBefore(script,append_to.firstChild); |
|
263 |
|
264 // manually fire execution callback for injected scripts, since events don't fire |
|
265 if (registry_item.text) { |
|
266 preload_execute_finished(); |
|
267 } |
|
268 } |
|
269 |
|
270 // process the script request setup |
|
271 function do_script(chain_opts,script_obj,chain_group,preload_this_script) { |
|
272 var registry_item, |
|
273 registry_items, |
|
274 ready_cb = function(){ script_obj.ready_cb(script_obj,function(){ execute_preloaded_script(chain_opts,script_obj,registry_item); }); }, |
|
275 finished_cb = function(){ script_obj.finished_cb(script_obj,chain_group); } |
|
276 ; |
|
277 |
|
278 script_obj.src = canonical_uri(script_obj.src,chain_opts[_BasePath]); |
|
279 script_obj.real_src = script_obj.src + |
|
280 // append cache-bust param to URL? |
|
281 (chain_opts[_CacheBust] ? ((/\?.*$/.test(script_obj.src) ? "&_" : "?_") + ~~(Math.random()*1E9) + "=") : "") |
|
282 ; |
|
283 |
|
284 if (!registry[script_obj.src]) registry[script_obj.src] = {items:[],finished:false}; |
|
285 registry_items = registry[script_obj.src].items; |
|
286 |
|
287 // allowing duplicates, or is this the first recorded load of this script? |
|
288 if (chain_opts[_AllowDuplicates] || registry_items.length == 0) { |
|
289 registry_item = registry_items[registry_items.length] = { |
|
290 ready:false, |
|
291 finished:false, |
|
292 ready_listeners:[ready_cb], |
|
293 finished_listeners:[finished_cb] |
|
294 }; |
|
295 |
|
296 request_script(chain_opts,script_obj,registry_item, |
|
297 // which callback type to pass? |
|
298 ( |
|
299 (preload_this_script) ? // depends on script-preloading |
|
300 function(){ |
|
301 registry_item.ready = true; |
|
302 for (var i=0; i<registry_item.ready_listeners.length; i++) { |
|
303 registry_item.ready_listeners[i](); |
|
304 } |
|
305 registry_item.ready_listeners = []; |
|
306 } : |
|
307 function(){ script_executed(registry_item); } |
|
308 ), |
|
309 // signal if script-preloading should be used or not |
|
310 preload_this_script |
|
311 ); |
|
312 } |
|
313 else { |
|
314 registry_item = registry_items[0]; |
|
315 if (registry_item.finished) { |
|
316 finished_cb(); |
|
317 } |
|
318 else { |
|
319 registry_item.finished_listeners.push(finished_cb); |
|
320 } |
|
321 } |
|
322 } |
|
323 |
|
324 // creates a closure for each separate chain spawned from this $LAB instance, to keep state cleanly separated between chains |
|
325 function create_chain() { |
|
326 var chainedAPI, |
|
327 chain_opts = merge_objs(global_defaults,{}), |
|
328 chain = [], |
|
329 exec_cursor = 0, |
|
330 scripts_currently_loading = false, |
|
331 group |
|
332 ; |
|
333 |
|
334 // called when a script has finished preloading |
|
335 function chain_script_ready(script_obj,exec_trigger) { |
|
336 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script preload finished: "+script_obj.real_src);/*!END_DEBUG*/ |
|
337 script_obj.ready = true; |
|
338 script_obj.exec_trigger = exec_trigger; |
|
339 advance_exec_cursor(); // will only check for 'ready' scripts to be executed |
|
340 } |
|
341 |
|
342 // called when a script has finished executing |
|
343 function chain_script_executed(script_obj,chain_group) { |
|
344 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script execution finished: "+script_obj.real_src);/*!END_DEBUG*/ |
|
345 script_obj.ready = script_obj.finished = true; |
|
346 script_obj.exec_trigger = null; |
|
347 // check if chain group is all finished |
|
348 for (var i=0; i<chain_group.scripts.length; i++) { |
|
349 if (!chain_group.scripts[i].finished) return; |
|
350 } |
|
351 // chain_group is all finished if we get this far |
|
352 chain_group.finished = true; |
|
353 advance_exec_cursor(); |
|
354 } |
|
355 |
|
356 // main driver for executing each part of the chain |
|
357 function advance_exec_cursor() { |
|
358 while (exec_cursor < chain.length) { |
|
359 if (is_func(chain[exec_cursor])) { |
|
360 /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("$LAB.wait() executing: "+chain[exec_cursor]);/*!END_DEBUG*/ |
|
361 try { chain[exec_cursor++](); } catch (err) { |
|
362 /*!START_DEBUG*/if (chain_opts[_Debug]) log_error("$LAB.wait() error caught: ",err);/*!END_DEBUG*/ |
|
363 } |
|
364 continue; |
|
365 } |
|
366 else if (!chain[exec_cursor].finished) { |
|
367 if (check_chain_group_scripts_ready(chain[exec_cursor])) continue; |
|
368 break; |
|
369 } |
|
370 exec_cursor++; |
|
371 } |
|
372 // we've reached the end of the chain (so far) |
|
373 if (exec_cursor == chain.length) { |
|
374 scripts_currently_loading = false; |
|
375 group = false; |
|
376 } |
|
377 } |
|
378 |
|
379 // setup next chain script group |
|
380 function init_script_chain_group() { |
|
381 if (!group || !group.scripts) { |
|
382 chain.push(group = {scripts:[],finished:true}); |
|
383 } |
|
384 } |
|
385 |
|
386 // API for $LAB chains |
|
387 chainedAPI = { |
|
388 // start loading one or more scripts |
|
389 script:function(){ |
|
390 for (var i=0; i<arguments.length; i++) { |
|
391 (function(script_obj,script_list){ |
|
392 var splice_args; |
|
393 |
|
394 if (!is_array(script_obj)) { |
|
395 script_list = [script_obj]; |
|
396 } |
|
397 for (var j=0; j<script_list.length; j++) { |
|
398 init_script_chain_group(); |
|
399 script_obj = script_list[j]; |
|
400 |
|
401 if (is_func(script_obj)) script_obj = script_obj(); |
|
402 if (!script_obj) continue; |
|
403 if (is_array(script_obj)) { |
|
404 // set up an array of arguments to pass to splice() |
|
405 splice_args = [].slice.call(script_obj); // first include the actual array elements we want to splice in |
|
406 splice_args.unshift(j,1); // next, put the `index` and `howMany` parameters onto the beginning of the splice-arguments array |
|
407 [].splice.apply(script_list,splice_args); // use the splice-arguments array as arguments for splice() |
|
408 j--; // adjust `j` to account for the loop's subsequent `j++`, so that the next loop iteration uses the same `j` index value |
|
409 continue; |
|
410 } |
|
411 if (typeof script_obj == "string") script_obj = {src:script_obj}; |
|
412 script_obj = merge_objs(script_obj,{ |
|
413 ready:false, |
|
414 ready_cb:chain_script_ready, |
|
415 finished:false, |
|
416 finished_cb:chain_script_executed |
|
417 }); |
|
418 group.finished = false; |
|
419 group.scripts.push(script_obj); |
|
420 |
|
421 do_script(chain_opts,script_obj,group,(can_use_preloading && scripts_currently_loading)); |
|
422 scripts_currently_loading = true; |
|
423 |
|
424 if (chain_opts[_AlwaysPreserveOrder]) chainedAPI.wait(); |
|
425 } |
|
426 })(arguments[i],arguments[i]); |
|
427 } |
|
428 return chainedAPI; |
|
429 }, |
|
430 // force LABjs to pause in execution at this point in the chain, until the execution thus far finishes, before proceeding |
|
431 wait:function(){ |
|
432 if (arguments.length > 0) { |
|
433 for (var i=0; i<arguments.length; i++) { |
|
434 chain.push(arguments[i]); |
|
435 } |
|
436 group = chain[chain.length-1]; |
|
437 } |
|
438 else group = false; |
|
439 |
|
440 advance_exec_cursor(); |
|
441 |
|
442 return chainedAPI; |
|
443 } |
|
444 }; |
|
445 |
|
446 // the first chain link API (includes `setOptions` only this first time) |
|
447 return { |
|
448 script:chainedAPI.script, |
|
449 wait:chainedAPI.wait, |
|
450 setOptions:function(opts){ |
|
451 merge_objs(opts,chain_opts); |
|
452 return chainedAPI; |
|
453 } |
|
454 }; |
|
455 } |
|
456 |
|
457 // API for each initial $LAB instance (before chaining starts) |
|
458 instanceAPI = { |
|
459 // main API functions |
|
460 setGlobalDefaults:function(opts){ |
|
461 merge_objs(opts,global_defaults); |
|
462 return instanceAPI; |
|
463 }, |
|
464 setOptions:function(){ |
|
465 return create_chain().setOptions.apply(null,arguments); |
|
466 }, |
|
467 script:function(){ |
|
468 return create_chain().script.apply(null,arguments); |
|
469 }, |
|
470 wait:function(){ |
|
471 return create_chain().wait.apply(null,arguments); |
|
472 }, |
|
473 |
|
474 // built-in queuing for $LAB `script()` and `wait()` calls |
|
475 // useful for building up a chain programmatically across various script locations, and simulating |
|
476 // execution of the chain |
|
477 queueScript:function(){ |
|
478 queue[queue.length] = {type:"script", args:[].slice.call(arguments)}; |
|
479 return instanceAPI; |
|
480 }, |
|
481 queueWait:function(){ |
|
482 queue[queue.length] = {type:"wait", args:[].slice.call(arguments)}; |
|
483 return instanceAPI; |
|
484 }, |
|
485 runQueue:function(){ |
|
486 var $L = instanceAPI, len=queue.length, i=len, val; |
|
487 for (;--i>=0;) { |
|
488 val = queue.shift(); |
|
489 $L = $L[val.type].apply(null,val.args); |
|
490 } |
|
491 return $L; |
|
492 }, |
|
493 |
|
494 // rollback `[global].$LAB` to what it was before this file was loaded, the return this current instance of $LAB |
|
495 noConflict:function(){ |
|
496 global.$LAB = _$LAB; |
|
497 return instanceAPI; |
|
498 }, |
|
499 |
|
500 // create another clean instance of $LAB |
|
501 sandbox:function(){ |
|
502 return create_sandbox(); |
|
503 } |
|
504 }; |
|
505 |
|
506 return instanceAPI; |
|
507 } |
|
508 |
|
509 // create the main instance of $LAB |
|
510 global.$LAB = create_sandbox(); |
|
511 |
|
512 |
|
513 /* The following "hack" was suggested by Andrea Giammarchi and adapted from: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html |
|
514 NOTE: this hack only operates in FF and then only in versions where document.readyState is not present (FF < 3.6?). |
|
515 |
|
516 The hack essentially "patches" the **page** that LABjs is loaded onto so that it has a proper conforming document.readyState, so that if a script which does |
|
517 proper and safe dom-ready detection is loaded onto a page, after dom-ready has passed, it will still be able to detect this state, by inspecting the now hacked |
|
518 document.readyState property. The loaded script in question can then immediately trigger any queued code executions that were waiting for the DOM to be ready. |
|
519 For instance, jQuery 1.4+ has been patched to take advantage of document.readyState, which is enabled by this hack. But 1.3.2 and before are **not** safe or |
|
520 fixed by this hack, and should therefore **not** be lazy-loaded by script loader tools such as LABjs. |
|
521 */ |
|
522 (function(addEvent,domLoaded,handler){ |
|
523 if (document.readyState == null && document[addEvent]){ |
|
524 document.readyState = "loading"; |
|
525 document[addEvent](domLoaded,handler = function(){ |
|
526 document.removeEventListener(domLoaded,handler,false); |
|
527 document.readyState = "complete"; |
|
528 },false); |
|
529 } |
|
530 })("addEventListener","DOMContentLoaded"); |
|
531 |
|
532 })(this);/* |
|
533 mustache.js — Logic-less templates in JavaScript |
|
534 |
|
535 See http://mustache.github.com/ for more info. |
|
536 */ |
|
537 |
|
538 var Mustache = function () { |
|
539 var _toString = Object.prototype.toString; |
|
540 |
|
541 Array.isArray = Array.isArray || function (obj) { |
|
542 return _toString.call(obj) == "[object Array]"; |
|
543 } |
|
544 |
|
545 var _trim = String.prototype.trim, trim; |
|
546 |
|
547 if (_trim) { |
|
548 trim = function (text) { |
|
549 return text == null ? "" : _trim.call(text); |
|
550 } |
|
551 } else { |
|
552 var trimLeft, trimRight; |
|
553 |
|
554 // IE doesn't match non-breaking spaces with \s. |
|
555 if ((/\S/).test("\xA0")) { |
|
556 trimLeft = /^[\s\xA0]+/; |
|
557 trimRight = /[\s\xA0]+$/; |
|
558 } else { |
|
559 trimLeft = /^\s+/; |
|
560 trimRight = /\s+$/; |
|
561 } |
|
562 |
|
563 trim = function (text) { |
|
564 return text == null ? "" : |
|
565 text.toString().replace(trimLeft, "").replace(trimRight, ""); |
|
566 } |
|
567 } |
|
568 |
|
569 var escapeMap = { |
|
570 "&": "&", |
|
571 "<": "<", |
|
572 ">": ">", |
|
573 '"': '"', |
|
574 "'": ''' |
|
575 }; |
|
576 |
|
577 function escapeHTML(string) { |
|
578 return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) { |
|
579 return escapeMap[s] || s; |
|
580 }); |
|
581 } |
|
582 |
|
583 var regexCache = {}; |
|
584 var Renderer = function () {}; |
|
585 |
|
586 Renderer.prototype = { |
|
587 otag: "{{", |
|
588 ctag: "}}", |
|
589 pragmas: {}, |
|
590 buffer: [], |
|
591 pragmas_implemented: { |
|
592 "IMPLICIT-ITERATOR": true |
|
593 }, |
|
594 context: {}, |
|
595 |
|
596 render: function (template, context, partials, in_recursion) { |
|
597 // reset buffer & set context |
|
598 if (!in_recursion) { |
|
599 this.context = context; |
|
600 this.buffer = []; // TODO: make this non-lazy |
|
601 } |
|
602 |
|
603 // fail fast |
|
604 if (!this.includes("", template)) { |
|
605 if (in_recursion) { |
|
606 return template; |
|
607 } else { |
|
608 this.send(template); |
|
609 return; |
|
610 } |
|
611 } |
|
612 |
|
613 // get the pragmas together |
|
614 template = this.render_pragmas(template); |
|
615 |
|
616 // render the template |
|
617 var html = this.render_section(template, context, partials); |
|
618 |
|
619 // render_section did not find any sections, we still need to render the tags |
|
620 if (html === false) { |
|
621 html = this.render_tags(template, context, partials, in_recursion); |
|
622 } |
|
623 |
|
624 if (in_recursion) { |
|
625 return html; |
|
626 } else { |
|
627 this.sendLines(html); |
|
628 } |
|
629 }, |
|
630 |
|
631 /* |
|
632 Sends parsed lines |
|
633 */ |
|
634 send: function (line) { |
|
635 if (line !== "") { |
|
636 this.buffer.push(line); |
|
637 } |
|
638 }, |
|
639 |
|
640 sendLines: function (text) { |
|
641 if (text) { |
|
642 var lines = text.split("\n"); |
|
643 for (var i = 0; i < lines.length; i++) { |
|
644 this.send(lines[i]); |
|
645 } |
|
646 } |
|
647 }, |
|
648 |
|
649 /* |
|
650 Looks for %PRAGMAS |
|
651 */ |
|
652 render_pragmas: function (template) { |
|
653 // no pragmas |
|
654 if (!this.includes("%", template)) { |
|
655 return template; |
|
656 } |
|
657 |
|
658 var that = this; |
|
659 var regex = this.getCachedRegex("render_pragmas", function (otag, ctag) { |
|
660 return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g"); |
|
661 }); |
|
662 |
|
663 return template.replace(regex, function (match, pragma, options) { |
|
664 if (!that.pragmas_implemented[pragma]) { |
|
665 throw({message: |
|
666 "This implementation of mustache doesn't understand the '" + |
|
667 pragma + "' pragma"}); |
|
668 } |
|
669 that.pragmas[pragma] = {}; |
|
670 if (options) { |
|
671 var opts = options.split("="); |
|
672 that.pragmas[pragma][opts[0]] = opts[1]; |
|
673 } |
|
674 return ""; |
|
675 // ignore unknown pragmas silently |
|
676 }); |
|
677 }, |
|
678 |
|
679 /* |
|
680 Tries to find a partial in the curent scope and render it |
|
681 */ |
|
682 render_partial: function (name, context, partials) { |
|
683 name = trim(name); |
|
684 if (!partials || partials[name] === undefined) { |
|
685 throw({message: "unknown_partial '" + name + "'"}); |
|
686 } |
|
687 if (!context || typeof context[name] != "object") { |
|
688 return this.render(partials[name], context, partials, true); |
|
689 } |
|
690 return this.render(partials[name], context[name], partials, true); |
|
691 }, |
|
692 |
|
693 /* |
|
694 Renders inverted (^) and normal (#) sections |
|
695 */ |
|
696 render_section: function (template, context, partials) { |
|
697 if (!this.includes("#", template) && !this.includes("^", template)) { |
|
698 // did not render anything, there were no sections |
|
699 return false; |
|
700 } |
|
701 |
|
702 var that = this; |
|
703 |
|
704 var regex = this.getCachedRegex("render_section", function (otag, ctag) { |
|
705 // This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder |
|
706 return new RegExp( |
|
707 "^([\\s\\S]*?)" + // all the crap at the beginning that is not {{*}} ($1) |
|
708 |
|
709 otag + // {{ |
|
710 "(\\^|\\#)\\s*(.+)\\s*" + // #foo (# == $2, foo == $3) |
|
711 ctag + // }} |
|
712 |
|
713 "\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped |
|
714 |
|
715 otag + // {{ |
|
716 "\\/\\s*\\3\\s*" + // /foo (backreference to the opening tag). |
|
717 ctag + // }} |
|
718 |
|
719 "\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped. |
|
720 |
|
721 "g"); |
|
722 }); |
|
723 |
|
724 |
|
725 // for each {{#foo}}{{/foo}} section do... |
|
726 return template.replace(regex, function (match, before, type, name, content, after) { |
|
727 // before contains only tags, no sections |
|
728 var renderedBefore = before ? that.render_tags(before, context, partials, true) : "", |
|
729 |
|
730 // after may contain both sections and tags, so use full rendering function |
|
731 renderedAfter = after ? that.render(after, context, partials, true) : "", |
|
732 |
|
733 // will be computed below |
|
734 renderedContent, |
|
735 |
|
736 value = that.find(name, context); |
|
737 |
|
738 if (type === "^") { // inverted section |
|
739 if (!value || Array.isArray(value) && value.length === 0) { |
|
740 // false or empty list, render it |
|
741 renderedContent = that.render(content, context, partials, true); |
|
742 } else { |
|
743 renderedContent = ""; |
|
744 } |
|
745 } else if (type === "#") { // normal section |
|
746 if (Array.isArray(value)) { // Enumerable, Let's loop! |
|
747 renderedContent = that.map(value, function (row) { |
|
748 return that.render(content, that.create_context(row), partials, true); |
|
749 }).join(""); |
|
750 } else if (that.is_object(value)) { // Object, Use it as subcontext! |
|
751 renderedContent = that.render(content, that.create_context(value), |
|
752 partials, true); |
|
753 } else if (typeof value == "function") { |
|
754 // higher order section |
|
755 renderedContent = value.call(context, content, function (text) { |
|
756 return that.render(text, context, partials, true); |
|
757 }); |
|
758 } else if (value) { // boolean section |
|
759 renderedContent = that.render(content, context, partials, true); |
|
760 } else { |
|
761 renderedContent = ""; |
|
762 } |
|
763 } |
|
764 |
|
765 return renderedBefore + renderedContent + renderedAfter; |
|
766 }); |
|
767 }, |
|
768 |
|
769 /* |
|
770 Replace {{foo}} and friends with values from our view |
|
771 */ |
|
772 render_tags: function (template, context, partials, in_recursion) { |
|
773 // tit for tat |
|
774 var that = this; |
|
775 |
|
776 var new_regex = function () { |
|
777 return that.getCachedRegex("render_tags", function (otag, ctag) { |
|
778 return new RegExp(otag + "(=|!|>|&|\\{|%)?([^#\\^]+?)\\1?" + ctag + "+", "g"); |
|
779 }); |
|
780 }; |
|
781 |
|
782 var regex = new_regex(); |
|
783 var tag_replace_callback = function (match, operator, name) { |
|
784 switch(operator) { |
|
785 case "!": // ignore comments |
|
786 return ""; |
|
787 case "=": // set new delimiters, rebuild the replace regexp |
|
788 that.set_delimiters(name); |
|
789 regex = new_regex(); |
|
790 return ""; |
|
791 case ">": // render partial |
|
792 return that.render_partial(name, context, partials); |
|
793 case "{": // the triple mustache is unescaped |
|
794 case "&": // & operator is an alternative unescape method |
|
795 return that.find(name, context); |
|
796 default: // escape the value |
|
797 return escapeHTML(that.find(name, context)); |
|
798 } |
|
799 }; |
|
800 var lines = template.split("\n"); |
|
801 for(var i = 0; i < lines.length; i++) { |
|
802 lines[i] = lines[i].replace(regex, tag_replace_callback, this); |
|
803 if (!in_recursion) { |
|
804 this.send(lines[i]); |
|
805 } |
|
806 } |
|
807 |
|
808 if (in_recursion) { |
|
809 return lines.join("\n"); |
|
810 } |
|
811 }, |
|
812 |
|
813 set_delimiters: function (delimiters) { |
|
814 var dels = delimiters.split(" "); |
|
815 this.otag = this.escape_regex(dels[0]); |
|
816 this.ctag = this.escape_regex(dels[1]); |
|
817 }, |
|
818 |
|
819 escape_regex: function (text) { |
|
820 // thank you Simon Willison |
|
821 if (!arguments.callee.sRE) { |
|
822 var specials = [ |
|
823 '/', '.', '*', '+', '?', '|', |
|
824 '(', ')', '[', ']', '{', '}', '\\' |
|
825 ]; |
|
826 arguments.callee.sRE = new RegExp( |
|
827 '(\\' + specials.join('|\\') + ')', 'g' |
|
828 ); |
|
829 } |
|
830 return text.replace(arguments.callee.sRE, '\\$1'); |
|
831 }, |
|
832 |
|
833 /* |
|
834 find `name` in current `context`. That is find me a value |
|
835 from the view object |
|
836 */ |
|
837 find: function (name, context) { |
|
838 name = trim(name); |
|
839 |
|
840 // Checks whether a value is thruthy or false or 0 |
|
841 function is_kinda_truthy(bool) { |
|
842 return bool === false || bool === 0 || bool; |
|
843 } |
|
844 |
|
845 var value; |
|
846 |
|
847 // check for dot notation eg. foo.bar |
|
848 if (name.match(/([a-z_]+)\./ig)) { |
|
849 var childValue = this.walk_context(name, context); |
|
850 if (is_kinda_truthy(childValue)) { |
|
851 value = childValue; |
|
852 } |
|
853 } else { |
|
854 if (is_kinda_truthy(context[name])) { |
|
855 value = context[name]; |
|
856 } else if (is_kinda_truthy(this.context[name])) { |
|
857 value = this.context[name]; |
|
858 } |
|
859 } |
|
860 |
|
861 if (typeof value == "function") { |
|
862 return value.apply(context); |
|
863 } |
|
864 if (value !== undefined) { |
|
865 return value; |
|
866 } |
|
867 // silently ignore unkown variables |
|
868 return ""; |
|
869 }, |
|
870 |
|
871 walk_context: function (name, context) { |
|
872 var path = name.split('.'); |
|
873 // if the var doesn't exist in current context, check the top level context |
|
874 var value_context = (context[path[0]] != undefined) ? context : this.context; |
|
875 var value = value_context[path.shift()]; |
|
876 while (value != undefined && path.length > 0) { |
|
877 value_context = value; |
|
878 value = value[path.shift()]; |
|
879 } |
|
880 // if the value is a function, call it, binding the correct context |
|
881 if (typeof value == "function") { |
|
882 return value.apply(value_context); |
|
883 } |
|
884 return value; |
|
885 }, |
|
886 |
|
887 // Utility methods |
|
888 |
|
889 /* includes tag */ |
|
890 includes: function (needle, haystack) { |
|
891 return haystack.indexOf(this.otag + needle) != -1; |
|
892 }, |
|
893 |
|
894 // by @langalex, support for arrays of strings |
|
895 create_context: function (_context) { |
|
896 if (this.is_object(_context)) { |
|
897 return _context; |
|
898 } else { |
|
899 var iterator = "."; |
|
900 if (this.pragmas["IMPLICIT-ITERATOR"]) { |
|
901 iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; |
|
902 } |
|
903 var ctx = {}; |
|
904 ctx[iterator] = _context; |
|
905 return ctx; |
|
906 } |
|
907 }, |
|
908 |
|
909 is_object: function (a) { |
|
910 return a && typeof a == "object"; |
|
911 }, |
|
912 |
|
913 /* |
|
914 Why, why, why? Because IE. Cry, cry cry. |
|
915 */ |
|
916 map: function (array, fn) { |
|
917 if (typeof array.map == "function") { |
|
918 return array.map(fn); |
|
919 } else { |
|
920 var r = []; |
|
921 var l = array.length; |
|
922 for(var i = 0; i < l; i++) { |
|
923 r.push(fn(array[i])); |
|
924 } |
|
925 return r; |
|
926 } |
|
927 }, |
|
928 |
|
929 getCachedRegex: function (name, generator) { |
|
930 var byOtag = regexCache[this.otag]; |
|
931 if (!byOtag) { |
|
932 byOtag = regexCache[this.otag] = {}; |
|
933 } |
|
934 |
|
935 var byCtag = byOtag[this.ctag]; |
|
936 if (!byCtag) { |
|
937 byCtag = byOtag[this.ctag] = {}; |
|
938 } |
|
939 |
|
940 var regex = byCtag[name]; |
|
941 if (!regex) { |
|
942 regex = byCtag[name] = generator(this.otag, this.ctag); |
|
943 } |
|
944 |
|
945 return regex; |
|
946 } |
|
947 }; |
|
948 |
|
949 return({ |
|
950 name: "mustache.js", |
|
951 version: "0.5.0-dev", |
|
952 |
|
953 /* |
|
954 Turns a template and view into HTML |
|
955 */ |
|
956 to_html: function (template, view, partials, send_fun) { |
|
957 var renderer = new Renderer(); |
|
958 if (send_fun) { |
|
959 renderer.send = send_fun; |
|
960 } |
|
961 renderer.render(template, view || {}, partials); |
|
962 if (!send_fun) { |
|
963 return renderer.buffer.join("\n"); |
|
964 } |
|
965 } |
|
966 }); |
|
967 }(); |
|
968 // Underscore.js 1.2.3 |
|
969 // (c) 2009-2011 Jeremy Ashkenas, DocumentCloud Inc. |
|
970 // Underscore is freely distributable under the MIT license. |
|
971 // Portions of Underscore are inspired or borrowed from Prototype, |
|
972 // Oliver Steele's Functional, and John Resig's Micro-Templating. |
|
973 // For all details and documentation: |
|
974 // http://documentcloud.github.com/underscore |
|
975 (function(){function r(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== |
|
976 c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&r(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(m.call(a,h)&&(f++,!(g=m.call(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(m.call(c, |
|
977 h)&&!f--)break;g=!f}}d.pop();return g}var s=this,F=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,G=k.concat,H=k.unshift,l=p.toString,m=p.hasOwnProperty,v=k.forEach,w=k.map,x=k.reduce,y=k.reduceRight,z=k.filter,A=k.every,B=k.some,q=k.indexOf,C=k.lastIndexOf,p=Array.isArray,I=Object.keys,t=Function.prototype.bind,b=function(a){return new n(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else typeof define==="function"&& |
|
978 define.amd?define("underscore",function(){return b}):s._=b;b.VERSION="1.2.3";var j=b.each=b.forEach=function(a,c,b){if(a!=null)if(v&&a.forEach===v)a.forEach(c,b);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(b,a[e],e,a)===o)break}else for(e in a)if(m.call(a,e)&&c.call(b,a[e],e,a)===o)break};b.map=function(a,c,b){var e=[];if(a==null)return e;if(w&&a.map===w)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});return e};b.reduce=b.foldl=b.inject=function(a, |
|
979 c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(x&&a.reduce===x)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(y&&a.reduceRight===y)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g, |
|
980 c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,c,b){var e;D(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.filter===z)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(A&&a.every===A)return a.every(c, |
|
981 b);j(a,function(a,g,h){if(!(e=e&&c.call(b,a,g,h)))return o});return e};var D=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(B&&a.some===B)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return q&&a.indexOf===q?a.indexOf(c)!=-1:b=D(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(c.call?c||a:a[c]).apply(a, |
|
982 d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a, |
|
983 computed:b})});return e.value};b.shuffle=function(a){var c=[],b;j(a,function(a,f){f==0?c[0]=a:(b=Math.floor(Math.random()*(f+1)),c[f]=c[b],c[b]=a)});return c};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,c){var b=a.criteria,d=c.criteria;return b<d?-1:b>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex= |
|
984 function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length- |
|
985 1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d}, |
|
986 []);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a, |
|
987 c,d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(q&&a.indexOf===q)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(C&&a.lastIndexOf===C)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g}; |
|
988 var E=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));E.prototype=a.prototype;var b=new E,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a, |
|
989 c){var d={};c||(c=b.identity);return function(){var b=c.apply(this,arguments);return m.call(d,b)?d[b]:d[b]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true: |
|
990 a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=G.apply([a],arguments);return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after= |
|
991 function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=I||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)m.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){j(i.call(arguments, |
|
992 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(m.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a=== |
|
993 Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!m.call(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)== |
|
994 "[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.noConflict=function(){s._=F;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a),function(c){J(c, |
|
995 b[c]=a[c])})};var K=0;b.uniqueId=function(a){var b=K++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape,function(a,b){return"',_.escape("+b.replace(/\\'/g,"'")+"),'"}).replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g, |
|
996 "'")+",'"}).replace(d.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};var n=function(a){this._wrapped=a};b.prototype=n.prototype;var u=function(a,c){return c?b(a).chain():a},J=function(a,c){n.prototype[a]=function(){var a=i.call(arguments);H.call(a,this._wrapped);return u(c.apply(b, |
|
997 a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];n.prototype[a]=function(){b.apply(this._wrapped,arguments);return u(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];n.prototype[a]=function(){return u(b.apply(this._wrapped,arguments),this._chain)}});n.prototype.chain=function(){this._chain=true;return this};n.prototype.value=function(){return this._wrapped}}).call(this); |
|
998 /* main file */ |
|
999 // Why is it called main ? It only loads the libs ! |
|
1000 |
|
1001 if ( window.IriSP === undefined && window.__IriSP === undefined ) { |
|
1002 /** |
|
1003 @class |
|
1004 the object under which everything goes. |
|
1005 */ |
|
1006 IriSP = {}; |
|
1007 |
|
1008 /** Alias to IriSP for backward compatibility */ |
|
1009 __IriSP = IriSP; |
|
1010 } |
|
1011 |
|
1012 /* underscore comes bundled with the player and we need |
|
1013 it ASAP, so load it that way |
|
1014 */ |
|
1015 |
|
1016 IriSP._ = window._.noConflict(); |
|
1017 IriSP.underscore = IriSP._; |
|
1018 |
|
1019 IriSP.getLib = function(lib) { |
|
1020 return ( |
|
1021 IriSP.libFiles.useCdn && typeof IriSP.libFiles.cdn[lib] == "string" |
|
1022 ? IriSP.libFiles.cdn[lib] |
|
1023 : ( |
|
1024 typeof IriSP.libFiles.locations[lib] == "string" |
|
1025 ? IriSP.libFiles.locations[lib] |
|
1026 : ( |
|
1027 typeof IriSP.libFiles.inDefaultDir[lib] == "string" |
|
1028 ? IriSP.libFiles.defaultDir + IriSP.libFiles.inDefaultDir[lib] |
|
1029 : null |
|
1030 ) |
|
1031 ) |
|
1032 ) |
|
1033 } |
|
1034 |
|
1035 IriSP.loadLibs = function( config, metadata_url, callback ) { |
|
1036 // Localize jQuery variable |
|
1037 IriSP.jQuery = null; |
|
1038 var $L = $LAB.script(IriSP.getLib("jQuery")).script(IriSP.getLib("swfObject")).wait() |
|
1039 .script(IriSP.getLib("jQueryUI")); |
|
1040 |
|
1041 if (config.player.type === "jwplayer" || config.player.type === "allocine") { |
|
1042 // load our popcorn.js lookalike |
|
1043 $L.script(IriSP.getLib("jwplayer")); |
|
1044 } else { |
|
1045 // load the real popcorn |
|
1046 $L.script(IriSP.getLib("popcorn")).script(IriSP.getLib("popcorn.code")); |
|
1047 if (config.player.type === "youtube") { |
|
1048 $L.script(IriSP.getLib("popcorn.youtube")); |
|
1049 } |
|
1050 if (config.player.type === "vimeo") |
|
1051 $L.script(IriSP.getLib("popcorn.vimeo")); |
|
1052 |
|
1053 /* do nothing for html5 */ |
|
1054 } |
|
1055 |
|
1056 /* widget specific requirements */ |
|
1057 for (var idx in config.gui.widgets) { |
|
1058 if (config.gui.widgets[idx].type === "PolemicWidget" || |
|
1059 config.gui.widgets[idx].type === "StackGraphWidget" || |
|
1060 config.gui.widgets[idx].type === "SparklineWidget") { |
|
1061 $L.script(IriSP.getLib("raphael")); |
|
1062 } |
|
1063 if (config.gui.widgets[idx].type === "TraceWidget") { |
|
1064 $L.script(IriSP.getLib("tracemanager")) |
|
1065 } |
|
1066 } |
|
1067 |
|
1068 // same for modules |
|
1069 /* |
|
1070 for (var idx in config.modules) { |
|
1071 if (config.modules[idx].type === "PolemicWidget") |
|
1072 $L.script(IriSP.getLib("raphaelJs")); |
|
1073 } |
|
1074 */ |
|
1075 |
|
1076 $L.wait(function() { |
|
1077 IriSP.jQuery = window.jQuery.noConflict( true ); |
|
1078 |
|
1079 var css_link_jquery = IriSP.jQuery( "<link>", { |
|
1080 rel: "stylesheet", |
|
1081 type: "text/css", |
|
1082 href: IriSP.getLib("cssjQueryUI"), |
|
1083 'class': "dynamic_css" |
|
1084 } ); |
|
1085 var css_link_custom = IriSP.jQuery( "<link>", { |
|
1086 rel: "stylesheet", |
|
1087 type: "text/css", |
|
1088 href: config.gui.css, |
|
1089 'class': "dynamic_css" |
|
1090 } ); |
|
1091 |
|
1092 css_link_jquery.appendTo('head'); |
|
1093 css_link_custom.appendTo('head'); |
|
1094 |
|
1095 IriSP.setupDataLoader(); |
|
1096 IriSP.__dataloader.get(metadata_url, |
|
1097 function(data) { |
|
1098 /* save the data so that we could re-use it to |
|
1099 configure the video |
|
1100 */ |
|
1101 IriSP.__jsonMetadata = data; |
|
1102 callback.call(window) }); |
|
1103 }); |
|
1104 }; |
|
1105 IriSP.annotation_template = "{{! template for an annotation displayed in a segmentWidget }}<div title='{{divTitle}}' id='{{id}}' class='Ldt-iri-chapter Ldt-TraceMe' style='left: {{startPixel}}px; width: {{pxWidth}}px; background-color:{{hexa_color}};' data-seek='{{seekPlace}}' thumbnail-url='{{thumbnailUrl}}' ></div>"; |
|
1106 IriSP.annotationWidget_template = "{{! template for the annotation widget }}<div class='Ldt-AnnotationsWidget'> <!-- ugly div because we want to have a double border --> <div class='Ldt-Annotation-DoubleBorder'> <div class='Ldt-AnnotationContent'> <div class='Ldt-AnnotationShareIcons'> <a target='_blank' class='Ldt-fbShare Ldt-TraceMe' title='{{i10n.share_on}} Facebook'></a> <a target='_blank' class='Ldt-TwShare Ldt-TraceMe' title='{{i10n.share_on}} Twitter'></a> <a target='_blank' class='Ldt-GplusShare Ldt-TraceMe' title='{{i10n.share_on}} Google+'></a> </div> <div class='Ldt-SaTitle'></div> <div class='Ldt-SaDescription'></div> <div class='Ldt-SaKeywords'></div> </div> </div></div>"; |
|
1107 IriSP.annotation_loading_template = "{{! template shown while the annotation widget is loading }}<div id='Ldt-load-container'><div id='Ldt-loader'> </div> Chargement... </div>"; |
|
1108 IriSP.annotationsListWidget_template = "{{! template for the annotation list widget }}<div class='Ldt-AnnotationsListWidget'> <ul class='Ldt-AnnotationsList-ul'> {{#annotations}} <li id='Ldt-Annotation-li-{{id}}' class='Ldt-AnnotationsList-li Ldt-TraceMe'> {{^url}} <a href='#id={{id}}'> {{/url}} {{! otherwise link to url }} {{#url}} <a href='{{url}}'> {{/url}} <img class='Ldt-AnnotationsList-Thumbnail' src='{{thumbnail}}' /> </a> <div class='Ldt-AnnotationsList-Duration'> <span class='Ldt-AnnotationsList-Begin'>{{begin}}</span> <span class='Ldt-AnnotationsList-TcSeparator'>-</span> <span class='Ldt-AnnotationsList-End'>{{end}}</span> </div> <div class='Ldt-AnnotationsList-Title'> {{! if the url is not present, it means that the annotation exists in the current project }} {{title}} </div> <div class='Ldt-AnnotationsList-Description'> {{^url}} <a href='#id={{id}}'> {{/url}} {{! otherwise link to url }} {{#url}} <a href='{{url}}'> {{/url}} {{desc}} </a> </div> {{#tags.length}} <ul class='Ldt-AnnotationsList-Tags'> {{#tags}} <li class='Ldt-AnnotationsList-Tag-Li'> <div class='Ldt-AnnotationsList-Tag-Div'>{{.}}</div> </li> {{/tags}} </ul> {{/tags.length}} </li> {{/annotations}} </ul></div>"; |
|
1109 IriSP.arrowWidget_template = "<div class='Ldt-arrowWidget Ldt-arrowLeftEdge'></div>"; |
|
1110 IriSP.createAnnotationWidget_template = "{{! template for the annotation creation widget }}<div class='Ldt-createAnnotationWidget'> <!-- ugly div because we want to have a double border --> <div class='Ldt-createAnnotation-DoubleBorder'> <div class='Ldt-createAnnotation-screen Ldt-createAnnotation-startScreen'> <div style='margin-bottom: 7px; overflow: auto;'> <div class='Ldt-createAnnotation-Title'></div> <div class='Ldt-createAnnotation-TimeFrame'></div> {{^cinecast_version}} <div class='Ldt-createAnnotation-Minimize Ldt-TraceMe' title='Cancel'></div> {{/cinecast_version}} </div> <div class='Ldt-createAnnotation-Container'> {{#show_from_field}} <label>{{l10n.your_name}} : </label><input class='Ldt-createAnnotation-userName Ldt-TraceMe' value='{{user_name}}' /> {{/show_from_field}} <textarea class='Ldt-createAnnotation-Description Ldt-TraceMe'></textarea> <div class='Ldt-createAnnotation-userAvatar Ldt-TraceMe'> {{^user_avatar}} <img src='https://si0.twimg.com/sticky/default_profile_images/default_profile_1_normal.png'></img> {{/user_avatar}} {{#user_avatar}} <img src='{{ user_avatar }}'></img> {{/user_avatar}} </div> <div class='Ldt-createAnnotation-profileArrow'></div> </div> <button class='Ldt-createAnnotation-submitButton Ldt-TraceMe'>{{l10n.submit}}</button> {{#tags.length}} <div class='Ldt-createAnnotation-btnblock Ldt-createAnnotation-keywords'> <label>{{l10n.add_keywords}} :</label> <ul class='Ldt-floatList'> {{#tags}} <li><button class='Ldt-createAnnotation-keyword-button Ldt-TraceMe' tag-id='{{id}}'>{{meta.description}}</button></li> {{/tags}} </ul> </div> {{#random_tags}} <button class='Ldt-createAnnotation-moar-keywordz'>{{l10n.more_tags}}</button> {{/random_tags}} {{/tags.length}} {{#polemic_mode}} {{#polemics.length}} <div class='Ldt-createAnnotation-btnblock Ldt-createAnnotation-polemics'> <label>{{l10n.add_polemic_keywords}} :</label> <ul class='Ldt-floatList'> {{#polemics}} <li><button class='Ldt-createAnnotation-polemic-{{className}} Ldt-createAnnotation-polemic-button Ldt-TraceMe'>{{keyword}}</button></li> {{/polemics}} </ul> </div> {{/polemics.length}} {{/polemic_mode}} </div> <div class='Ldt-createAnnotation-screen Ldt-createAnnotation-waitScreen' style='display: none; text-align: center'> <div class='Ldt-createAnnotation-spinner'></div> {{l10n.wait_while_processed}} </div> <div class='Ldt-createAnnotation-screen Ldt-createAnnotation-errorScreen' style='display: none; text-align: center'> <div class='Ldt-createAnnotation-Minimize' title='Hide'></div> {{l10n.error_while_contacting}} </div> <div class='Ldt-createAnnotation-screen Ldt-createAnnotation-endScreen' style='display: none'> <div class='Ldt-createAnnotation-Minimize' title='Hide'></div> {{l10n.annotation_saved}} <br> {{^disable_share}} {{l10n.share_annotation}} <div style='margin-top: 12px; text-align: center;'> <a target='_blank' class='Ldt-createAnnotation-endScreen-TweetLink Ldt-TraceMe'></a> <a target='_blank' class='Ldt-createAnnotation-endScreen-FbLink Ldt-TraceMe'></a> <a target='_blank' class='Ldt-createAnnotation-endScreen-GplusLink Ldt-TraceMe'></a> </div> {{/disable_share}} </div> <div class='Ldt-floatClear'></div> </div></div>"; |
|
1111 IriSP.createAnnotation_errorMessage_template = "<p class='Ldt-createAnnotation-errorMessage'> {{l10n.empty_annotation}}</p>"; |
|
1112 IriSP.loading_template = "<div id='Ldt-loader' style='width: {{width}}px; height: {{height}}px;'>{{l10n.loading_wait}}</div>"; |
|
1113 IriSP.overlay_marker_template = "{{! the template for the small bars which is z-indexed over our segment widget }}<div class='Ldt-SegmentPositionMarker' style='background-color: #F7268E;'></div>"; |
|
1114 IriSP.player_template = "{{! template for the radio player }}<div class='Ldt-controler'> <div class='Ldt-LeftPlayerControls'> <div class='Ldt-Ctrl-button Ldt-CtrlPlay Ldt-CtrlPlay-PlayState Ldt-TraceMe' title='{{l10n.play_pause}}'></div> <div class='Ldt-Ctrl-spacer'></div> {{^disable_annotate_btn}} <div class='Ldt-Ctrl-button Ldt-CtrlAnnotate Ldt-TraceMe' title='{{l10n.annotate}}'></div> <div class='Ldt-Ctrl-spacer'></div> {{/disable_annotate_btn}} {{^disable_search_btn}} <div class='Ldt-Ctrl-button Ldt-CtrlSearch Ldt-TraceMe' title='{{l10n.search}}'></div> <div class='Ldt-Ctrl-spacer'></div> {{/disable_search_btn}} <div class='LdtSearch'> <input class='LdtSearchInput Ldt-TraceMe'></input> </div> </div> <div class='Ldt-RightPlayerControls'> <div class='Ldt-Ctrl-spacer'></div> <div class='Ldt-Time'> <div class='Ldt-ElapsedTime' title='{{l10n.elapsed_time}}'>00:00</div> <div class='Ldt-TimeSeparator'>/</div> <div class='Ldt-TotalTime' title='{{l10n.total_time}}'>00:00</div> </div> <div class='Ldt-Ctrl-spacer'></div> <div class='Ldt-Ctrl-button Ldt-CtrlSound Ldt-CtrlSound-Full Ldt-TraceMe' title='{{l10n.mute_unmute}}'></div> </div> <div class='Ldt-Ctrl-Volume-Control' title='{{l10n.volume_control}}'> <div class='Ldt-Ctrl-Volume-Bar'></div> <div class='Ldt-Ctrl-Volume-Cursor'></div> </div></div>"; |
|
1115 IriSP.search_template = "{{! template for the search container }}<div class='LdtSearchContainer' style='margin-left: {{margin_left}}; position: absolute; margin-top: -60px;'> <div class='LdtSearch' style='display: none; background-color: #EEE; width: 165px; border-color: #CFCFCF; position: absolute; text-align: center;'> <input class='LdtSearchInput' style='margin-top: 1px; margin-bottom: 2px;' /> </div></div><div class='cleaner'></div>"; |
|
1116 IriSP.share_template = "{{! social network sharing template }}<a onclick='__IriSP.MyApiPlayer.share(\'delicious\');' title='{{l10n.share_on}} delicious'><span class='share shareDelicious'> </span></a> <a onclick='__IriSP.MyApiPlayer.share(\'facebook\');' title='{{l10n.share_on}} facebook'> <span class='share shareFacebook'> </span></a><a onclick='__IriSP.MyApiPlayer.share(\'twitter\');' title='{{l10n.share_on}} twitter'> <span class='share shareTwitter'> </span></a><a onclick='__IriSP.MyApiPlayer.share(\'myspace\');' title='{{l10n.share_on}} Myspace'> <span class='share shareMySpace'> </span></a>"; |
|
1117 IriSP.sliceWidget_template = "{{! template for the slice widget }}<div class='Ldt-sliceWidget'> {{! the whole bar }} <div class='Ldt-sliceBackground'></div> <div class='Ldt-sliceLeftHandle'></div> {{! the zone which represents our slice }} <div class='Ldt-sliceZone'></div> <div class='Ldt-sliceRightHandle'></div></div>"; |
|
1118 IriSP.sliderWidget_template = "{{! template for the slider widget - it's composed of two divs we one overlayed on top of the other }}<div class='Ldt-sliderBackground'></div><div class='Ldt-sliderForeground'></div><div class='Ldt-sliderPositionMarker Ldt-TraceMe'></div>"; |
|
1119 IriSP.tooltip_template = "{{! template used by the jquery ui tooltip }}<div class='Ldt-tooltip'> <div class='title'>{{title}}</div> <div class='time'>{{begin}} : {{end}} </div> <div class='description'>{{description}}</div></div>"; |
|
1120 IriSP.tooltipWidget_template = "{{! template for the tooltip widget }}<div class='tip'> <div class='tipcolor' style='height:10px;width:10px'></div> <div class='tiptext'></div>"; |
|
1121 IriSP.tweetWidget_template = "{{! template for the tweet widget }}<div class='Ldt-tweetWidget'> <div class='Ldt-tweet-DoubleBorder'> <div class='Ldt-tweetWidgetKeepOpen Ldt-TraceMe' title='dont minimize automatically'></div> <div class='Ldt-tweetWidgetMinimize Ldt-TraceMe' title='minimize window'></div> <div class='Ldt-tweetAvatar'></div> <div class='Ldt-tweetAvatar-profileArrow'></div> <div class='Ldt-tweetContents'></div> <a href='' target='_blank' class='Ldt-Retweet Ldt-TraceMe'><div class='Ldt-RetweetIcon'></div> - Retweet </a> <a href='' target='_blank' class='Ldt-TweetReply Ldt-TraceMe'><div class='Ldt-TweetReplyIcon'></div> - Reply</a> </div></div>";/* utils.js - various utils that don't belong anywhere else */ |
|
1122 |
|
1123 /* trace function, for debugging */ |
|
1124 |
|
1125 IriSP.traceNum = 0; |
|
1126 IriSP.trace = function( msg, value ) { |
|
1127 /* |
|
1128 if( IriSP.config.gui.debug === true ) { |
|
1129 IriSP.traceNum += 1; |
|
1130 IriSP.jQuery( "<div>"+IriSP.traceNum+" - "+msg+" : "+value+"</div>" ).appendTo( "#Ldt-output" ); |
|
1131 } |
|
1132 */ |
|
1133 }; |
|
1134 |
|
1135 /* used in callbacks - because in callbacks we lose "this", |
|
1136 we need to have a special function which wraps "this" in |
|
1137 a closure. This way, the |
|
1138 */ |
|
1139 IriSP.wrap = function (obj, fn) { |
|
1140 return function() { |
|
1141 var args = Array.prototype.slice.call(arguments, 0); |
|
1142 return fn.apply(obj, args); |
|
1143 } |
|
1144 } |
|
1145 |
|
1146 /* convert a time to a percentage in the media */ |
|
1147 IriSP.timeToPourcent = function(time, timetotal){ |
|
1148 var time = Math.abs(time); |
|
1149 var timetotal = Math.abs(timetotal); |
|
1150 |
|
1151 return Math.floor((time/timetotal) * 100); |
|
1152 }; |
|
1153 |
|
1154 IriSP.padWithZeros = function(num) { |
|
1155 if (Math.abs(num) < 10) { |
|
1156 return "0" + num.toString(); |
|
1157 } else { |
|
1158 return num.toString(); |
|
1159 } |
|
1160 }; |
|
1161 |
|
1162 /* convert a number of milliseconds to a tuple of the form |
|
1163 [hours, minutes, seconds] |
|
1164 */ |
|
1165 IriSP.msToTime = function(ms) { |
|
1166 return IriSP.secondsToTime(ms / 1000); |
|
1167 } |
|
1168 /* convert a number of seconds to a tuple of the form |
|
1169 [hours, minutes, seconds] |
|
1170 */ |
|
1171 IriSP.secondsToTime = function(secs) { |
|
1172 var hours = Math.abs(parseInt( secs / 3600 ) % 24); |
|
1173 var minutes = Math.abs(parseInt( secs / 60 ) % 60); |
|
1174 var seconds = parseFloat(Math.abs(secs % 60).toFixed(0)); |
|
1175 |
|
1176 var toString_fn = function() { |
|
1177 var ret = ""; |
|
1178 if (hours > 0) |
|
1179 ret = IriSP.padWithZeros(this.hours) + ":"; |
|
1180 ret += IriSP.padWithZeros(this.minutes) + ":" + IriSP.padWithZeros(this.seconds); |
|
1181 |
|
1182 return ret; |
|
1183 } |
|
1184 return {"hours" : hours, "minutes" : minutes, "seconds" : seconds, toString: toString_fn}; |
|
1185 }; |
|
1186 |
|
1187 /* format a tweet - replaces @name by a link to the profile, #hashtag, etc. */ |
|
1188 IriSP.formatTweet = function(tweet) { |
|
1189 /* |
|
1190 an array of arrays which hold a regexp and its replacement. |
|
1191 */ |
|
1192 var regExps = [ |
|
1193 /* copied from http://codegolf.stackexchange.com/questions/464/shortest-url-regex-match-in-javascript/480#480 */ |
|
1194 [/((https?:\/\/)?[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?)/gi, "<a href='$1'>$1</a>"], |
|
1195 [/@(\w+)/gi, "<a href='http://twitter.com/$1'>@$1</a>"], // matches a @handle |
|
1196 [/#(\w+)/gi, "<a href='http://twitter.com/search?q=%23$1'>#$1</a>"],// matches a hashtag |
|
1197 [/(\+\+)/gi, "<span class='Ldt-PolemicPlusPlus'>$1</span>"], |
|
1198 [/(--)/gi, "<span class='Ldt-PolemicMinusMinus'>$1</span>"], |
|
1199 [/(==)/gi, "<span class='Ldt-PolemicEqualEqual'>$1</span>"], |
|
1200 [/(\?\?)/gi, "<span class='Ldt-PolemicQuestion'>$1</span>"] |
|
1201 ]; |
|
1202 |
|
1203 var i = 0; |
|
1204 for(i = 0; i < regExps.length; i++) { |
|
1205 tweet = tweet.replace(regExps[i][0], regExps[i][1]); |
|
1206 } |
|
1207 |
|
1208 return tweet; |
|
1209 }; |
|
1210 |
|
1211 IriSP.countProperties = function(obj) { |
|
1212 var count = 0; |
|
1213 |
|
1214 for(var prop in obj) { |
|
1215 if(obj.hasOwnProperty(prop)) |
|
1216 ++count; |
|
1217 } |
|
1218 |
|
1219 return count; |
|
1220 }; |
|
1221 |
|
1222 // conversion de couleur Decimal vers HexaDecimal || 000 si fff |
|
1223 IriSP.DEC_HEXA_COLOR = function (dec) { |
|
1224 var val = +dec; |
|
1225 var str = val.toString(16); |
|
1226 var zeroes = ""; |
|
1227 if (str.length < 6) { |
|
1228 for (var i = 0; i < 6 - str.length; i++) |
|
1229 zeroes += "0"; |
|
1230 } |
|
1231 return zeroes + str; |
|
1232 }; |
|
1233 |
|
1234 /* shortcut to have global variables in templates */ |
|
1235 IriSP.templToHTML = function(template, values) { |
|
1236 var params = IriSP.underscore.extend( |
|
1237 { "defaults" : IriSP.default_templates_vars, |
|
1238 "l10n" : IriSP.i18n.getMessages() |
|
1239 }, |
|
1240 values); |
|
1241 return Mustache.to_html(template, params); |
|
1242 }; |
|
1243 |
|
1244 /* we need to be stricter than encodeURIComponent, |
|
1245 because of twitter |
|
1246 */ |
|
1247 IriSP.encodeURI = function(str) { |
|
1248 return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28'). |
|
1249 replace(/\)/g, '%29').replace(/\*/g, '%2A'); |
|
1250 } |
|
1251 |
|
1252 IriSP.jqEscape = function(text) { |
|
1253 return text.replace(/(:|\.)/g,'\\$1') |
|
1254 } |
|
1255 |
|
1256 IriSP.jqId = function (text) { |
|
1257 return IriSP.jQuery('#' + IriSP.jqEscape(text)); |
|
1258 } |
|
1259 |
|
1260 IriSP.__guidCounter = 0; |
|
1261 IriSP.guid = function(prefix) { |
|
1262 IriSP.__guidCounter += 1; |
|
1263 return prefix + IriSP.__guidCounter; |
|
1264 }; |
|
1265 |
|
1266 /** returns an url to share on facebook */ |
|
1267 IriSP.mkFbUrl = function(url, text) { |
|
1268 if (typeof(text) === "undefined") |
|
1269 text = "I'm watching "; |
|
1270 |
|
1271 return "http://www.facebook.com/share.php?u=" + IriSP.encodeURI(text) + IriSP.shorten_url(url); |
|
1272 }; |
|
1273 |
|
1274 /** returns an url to share on twitter */ |
|
1275 IriSP.mkTweetUrl = function(url, text) { |
|
1276 if (typeof(text) === "undefined") |
|
1277 text = "I'm watching "; |
|
1278 |
|
1279 return "http://twitter.com/home?status=" + IriSP.encodeURI(text) + IriSP.shorten_url(url); |
|
1280 }; |
|
1281 |
|
1282 /** returns an url to share on google + */ |
|
1283 IriSP.mkGplusUrl = function(url, text) { |
|
1284 return "https://plusone.google.com/_/+1/confirm?hl=en&url=" + IriSP.shorten_url(url); |
|
1285 }; |
|
1286 |
|
1287 /** test if a value is null or undefined */ |
|
1288 IriSP.null_or_undefined = function(val) { |
|
1289 return (typeof(val) === "undefined" || val === null); |
|
1290 }; |
|
1291 |
|
1292 /** get a property that can have multiple names **/ |
|
1293 |
|
1294 IriSP.get_aliased = function(_obj, _aliases) { |
|
1295 for (var _i = 0; _i < _aliases.length; _i++) { |
|
1296 if (typeof _obj[_aliases[_i]] !== "undefined") { |
|
1297 return _obj[_aliases[_i]]; |
|
1298 } |
|
1299 } |
|
1300 return null; |
|
1301 } |
|
1302 |
|
1303 /** issue a call to an url shortener and return the shortened url */ |
|
1304 IriSP.shorten_url = function(url) { |
|
1305 return encodeURIComponent(url); |
|
1306 }; |
|
1307 |
|
1308 |
|
1309 /* for ie compatibility |
|
1310 if (Object.prototype.__defineGetter__&&!Object.defineProperty) { |
|
1311 Object.defineProperty=function(obj,prop,desc) { |
|
1312 if ("get" in desc) obj.__defineGetter__(prop,desc.get); |
|
1313 if ("set" in desc) obj.__defineSetter__(prop,desc.set); |
|
1314 } |
|
1315 } |
|
1316 */ |
|
1317 |
|
1318 /* Creates regexps from text */ |
|
1319 IriSP.regexpFromText = function(_text) { |
|
1320 return new RegExp('(' + _text.replace(/(\W)/gim,'\\$1') + ')','gim'); |
|
1321 } |
|
1322 /* wrapper that simulates popcorn.js because |
|
1323 popcorn is a bit unstable at the time */ |
|
1324 |
|
1325 IriSP.PopcornReplacement = { |
|
1326 }; |
|
1327 |
|
1328 /** base class for our popcorn-compatible players. |
|
1329 */ |
|
1330 IriSP.PopcornReplacement.player = function(container, options) { |
|
1331 /* the jwplayer calls the callbacks in the global space so we need to |
|
1332 preserve them using IriSP.wrap */ |
|
1333 this.callbacks = { |
|
1334 onReady: IriSP.wrap(this, this.__initApi), |
|
1335 onTime: IriSP.wrap(this, this.__timeHandler), |
|
1336 onPlay: IriSP.wrap(this, this.__playHandler), |
|
1337 onPause: IriSP.wrap(this, this.__pauseHandler), |
|
1338 onSeek: IriSP.wrap(this, this.__seekHandler) |
|
1339 }; |
|
1340 |
|
1341 this.media = { |
|
1342 "paused": true, |
|
1343 "muted": false |
|
1344 }; |
|
1345 |
|
1346 this.container = container.slice(1); //eschew the '#' |
|
1347 |
|
1348 this.msgPump = {}; /* dictionnary used to receive and send messages */ |
|
1349 this.__codes = []; /* used to schedule the execution of a piece of code in |
|
1350 a segment (similar to the popcorn.code plugin). */ |
|
1351 |
|
1352 this._options = options; |
|
1353 |
|
1354 }; |
|
1355 |
|
1356 IriSP.PopcornReplacement.player.prototype.listen = function(msg, callback) { |
|
1357 if (!this.msgPump.hasOwnProperty(msg)) |
|
1358 this.msgPump[msg] = []; |
|
1359 |
|
1360 this.msgPump[msg].push(callback); |
|
1361 }; |
|
1362 |
|
1363 IriSP.PopcornReplacement.player.prototype.trigger = function(msg, params) { |
|
1364 if (!this.msgPump.hasOwnProperty(msg)) |
|
1365 return; |
|
1366 |
|
1367 var d = this.msgPump[msg]; |
|
1368 |
|
1369 for(var i = 0; i < d.length; i++) { |
|
1370 d[i].call(window, params); |
|
1371 } |
|
1372 |
|
1373 }; |
|
1374 |
|
1375 IriSP.PopcornReplacement.player.prototype.guid = function(prefix) { |
|
1376 var str = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { |
|
1377 var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); |
|
1378 return v.toString(16); |
|
1379 }); |
|
1380 |
|
1381 return prefix + str; |
|
1382 }; |
|
1383 |
|
1384 /** init the api after that flash player has been setup - called by the callback |
|
1385 defined by the embedded flash player |
|
1386 */ |
|
1387 IriSP.PopcornReplacement.player.prototype.__initApi = function() { |
|
1388 this.trigger("loadedmetadata"); // we've done more than loading metadata of course, |
|
1389 // but popcorn doesn't need to know more. |
|
1390 this.media.muted = this.playerFns.getMute(); |
|
1391 /* some programmed segments are supposed to be run at the beginning */ |
|
1392 var i = 0; |
|
1393 for(i = 0; i < this.__codes.length; i++) { |
|
1394 var c = this.__codes[i]; |
|
1395 if (0 == c.start) { |
|
1396 c.onStart(); |
|
1397 } |
|
1398 |
|
1399 if (0 == c.end) { |
|
1400 c.onEnd(); |
|
1401 } |
|
1402 } |
|
1403 }; |
|
1404 |
|
1405 /* |
|
1406 IriSP.PopcornReplacement.jwplayer = function(container, options) { |
|
1407 IriSP.PopcornReplacement._container = container.slice(1); //eschew the '#' |
|
1408 options.events = { |
|
1409 onReady: IriSP.PopcornReplacement.__initApi, |
|
1410 onTime: IriSP.PopcornReplacement.__timeHandler, |
|
1411 onPlay: IriSP.PopcornReplacement.__playHandler, |
|
1412 onPause: IriSP.PopcornReplacement.__pauseHandler, |
|
1413 onSeek: IriSP.PopcornReplacement.__seekHandler |
|
1414 } |
|
1415 |
|
1416 jwplayer(IriSP.PopcornReplacement._container).setup(options); |
|
1417 IriSP.PopcornReplacement.media.duration = options.duration; |
|
1418 return IriSP.PopcornReplacement; |
|
1419 }; |
|
1420 */ |
|
1421 |
|
1422 IriSP.PopcornReplacement.player.prototype.currentTime = function(time) { |
|
1423 if (typeof(time) === "undefined") { |
|
1424 return this.playerFns.getPosition(); |
|
1425 } else { |
|
1426 var currentTime = +time; |
|
1427 this.playerFns.seek(currentTime); |
|
1428 return currentTime; |
|
1429 } |
|
1430 }; |
|
1431 |
|
1432 IriSP.PopcornReplacement.player.prototype.play = function() { |
|
1433 this.media.paused = false; |
|
1434 this.trigger("play"); |
|
1435 //IriSP.PopcornReplacement.trigger("playing"); |
|
1436 this.playerFns.play(); |
|
1437 }; |
|
1438 |
|
1439 IriSP.PopcornReplacement.player.prototype.pause = function() { |
|
1440 // if ( !this.media.paused ) { |
|
1441 this.media.paused = true; |
|
1442 this.trigger( "pause" ); |
|
1443 this.playerFns.pause(); |
|
1444 // } |
|
1445 }; |
|
1446 |
|
1447 IriSP.PopcornReplacement.player.prototype.muted = function(val) { |
|
1448 if (typeof(val) !== "undefined") { |
|
1449 |
|
1450 if (this.playerFns.getMute() !== val) { |
|
1451 if (val) { |
|
1452 this.playerFns.setMute(true); |
|
1453 this.media.muted = true; |
|
1454 } else { |
|
1455 this.playerFns.setMute(false); |
|
1456 this.media.muted = false; |
|
1457 } |
|
1458 |
|
1459 this.trigger( "volumechange" ); |
|
1460 } |
|
1461 |
|
1462 return this.playerFns.getMute(); |
|
1463 } else { |
|
1464 return this.playerFns.getMute(); |
|
1465 } |
|
1466 }; |
|
1467 |
|
1468 IriSP.PopcornReplacement.player.prototype.volume = function(val) { |
|
1469 if (typeof this.playerFns.getVolume == "undefined" || typeof this.playerFns.setVolume == "undefined") { |
|
1470 return false; |
|
1471 } |
|
1472 var _vol = this.playerFns.getVolume(); |
|
1473 if (typeof(val) !== "undefined" && parseFloat(val) !== NaN) { |
|
1474 val = Math.max(0, Math.min(1, val)); |
|
1475 if (parseFloat(val) != parseFloat(_vol)) { |
|
1476 this.playerFns.setVolume(val); |
|
1477 this.trigger("volumechange"); |
|
1478 _vol = this.playerFns.getVolume(); |
|
1479 } |
|
1480 } |
|
1481 return _vol; |
|
1482 }; |
|
1483 |
|
1484 IriSP.PopcornReplacement.player.prototype.mute = IriSP.PopcornReplacement.player.prototype.muted; |
|
1485 |
|
1486 IriSP.PopcornReplacement.player.prototype.code = function(options) { |
|
1487 this.__codes.push(options); |
|
1488 return this; |
|
1489 }; |
|
1490 |
|
1491 /* called everytime the player updates itself |
|
1492 (onTime event) |
|
1493 */ |
|
1494 |
|
1495 IriSP.PopcornReplacement.player.prototype.__timeHandler = function(event) { |
|
1496 var pos = event.position; |
|
1497 |
|
1498 var i = 0; |
|
1499 for(i = 0; i < this.__codes.length; i++) { |
|
1500 var c = this.__codes[i]; |
|
1501 |
|
1502 if (pos >= c.start && pos < c.end && |
|
1503 pos - 1 <= c.start) { |
|
1504 c.onStart(); |
|
1505 } |
|
1506 |
|
1507 if (pos > c.start && pos > c.end && |
|
1508 pos - 1 <= c.end) { |
|
1509 c.onEnd(); |
|
1510 } |
|
1511 |
|
1512 } |
|
1513 |
|
1514 this.trigger("timeupdate"); |
|
1515 }; |
|
1516 |
|
1517 IriSP.PopcornReplacement.player.prototype.__seekHandler = function(event) { |
|
1518 var i = 0; |
|
1519 |
|
1520 for(i = 0; i < this.__codes.length; i++) { |
|
1521 var c = this.__codes[i]; |
|
1522 |
|
1523 if (event.position >= c.start && event.position < c.end) { |
|
1524 c.onEnd(); |
|
1525 } |
|
1526 } |
|
1527 |
|
1528 for(i = 0; i < this.__codes.length; i++) { |
|
1529 var c = this.__codes[i]; |
|
1530 |
|
1531 if (typeof(event.offset) === "undefined") |
|
1532 event.offset = 0; |
|
1533 |
|
1534 if (event.offset >= c.start && event.offset < c.end) { |
|
1535 c.onStart(); |
|
1536 } |
|
1537 |
|
1538 } |
|
1539 |
|
1540 /* this signal sends as an extra argument the position in the video. |
|
1541 As far as I know, this argument is not provided by popcorn */ |
|
1542 this.trigger("seeked", event.offset); |
|
1543 }; |
|
1544 |
|
1545 IriSP.PopcornReplacement.player.prototype.__playHandler = function(event) { |
|
1546 this.media.paused = false; |
|
1547 this.trigger("play"); |
|
1548 }; |
|
1549 |
|
1550 IriSP.PopcornReplacement.player.prototype.__pauseHandler = function(event) { |
|
1551 this.media.paused = true; |
|
1552 this.trigger("pause"); |
|
1553 }; |
|
1554 |
|
1555 IriSP.PopcornReplacement.player.prototype.roundTime = function() { |
|
1556 var currentTime = this.currentTime(); |
|
1557 return Math.round(currentTime); |
|
1558 };/* data.js - this file deals with how the players gets and sends data */ |
|
1559 |
|
1560 IriSP.DataLoader = function() { |
|
1561 this._cache = {}; |
|
1562 |
|
1563 /* |
|
1564 A structure to hold callbacks for specific urls. We need it because |
|
1565 ajax calls are asynchronous, so it means that sometimes we ask |
|
1566 multiple times for a ressource because the first call hasn't been |
|
1567 received yet. |
|
1568 */ |
|
1569 this._callbacks = {}; |
|
1570 }; |
|
1571 |
|
1572 IriSP.DataLoader.prototype.get = function(url, callback, force_reload) { |
|
1573 var base_url = url.split("&")[0]; |
|
1574 if (typeof force_reload != "undefined" && force_reload && this._cache.hasOwnProperty(base_url)) { |
|
1575 delete this._cache[base_url] |
|
1576 } |
|
1577 if (this._cache.hasOwnProperty(base_url)) { |
|
1578 callback(this._cache[base_url]); |
|
1579 } else { |
|
1580 if (!this._callbacks.hasOwnProperty(base_url)) { |
|
1581 this._callbacks[base_url] = [callback]; |
|
1582 /* we need a closure because this gets lost when it's called back */ |
|
1583 |
|
1584 // uncomment you don't want to use caching. |
|
1585 // IriSP.jQuery.get(url, callback); |
|
1586 |
|
1587 var func = function(data) { |
|
1588 this._cache[base_url] = data; |
|
1589 var i = 0; |
|
1590 |
|
1591 for (i = 0; i < this._callbacks[base_url].length; i++) { |
|
1592 this._callbacks[base_url][i](this._cache[base_url]); |
|
1593 } |
|
1594 delete this._callbacks[base_url]; |
|
1595 }; |
|
1596 |
|
1597 /* automagically choose between json and jsonp */ |
|
1598 if (url.indexOf(document.location.hostname) === -1 && |
|
1599 url.indexOf("http://") !== -1 /* not a relative url */ ) { |
|
1600 // we contacting a foreign domain, use JSONP |
|
1601 |
|
1602 IriSP.jQuery.get(url, {}, IriSP.wrap(this, func), "jsonp"); |
|
1603 } else { |
|
1604 |
|
1605 // otherwise, hey, whatever rows your boat |
|
1606 IriSP.jQuery.get(url, IriSP.wrap(this, func)); |
|
1607 } |
|
1608 |
|
1609 } else { |
|
1610 /* simply push the callback - it'll get called when the ressource |
|
1611 has been received */ |
|
1612 |
|
1613 this._callbacks[base_url].push(callback); |
|
1614 |
|
1615 } |
|
1616 } |
|
1617 } |
|
1618 |
|
1619 /* the base abstract "class" */ |
|
1620 IriSP.Serializer = function(DataLoader, url) { |
|
1621 this._DataLoader = DataLoader; |
|
1622 this._url = url; |
|
1623 this._data = []; |
|
1624 }; |
|
1625 |
|
1626 IriSP.Serializer.prototype.serialize = function(data) { }; |
|
1627 IriSP.Serializer.prototype.deserialize = function(data) {}; |
|
1628 |
|
1629 IriSP.Serializer.prototype.currentMedia = function() { |
|
1630 }; |
|
1631 |
|
1632 IriSP.Serializer.prototype.getDuration = function() { |
|
1633 }; |
|
1634 |
|
1635 IriSP.Serializer.prototype.sync = function(callback) { |
|
1636 callback.call(this, this._data); |
|
1637 }; |
|
1638 |
|
1639 IriSP.SerializerFactory = function(DataLoader) { |
|
1640 this._dataloader = DataLoader; |
|
1641 }; |
|
1642 |
|
1643 IriSP.SerializerFactory.prototype.getSerializer = function(metadataOptions) { |
|
1644 /* This function returns serializer set-up with the correct |
|
1645 configuration - takes a metadata struct describing the metadata source |
|
1646 */ |
|
1647 if (metadataOptions === undefined) |
|
1648 /* return an empty serializer */ |
|
1649 return IriSP.Serializer("", ""); |
|
1650 |
|
1651 switch(metadataOptions.type) { |
|
1652 case "json": |
|
1653 return new IriSP.JSONSerializer(this._dataloader, metadataOptions.src); |
|
1654 break; |
|
1655 |
|
1656 case "dummy": /* only used for unit testing - not defined in production */ |
|
1657 return new IriSP.MockSerializer(this._dataloader, metadataOptions.src); |
|
1658 break; |
|
1659 |
|
1660 case "empty": |
|
1661 return new IriSP.Serializer("", "empty"); |
|
1662 break; |
|
1663 |
|
1664 default: |
|
1665 return undefined; |
|
1666 } |
|
1667 }; |
|
1668 IriSP.language = 'en'; |
|
1669 |
|
1670 IriSP.libFiles = { |
|
1671 defaultDir : "js/libs/", |
|
1672 inDefaultDir : { |
|
1673 jQuery : "jquery.min.js", |
|
1674 jQueryUI : "jquery-ui.min.js", |
|
1675 jQueryToolTip : "jquery.tools.min.js", |
|
1676 swfObject : "swfobject.js", |
|
1677 cssjQueryUI : "jquery-ui.css", |
|
1678 popcorn : "popcorn.js", |
|
1679 jwplayer : "jwplayer.js", |
|
1680 raphael : "raphael.js", |
|
1681 "popcorn.mediafragment" : "popcorn.mediafragment.js", |
|
1682 "popcorn.code" : "popcorn.code.js", |
|
1683 "popcorn.jwplayer" : "popcorn.jwplayer.js", |
|
1684 "popcorn.youtube" : "popcorn.youtube.js", |
|
1685 "tracemanager" : "tracemanager.js" |
|
1686 }, |
|
1687 locations : { |
|
1688 // use to define locations outside defautl_dir |
|
1689 }, |
|
1690 cdn : { |
|
1691 jQueryUI : "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.17/jquery-ui.js", |
|
1692 jQueryToolTip : "http://cdn.jquerytools.org/1.2.4/all/jquery.tools.min.js", |
|
1693 swfObject : "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js", |
|
1694 cssjQueryUI : "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.4/themes/base/jquery-ui.css" |
|
1695 }, |
|
1696 useCdn : false |
|
1697 } |
|
1698 |
|
1699 IriSP.widgetsDefaults = { |
|
1700 "LayoutManager" : { |
|
1701 spacer_div_height : 0 |
|
1702 }, |
|
1703 "PlayerWidget" : { |
|
1704 |
|
1705 }, |
|
1706 "AnnotationsWidget" : { |
|
1707 "share_text" : "I'm watching " |
|
1708 }, |
|
1709 "TweetsWidget" : { |
|
1710 default_profile_picture : "https://si0.twimg.com/sticky/default_profile_images/default_profile_1_normal.png", |
|
1711 tweet_display_period : 10000 // how long do we show a tweet ? |
|
1712 }, |
|
1713 "SliderWidget" : { |
|
1714 minimize_period : 850 // how long does the slider stays maximized after the user leaves the zone ? |
|
1715 }, |
|
1716 "SegmentsWidget" : { |
|
1717 cinecast_version : false |
|
1718 }, |
|
1719 "createAnnotationWidget" : { |
|
1720 tags : [ |
|
1721 { |
|
1722 "id" : "digitalstudies", |
|
1723 "meta" : { |
|
1724 "description" : "#digital-studies" |
|
1725 } |
|
1726 }, |
|
1727 { |
|
1728 "id" : "amateur", |
|
1729 "meta" : { |
|
1730 "description" : "#amateur" |
|
1731 }, |
|
1732 } |
|
1733 ], |
|
1734 remote_tags : false, |
|
1735 random_tags : false, |
|
1736 show_from_field : false, |
|
1737 disable_share : false, |
|
1738 polemic_mode : true, /* enable polemics ? */ |
|
1739 polemics : [{ |
|
1740 "className" : "positive", |
|
1741 "keyword" : "++" |
|
1742 }, { |
|
1743 "className" : "negative", |
|
1744 "keyword" : "--" |
|
1745 }, { |
|
1746 "className" : "reference", |
|
1747 "keyword" : "==" |
|
1748 }, { |
|
1749 "className" : "question", |
|
1750 "keyword" : "??" |
|
1751 }], |
|
1752 cinecast_version : false, /* put to false to enable the platform version, true for the festival cinecast one. */ |
|
1753 |
|
1754 /* where does the widget PUT the annotations - this is a mustache template. id refers to the id of the media ans is filled |
|
1755 by the widget. |
|
1756 */ |
|
1757 api_endpoint_template : "", // platform_url + "/ldtplatform/api/ldt/annotations/{{id}}.json", |
|
1758 api_method : "PUT" |
|
1759 }, |
|
1760 "SparklineWidget" : { |
|
1761 lineColor : "#7492b4", |
|
1762 fillColor : "#aeaeb8", |
|
1763 lineWidth : 2, |
|
1764 cinecast_version : false |
|
1765 }, |
|
1766 "AnnotationsListWidget" : { |
|
1767 ajax_mode : true, /* use ajax to get information about the annotations. |
|
1768 if set to false, only search in the annotations for the |
|
1769 current project. */ |
|
1770 /* the platform generates some funky urls. We replace them afterwards to point to the |
|
1771 correct place - this setting will probably be overwritten by the platform |
|
1772 implementers. |
|
1773 Note that the player has to replace the variables between {{ and }} by its own values. |
|
1774 */ |
|
1775 ajax_url : "", //platform_url + "/ldtplatform/api/ldt/segments/{{media}}/{{begin}}/{{end}}", |
|
1776 ajax_granularity : 10000, /* how much ms should we look before and after the current timecode */ |
|
1777 default_thumbnail : "http://ldt.iri.centrepompidou.fr/static/site/ldt/css/imgs/video_sequence.png", |
|
1778 project_url : "", //platform_url + "/ldtplatform/ldt/front/player/" |
|
1779 /* the beginning of a link to the new front */ |
|
1780 cinecast_version : false, |
|
1781 refresh_interval : 10000 |
|
1782 }, |
|
1783 "StackGraphWidget" : { |
|
1784 defaultcolor : "#585858", |
|
1785 tags : [ |
|
1786 { |
|
1787 "keywords" : [ "++" ], |
|
1788 "description" : "positif", |
|
1789 "color" : "#1D973D" |
|
1790 }, |
|
1791 { |
|
1792 "keywords" : [ "--" ], |
|
1793 "description" : "negatif", |
|
1794 "color" : "#CE0A15" |
|
1795 }, |
|
1796 { |
|
1797 "keywords" : [ "==" ], |
|
1798 "description" : "reference", |
|
1799 "color" : "#C5A62D" |
|
1800 }, |
|
1801 { |
|
1802 "keywords" : [ "??" ], |
|
1803 "description" : "question", |
|
1804 "color" : "#036AAE" |
|
1805 } |
|
1806 ], |
|
1807 streamgraph : false |
|
1808 } |
|
1809 }/* the widget classes and definitions */ |
|
1810 |
|
1811 /** |
|
1812 * @class Widget is an "abstract" class. It's mostly used to define some properties common to every widget. |
|
1813 * |
|
1814 * Note that widget constructors are never called directly by the user. Instead, the widgets are instantiated by functions |
|
1815 * defined in init.js |
|
1816 * |
|
1817 * @constructor |
|
1818 * @param Popcorn a reference to the popcorn Object |
|
1819 * @param config configuration options for the widget |
|
1820 * @param Serializer a serializer instance from which the widget reads data fromCharCode |
|
1821 */ |
|
1822 IriSP.Widget = function(Popcorn, config, Serializer) { |
|
1823 |
|
1824 if (config === undefined || config === null) { |
|
1825 config = {} |
|
1826 } |
|
1827 |
|
1828 this._Popcorn = Popcorn; |
|
1829 this._config = config; |
|
1830 this._serializer = Serializer; |
|
1831 |
|
1832 if (config.hasOwnProperty("container")) { |
|
1833 this._id = config.container; |
|
1834 this.selector = IriSP.jQuery("#" + this._id); |
|
1835 } |
|
1836 |
|
1837 if (config.hasOwnProperty("spacer")) { |
|
1838 this._spacerId = config.spacer; |
|
1839 this.spacer = IriSP.jQuery("#" + this._spacerId); |
|
1840 } |
|
1841 |
|
1842 |
|
1843 if (config.hasOwnProperty("width")) { |
|
1844 // this.width and not this._width because we consider it public. |
|
1845 this.width = config.width; |
|
1846 } |
|
1847 |
|
1848 if (config.hasOwnProperty("height")) { |
|
1849 this.height = config.height; |
|
1850 } |
|
1851 |
|
1852 if (config.hasOwnProperty("heightmax")) { |
|
1853 this.heightmax = config.heightmax; |
|
1854 } |
|
1855 |
|
1856 if (config.hasOwnProperty("widthmax")) { |
|
1857 this.widthmax = config.widthmax; |
|
1858 } |
|
1859 |
|
1860 if (config.hasOwnProperty("layoutManager")) { |
|
1861 this.layoutManager = config.layoutManager; |
|
1862 } |
|
1863 if (typeof this.selector != "undefined") { |
|
1864 this.selector.addClass("Ldt-TraceMe").addClass("Ldt-Widget"); |
|
1865 this.selector.attr("widget-type", this._config.type); |
|
1866 } |
|
1867 |
|
1868 // Parsing Widget Defaults |
|
1869 var _this = this; |
|
1870 |
|
1871 if (typeof config.type == "string" && typeof IriSP.widgetsDefaults[config.type] == "object") { |
|
1872 IriSP._(IriSP.widgetsDefaults[config.type]).each(function(_v, _k) { |
|
1873 if (typeof config[_k] != "undefined") { |
|
1874 _this[_k] = config[_k]; |
|
1875 } else { |
|
1876 _this[_k] = _v; |
|
1877 } |
|
1878 }); |
|
1879 } |
|
1880 |
|
1881 }; |
|
1882 |
|
1883 |
|
1884 IriSP.Widget.prototype.currentMedia = function() { |
|
1885 return this._serializer.currentMedia(); |
|
1886 } |
|
1887 |
|
1888 IriSP.Widget.prototype.getDuration = function() { |
|
1889 return this._serializer.getDuration(); |
|
1890 } |
|
1891 |
|
1892 /** |
|
1893 * This method responsible of drawing a widget on screen. |
|
1894 */ |
|
1895 IriSP.Widget.prototype.draw = function() { |
|
1896 /* implemented by "sub-classes" */ |
|
1897 }; |
|
1898 |
|
1899 /** |
|
1900 * Optional method if you want your widget to support redraws. |
|
1901 */ |
|
1902 IriSP.Widget.prototype.redraw = function() { |
|
1903 /* implemented by "sub-classes" */ |
|
1904 }; |
|
1905 /* modules are non-graphical entities, similar to widgets */ |
|
1906 |
|
1907 IriSP.Module = function(Popcorn, config, Serializer) { |
|
1908 |
|
1909 if (config === undefined || config === null) { |
|
1910 config = {} |
|
1911 } |
|
1912 |
|
1913 this._Popcorn = Popcorn; |
|
1914 this._config = config; |
|
1915 this._serializer = Serializer; |
|
1916 }; |
|
1917 /* layout.js - very basic layout management */ |
|
1918 |
|
1919 /** |
|
1920 @class a layout manager manages a div and the layout of objects |
|
1921 inside it. |
|
1922 */ |
|
1923 IriSP.LayoutManager = function(options) { |
|
1924 this._Popcorn = null; |
|
1925 this._widgets = []; |
|
1926 |
|
1927 this._div = "LdtPlayer"; |
|
1928 this._width = 640; |
|
1929 |
|
1930 if (options === undefined) { |
|
1931 options = {}; |
|
1932 }; |
|
1933 |
|
1934 if (options.hasOwnProperty('container')) { |
|
1935 this._div = options.container; |
|
1936 } |
|
1937 |
|
1938 if (options.hasOwnProperty('width')) { |
|
1939 this._width = options.width; |
|
1940 } |
|
1941 |
|
1942 if (options.hasOwnProperty('height')) { |
|
1943 this._height = options.height; |
|
1944 } |
|
1945 |
|
1946 /* this is a shortcut */ |
|
1947 this.selector = IriSP.jQuery("#" + this._div); |
|
1948 |
|
1949 this.selector.css({ |
|
1950 "width": this._width, |
|
1951 "clear": "both" |
|
1952 }); |
|
1953 |
|
1954 if (this._height !== undefined) |
|
1955 this.selector.css("height", this._height); |
|
1956 }; |
|
1957 |
|
1958 /** |
|
1959 Set the popcorn instance used by the manager. |
|
1960 |
|
1961 we need this special setter because of a chicken and egg problem : |
|
1962 we want the manager to use popcorn but the popcorn div will be managed |
|
1963 by the manager. So we need a way to set the instance the manager uses |
|
1964 */ |
|
1965 |
|
1966 IriSP.LayoutManager.prototype.setPopcornInstance = function(popcorn) { |
|
1967 this._Popcorn = popcorn; |
|
1968 } |
|
1969 |
|
1970 /** create a subdiv with an unique id, and a spacer div as well. |
|
1971 @param widgetName the name of the widget. |
|
1972 @return an array of the form [createdivId, spacerdivId]. |
|
1973 */ |
|
1974 IriSP.LayoutManager.prototype.createDiv = function(widgetName) { |
|
1975 if (typeof(widgetName) === "undefined") |
|
1976 widgetName = ""; |
|
1977 |
|
1978 var newDiv = IriSP.guid(this._div + "_widget_" + widgetName + "_"); |
|
1979 var spacerDiv = IriSP.guid("LdtPlayer_spacer_"); |
|
1980 this._widgets.push([widgetName, newDiv]); |
|
1981 |
|
1982 var divTempl = "<div id='{{id}}' style='width: {{width}}px; position: relative; clear: both;'></div"; |
|
1983 var spacerTempl = "<div id='{{spacer_id}}' style='width: {{width}}px; position: relative; height: {{spacer_div_height}}px;'></div"; |
|
1984 |
|
1985 var divCode = Mustache.to_html(divTempl, {id: newDiv, width: this._width}); |
|
1986 var spacerCode = Mustache.to_html(spacerTempl, {spacer_id: spacerDiv, width: this._width, |
|
1987 spacer_div_height: IriSP.widgetsDefaults.LayoutManager.spacer_div_height }); |
|
1988 |
|
1989 this.selector.append(divCode); |
|
1990 this.selector.append(spacerCode); |
|
1991 |
|
1992 return [newDiv, spacerDiv]; |
|
1993 };/* init.js - initialization and configuration of Popcorn and the widgets |
|
1994 exemple json configuration: |
|
1995 |
|
1996 */ |
|
1997 |
|
1998 /** |
|
1999 set up the IriSP.__dataloader instance - |
|
2000 we need it because we have to get the metadata |
|
2001 about the video before that the widget have even |
|
2002 loaded. |
|
2003 */ |
|
2004 IriSP.setupDataLoader = function() { |
|
2005 /* we set it up separately because we need to |
|
2006 get data at the very beginning, for instance when |
|
2007 setting up the video */ |
|
2008 IriSP.__dataloader = new IriSP.DataLoader(); |
|
2009 }; |
|
2010 |
|
2011 /** do some magic to configure popcorn according to the options object passed. |
|
2012 Works for html5, jwplayer and youtube videos |
|
2013 */ |
|
2014 IriSP.configurePopcorn = function (layoutManager, options) { |
|
2015 var pop; |
|
2016 var ret = layoutManager.createDiv(); |
|
2017 var containerDiv = ret[0]; |
|
2018 var spacerDiv = ret[1]; |
|
2019 |
|
2020 /* insert one pixel of margin between the video and the first widget, using the |
|
2021 spacer. |
|
2022 */ |
|
2023 IriSP.jQuery("#" + spacerDiv).css("height", "1px"); |
|
2024 |
|
2025 switch(options.type) { |
|
2026 /* |
|
2027 todo : dynamically create the div/video tag which |
|
2028 will contain the video. |
|
2029 */ |
|
2030 case "html5": |
|
2031 var tmpId = Popcorn.guid("video"); |
|
2032 IriSP.jQuery("#" + containerDiv).append("<video src='" + options.file + "' id='" + tmpId + "'></video>"); |
|
2033 |
|
2034 if (options.hasOwnProperty("width")) |
|
2035 IriSP.jQuery("#" + containerDiv).css("width", options.width); |
|
2036 |
|
2037 if (options.hasOwnProperty("height")) |
|
2038 IriSP.jQuery("#" + containerDiv).css("height", options.height); |
|
2039 |
|
2040 pop = Popcorn("#" + tmpId); |
|
2041 break; |
|
2042 |
|
2043 case "jwplayer": |
|
2044 var opts = IriSP.jQuery.extend({}, options); |
|
2045 delete opts.container; |
|
2046 delete opts.type; |
|
2047 |
|
2048 |
|
2049 /* Try to guess options.file and options.streamer only if file and streamer |
|
2050 are not already defined in the configuration */ |
|
2051 if (options.provider === "rtmp" && !opts.hasOwnProperty("file") && !opts.hasOwnProperty("streamer")) { |
|
2052 /* exit if we can't access the metadata */ |
|
2053 if (typeof(IriSP.__jsonMetadata) === "undefined") { |
|
2054 break; |
|
2055 }; |
|
2056 |
|
2057 // the json format is totally illogical |
|
2058 //opts.streamer = IriSP.__jsonMetadata["medias"][0]["meta"]["item"]["value"]; |
|
2059 //var source = IriSP.__jsonMetadata["medias"][0]["href"]; |
|
2060 |
|
2061 // the source if a full url but jwplayer wants an url relative to the |
|
2062 // streamer url, so we've got to remove the common part. |
|
2063 //opts.file = source.slice(opts.streamer.length); |
|
2064 |
|
2065 /* sometimes we get served a file with a wrong path and streamer. |
|
2066 as a streamer is of the form rtmp://domain/path/ and the media is |
|
2067 the rest, we uglily do this : |
|
2068 */ |
|
2069 opts.file = ""; |
|
2070 opts.streamer = ""; |
|
2071 var fullPath = IriSP.get_aliased(IriSP.__jsonMetadata["medias"][0], ["href","url"]); |
|
2072 |
|
2073 if (fullPath === null) { |
|
2074 console.log("no url or href field defined in the metadata."); |
|
2075 } |
|
2076 |
|
2077 var pathSplit = fullPath.split('/'); |
|
2078 |
|
2079 for (var i = 0; i < pathSplit.length; i++) { |
|
2080 if (i < 4) { |
|
2081 opts.streamer += pathSplit[i] + "/"; |
|
2082 } else { |
|
2083 opts.file += pathSplit[i]; |
|
2084 /* omit the last slash if we're on the last element */ |
|
2085 if (i < pathSplit.length - 1) |
|
2086 opts.file += "/"; |
|
2087 } |
|
2088 } |
|
2089 } else { |
|
2090 /* other providers type, video for instance - |
|
2091 pass everything as is */ |
|
2092 } |
|
2093 |
|
2094 if (!options.hasOwnProperty("flashplayer")) { |
|
2095 opts.flashplayer = IriSP.jwplayer_swf_path; |
|
2096 } |
|
2097 |
|
2098 if (!options.hasOwnProperty("controlbar.position")) { |
|
2099 opts["controlbar.position"] = "none"; |
|
2100 } |
|
2101 |
|
2102 pop = new IriSP.PopcornReplacement.jwplayer("#" + containerDiv, opts); |
|
2103 break; |
|
2104 |
|
2105 case "youtube": |
|
2106 var opts = IriSP.jQuery.extend({}, options); |
|
2107 delete opts.container; |
|
2108 opts.controls = 0; |
|
2109 opts.autostart = false; |
|
2110 templ = "width: {{width}}px; height: {{height}}px;"; |
|
2111 var str = Mustache.to_html(templ, {width: opts.width, height: opts.height}); |
|
2112 // Popcorn.youtube wants us to specify the size of the player in the style attribute of its container div. |
|
2113 IriSP.jQuery("#" + containerDiv).attr("style", str); |
|
2114 |
|
2115 pop = Popcorn.youtube("#" + containerDiv, opts.video, opts); |
|
2116 break; |
|
2117 |
|
2118 case "dailymotion": |
|
2119 pop = new IriSP.PopcornReplacement.dailymotion("#" + containerDiv, options); |
|
2120 break; |
|
2121 |
|
2122 case "allocine": |
|
2123 /* pass the options as-is to the allocine player and let it handle everything */ |
|
2124 pop = new IriSP.PopcornReplacement.allocine("#" + containerDiv, options); |
|
2125 break; |
|
2126 |
|
2127 default: |
|
2128 pop = undefined; |
|
2129 }; |
|
2130 |
|
2131 return pop; |
|
2132 }; |
|
2133 |
|
2134 /** Configure the gui and instantiate the widgets passed as parameters |
|
2135 @param guiOptions the gui object as seen in the examples. |
|
2136 */ |
|
2137 IriSP.configureWidgets = function (popcornInstance, layoutManager, guiOptions) { |
|
2138 |
|
2139 var serialFactory = new IriSP.SerializerFactory(IriSP.__dataloader); |
|
2140 var params = {width: guiOptions.width, height: guiOptions.height}; |
|
2141 |
|
2142 var default_options = guiOptions.default_options; |
|
2143 if (IriSP.null_or_undefined(default_options)) |
|
2144 default_options = {}; |
|
2145 |
|
2146 var ret_widgets = []; |
|
2147 var index; |
|
2148 |
|
2149 for (index = 0; index < guiOptions.widgets.length; index++) { |
|
2150 var widget = IriSP.instantiateWidget(popcornInstance, serialFactory, layoutManager, guiOptions.widgets[index], default_options); |
|
2151 |
|
2152 ret_widgets.push(widget); |
|
2153 }; |
|
2154 |
|
2155 return ret_widgets; |
|
2156 }; |
|
2157 |
|
2158 /** configure modules. @see configureWidgets */ |
|
2159 IriSP.configureModules = function (popcornInstance, modulesList) { |
|
2160 if (IriSP.null_or_undefined(modulesList)) |
|
2161 return; |
|
2162 |
|
2163 var serialFactory = new IriSP.SerializerFactory(IriSP.__dataloader); |
|
2164 var ret_modules = []; |
|
2165 var index; |
|
2166 |
|
2167 for (index = 0; index < modulesList.length; index++) { |
|
2168 var moduleConfig = modulesList[index]; |
|
2169 |
|
2170 var serializer = serialFactory.getSerializer(moduleConfig.metadata); |
|
2171 var module = new IriSP[moduleConfig.type](popcornInstance, moduleConfig, serializer); |
|
2172 ret_modules.push(module); |
|
2173 }; |
|
2174 |
|
2175 return ret_modules; |
|
2176 }; |
|
2177 |
|
2178 /** instantiate a widget - only called by configureWidgets, never by the user. Handles widget |
|
2179 dependencies. |
|
2180 @param popcornInstance popcorn instance the widget will user |
|
2181 @param serialFactory serializer factory to instantiate the widget with |
|
2182 @param layoutManager layout manager |
|
2183 @param widgetConfig configuration options for the widget |
|
2184 @param defaultOptions a dictionnary with some options defined for every widget. |
|
2185 */ |
|
2186 IriSP.instantiateWidget = function(popcornInstance, serialFactory, layoutManager, widgetConfig, defaultOptions) { |
|
2187 |
|
2188 if (IriSP.null_or_undefined(defaultOptions)) |
|
2189 defaultOptions = {}; |
|
2190 |
|
2191 widgetConfig = IriSP.underscore.defaults(widgetConfig, defaultOptions); |
|
2192 |
|
2193 var arr = IriSP.jQuery.extend({}, widgetConfig); |
|
2194 |
|
2195 /* create a div for those widgets who didn't already specify a container; */ |
|
2196 if (!arr.hasOwnProperty("container")) { |
|
2197 /* create div returns us a container for the widget and a spacer */ |
|
2198 var ret = layoutManager.createDiv(widgetConfig.type); |
|
2199 var container = ret[0]; |
|
2200 var spacer = ret[1]; |
|
2201 arr.container = container; |
|
2202 arr.spacer = spacer; |
|
2203 arr.layoutManager = layoutManager; |
|
2204 } |
|
2205 var serializer = serialFactory.getSerializer(widgetConfig.metadata); |
|
2206 |
|
2207 if (typeof serializer == "undefined") |
|
2208 debugger; |
|
2209 |
|
2210 // instantiate the object passed as a string |
|
2211 var widget = new IriSP[widgetConfig.type](popcornInstance, arr, serializer); |
|
2212 |
|
2213 if (widgetConfig.hasOwnProperty("requires")) { |
|
2214 // also create the widgets this one depends on. |
|
2215 // the dependency widget is available in the parent widget context as |
|
2216 // this.WidgetName (for instance, this.TipWidget); |
|
2217 |
|
2218 var i = 0; |
|
2219 for(i = 0; i < widgetConfig.requires.length; i++) { |
|
2220 var widgetName = widgetConfig.requires[i]["type"], |
|
2221 _configobj = IriSP.jQuery.extend({}, widgetConfig.requires[i]), |
|
2222 _div = document.createElement('div'), |
|
2223 _container = IriSP.guid(arr.container + '_' + widgetName + '_'); |
|
2224 _configobj.container = _container; |
|
2225 _div.id = _container; |
|
2226 widget.selector.append(_div); |
|
2227 widget[widgetName] = IriSP.instantiateWidget(popcornInstance, serialFactory, layoutManager, _configobj, defaultOptions); |
|
2228 } |
|
2229 } |
|
2230 |
|
2231 serializer.sync(IriSP.wrap(widget, function() { this.draw(); })); |
|
2232 return widget; |
|
2233 }; |
|
2234 |
|
2235 /** single point of entry for the metadataplayer */ |
|
2236 IriSP.initPlayer = function(config, metadata_url) { |
|
2237 document.getElementById(config.gui.container).innerHTML = IriSP.templToHTML(IriSP.loading_template, config.gui); |
|
2238 IriSP.loadLibs(config, metadata_url, |
|
2239 function() { |
|
2240 |
|
2241 var layoutManager = new IriSP.LayoutManager(config.gui); |
|
2242 |
|
2243 var pop = IriSP.configurePopcorn(layoutManager, config.player); |
|
2244 |
|
2245 IriSP._widgets = IriSP.configureWidgets(pop, layoutManager, config.gui); |
|
2246 IriSP._modules = IriSP.configureModules(pop, config.modules); |
|
2247 IriSP.jQuery('#Ldt-loader').detach(); |
|
2248 }); |
|
2249 };IriSP.I18n = function() { |
|
2250 this.messages = {}; |
|
2251 this.base_lang = 'en'; |
|
2252 } |
|
2253 |
|
2254 IriSP.I18n.prototype.getLanguage = function(lang) { |
|
2255 var _lang = ( |
|
2256 typeof lang != "undefined" |
|
2257 ? lang |
|
2258 : ( |
|
2259 typeof IriSP.language != "undefined" |
|
2260 ? IriSP.language |
|
2261 : this.base_lang |
|
2262 ) |
|
2263 ); |
|
2264 return ( |
|
2265 typeof this.messages[_lang] == "object" |
|
2266 ? _lang |
|
2267 : ( |
|
2268 typeof this.messages[this.base_lang] == "object" |
|
2269 ? this.base_lang |
|
2270 : null |
|
2271 ) |
|
2272 ) |
|
2273 } |
|
2274 |
|
2275 IriSP.I18n.prototype.getMessages = function(lang) { |
|
2276 var _lang = this.getLanguage(lang); |
|
2277 return ( |
|
2278 _lang != null |
|
2279 ? this.messages[_lang] |
|
2280 : {} |
|
2281 ); |
|
2282 } |
|
2283 |
|
2284 IriSP.I18n.prototype.getMessage = function(message, lang) { |
|
2285 var _msgs = this.getMessages(lang); |
|
2286 return ( |
|
2287 typeof _msgs[message] != "undefined" |
|
2288 ? _msgs[message] |
|
2289 : message |
|
2290 ) |
|
2291 } |
|
2292 |
|
2293 IriSP.I18n.prototype.addMessage = function(lang, messagekey, messagevalue) { |
|
2294 if (typeof this.messages[lang] == "undefined") { |
|
2295 this.messages[lang] = {}; |
|
2296 } |
|
2297 this.messages[lang][messagekey] = messagevalue; |
|
2298 } |
|
2299 |
|
2300 IriSP.I18n.prototype.addMessages = function(messagesObj) { |
|
2301 var _this = this; |
|
2302 IriSP.underscore(messagesObj).each(function(_messages, _lang) { |
|
2303 IriSP.underscore(_messages).each(function(_value, _key) { |
|
2304 _this.addMessage(_lang, _key, _value); |
|
2305 }) |
|
2306 }); |
|
2307 } |
|
2308 |
|
2309 IriSP.i18n = new IriSP.I18n(); |
|
2310 |
|
2311 IriSP.i18n.addMessages({ |
|
2312 "fr": { |
|
2313 "loading_wait": "Chargement en cours, veuillez patienter…" |
|
2314 }, |
|
2315 "en": { |
|
2316 "loading_wait": "Loading, please wait…" |
|
2317 } |
|
2318 }) |
|
2319 /* To wrap a player the develop should create a new class derived from |
|
2320 the IriSP.PopcornReplacement.player and defining the correct functions */ |
|
2321 |
|
2322 /** allocine player wrapper */ |
|
2323 IriSP.PopcornReplacement.allocine = function(container, options) { |
|
2324 // console.log("Calling allocine player"); |
|
2325 /* appel du parent pour initialiser les structures communes à tous les players */ |
|
2326 IriSP.PopcornReplacement.player.call(this, container, options); |
|
2327 |
|
2328 var _this = this; |
|
2329 |
|
2330 /* Définition des fonctions de l'API - */ |
|
2331 |
|
2332 this.playerFns = { |
|
2333 play : function() { |
|
2334 return _this.apiCall("play"); |
|
2335 }, |
|
2336 pause : function() { |
|
2337 return _this.apiCall("pause"); |
|
2338 }, |
|
2339 getPosition : function() { |
|
2340 return _this.apiCall("getSeek","return") || 0; |
|
2341 }, |
|
2342 seek : function(pos) { |
|
2343 return _this.apiCall("seek",pos); |
|
2344 }, |
|
2345 getMute : function() { |
|
2346 return _this.apiCall("getMute","return"); |
|
2347 }, |
|
2348 setMute : function(p) { |
|
2349 return _this.apiCall("setMute", p); |
|
2350 } |
|
2351 } |
|
2352 |
|
2353 window.onReady = IriSP.wrap(this, this.ready); |
|
2354 window.onAllocineStateChange = IriSP.wrap(this, this.stateHandler); |
|
2355 window.onTime = IriSP.wrap(this, this.progressHandler); |
|
2356 |
|
2357 var _videoUrl = ( |
|
2358 typeof options.directVideoPath == "string" |
|
2359 ? options.directVideoPath |
|
2360 : IriSP.get_aliased(IriSP.__jsonMetadata["medias"][0], ["href","url"]) |
|
2361 ); |
|
2362 var _flashVars = { |
|
2363 "streamFMS" : true, |
|
2364 "adVast" : false, |
|
2365 "lg" : "fr_cinecast", |
|
2366 "autoPlay" : options.autoPlay, |
|
2367 "directVideoTitle" : "", |
|
2368 "urlAcData" : options.urlAcData, |
|
2369 "directVideoPath" : _videoUrl, |
|
2370 "host" : "http://allocine.fr" |
|
2371 } |
|
2372 |
|
2373 if (typeof IriSP.__jsonMetadata["medias"][0].meta == "object" && typeof IriSP.__jsonMetadata["medias"][0].meta.subtitles == "string") { |
|
2374 _flashVars.subTitlePath = IriSP.__jsonMetadata["medias"][0].meta.subtitles; |
|
2375 } |
|
2376 |
|
2377 |
|
2378 var params = { |
|
2379 "allowScriptAccess" : "always", |
|
2380 "wmode": "opaque", |
|
2381 "flashvars" : IriSP.jQuery.param(_flashVars), |
|
2382 "allowfullscreen" : true |
|
2383 }; |
|
2384 var atts = { |
|
2385 id : this.container |
|
2386 }; |
|
2387 swfobject.embedSWF(options.acPlayerUrl, this.container, options.width, options.height, "10", null, null, params, atts); |
|
2388 |
|
2389 }; |
|
2390 |
|
2391 IriSP.PopcornReplacement.allocine.prototype = new IriSP.PopcornReplacement.player("", {}); |
|
2392 |
|
2393 IriSP.PopcornReplacement.allocine.prototype.ready = function() { |
|
2394 this.player = document.getElementById(this.container); |
|
2395 this.player.addEventListener("onStateChange", "onAllocineStateChange"); |
|
2396 this.player.cueVideoByUrl(this._options.video); |
|
2397 this.callbacks.onReady(); |
|
2398 }; |
|
2399 |
|
2400 IriSP.PopcornReplacement.allocine.prototype.progressHandler = function(progressInfo) { |
|
2401 this.callbacks.onTime({ |
|
2402 position: progressInfo.mediaTime |
|
2403 }); |
|
2404 } |
|
2405 |
|
2406 |
|
2407 IriSP.PopcornReplacement.allocine.prototype.apiCall = function(_method, _arg) { |
|
2408 if (this.player) { |
|
2409 try { |
|
2410 if (typeof _arg == "undefined") { |
|
2411 return this.player.sendToActionScript(_method); |
|
2412 } else { |
|
2413 return this.player.sendToActionScript(_method, _arg); |
|
2414 } |
|
2415 } catch(e) { |
|
2416 console.error('Exception while requesting AcPlayer for "' + _method + (typeof _arg == "undefined" ? '' : '" with argument "' + _arg ) + '"\n', e); |
|
2417 return false; |
|
2418 } |
|
2419 } else { |
|
2420 return false; |
|
2421 } |
|
2422 } |
|
2423 |
|
2424 IriSP.PopcornReplacement.allocine.prototype.stateHandler = function(state) { |
|
2425 console.log("stateHandler"); |
|
2426 switch(state) { |
|
2427 case 1: |
|
2428 this.callbacks.onPlay(); |
|
2429 break; |
|
2430 |
|
2431 case 2: |
|
2432 this.callbacks.onPause(); |
|
2433 break; |
|
2434 |
|
2435 case 3: |
|
2436 this.callbacks.onSeek({ |
|
2437 position: this.player.getCurrentTime() |
|
2438 }); |
|
2439 break; |
|
2440 |
|
2441 /* |
|
2442 case 5: |
|
2443 this.callbacks.onReady(); |
|
2444 break; |
|
2445 */ |
|
2446 } |
|
2447 |
|
2448 };/* To wrap a player the develop should create a new class derived from |
|
2449 the IriSP.PopcornReplacement.player and defining the correct functions */ |
|
2450 |
|
2451 /** jwplayer player wrapper */ |
|
2452 IriSP.PopcornReplacement.dailymotion = function(container, options) { |
|
2453 console.log("Calling"); |
|
2454 /* appel du parent pour initialiser les structures communes à tous les players */ |
|
2455 IriSP.PopcornReplacement.player.call(this, container, options); |
|
2456 |
|
2457 var _this = this; |
|
2458 |
|
2459 /* Définition des fonctions de l'API - */ |
|
2460 |
|
2461 this.playerFns = { |
|
2462 play : function() { |
|
2463 if (_this.player) { |
|
2464 return _this.player.playVideo(); |
|
2465 } else { |
|
2466 return false; |
|
2467 } |
|
2468 }, |
|
2469 pause : function() { |
|
2470 if (_this.player) { |
|
2471 return _this.player.pauseVideo(); |
|
2472 } else { |
|
2473 return false; |
|
2474 } |
|
2475 }, |
|
2476 getPosition : function() { |
|
2477 if (_this.player) { |
|
2478 return _this.player.getCurrentTime(); |
|
2479 } else { |
|
2480 return 0; |
|
2481 } |
|
2482 }, |
|
2483 seek : function(pos) { |
|
2484 if (_this.player) { |
|
2485 return _this.player.seekTo(pos); |
|
2486 } else { |
|
2487 return false; |
|
2488 } |
|
2489 }, |
|
2490 getMute : function() { |
|
2491 if (_this.player) { |
|
2492 return _this.player.isMuted(); |
|
2493 } else { |
|
2494 return false; |
|
2495 } |
|
2496 }, |
|
2497 setMute : function(p) { |
|
2498 if (_this.player) { |
|
2499 if (p) { |
|
2500 _this.player.mute(); |
|
2501 } |
|
2502 else { |
|
2503 _this.player.unMute(); |
|
2504 } |
|
2505 } |
|
2506 }, |
|
2507 getVolume : function() { |
|
2508 if (_this.player) { |
|
2509 return _this.player.getVolume() / 100; |
|
2510 } else { |
|
2511 return false; |
|
2512 } |
|
2513 }, |
|
2514 setVolume : function(p) { |
|
2515 if (_this.player) { |
|
2516 _this.player.setVolume(Math.floor(100 * p)); |
|
2517 } |
|
2518 }, |
|
2519 } |
|
2520 |
|
2521 window.onDailymotionPlayerReady = IriSP.wrap(this, this.ready); |
|
2522 window.onDailymotionStateChange = IriSP.wrap(this, this.stateHandler); |
|
2523 window.onDailymotionVideoProgress = IriSP.wrap(this, this.progressHandler); |
|
2524 |
|
2525 var params = { |
|
2526 "allowScriptAccess" : "always", |
|
2527 "wmode": "opaque" |
|
2528 }; |
|
2529 var atts = { |
|
2530 id : this.container |
|
2531 }; |
|
2532 swfobject.embedSWF("http://www.dailymotion.com/swf?chromeless=1&enableApi=1", this.container, options.width, options.height, "8", null, null, params, atts); |
|
2533 |
|
2534 }; |
|
2535 |
|
2536 IriSP.PopcornReplacement.dailymotion.prototype = new IriSP.PopcornReplacement.player("", {}); |
|
2537 |
|
2538 IriSP.PopcornReplacement.dailymotion.prototype.ready = function() { |
|
2539 |
|
2540 this.player = document.getElementById(this.container); |
|
2541 |
|
2542 this.player.addEventListener("onStateChange", "onDailymotionStateChange"); |
|
2543 this.player.addEventListener("onVideoProgress", "onDailymotionVideoProgress"); |
|
2544 this.player.cueVideoByUrl(this._options.video); |
|
2545 |
|
2546 this.callbacks.onReady(); |
|
2547 }; |
|
2548 |
|
2549 IriSP.PopcornReplacement.dailymotion.prototype.progressHandler = function(progressInfo) { |
|
2550 |
|
2551 this.callbacks.onTime({ |
|
2552 position: progressInfo.mediaTime |
|
2553 }); |
|
2554 } |
|
2555 |
|
2556 IriSP.PopcornReplacement.dailymotion.prototype.stateHandler = function(state) { |
|
2557 |
|
2558 switch(state) { |
|
2559 case 1: |
|
2560 this.callbacks.onPlay(); |
|
2561 break; |
|
2562 |
|
2563 case 2: |
|
2564 this.callbacks.onPause(); |
|
2565 break; |
|
2566 |
|
2567 case 3: |
|
2568 this.callbacks.onSeek({ |
|
2569 position: this.player.getCurrentTime() |
|
2570 }); |
|
2571 break; |
|
2572 |
|
2573 /* |
|
2574 case 5: |
|
2575 this.callbacks.onReady(); |
|
2576 break; |
|
2577 */ |
|
2578 } |
|
2579 |
|
2580 };/* To wrap a player the develop should create a new class derived from |
|
2581 the IriSP.PopcornReplacement.player and defining the correct functions */ |
|
2582 |
|
2583 /** jwplayer player wrapper */ |
|
2584 IriSP.PopcornReplacement.jwplayer = function(container, options) { |
|
2585 /* appel du parent pour initialiser les structures communes à tous les players */ |
|
2586 IriSP.PopcornReplacement.player.call(this, container, options); |
|
2587 |
|
2588 this.media.duration = options.duration; /* optional */ |
|
2589 |
|
2590 /* Définition des fonctions de l'API - */ |
|
2591 this.playerFns = { |
|
2592 play: function() { return jwplayer(this.container).play(); }, |
|
2593 pause: function() { return jwplayer(this.container).pause(); }, |
|
2594 getPosition: function() { return jwplayer(this.container).getPosition(); }, |
|
2595 seek: function(pos) { return jwplayer(this.container).seek(pos); }, |
|
2596 getMute: function() { return jwplayer(this.container).getMute() }, |
|
2597 setMute: function(p) { return jwplayer(this.container).setMute(p); }, |
|
2598 getVolume: function() { return jwplayer(this.container).getVolume() / 100; }, |
|
2599 setVolume: function(p) { return jwplayer(this.container).setVolume(Math.floor(100*p)); } |
|
2600 } |
|
2601 |
|
2602 options.events = this.callbacks; |
|
2603 |
|
2604 jwplayer(this.container).setup(options); |
|
2605 }; |
|
2606 |
|
2607 IriSP.PopcornReplacement.jwplayer.prototype = new IriSP.PopcornReplacement.player("", {}); |
|
2608 /* embed module - listens and relay hash changes to a parent window. */ |
|
2609 |
|
2610 IriSP.EmbedModule = function(Popcorn, config, Serializer) { |
|
2611 IriSP.Module.call(this, Popcorn, config, Serializer); |
|
2612 |
|
2613 window.addEventListener('message', IriSP.wrap(this, this.handleMessages), false); |
|
2614 this._Popcorn.listen("IriSP.Mediafragment.hashchange", IriSP.wrap(this, this.relayChanges)); |
|
2615 }; |
|
2616 |
|
2617 IriSP.EmbedModule.prototype = new IriSP.Module(); |
|
2618 |
|
2619 IriSP.EmbedModule.prototype.handleMessages = function(e) { |
|
2620 if (e.data.type === "hashchange") { |
|
2621 window.location.hash = e.data.value; |
|
2622 } |
|
2623 }; |
|
2624 |
|
2625 IriSP.EmbedModule.prototype.relayChanges = function(newHash) { |
|
2626 window.parent.postMessage({type: "hashchange", value: newHash}, "*"); |
|
2627 return; |
|
2628 };/* mediafragment module */ |
|
2629 |
|
2630 IriSP.MediaFragment = function(Popcorn, config, Serializer) { |
|
2631 IriSP.Module.call(this, Popcorn, config, Serializer); |
|
2632 |
|
2633 this.mutex = false; /* a mutex because we access the url from two different functions */ |
|
2634 |
|
2635 this._Popcorn.listen( "loadedmetadata", IriSP.wrap(this,this.advanceTime)); |
|
2636 this._Popcorn.listen( "pause", IriSP.wrap(this,this.updateTime)); |
|
2637 this._Popcorn.listen( "seeked", IriSP.wrap(this,this.updateTime)); |
|
2638 this._Popcorn.listen( "IriSP.PolemicTweet.click", IriSP.wrap(this,this.updateAnnotation)); |
|
2639 this._Popcorn.listen( "IriSP.SegmentsWidget.click", IriSP.wrap(this,this.updateAnnotation)); |
|
2640 |
|
2641 window.onhashchange = IriSP.wrap(this, this.advanceTime); |
|
2642 }; |
|
2643 |
|
2644 IriSP.MediaFragment.prototype = new IriSP.Module(); |
|
2645 |
|
2646 IriSP.MediaFragment.prototype.advanceTime = function() { |
|
2647 var url = window.location.href; |
|
2648 |
|
2649 if ( url.split( "#" )[ 1 ] != null ) { |
|
2650 pageoffset = url.split( "#" )[1]; |
|
2651 |
|
2652 if ( pageoffset.substring(0, 2) === "t=") { |
|
2653 // timecode |
|
2654 if ( pageoffset.substring( 2 ) != null ) { |
|
2655 var offsettime = pageoffset.substring( 2 ); |
|
2656 this._Popcorn.currentTime( parseFloat(offsettime) ); |
|
2657 |
|
2658 /* we have to trigger this signal manually because of a |
|
2659 bug in the jwplayer */ |
|
2660 this._Popcorn.trigger("seeked", parseFloat(offsettime)); |
|
2661 } |
|
2662 } else if ( pageoffset.substring(0, 3) === "id=") { |
|
2663 // annotation |
|
2664 var annotationId = pageoffset.substring( 3 ); |
|
2665 // there's no better way than that because |
|
2666 // of possible race conditions |
|
2667 this._serializer.sync(IriSP.wrap(this, function() { |
|
2668 this.lookupAnnotation.call(this, annotationId); |
|
2669 })); |
|
2670 } |
|
2671 } |
|
2672 }; |
|
2673 |
|
2674 /** handler for the seeked signal. It may have or may have not an argument. |
|
2675 @param time if not undefined, the time we're seeking to |
|
2676 */ |
|
2677 IriSP.MediaFragment.prototype.updateTime = function(time) { |
|
2678 if (this.mutex === true) { |
|
2679 return; |
|
2680 } |
|
2681 |
|
2682 var history = window.history; |
|
2683 if ( !history.pushState ) { |
|
2684 return false; |
|
2685 } |
|
2686 |
|
2687 if (IriSP.null_or_undefined(time) || typeof(time) != "number") { |
|
2688 var ntime = this._Popcorn.currentTime().toFixed(2) |
|
2689 } else { |
|
2690 var ntime = time.toFixed(2); |
|
2691 } |
|
2692 |
|
2693 // used to relay the new hash to the embedder |
|
2694 this._Popcorn.trigger("IriSP.Mediafragment.hashchange", "#t=" + ntime); |
|
2695 |
|
2696 splitArr = window.location.href.split( "#" ) |
|
2697 history.replaceState( {}, "", splitArr[0] + "#t=" + ntime ); |
|
2698 }; |
|
2699 |
|
2700 |
|
2701 IriSP.MediaFragment.prototype.updateAnnotation = function(annotationId) { |
|
2702 var _this = this; |
|
2703 this.mutex = true; |
|
2704 |
|
2705 var history = window.history; |
|
2706 if ( !history.pushState ) { |
|
2707 return false; |
|
2708 } |
|
2709 |
|
2710 |
|
2711 // used to relay the new hash to the embedder |
|
2712 this._Popcorn.trigger("IriSP.Mediafragment.hashchange", "#id=" + annotationId); |
|
2713 |
|
2714 splitArr = window.location.href.split( "#" ) |
|
2715 history.replaceState( {}, "", splitArr[0] + "#id=" + annotationId); |
|
2716 |
|
2717 |
|
2718 // reset the mutex afterwards to prevent the module from reacting to his own changes. |
|
2719 window.setTimeout(function() { _this.mutex = false }, 50); |
|
2720 }; |
|
2721 |
|
2722 // lookup and seek to the beginning of an annotation |
|
2723 IriSP.MediaFragment.prototype.lookupAnnotation = function(annotationId) { |
|
2724 var _this = this; |
|
2725 this.mutex = true; |
|
2726 |
|
2727 var annotation = undefined; |
|
2728 var annotations = this._serializer._data.annotations; |
|
2729 |
|
2730 var i; |
|
2731 for (i = 0; i < annotations.length; i++) { |
|
2732 if (annotations[i].id === annotationId) { |
|
2733 annotation = annotations[i]; |
|
2734 break; |
|
2735 } |
|
2736 } |
|
2737 |
|
2738 if (typeof(annotation) !== "undefined") { |
|
2739 this._Popcorn.currentTime(annotation.begin / 1000); |
|
2740 |
|
2741 /* we have to trigger this signal manually because of a |
|
2742 bug in the jwplayer */ |
|
2743 this._Popcorn.trigger("seeked", annotation.begin / 1000); |
|
2744 this._Popcorn.trigger("IriSP.Mediafragment.showAnnotation", annotationId); |
|
2745 } |
|
2746 |
|
2747 window.setTimeout(function() { _this.mutex = false }, 50); |
|
2748 }; |
|
2749 IriSP.AnnotationsListWidget = function(Popcorn, config, Serializer) { |
|
2750 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
2751 this.__counter = 0; |
|
2752 this.__oldList = []; |
|
2753 this.searchRe = null; |
|
2754 this._ajax_cache = []; |
|
2755 var _this = this; |
|
2756 |
|
2757 this._Popcorn.listen("IriSP.search", function(searchString) { |
|
2758 _this.searchHandler(searchString); |
|
2759 }); |
|
2760 this._Popcorn.listen("IriSP.search.closed", function() { |
|
2761 _this.searchHandler(false); |
|
2762 }); |
|
2763 this._Popcorn.listen("IriSP.search.cleared", function() { |
|
2764 _this.searchHandler(false); |
|
2765 }); |
|
2766 }; |
|
2767 |
|
2768 |
|
2769 IriSP.AnnotationsListWidget.prototype = new IriSP.Widget(); |
|
2770 |
|
2771 IriSP.AnnotationsListWidget.prototype.clear = function() { |
|
2772 }; |
|
2773 |
|
2774 IriSP.AnnotationsListWidget.prototype.clearWidget = function() { |
|
2775 }; |
|
2776 |
|
2777 IriSP.AnnotationsListWidget.prototype.searchHandler = function(searchString) { |
|
2778 this.searchRe = (searchString && searchString.length) ? IriSP.regexpFromText(searchString) : null; |
|
2779 if (this.ajax_mode && !this.cinecast_version) { |
|
2780 var _this = this, |
|
2781 _annotations = ( |
|
2782 this.searchRe === null |
|
2783 ? this._ajax_cache |
|
2784 : IriSP.underscore.filter(this._ajax_cache, function(_a) { |
|
2785 return (_this.searchRe.test(_a.desc) || _this.searchRe.test(_a.title)); |
|
2786 }) |
|
2787 ); |
|
2788 this.do_redraw(_annotations); |
|
2789 if (_annotations.length) { |
|
2790 this._Popcorn.trigger("IriSP.search.matchFound"); |
|
2791 } else { |
|
2792 this._Popcorn.trigger("IriSP.search.noMatchFound"); |
|
2793 } |
|
2794 } else { |
|
2795 this.drawList(); |
|
2796 } |
|
2797 } |
|
2798 |
|
2799 /** effectively redraw the widget - called by drawList */ |
|
2800 IriSP.AnnotationsListWidget.prototype.do_redraw = function(list) { |
|
2801 var _html = IriSP.templToHTML( |
|
2802 IriSP.annotationsListWidget_template, { |
|
2803 annotations: list |
|
2804 }), |
|
2805 _this = this; |
|
2806 |
|
2807 this.selector.html(_html); |
|
2808 |
|
2809 this.selector.find('.Ldt-AnnotationsList-Tag-Li').click(function() { |
|
2810 _this._Popcorn.trigger("IriSP.search.triggeredSearch", IriSP.jQuery(this).text().trim()); |
|
2811 }) |
|
2812 |
|
2813 if (this.searchRe !== null) { |
|
2814 this.selector.find(".Ldt-AnnotationsList-Title a, .Ldt-AnnotationsList-Description") |
|
2815 .each(function() { |
|
2816 var _$ = IriSP.jQuery(this); |
|
2817 _$.html(_$.text().trim().replace(_this.searchRe, '<span class="Ldt-AnnotationsList-highlight">$1</span>')) |
|
2818 }) |
|
2819 } |
|
2820 }; |
|
2821 |
|
2822 IriSP.AnnotationsListWidget.prototype.transformAnnotation = function(a) { |
|
2823 var _this = this; |
|
2824 return { |
|
2825 "id" : a.id, |
|
2826 "title": this.cinecast_version ? IriSP.get_aliased(a.meta, ['creator_name', 'creator']) : a.content.title, |
|
2827 "desc" : this.cinecast_version ? a.content.data : a.content.description, |
|
2828 "begin": IriSP.msToTime(a.begin), |
|
2829 "end" : IriSP.msToTime(a.end), |
|
2830 "thumbnail" : (typeof a.meta == "object" && typeof a.meta.thumbnail == "string") ? a.meta.thumbnail : this.default_thumbnail, |
|
2831 "url" : (typeof a.meta == "object" && typeof a.meta.url == "string") ? a.meta.url : null, |
|
2832 "created_at" :(typeof a.meta == "object" && typeof a.meta.created == "string") ? Date.parse(a.meta.created.replace(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}\:\d{2}\:\d{2}).*$/,"$2/$3/$1 $4 UTC+0000")) : null, |
|
2833 "tags": typeof a.tags == "object" |
|
2834 ? IriSP.underscore(a.tags) |
|
2835 .chain() |
|
2836 .map(function(_t) { |
|
2837 if (typeof _t == "string") { |
|
2838 return _t.replace(/^.*:/,'#'); |
|
2839 } else { |
|
2840 if (typeof _t['id-ref'] != "undefined") { |
|
2841 var _f = IriSP.underscore.find(_this._serializer._data.tags, function(_tag) { |
|
2842 return _tag.id == _t['id-ref']; |
|
2843 }); |
|
2844 if (typeof _f != "undefined") { |
|
2845 return IriSP.get_aliased(_f.meta, ['dc:title', 'title']); |
|
2846 } |
|
2847 } |
|
2848 } |
|
2849 return null; |
|
2850 }) |
|
2851 .filter(function(_t) { |
|
2852 return _t !== null && _t !== "" |
|
2853 }) |
|
2854 .value() |
|
2855 : [] |
|
2856 } |
|
2857 } |
|
2858 |
|
2859 /** draw the annotation list */ |
|
2860 IriSP.AnnotationsListWidget.prototype.drawList = function(force_redraw) { |
|
2861 var _this = this; |
|
2862 |
|
2863 // var view_type = this._serializer.getContributions(); |
|
2864 var annotations = this._serializer._data.annotations; |
|
2865 var currentTime = this._Popcorn.currentTime(); |
|
2866 var list = []; |
|
2867 |
|
2868 /* if (typeof(view_type) === "undefined") { |
|
2869 return; |
|
2870 } */ |
|
2871 for (i = 0; i < annotations.length; i++) { |
|
2872 var obj = this.transformAnnotation(annotations[i]); |
|
2873 obj.iterator = i; |
|
2874 obj.distance = Math.abs((annotations[i].end + annotations[i].begin) / 2000 - currentTime); |
|
2875 if (!this.cinecast_version || annotations[i].type == "cinecast:UserAnnotation") { |
|
2876 list.push(obj); |
|
2877 } |
|
2878 |
|
2879 } |
|
2880 |
|
2881 if (this.searchRe !== null) { |
|
2882 list = list.filter(function(_a) { |
|
2883 return (_this.searchRe.test(_a.desc) || _this.searchRe.test(_a.title)); |
|
2884 }); |
|
2885 if (list.length) { |
|
2886 this._Popcorn.trigger("IriSP.search.matchFound"); |
|
2887 } else { |
|
2888 this._Popcorn.trigger("IriSP.search.noMatchFound"); |
|
2889 } |
|
2890 } |
|
2891 list = IriSP.underscore(list) |
|
2892 .chain() |
|
2893 .sortBy(function(_o) { |
|
2894 return _o.distance; |
|
2895 }) |
|
2896 .first(10) |
|
2897 .sortBy(function(_o) { |
|
2898 return (_this.cinecast_version ? - _o.created_at : _o.iterator); |
|
2899 }) |
|
2900 .value(); |
|
2901 var idList = IriSP.underscore.pluck(list, "id").sort(); |
|
2902 |
|
2903 if (!IriSP.underscore.isEqual(this.__oldList, idList) || this.lastSearch !== this.searchRe || typeof(force_redraw) !== "undefined") { |
|
2904 this.do_redraw(list); |
|
2905 this.__oldList = idList; |
|
2906 this.lastSearch = this.searchRe; |
|
2907 } |
|
2908 /* save for next call */ |
|
2909 |
|
2910 |
|
2911 }; |
|
2912 |
|
2913 IriSP.AnnotationsListWidget.prototype.ajaxRedraw = function(timecode) { |
|
2914 |
|
2915 /* the seeked signal sometimes passes an argument - depending on if we're using |
|
2916 our popcorn lookalike or the real thing - if it's the case, use it as it's |
|
2917 more precise than currentTime which sometimes contains the place we where at */ |
|
2918 if (IriSP.null_or_undefined(timecode) || typeof(timecode) != "number") { |
|
2919 var tcode = this._Popcorn.currentTime(); |
|
2920 } else { |
|
2921 var tcode = timecode; |
|
2922 } |
|
2923 |
|
2924 |
|
2925 /* the platform gives us a special url - of the type : http://path/{{media}}/{{begin}}/{{end}} |
|
2926 we double the braces using regexps and we feed it to mustache to build the correct url |
|
2927 we have to do that because the platform only knows at run time what view it's displaying. |
|
2928 */ |
|
2929 |
|
2930 var media_id = this.currentMedia()["id"]; |
|
2931 var duration = this.getDuration(); |
|
2932 |
|
2933 var begin_timecode = (Math.floor(tcode) - 300) * 1000; |
|
2934 if (begin_timecode < 0) |
|
2935 begin_timecode = 0; |
|
2936 |
|
2937 var end_timecode = (Math.floor(tcode) + 300) * 1000; |
|
2938 if (end_timecode > duration) |
|
2939 end_timecode = duration; |
|
2940 |
|
2941 var templ = Mustache.to_html(this.ajax_url, {media: media_id, begin: begin_timecode, |
|
2942 end: end_timecode}); |
|
2943 |
|
2944 /* we create on the fly a serializer to get the ajax */ |
|
2945 var serializer = new IriSP.JSONSerializer(IriSP.__dataloader, templ); |
|
2946 serializer.sync(IriSP.wrap(this, function(json) { this.processJson(json, serializer) })); |
|
2947 }; |
|
2948 |
|
2949 /** process the received json - it's a bit hackish */ |
|
2950 IriSP.AnnotationsListWidget.prototype.processJson = function(json, serializer) { |
|
2951 /* FIXME: DRY the whole thing */ |
|
2952 var annotations = serializer._data.annotations; |
|
2953 if (IriSP.null_or_undefined(annotations)) |
|
2954 return; |
|
2955 |
|
2956 /* |
|
2957 commented in case we wanted to discriminate against some annotation types. |
|
2958 var view_types = serializer.getIds("Contributions"); |
|
2959 */ |
|
2960 var l = []; |
|
2961 |
|
2962 var media = this.currentMedia()["id"]; |
|
2963 |
|
2964 for (i = 0; i < annotations.length; i++) { |
|
2965 var obj = this.transformAnnotation(annotations[i]) |
|
2966 if (typeof obj.url == "undefined" || !obj.url) { |
|
2967 /* only if the annotation isn't present in the document create an |
|
2968 external link */ |
|
2969 if (this.annotations_ids.indexOf(obj.id.toLowerCase()) == -1) { |
|
2970 // braindead url; jacques didn't want to create a new one in the platform, |
|
2971 // so we append the cutting id to the url. |
|
2972 obj.url = this.project_url + "/" + media + "/" + |
|
2973 annotations[i].meta.project + "/" + |
|
2974 annotations[i].meta["id-ref"] + '#id=' + annotations[i].id; |
|
2975 |
|
2976 // obj.url = document.location.href.split("#")[0] + "/" + annotation.meta.project; |
|
2977 } |
|
2978 } |
|
2979 l.push(obj); |
|
2980 } |
|
2981 this._ajax_cache = l; |
|
2982 this.do_redraw(l); |
|
2983 }; |
|
2984 IriSP.AnnotationsListWidget.prototype.draw = function() { |
|
2985 |
|
2986 /* build a table of the annotations present in the document for faster |
|
2987 lookup |
|
2988 */ |
|
2989 this.annotations_ids = IriSP.underscore(this._serializer._data.annotations).map(function(_a) { |
|
2990 return _a.id.toLowerCase(); |
|
2991 }); |
|
2992 |
|
2993 var _this = this; |
|
2994 |
|
2995 if (!this.ajax_mode || this.cinecast_version) { |
|
2996 var _throttled = IriSP.underscore.throttle(function() { |
|
2997 _this.drawList(); |
|
2998 }, 1500); |
|
2999 _throttled(); |
|
3000 this._Popcorn.listen("IriSP.createAnnotationWidget.addedAnnotation", _throttled); |
|
3001 this._Popcorn.listen("timeupdate", _throttled); |
|
3002 if (this.cinecast_version) { |
|
3003 window.setInterval(function() { |
|
3004 var _tmpSerializer = new IriSP.JSONSerializer(IriSP.__dataloader, _this._config.metadata.src, true); |
|
3005 _tmpSerializer.sync(function(json) { |
|
3006 _this.annotations_ids = IriSP.underscore(_this._serializer._data.annotations).map(function(_a) { |
|
3007 return _a.id.toLowerCase(); |
|
3008 }); |
|
3009 IriSP.underscore(json.annotations).each(function(_a) { |
|
3010 var _j = _this.annotations_ids.indexOf(_a.id); |
|
3011 if (_j == -1) { |
|
3012 _this._serializer._data.annotations.push(_a); |
|
3013 _this.annotations_ids.push(_a.id); |
|
3014 } else { |
|
3015 _this._serializer._data.annotations[_j] = _a; |
|
3016 } |
|
3017 _throttled(); |
|
3018 }); |
|
3019 }, true); // true is for force_refresh |
|
3020 },this.refresh_interval); |
|
3021 } |
|
3022 } else { |
|
3023 /* update the widget when the video has finished loading and when it's seeked and paused */ |
|
3024 this._Popcorn.listen("seeked", IriSP.wrap(this, this.ajaxRedraw)); |
|
3025 this._Popcorn.listen("loadedmetadata", IriSP.wrap(this, this.ajaxRedraw)); |
|
3026 this._Popcorn.listen("paused", IriSP.wrap(this, this.ajaxRedraw)); |
|
3027 |
|
3028 this._Popcorn.listen("IriSP.createAnnotationWidget.addedAnnotation", IriSP.wrap(this, this.ajaxRedraw)); |
|
3029 } |
|
3030 |
|
3031 };/* Internationalization for this widget */ |
|
3032 |
|
3033 IriSP.i18n.addMessages( |
|
3034 { |
|
3035 "fr": { |
|
3036 "keywords": "Mots-clés" |
|
3037 }, |
|
3038 "en": { |
|
3039 "keywords": "Keywords" |
|
3040 } |
|
3041 } |
|
3042 ); |
|
3043 |
|
3044 IriSP.AnnotationsWidget = function(Popcorn, config, Serializer) { |
|
3045 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
3046 /* flag used when we're creating an annotation */ |
|
3047 this._hidden = false; |
|
3048 }; |
|
3049 |
|
3050 |
|
3051 IriSP.AnnotationsWidget.prototype = new IriSP.Widget(); |
|
3052 |
|
3053 IriSP.AnnotationsWidget.prototype.clear = function() { |
|
3054 this.selector.find(".Ldt-SaTitle").text(""); |
|
3055 this.selector.find(".Ldt-SaDescription").text(""); |
|
3056 this.selector.find(".Ldt-SaKeywordText").text(""); |
|
3057 }; |
|
3058 |
|
3059 IriSP.AnnotationsWidget.prototype.displayAnnotation = function(annotation) { |
|
3060 var title = annotation.content.title; |
|
3061 var description = annotation.content.description; |
|
3062 var keywords = ""; |
|
3063 var begin = +annotation.begin / 1000; |
|
3064 var end = +annotation.end / 1000; |
|
3065 var duration = this.getDuration(); |
|
3066 var tags = ""; |
|
3067 |
|
3068 var title_templ = "{{title}} - ( {{begin}} - {{end}} )"; |
|
3069 var endstr = Mustache.to_html(title_templ, {title: title, begin: IriSP.secondsToTime(begin), end: IriSP.secondsToTime(end)}); |
|
3070 |
|
3071 this.selector.find(".Ldt-SaTitle").text(endstr); |
|
3072 this.selector.find(".Ldt-SaDescription").text(description); |
|
3073 |
|
3074 |
|
3075 if (!IriSP.null_or_undefined(annotation.tags) && !IriSP.null_or_undefined(this._serializer._data.tags)) { |
|
3076 /* save the tag id and keywords in a unique structure */ |
|
3077 var tag_list = {}; |
|
3078 for (var i = 0; i < this._serializer._data.tags.length; i++) { |
|
3079 var id = this._serializer._data.tags[i]["id"]; |
|
3080 var keyword = IriSP.get_aliased(this._serializer._data.tags[i]["meta"], ["dc:title", "title"]); |
|
3081 |
|
3082 tag_list[id] = keyword; |
|
3083 } |
|
3084 |
|
3085 /* then browse the list of defined tags for the current annotation */ |
|
3086 for (var i = 0; i < annotation.tags.length; i++) { |
|
3087 if (tag_list.hasOwnProperty(annotation.tags[i]["id-ref"])) |
|
3088 tags += tag_list[annotation.tags[i]["id-ref"]] + ", "; |
|
3089 } |
|
3090 } |
|
3091 |
|
3092 tags = IriSP.i18n.getMessage("keywords") + ": " + tags.slice(0, tags.length - 2); |
|
3093 |
|
3094 this.selector.find(".Ldt-SaKeywords").text(tags); |
|
3095 |
|
3096 // update sharing buttons |
|
3097 var url = document.location.href + "#id=" + annotation.id; |
|
3098 this.selector.find(".Ldt-fbShare").attr("href", IriSP.mkFbUrl(url, this.share_text)); |
|
3099 this.selector.find(".Ldt-TwShare").attr("href", IriSP.mkTweetUrl(url, this.share_text)); |
|
3100 this.selector.find(".Ldt-GplusShare").attr("href", IriSP.mkGplusUrl(url, this.share_text)); |
|
3101 }; |
|
3102 |
|
3103 IriSP.AnnotationsWidget.prototype.clearWidget = function() { |
|
3104 /* retract the pane between two annotations */ |
|
3105 this.selector.find(".Ldt-SaTitle").text(""); |
|
3106 this.selector.find(".Ldt-SaDescription").text(""); |
|
3107 this.selector.find(".Ldt-SaKeywordText").html(""); |
|
3108 this.selector.find(".Ldt-ShowAnnotation").slideUp(); |
|
3109 }; |
|
3110 |
|
3111 IriSP.AnnotationsWidget.prototype.draw = function() { |
|
3112 var _this = this; |
|
3113 |
|
3114 var annotationMarkup = IriSP.templToHTML(IriSP.annotationWidget_template); |
|
3115 this.selector.append(annotationMarkup); |
|
3116 |
|
3117 this._Popcorn.listen("IriSP.AnnotationsWidget.show", |
|
3118 IriSP.wrap(this, this.show)); |
|
3119 this._Popcorn.listen("IriSP.AnnotationsWidget.hide", |
|
3120 IriSP.wrap(this, this.hide)); |
|
3121 |
|
3122 var legal_ids = []; |
|
3123 if (typeof(this._serializer.getChapitrage()) !== "undefined") |
|
3124 legal_ids.push(this._serializer.getChapitrage()); |
|
3125 else |
|
3126 legal_ids = this._serializer.getNonTweetIds(); |
|
3127 |
|
3128 var annotations = this._serializer._data.annotations; |
|
3129 var i; |
|
3130 |
|
3131 for (i in annotations) { |
|
3132 var annotation = annotations[i]; |
|
3133 var begin = Math.round((+ annotation.begin) / 1000); |
|
3134 var end = Math.round((+ annotation.end) / 1000); |
|
3135 |
|
3136 if (typeof(annotation.meta) !== "undefined" && typeof(annotation.meta["id-ref"]) !== "undefined" |
|
3137 && !IriSP.underscore.include(legal_ids, annotation.meta["id-ref"])) { |
|
3138 continue; |
|
3139 } |
|
3140 |
|
3141 |
|
3142 var conf = {start: begin, end: end, |
|
3143 onStart: |
|
3144 function(annotation) { |
|
3145 return function() { |
|
3146 _this.displayAnnotation(annotation); |
|
3147 |
|
3148 } }(annotation), |
|
3149 onEnd: |
|
3150 function() { _this.clearWidget.call(_this); } |
|
3151 }; |
|
3152 this._Popcorn = this._Popcorn.code(conf); |
|
3153 } |
|
3154 |
|
3155 }; |
|
3156 |
|
3157 IriSP.AnnotationsWidget.prototype.hide = function() { |
|
3158 if (this._hidden == false) { |
|
3159 this.selector.hide(); |
|
3160 this._hidden = true; |
|
3161 } |
|
3162 }; |
|
3163 |
|
3164 IriSP.AnnotationsWidget.prototype.show = function() { |
|
3165 if (this._hidden == true) { |
|
3166 this.selector.show(); |
|
3167 this._hidden = false; |
|
3168 } |
|
3169 };IriSP.ArrowWidget = function(Popcorn, config, Serializer) { |
|
3170 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
3171 |
|
3172 this._oldAnnotation = null; |
|
3173 this._blockArrow = false; |
|
3174 }; |
|
3175 |
|
3176 |
|
3177 IriSP.ArrowWidget.prototype = new IriSP.Widget(); |
|
3178 |
|
3179 IriSP.ArrowWidget.prototype.clear = function() { |
|
3180 |
|
3181 }; |
|
3182 |
|
3183 IriSP.ArrowWidget.prototype.clearWidget = function() { |
|
3184 }; |
|
3185 |
|
3186 IriSP.ArrowWidget.prototype.draw = function() { |
|
3187 var templ = Mustache.to_html(IriSP.arrowWidget_template, {}); |
|
3188 this.selector.append(templ); |
|
3189 this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeUpdateHandler)); |
|
3190 this._Popcorn.listen("IriSP.ArrowWidget.blockArrow", IriSP.wrap(this, this.blockArrow)); |
|
3191 this._Popcorn.listen("IriSP.ArrowWidget.releaseArrow", IriSP.wrap(this, this.releaseArrow)); |
|
3192 |
|
3193 }; |
|
3194 |
|
3195 IriSP.ArrowWidget.prototype.timeUpdateHandler = function(percents) { |
|
3196 if (this._blockArrow) |
|
3197 return; |
|
3198 |
|
3199 var currentTime = this._Popcorn.currentTime(); |
|
3200 var currentAnnotation = this._serializer.currentChapitre(currentTime); |
|
3201 if (IriSP.null_or_undefined(currentAnnotation)) { |
|
3202 var c_annots = this._serializer.currentAnnotation(currentTime) |
|
3203 if (c_annots.length != 0) |
|
3204 var currentAnnotation = c_annots[0]; // FIXME : use the others ? |
|
3205 else |
|
3206 return; |
|
3207 } |
|
3208 |
|
3209 /* move the arrow only if the current annotation changes */ |
|
3210 if (currentAnnotation != this._oldAnnotation) { |
|
3211 var begin = (+ currentAnnotation.begin) / 1000; |
|
3212 var end = (+ currentAnnotation.end) / 1000; |
|
3213 |
|
3214 var duration = this.getDuration() / 1000; |
|
3215 var middle_time = (begin + end) / 2; |
|
3216 var percents = middle_time / duration; |
|
3217 |
|
3218 // we need to apply a fix because the arrow has a certain length |
|
3219 // it's half the length of the arrow (27 / 2). We need to convert |
|
3220 // it in percents though. |
|
3221 var totalWidth = this.selector.width(); |
|
3222 var pixels = percents * totalWidth; |
|
3223 var correction = (27 / 2); |
|
3224 var corrected_pixels = pixels - correction; |
|
3225 |
|
3226 /* make sure that the arrow is aligned with the pattern |
|
3227 of the widget under it */ |
|
3228 if (corrected_pixels % 3 != 0) |
|
3229 corrected_pixels -= (corrected_pixels % 3 - 1); |
|
3230 |
|
3231 /* don't move out of the screen */ |
|
3232 if (corrected_pixels <= 0) |
|
3233 corrected_pixels = 0; |
|
3234 |
|
3235 if (corrected_pixels <= 15) { |
|
3236 this.selector.children(".Ldt-arrowWidget").removeClass("Ldt-arrowLeftEdge Ldt-arrowCenter Ldt-arrowRightEdge") |
|
3237 .addClass("Ldt-arrowLeftEdge"); |
|
3238 } else if (corrected_pixels >= totalWidth - 25) { |
|
3239 this.selector.children(".Ldt-arrowWidget").removeClass("Ldt-arrowLeftEdge Ldt-arrowCenter Ldt-arrowRightEdge") |
|
3240 .addClass("Ldt-arrowRightEdge"); |
|
3241 } else { |
|
3242 this.selector.children(".Ldt-arrowWidget").removeClass("Ldt-arrowLeftEdge Ldt-arrowCenter Ldt-arrowRightEdge") |
|
3243 .addClass("Ldt-arrowCenter"); |
|
3244 } |
|
3245 |
|
3246 this.selector.children(".Ldt-arrowWidget").animate({"left" : corrected_pixels + "px"}); |
|
3247 |
|
3248 this._oldAnnotation = currentAnnotation; |
|
3249 } |
|
3250 }; |
|
3251 |
|
3252 /** Block the arrow for instance when the user is annotating */ |
|
3253 IriSP.ArrowWidget.prototype.blockArrow = function() { |
|
3254 this._blockArrow = true; |
|
3255 }; |
|
3256 |
|
3257 IriSP.ArrowWidget.prototype.releaseArrow = function() { |
|
3258 this._blockArrow = false; |
|
3259 }; |
|
3260 /* Internationalization for this widget */ |
|
3261 |
|
3262 IriSP.i18n.addMessages( |
|
3263 { |
|
3264 "en": { |
|
3265 "submit": "Submit", |
|
3266 "add_keywords": "Add keywords", |
|
3267 "add_polemic_keywords": "Add polemic keywords", |
|
3268 "your_name": "Your name", |
|
3269 "type_here": "Type your annotation here.", |
|
3270 "wait_while_processed": "Please wait while your request is being processed...", |
|
3271 "error_while_contacting": "An error happened while contacting the server. Your annotation has not been saved.", |
|
3272 "empty_annotation": "Your annotation is empty. Please write something before submitting.", |
|
3273 "annotation_saved": "Thank you, your annotation has been saved.", |
|
3274 "share_annotation": "Would you like to share it on social networks ?", |
|
3275 "share_on": "Share on", |
|
3276 "more_tags": "More tags" |
|
3277 }, |
|
3278 "fr": { |
|
3279 "submit": "Envoyer", |
|
3280 "add_keywords": "Ajouter des mots-clés", |
|
3281 "add_polemic_keywords": "Ajouter des mots-clés polémiques", |
|
3282 "your_name": "Votre nom", |
|
3283 "type_here": "Rédigez votre annotation ici.", |
|
3284 "wait_while_processed": "Veuillez patienter pendant le traitement de votre requête...", |
|
3285 "error_while_contacting": "Une erreur s'est produite en contactant le serveur. Votre annotation n'a pas été enregistrée", |
|
3286 "empty_annotation": "Votre annotation est vide. Merci de rédiger un texte avant de l'envoyer.", |
|
3287 "annotation_saved": "Merci, votre annotation a été enregistrée.", |
|
3288 "share_annotation": "Souhaitez-vous la partager sur les réseaux sociaux ?", |
|
3289 "share_on": "Partager sur", |
|
3290 "more_tags": "Plus de mots-clés" |
|
3291 } |
|
3292 } |
|
3293 ); |
|
3294 |
|
3295 IriSP.createAnnotationWidget = function(Popcorn, config, Serializer) { |
|
3296 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
3297 this._hidden = true; |
|
3298 |
|
3299 if (!IriSP.null_or_undefined(IriSP.user)) { |
|
3300 if (!IriSP.null_or_undefined(IriSP.user.avatar)) { |
|
3301 this.user_avatar = IriSP.user.avatar; |
|
3302 } |
|
3303 if (!IriSP.null_or_undefined(IriSP.user.name)) { |
|
3304 this.user_name = IriSP.user.name; |
|
3305 } |
|
3306 } |
|
3307 |
|
3308 /* variables to save the current position of the slicer */ |
|
3309 if (this.cinecast_version) { |
|
3310 this.sliceLeft = 0; |
|
3311 this.sliceWidth = 0; |
|
3312 } |
|
3313 }; |
|
3314 |
|
3315 |
|
3316 IriSP.createAnnotationWidget.prototype = new IriSP.Widget(); |
|
3317 |
|
3318 IriSP.createAnnotationWidget.prototype.clear = function() { |
|
3319 this.selector.find(".Ldt-SaTitle").text(""); |
|
3320 this.selector.find(".Ldt-SaDescription").text(""); |
|
3321 this.selector.find(".Ldt-SaKeywordText").text(""); |
|
3322 }; |
|
3323 |
|
3324 IriSP.createAnnotationWidget.prototype.draw = function() { |
|
3325 var _this = this; |
|
3326 if (typeof this.remote_tags == "object") { |
|
3327 IriSP.jQuery.getJSON((typeof this.remote_tags.alias == "string" ? this.remote_tags.alias : this.remote_tags.url), function(_json) { |
|
3328 _this.tags = _json.tags; |
|
3329 _this.drawCallback(); |
|
3330 }); |
|
3331 } else { |
|
3332 this.drawCallback(); |
|
3333 } |
|
3334 } |
|
3335 |
|
3336 IriSP.createAnnotationWidget.prototype.drawCallback = function() { |
|
3337 var _this = this; |
|
3338 |
|
3339 var annotationMarkup = IriSP.templToHTML(IriSP.createAnnotationWidget_template, |
|
3340 this); |
|
3341 |
|
3342 this.selector.append(annotationMarkup); |
|
3343 |
|
3344 if (!this.cinecast_version) |
|
3345 this.selector.hide(); |
|
3346 else { |
|
3347 this.showStartScreen(); |
|
3348 } |
|
3349 |
|
3350 if (this.random_tags) { |
|
3351 this.selector.find(".Ldt-createAnnotation-keywords li").hide(); |
|
3352 this.showMoreTags(); |
|
3353 this.selector.find('.Ldt-createAnnotation-moar-keywordz').click(function() { |
|
3354 _this.showMoreTags(); |
|
3355 }) |
|
3356 } |
|
3357 // Add onclick event to both polemic and keywords buttons |
|
3358 |
|
3359 this.selector.find(".Ldt-createAnnotation-keyword-button, .Ldt-createAnnotation-polemic-button").click(function() { |
|
3360 _this.addKeyword(IriSP.jQuery(this).text()); |
|
3361 return false; |
|
3362 }); |
|
3363 |
|
3364 // js_mod is a custom event because there's no simple way to test for a js |
|
3365 // change in a textfield. |
|
3366 this.selector.find(".Ldt-createAnnotation-Description") |
|
3367 .bind("propertychange keyup input paste click js_mod", IriSP.wrap(this, this.handleTextChanges)); |
|
3368 |
|
3369 /* the cinecast version of the player is supposed to pause when the user clicks on the button */ |
|
3370 |
|
3371 /* the cinecast version expects the user to comment on a defined segment. |
|
3372 As the widget is always shown, we need a way to update it's content as |
|
3373 time passes. We do this like we did with the annotationsWidget : we schedule |
|
3374 a .code start function which will be called at the right time. |
|
3375 */ |
|
3376 if (this.cinecast_version) { |
|
3377 var legal_ids; |
|
3378 if (typeof(this._serializer.getChapitrage()) !== "undefined") |
|
3379 legal_id = this._serializer.getChapitrage(); |
|
3380 else |
|
3381 legal_id = this._serializer.getNonTweetIds()[0]; |
|
3382 |
|
3383 var annotations = this._serializer._data.annotations; |
|
3384 var i; |
|
3385 |
|
3386 for (i in annotations) { |
|
3387 var annotation = annotations[i]; |
|
3388 if (typeof(annotation.meta) !== "undefined" && typeof(annotation.meta["id-ref"]) !== "undefined" |
|
3389 && legal_id !== annotation.meta["id-ref"]) { |
|
3390 continue; |
|
3391 } |
|
3392 |
|
3393 code = {start: annotation.begin / 1000, end: annotation.end / 1000, |
|
3394 onStart: function(annotation) { return function() { |
|
3395 if (typeof(annotation.content) !== "undefined") |
|
3396 _this.selector.find(".Ldt-createAnnotation-Title").html(annotation.content.title); |
|
3397 |
|
3398 _this._currentAnnotation = annotation; |
|
3399 var beginTime = IriSP.msToTime(annotation.begin); |
|
3400 var endTime = IriSP.msToTime(annotation.end); |
|
3401 var timeTemplate = IriSP.templToHTML("- ({{begin}} - {{ end }})", {begin: beginTime, end: endTime }); |
|
3402 _this.selector.find(".Ldt-createAnnotation-TimeFrame").html(timeTemplate); |
|
3403 } }(annotation) |
|
3404 }; |
|
3405 |
|
3406 this._Popcorn.code(code); |
|
3407 } |
|
3408 } |
|
3409 |
|
3410 this.selector.find(".Ldt-createAnnotation-submitButton").click(IriSP.wrap(this, this.handleButtonClick)); |
|
3411 |
|
3412 if (!this.cinecast_version) { |
|
3413 this._Popcorn.listen("IriSP.PlayerWidget.AnnotateButton.clicked", |
|
3414 IriSP.wrap(this, this.handleAnnotateSignal)); |
|
3415 |
|
3416 // handle clicks on the cancel button too. |
|
3417 this.selector.find(".Ldt-createAnnotation-Minimize").click(IriSP.wrap(this, |
|
3418 function() { |
|
3419 // we've got to simulate the pressing of the button because there's no |
|
3420 // other way to minimize the widget and show the widgets that were hidden |
|
3421 // same time |
|
3422 this._Popcorn.trigger("IriSP.PlayerWidget.AnnotateButton.clicked"); |
|
3423 } |
|
3424 )); |
|
3425 } |
|
3426 }; |
|
3427 |
|
3428 IriSP.createAnnotationWidget.prototype.showMoreTags = function() { |
|
3429 for (var j=0; j < this.random_tags; j++) { |
|
3430 var _jq = this.selector.find(".Ldt-createAnnotation-keywords li:hidden"); |
|
3431 if (_jq.length > 1) { |
|
3432 IriSP.jQuery(_jq[Math.floor(_jq.length*Math.random())]).show(); |
|
3433 } else { |
|
3434 _jq.show(); |
|
3435 break; |
|
3436 } |
|
3437 } |
|
3438 if (this.selector.find(".Ldt-createAnnotation-keywords li:hidden").length == 0) { |
|
3439 this.selector.find('.Ldt-createAnnotation-moar-keywordz').hide(); |
|
3440 } |
|
3441 } |
|
3442 |
|
3443 /* Handles adding keywords and polemics */ |
|
3444 IriSP.createAnnotationWidget.prototype.addKeyword = function(_keyword) { |
|
3445 var _field = this.selector.find(".Ldt-createAnnotation-Description"), |
|
3446 _rx = IriSP.regexpFromText(_keyword), |
|
3447 _contents = _field.val(); |
|
3448 _contents = ( _rx.test(_contents) |
|
3449 ? _contents.replace(_rx,"").replace(" "," ").trim() |
|
3450 : _contents.trim() + " " + _keyword |
|
3451 ); |
|
3452 _field.val(_contents.trim()).trigger("js_mod"); |
|
3453 } |
|
3454 |
|
3455 /** handles clicks on the annotate button. Works only for the non-cinecast version */ |
|
3456 IriSP.createAnnotationWidget.prototype.handleAnnotateSignal = function() { |
|
3457 |
|
3458 if (this._hidden == false && this._state == 'startScreen') { |
|
3459 this.selector.hide(); |
|
3460 this._hidden = true; |
|
3461 |
|
3462 // free the arrow. |
|
3463 this._Popcorn.trigger("IriSP.ArrowWidget.releaseArrow"); |
|
3464 this._Popcorn.trigger("IriSP.SliceWidget.hide"); |
|
3465 this._Popcorn.trigger("IriSP.AnnotationsWidget.show"); |
|
3466 |
|
3467 } else { |
|
3468 this._Popcorn.trigger("IriSP.AnnotationsWidget.hide"); |
|
3469 this.showStartScreen(); |
|
3470 this.selector.show(); |
|
3471 this._hidden = false; |
|
3472 var currentTime = this._Popcorn.currentTime(); |
|
3473 |
|
3474 // block the arrow. |
|
3475 this._Popcorn.trigger("IriSP.ArrowWidget.blockArrow"); |
|
3476 |
|
3477 var duration = this.getDuration(); |
|
3478 |
|
3479 var currentChapter = this._serializer.currentChapitre(currentTime); |
|
3480 |
|
3481 if (IriSP.null_or_undefined(currentChapter)) { |
|
3482 var left = this.selector.width() / 2; |
|
3483 var width = this.selector.width() / 10; |
|
3484 } else { |
|
3485 var left = (currentChapter.begin / duration) * this.selector.width(); |
|
3486 var width = (currentChapter.end / duration) * this.selector.width() - left; |
|
3487 } |
|
3488 |
|
3489 // slider position and length is kept in percents. |
|
3490 this.sliceLeft = (left / this.selector.width()) * 100; |
|
3491 this.sliceWidth = (width / this.selector.width()) * 100; |
|
3492 |
|
3493 this._Popcorn.trigger("IriSP.SliceWidget.position", [left, width]); |
|
3494 this._Popcorn.listen("IriSP.SliceWidget.zoneChange", IriSP.wrap(this, this.handleSliderChanges)); |
|
3495 this._Popcorn.trigger("IriSP.SliceWidget.show"); |
|
3496 |
|
3497 if (!IriSP.null_or_undefined(currentChapter)) { |
|
3498 this.selector.find(".Ldt-createAnnotation-Title").html(currentChapter.content.title); |
|
3499 |
|
3500 this._currentcurrentChapter = currentChapter; |
|
3501 var beginTime = IriSP.msToTime(currentChapter.begin); |
|
3502 var endTime = IriSP.msToTime(currentChapter.end); |
|
3503 var timeTemplate = IriSP.templToHTML("- ({{begin}} - {{ end }})", {begin: beginTime, end: endTime }); |
|
3504 this.selector.find(".Ldt-createAnnotation-TimeFrame").html(timeTemplate); |
|
3505 } |
|
3506 } |
|
3507 }; |
|
3508 |
|
3509 |
|
3510 /** watch for changes in the textfield and change the buttons accordingly */ |
|
3511 IriSP.createAnnotationWidget.prototype.handleTextChanges = function(event) { |
|
3512 var contents = this.selector.find(".Ldt-createAnnotation-Description").val(); |
|
3513 if (this.cinecast_version) { |
|
3514 this._Popcorn.pause(); |
|
3515 } |
|
3516 this.selector.find(".Ldt-createAnnotation-btnblock button").each(function() { |
|
3517 var _rx = IriSP.regexpFromText(IriSP.jQuery(this).text()); |
|
3518 if (_rx.test(contents)) { |
|
3519 IriSP.jQuery(this).parent().addClass("Ldt-createAnnotation-active-button"); |
|
3520 } else { |
|
3521 IriSP.jQuery(this).parent().removeClass("Ldt-createAnnotation-active-button"); |
|
3522 } |
|
3523 }); |
|
3524 |
|
3525 }; |
|
3526 |
|
3527 IriSP.createAnnotationWidget.prototype.showStartScreen = function() { |
|
3528 this.selector.find(".Ldt-createAnnotation-screen").hide(); |
|
3529 this.selector.find(".Ldt-createAnnotation-startScreen").show(); |
|
3530 |
|
3531 var jqTextfield = this.selector.find(".Ldt-createAnnotation-Description"); // handle on the textfield. used for the closure |
|
3532 |
|
3533 /* test if the browser supports the placeholder attribute */ |
|
3534 if (!IriSP.null_or_undefined(jqTextfield.get(0).placeholder)) { |
|
3535 jqTextfield.attr("placeholder", IriSP.i18n.getMessage('type_here')); |
|
3536 } else { |
|
3537 jqTextfield.val(IriSP.i18n.getMessage('type_here')); |
|
3538 jqTextfield.one("click", IriSP.wrap(this, function() { jqTextfield.val(""); })); |
|
3539 } |
|
3540 |
|
3541 |
|
3542 |
|
3543 this._state = "startScreen"; |
|
3544 }; |
|
3545 |
|
3546 IriSP.createAnnotationWidget.prototype.showWaitScreen = function() { |
|
3547 this.selector.find(".Ldt-createAnnotation-screen").hide(); |
|
3548 this.selector.find(".Ldt-createAnnotation-waitScreen").show(); |
|
3549 this._state = "waitScreen"; |
|
3550 }; |
|
3551 |
|
3552 IriSP.createAnnotationWidget.prototype.showErrorScreen = function() { |
|
3553 this.selector.find(".Ldt-createAnnotation-screen").hide(); |
|
3554 this.selector.find(".Ldt-createAnnotation-errorScreen").show(); |
|
3555 this._state = "errorScreen"; |
|
3556 var _this = this; |
|
3557 window.setTimeout(function() { _this.showStartScreen(); }, 2000); |
|
3558 }; |
|
3559 |
|
3560 /** update show the final screen with links to share the created annotation */ |
|
3561 IriSP.createAnnotationWidget.prototype.showEndScreen = function(annotation) { |
|
3562 this.selector.find(".Ldt-createAnnotation-screen").hide(); |
|
3563 |
|
3564 if (this.cinecast_version) { |
|
3565 this.selector.find(".Ldt-createAnnotation-Title").parent().show(); |
|
3566 } |
|
3567 |
|
3568 var url = ( (typeof annotation.meta == "object" && typeof annotation.meta.url == "string" && annotation.meta.url.length) |
|
3569 ? annotation.meta.url |
|
3570 : ( document.location.href + "#id=" + annotation.id ) ); |
|
3571 var twStatus = IriSP.mkTweetUrl(url); |
|
3572 var gpStatus = IriSP.mkGplusUrl(url); |
|
3573 var fbStatus = IriSP.mkFbUrl(url); |
|
3574 |
|
3575 this.selector.find(".Ldt-createAnnotation-endScreen-TweetLink").attr("href", twStatus); |
|
3576 this.selector.find(".Ldt-createAnnotation-endScreen-FbLink").attr("href", fbStatus); |
|
3577 this.selector.find(".Ldt-createAnnotation-endScreen-GplusLink").attr("href", gpStatus); |
|
3578 |
|
3579 this.selector.find(".Ldt-createAnnotation-endScreen").show(); |
|
3580 this._state = "endScreen"; |
|
3581 }; |
|
3582 |
|
3583 /** handle clicks on "send annotation" button */ |
|
3584 IriSP.createAnnotationWidget.prototype.handleButtonClick = function(event) { |
|
3585 var _this = this; |
|
3586 var textfield = this.selector.find(".Ldt-createAnnotation-Description"); |
|
3587 var contents = textfield.val(); |
|
3588 |
|
3589 if (contents === "") { |
|
3590 if (this.selector.find(".Ldt-createAnnotation-errorMessage").length === 0) { |
|
3591 this.selector.find(".Ldt-createAnnotation-Container") |
|
3592 .after(IriSP.templToHTML(IriSP.createAnnotation_errorMessage_template)); |
|
3593 textfield.css("background-color", "#d93c71"); |
|
3594 } else { |
|
3595 this.selector.find(".Ldt-createAnnotation-errorMessage").show(); |
|
3596 } |
|
3597 |
|
3598 textfield.one("js_mod propertychange keyup input paste", IriSP.wrap(this, function() { |
|
3599 var contents = textfield.val(); |
|
3600 |
|
3601 if (contents !== "") { |
|
3602 this.selector.find(".Ldt-createAnnotation-errorMessage").hide(); |
|
3603 textfield.css("background-color", ""); |
|
3604 } |
|
3605 })); |
|
3606 } else { |
|
3607 this.showWaitScreen(); |
|
3608 |
|
3609 this.sendLdtData(contents, function(annotation) { |
|
3610 if (_this.cinecast_version) { |
|
3611 if (_this._Popcorn.media.paused) |
|
3612 _this._Popcorn.play(); |
|
3613 } |
|
3614 |
|
3615 if (_this._state == "waitScreen") { |
|
3616 _this.showEndScreen(annotation); |
|
3617 if (_this.cinecast_version) { |
|
3618 window.setTimeout(function() { _this.showStartScreen(); }, 5000); |
|
3619 } |
|
3620 } |
|
3621 // hide the slicer widget |
|
3622 if (!_this.cinecast_version) { |
|
3623 _this._Popcorn.trigger("IriSP.SliceWidget.hide"); |
|
3624 } |
|
3625 }); |
|
3626 } |
|
3627 }; |
|
3628 |
|
3629 IriSP.createAnnotationWidget.prototype.handleSliderChanges = function(params) { |
|
3630 this.sliceLeft = params[0]; |
|
3631 this.sliceWidth = params[1]; |
|
3632 }; |
|
3633 |
|
3634 IriSP.createAnnotationWidget.prototype.sendLdtData = function(contents, callback) { |
|
3635 var _this = this; |
|
3636 var apiJson = { |
|
3637 format : "http://advene.org/ns/cinelab/", |
|
3638 annotations : [ |
|
3639 {} |
|
3640 ], |
|
3641 meta: {}}; |
|
3642 var annotation = apiJson.annotations[0]; |
|
3643 |
|
3644 annotation.media = this.currentMedia()["id"]; |
|
3645 |
|
3646 if (this.cinecast_version) { |
|
3647 annotation.begin = Math.round(this._Popcorn.currentTime() * 1000); |
|
3648 annotation.end = annotation.begin; |
|
3649 } else { |
|
3650 var duration = this.getDuration(); |
|
3651 annotation.begin = +((duration * (this.sliceLeft / 100)).toFixed(0)); |
|
3652 annotation.end = +((duration * ((this.sliceWidth + this.sliceLeft) / 100)).toFixed(0)); |
|
3653 } |
|
3654 |
|
3655 // boundary checks |
|
3656 annotation.begin = Math.max(0, annotation.begin); |
|
3657 annotation.end = Math.min(this.getDuration(), annotation.end); |
|
3658 |
|
3659 annotation.type = ( this.cinecast_version ? "cinecast:UserAnnotation" : ( this._serializer.getContributions() || "" )); |
|
3660 if (typeof(annotation.type) === "undefined") |
|
3661 annotation.type = ""; |
|
3662 |
|
3663 annotation.type_title = "Contributions"; |
|
3664 annotation.content = {}; |
|
3665 annotation.content.data = contents; |
|
3666 if (this.cinecast_version) { |
|
3667 var _extract = IriSP.underscore(this._serializer._data.annotations) |
|
3668 .filter(function(_a) { |
|
3669 return (_a.begin <= annotation.begin && _a.end >= annotation.begin && _a.type == "cinecast:MovieExtract"); |
|
3670 }) |
|
3671 if (_extract.length) { |
|
3672 annotation.extract = _extract[0].id; |
|
3673 } |
|
3674 } |
|
3675 |
|
3676 var meta = apiJson.meta; |
|
3677 |
|
3678 |
|
3679 var _username = this.selector.find(".Ldt-createAnnotation-userName").val(); |
|
3680 meta.creator = ( |
|
3681 (_username && _username.length) |
|
3682 ? _username |
|
3683 : ( |
|
3684 (!IriSP.null_or_undefined(IriSP.user) && !IriSP.null_or_undefined(IriSP.user.name)) |
|
3685 ? IriSP.user.name |
|
3686 : "Anonymous user" |
|
3687 ) |
|
3688 ); |
|
3689 |
|
3690 meta.created = Date().toString(); |
|
3691 |
|
3692 var _tags = []; |
|
3693 IriSP._(this.tags).each(function(_v) { |
|
3694 var _rx = IriSP.regexpFromText(_v.meta.description); |
|
3695 if (_rx.test(contents)) { |
|
3696 _tags.push(_v.id); |
|
3697 } |
|
3698 }); |
|
3699 |
|
3700 if (typeof this.remote_tags == "object") { |
|
3701 _tags = IriSP._(_tags).map(function(_t) { |
|
3702 return _this.remote_tags.id + ':' + _t |
|
3703 }); |
|
3704 if (typeof apiJson.imports == "undefined") { |
|
3705 apiJson.imports = []; |
|
3706 } |
|
3707 apiJson.imports.push({ |
|
3708 "id" : this.remote_tags.id, |
|
3709 "url" : this.remote_tags.url |
|
3710 }) |
|
3711 } |
|
3712 annotation.tags = IriSP.underscore.uniq(_tags); |
|
3713 |
|
3714 var jsonString = JSON.stringify(apiJson); |
|
3715 var project_id = this._serializer._data.meta.id; |
|
3716 |
|
3717 //TODO: extract magic url |
|
3718 var url = Mustache.to_html(this.api_endpoint_template, |
|
3719 {id: project_id}); |
|
3720 |
|
3721 IriSP.jQuery.ajax({ |
|
3722 url: url, |
|
3723 type: this.api_method, |
|
3724 contentType: 'application/json', |
|
3725 data: jsonString, |
|
3726 //dataType: 'json', |
|
3727 success: IriSP.wrap(this, function(json, textStatus, XMLHttpRequest) { |
|
3728 /* add the annotation to the annotation and tell the world */ |
|
3729 var annotation = json.annotations[0]; |
|
3730 |
|
3731 if (!this.cinecast_version) { |
|
3732 /* if the media doesn't have a contributions line, we need to add one */ |
|
3733 if (typeof(this._serializer.getContributions()) === "undefined") { |
|
3734 /* set up a basic view */ |
|
3735 var tmp_view = {"dc:contributor": "perso", "dc:creator": "perso", "dc:title": "Contributions", |
|
3736 "id": json.annotations[0].type} |
|
3737 |
|
3738 |
|
3739 IriSP.get_aliased(this._serializer._data, ["annotation_types", "annotation-types"]).push(tmp_view); |
|
3740 } |
|
3741 |
|
3742 delete annotation.tags; |
|
3743 annotation.content.description = annotation.content.data; |
|
3744 annotation.content.title = ""; |
|
3745 delete annotation.content.data; |
|
3746 annotation.id = json.annotations[0].id; |
|
3747 |
|
3748 annotation.meta = meta; |
|
3749 annotation.meta["id-ref"] = json.annotations[0]["type"]; |
|
3750 } else { |
|
3751 annotation.type = "cinecast:UserAnnotation"; |
|
3752 } |
|
3753 // everything is shared so there's no need to propagate the change |
|
3754 var _an_ids = IriSP.underscore(this._serializer._data.annotations).map(function(_a) { |
|
3755 return _a.id.toLowerCase(); |
|
3756 }); |
|
3757 if (_an_ids.indexOf(annotation.id.toLowerCase()) == -1) { |
|
3758 _this._serializer._data.annotations.push(annotation); |
|
3759 } |
|
3760 |
|
3761 _this._Popcorn.trigger("IriSP.createAnnotationWidget.addedAnnotation", annotation); |
|
3762 this.selector.find(".Ldt-createAnnotation-Description").val("").trigger("js_mod"); |
|
3763 callback(annotation); |
|
3764 }), |
|
3765 error: |
|
3766 function(jqXHR, textStatus, errorThrown) { |
|
3767 console.log("an error occured while contacting " |
|
3768 + url + " and sending " + jsonString + textStatus ); |
|
3769 _this.showErrorScreen(); } }); |
|
3770 };IriSP.HelloWorldWidget = function(Popcorn, config, Serializer) { |
|
3771 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
3772 } |
|
3773 |
|
3774 IriSP.HelloWorldWidget.prototype = new IriSP.Widget(); |
|
3775 |
|
3776 IriSP.HelloWorldWidget.prototype.draw = function() { |
|
3777 this.selector |
|
3778 .html('Hello, world') |
|
3779 .css({ |
|
3780 "text-align" : "center", |
|
3781 "padding": "10px 0", |
|
3782 "font-size" : "14px" |
|
3783 }); |
|
3784 |
|
3785 console.log(this); |
|
3786 } |
|
3787 /* Internationalization for this widget */ |
|
3788 |
|
3789 IriSP.i18n.addMessages( |
|
3790 { |
|
3791 "en": { |
|
3792 "play_pause": "Play/Pause", |
|
3793 "mute_unmute": "Mute/Unmute", |
|
3794 "play": "Play", |
|
3795 "pause": "Pause", |
|
3796 "mute": "Mute", |
|
3797 "unmute": "Unmute", |
|
3798 "annotate": "Annotate", |
|
3799 "search": "Search", |
|
3800 "elapsed_time": "Elapsed time", |
|
3801 "total_time": "Total time", |
|
3802 "volume": "Volume", |
|
3803 "volume_control": "Volume control" |
|
3804 }, |
|
3805 "fr": { |
|
3806 "play_pause": "Lecture/Pause", |
|
3807 "mute_unmute": "Couper/Activer le son", |
|
3808 "play": "Lecture", |
|
3809 "pause": "Pause", |
|
3810 "mute": "Couper le son", |
|
3811 "unmute": "Activer le son", |
|
3812 "annotate": "Annoter", |
|
3813 "search": "Rechercher", |
|
3814 "elapsed_time": "Durée écoulée", |
|
3815 "total_time": "Durée totale", |
|
3816 "volume": "Niveau sonore", |
|
3817 "volume_control": "Réglage du niveau sonore" |
|
3818 } |
|
3819 } |
|
3820 ); |
|
3821 |
|
3822 |
|
3823 IriSP.PlayerWidget = function(Popcorn, config, Serializer) { |
|
3824 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
3825 |
|
3826 this._searchBlockOpen = false; |
|
3827 this._searchLastValue = ""; |
|
3828 }; |
|
3829 |
|
3830 IriSP.PlayerWidget.prototype = new IriSP.Widget(); |
|
3831 |
|
3832 IriSP.PlayerWidget.prototype.draw = function() { |
|
3833 var self = this; |
|
3834 var width = this.width; |
|
3835 var height = this.height; |
|
3836 var heightS = this.height-20; |
|
3837 |
|
3838 var playerTempl = IriSP.templToHTML(IriSP.player_template, this._config); |
|
3839 this.selector.append(playerTempl); |
|
3840 |
|
3841 this.selector.children(".Ldt-controler").show(); |
|
3842 |
|
3843 // handle clicks by the user on the video. |
|
3844 this._Popcorn.listen("play", IriSP.wrap(this, this.playButtonUpdater)); |
|
3845 this._Popcorn.listen("pause", IriSP.wrap(this, this.playButtonUpdater)); |
|
3846 |
|
3847 this._Popcorn.listen("volumechange", IriSP.wrap(this, this.volumeUpdater)); |
|
3848 |
|
3849 this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeDisplayUpdater)); |
|
3850 // update the time display for the first time. |
|
3851 this._Popcorn.listen("loadedmetadata", IriSP.wrap(this, this.timeDisplayUpdater)); |
|
3852 |
|
3853 this._Popcorn.listen("IriSP.search.matchFound", IriSP.wrap(this, this.searchMatch)); |
|
3854 this._Popcorn.listen("IriSP.search.noMatchFound", IriSP.wrap(this, this.searchNoMatch)); |
|
3855 this._Popcorn.listen("IriSP.search.triggeredSearch", IriSP.wrap(this, this.triggeredSearch)); |
|
3856 |
|
3857 |
|
3858 this.selector.find(".Ldt-CtrlPlay").click(function() { self.playHandler.call(self); }); |
|
3859 this.selector.find(".Ldt-CtrlAnnotate").click(function() |
|
3860 { self._Popcorn.trigger("IriSP.PlayerWidget.AnnotateButton.clicked"); }); |
|
3861 this.selector.find(".Ldt-CtrlSearch").click(function() { self.searchButtonHandler.call(self); }); |
|
3862 |
|
3863 var _volctrl = this.selector.find(".Ldt-Ctrl-Volume-Control"); |
|
3864 this.selector.find('.Ldt-CtrlSound') |
|
3865 .click(function() { self.muteHandler.call(self); } ) |
|
3866 .mouseover(function() { |
|
3867 _volctrl.show(); |
|
3868 }) |
|
3869 .mouseout(function() { |
|
3870 _volctrl.hide(); |
|
3871 }); |
|
3872 _volctrl.mouseover(function() { |
|
3873 _volctrl.show(); |
|
3874 }).mouseout(function() { |
|
3875 _volctrl.hide(); |
|
3876 }); |
|
3877 |
|
3878 /* |
|
3879 var searchButtonPos = this.selector.find(".Ldt-CtrlSearch").position(); |
|
3880 var searchBox = Mustache.to_html(IriSP.search_template, {margin_left : searchButtonPos.left + "px"}); |
|
3881 this.selector.find(".Ldt-CtrlSearch").after(searchBox); |
|
3882 */ |
|
3883 |
|
3884 // trigger an IriSP.PlayerWidget.MouseOver to the widgets that are interested (i.e : sliderWidget) |
|
3885 this.selector.hover(function() { self._Popcorn.trigger("IriSP.PlayerWidget.MouseOver"); }, |
|
3886 function() { self._Popcorn.trigger("IriSP.PlayerWidget.MouseOut"); }); |
|
3887 this.selector.find(".Ldt-Ctrl-Volume-Cursor").draggable({ |
|
3888 axis: "x", |
|
3889 drag: function(event, ui) { |
|
3890 var _vol = Math.max(0, Math.min( 1, ui.position.left / (ui.helper.parent().width() - ui.helper.outerWidth()))); |
|
3891 ui.helper.attr("title",IriSP.i18n.getMessage('volume')+': ' + Math.floor(100*_vol) + '%'); |
|
3892 self._Popcorn.volume(_vol); |
|
3893 }, |
|
3894 containment: "parent" |
|
3895 }); |
|
3896 |
|
3897 setTimeout(function() { |
|
3898 self.volumeUpdater(); |
|
3899 }, 1000); /* some player - jwplayer notable - save the state of the mute button between sessions */ |
|
3900 }; |
|
3901 |
|
3902 /* Update the elasped time div */ |
|
3903 IriSP.PlayerWidget.prototype.timeDisplayUpdater = function() { |
|
3904 |
|
3905 if (this._previousSecond === undefined) { |
|
3906 this._previousSecond = this._Popcorn.roundTime(); |
|
3907 } |
|
3908 else { |
|
3909 /* we're still in the same second, so it's not necessary to update time */ |
|
3910 if (this._Popcorn.roundTime() == this._previousSecond) |
|
3911 return; |
|
3912 |
|
3913 } |
|
3914 |
|
3915 // we get it at each call because it may change. |
|
3916 var duration = this.getDuration() / 1000; |
|
3917 var totalTime = IriSP.secondsToTime(duration); |
|
3918 var elapsedTime = IriSP.secondsToTime(this._Popcorn.currentTime()); |
|
3919 |
|
3920 this.selector.find(".Ldt-ElapsedTime").html(elapsedTime.toString()); |
|
3921 this.selector.find(".Ldt-TotalTime").html(totalTime.toString()); |
|
3922 this._previousSecond = this._Popcorn.roundTime(); |
|
3923 }; |
|
3924 |
|
3925 /* update the icon of the button - separate function from playHandler |
|
3926 because in some cases (for instance, when the user directly clicks on |
|
3927 the jwplayer window) we have to change the icon without playing/pausing |
|
3928 */ |
|
3929 IriSP.PlayerWidget.prototype.playButtonUpdater = function() { |
|
3930 var status = this._Popcorn.media.paused; |
|
3931 |
|
3932 if ( status == true ){ |
|
3933 /* the background sprite is changed by adding/removing the correct classes */ |
|
3934 this.selector.find(".Ldt-CtrlPlay").attr("title", IriSP.i18n.getMessage('play')); |
|
3935 this.selector.find(".Ldt-CtrlPlay").removeClass("Ldt-CtrlPlay-PauseState").addClass("Ldt-CtrlPlay-PlayState"); |
|
3936 } else { |
|
3937 this.selector.find(".Ldt-CtrlPlay").attr("title", IriSP.i18n.getMessage('pause')); |
|
3938 this.selector.find(".Ldt-CtrlPlay").removeClass("Ldt-CtrlPlay-PlayState").addClass("Ldt-CtrlPlay-PauseState"); |
|
3939 } |
|
3940 |
|
3941 return; |
|
3942 }; |
|
3943 |
|
3944 |
|
3945 IriSP.PlayerWidget.prototype.playHandler = function() { |
|
3946 var status = this._Popcorn.media.paused; |
|
3947 |
|
3948 if ( status == true ){ |
|
3949 this._Popcorn.play(); |
|
3950 } else { |
|
3951 this._Popcorn.pause(); |
|
3952 } |
|
3953 }; |
|
3954 |
|
3955 IriSP.PlayerWidget.prototype.muteHandler = function() { |
|
3956 this._Popcorn.mute(!this._Popcorn.muted()); |
|
3957 }; |
|
3958 |
|
3959 IriSP.PlayerWidget.prototype.volumeUpdater = function() { |
|
3960 var _muted = this._Popcorn.muted(), |
|
3961 _vol = this._Popcorn.volume(); |
|
3962 if (_vol === false) { |
|
3963 _vol = .5; |
|
3964 } |
|
3965 var _soundCtl = this.selector.find(".Ldt-CtrlSound"); |
|
3966 _soundCtl.removeClass("Ldt-CtrlSound-Mute Ldt-CtrlSound-Half Ldt-CtrlSound-Full"); |
|
3967 if (_muted) { |
|
3968 _soundCtl.attr("title", IriSP.i18n.getMessage('unmute')) |
|
3969 .addClass("Ldt-CtrlSound-Mute"); |
|
3970 } else { |
|
3971 _soundCtl.attr("title", IriSP.i18n.getMessage('mute')) |
|
3972 .addClass(_vol < .5 ? "Ldt-CtrlSound-Half" : "Ldt-CtrlSound-Full" ) |
|
3973 } |
|
3974 var _cursor = this.selector.find(".Ldt-Ctrl-Volume-Cursor"); |
|
3975 _cursor.css({ |
|
3976 "left": ( _muted ? 0 : Math.floor(_vol * (_cursor.parent().width() - _cursor.outerWidth())) ) + "px" |
|
3977 }) |
|
3978 }; |
|
3979 |
|
3980 IriSP.PlayerWidget.prototype.showSearchBlock = function() { |
|
3981 var self = this; |
|
3982 |
|
3983 if (this._searchBlockOpen == false) { |
|
3984 this.selector.find(".LdtSearch").show("blind", { direction: "horizontal"}, 100); |
|
3985 this.selector.find(".LdtSearchInput").css('background-color','#fff'); |
|
3986 |
|
3987 this._searchBlockOpen = true; |
|
3988 this.selector.find(".LdtSearchInput").bind('keyup', null, function() { self.searchHandler.call(self); } ); |
|
3989 this.selector.find(".LdtSearchInput").focus(); |
|
3990 |
|
3991 // we need this variable because some widget can find a match in |
|
3992 // their data while at the same time other's don't. As we want the |
|
3993 // search field to become green when there's a match, we need a |
|
3994 // variable to remember that we had one. |
|
3995 this._positiveMatch = false; |
|
3996 |
|
3997 // tell the world the field is open |
|
3998 this._Popcorn.trigger("IriSP.search.open"); |
|
3999 } |
|
4000 }; |
|
4001 |
|
4002 IriSP.PlayerWidget.prototype.hideSearchBlock = function() { |
|
4003 if (this._searchBlockOpen == true) { |
|
4004 this._searchLastValue = this.selector.find(".LdtSearchInput").attr('value'); |
|
4005 this.selector.find(".LdtSearchInput").attr('value',''); |
|
4006 this.selector.find(".LdtSearch").hide("blind", { direction: "horizontal"}, 75); |
|
4007 |
|
4008 // unbind the watcher event. |
|
4009 this.selector.find(".LdtSearchInput").unbind('keypress set'); |
|
4010 this._searchBlockOpen = false; |
|
4011 |
|
4012 this._positiveMatch = false; |
|
4013 |
|
4014 this._Popcorn.trigger("IriSP.search.closed"); |
|
4015 } |
|
4016 }; |
|
4017 |
|
4018 /** react to clicks on the search button */ |
|
4019 IriSP.PlayerWidget.prototype.searchButtonHandler = function() { |
|
4020 var self = this; |
|
4021 |
|
4022 /* show the search field if it is not shown */ |
|
4023 if ( this._searchBlockOpen == false ) { |
|
4024 this.showSearchBlock(); |
|
4025 this.selector.find(".LdtSearchInput").attr('value', this._searchLastValue); |
|
4026 this._Popcorn.trigger("IriSP.search", this._searchLastValue); // trigger the search to make it more natural. |
|
4027 } else { |
|
4028 this.hideSearchBlock(); |
|
4029 } |
|
4030 }; |
|
4031 |
|
4032 /** this handler is called whenever the content of the search |
|
4033 field changes */ |
|
4034 IriSP.PlayerWidget.prototype.searchHandler = function() { |
|
4035 this._searchLastValue = this.selector.find(".LdtSearchInput").attr('value'); |
|
4036 this._positiveMatch = false; |
|
4037 |
|
4038 // do nothing if the search field is empty, instead of highlighting everything. |
|
4039 if (this._searchLastValue == "") { |
|
4040 this._Popcorn.trigger("IriSP.search.cleared"); |
|
4041 this.selector.find(".LdtSearchInput").css('background-color',''); |
|
4042 } else { |
|
4043 this._Popcorn.trigger("IriSP.search", this._searchLastValue); |
|
4044 } |
|
4045 }; |
|
4046 |
|
4047 /** |
|
4048 handler for the IriSP.search.found message, which is sent by some views when they |
|
4049 highlight a match. |
|
4050 */ |
|
4051 IriSP.PlayerWidget.prototype.searchMatch = function() { |
|
4052 this._positiveMatch = true; |
|
4053 this.selector.find(".LdtSearchInput").css('background-color','#e1ffe1'); |
|
4054 }; |
|
4055 |
|
4056 /** the same, except that no value could be found */ |
|
4057 IriSP.PlayerWidget.prototype.searchNoMatch = function() { |
|
4058 if (this._positiveMatch !== true) |
|
4059 this.selector.find(".LdtSearchInput").css('background-color', "#d62e3a"); |
|
4060 }; |
|
4061 |
|
4062 /** react to an IriSP.Player.triggeredSearch - that is, when |
|
4063 a widget ask the PlayerWidget to do a search on his behalf */ |
|
4064 IriSP.PlayerWidget.prototype.triggeredSearch = function(searchString) { |
|
4065 this.showSearchBlock(); |
|
4066 this.selector.find(".LdtSearchInput").attr('value', searchString); |
|
4067 this._Popcorn.trigger("IriSP.search", searchString); // trigger the search to make it more natural. |
|
4068 }; |
|
4069 |
|
4070 |
|
4071 /* |
|
4072 * |
|
4073 * Copyright 2010 Institut de recherche et d'innovation |
|
4074 * contributor(s) : Samuel Huron |
|
4075 * |
|
4076 * contact@iri.centrepompidou.fr |
|
4077 * http://www.iri.centrepompidou.fr |
|
4078 * |
|
4079 * This software is a computer program whose purpose is to show and add annotations on a video . |
|
4080 * This software is governed by the CeCILL-C license under French law and |
|
4081 * abiding by the rules of distribution of free software. You can use, |
|
4082 * modify and/ or redistribute the software under the terms of the CeCILL-C |
|
4083 * license as circulated by CEA, CNRS and INRIA at the following URL |
|
4084 * "http://www.cecill.info". |
|
4085 * |
|
4086 * The fact that you are presently reading this means that you have had |
|
4087 * knowledge of the CeCILL-C license and that you accept its terms. |
|
4088 */ |
|
4089 // CHART TIMELINE / VERSION PROTOTYPE :: |
|
4090 |
|
4091 /** the polemic widget */ |
|
4092 IriSP.PolemicWidget = function(Popcorn, config, Serializer) { |
|
4093 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
4094 |
|
4095 this.userPol = new Array(); |
|
4096 this.userNoPol = new Array(); |
|
4097 this.userst = new Array(); |
|
4098 this.numberOfTweet = 0; |
|
4099 this.Users; |
|
4100 this.TweetPolemic; |
|
4101 this.yMax = this.height; |
|
4102 this.PaperSlider; |
|
4103 this.heightOfChart; |
|
4104 this.tweets = new Array(); |
|
4105 this.svgElements = {}; |
|
4106 |
|
4107 this.oldSearchMatches = []; |
|
4108 }; |
|
4109 |
|
4110 IriSP.PolemicWidget.prototype = new IriSP.Widget(); |
|
4111 |
|
4112 IriSP.PolemicWidget.prototype.draw = function() { |
|
4113 |
|
4114 // variable |
|
4115 // yMax |
|
4116 |
|
4117 var self = this; |
|
4118 var yCoef = 2; // coef for height of 1 tweet |
|
4119 var frameSize = 5; // frame size |
|
4120 var margin = 1; // marge between frame |
|
4121 var lineSize = this.width; // timeline pixel width |
|
4122 var nbrframes = lineSize/frameSize; // frame numbers |
|
4123 var numberOfTweet = 0; // number of tweet overide later |
|
4124 var duration = this.getDuration(); // timescale width |
|
4125 var frameLength = lineSize / frameSize; // frame timescale |
|
4126 var timeline; |
|
4127 var colors = new Array("","#1D973D","#036AAE","#CE0A15","#C5A62D","#585858"); |
|
4128 |
|
4129 // array |
|
4130 //var tweets = new Array(); |
|
4131 var element = new Array(); |
|
4132 var cluster = new Array(); |
|
4133 var frames = new Array(frameLength); |
|
4134 var slices = new Array(); |
|
4135 |
|
4136 |
|
4137 // Classes ======================================================================= |
|
4138 var Frames = function(){ |
|
4139 |
|
4140 var Myclusters; |
|
4141 var x; |
|
4142 var y; |
|
4143 var width; |
|
4144 var height; |
|
4145 }; |
|
4146 Frames = function(json){ |
|
4147 // make my clusters |
|
4148 // ou Frame vide |
|
4149 }; |
|
4150 Frames.prototype.draw = function(){ |
|
4151 }; |
|
4152 Frames.prototype.zoom = function(){ |
|
4153 }; |
|
4154 Frames.prototype.inside = function(){ |
|
4155 }; |
|
4156 var Clusters = function(){ |
|
4157 var Object; |
|
4158 var yDist; |
|
4159 var x; |
|
4160 var y; |
|
4161 var width; |
|
4162 var height; |
|
4163 }; |
|
4164 Clusters = function(json){ |
|
4165 // make my object |
|
4166 }; |
|
4167 var Tweet = function(){ |
|
4168 }; |
|
4169 // Classes ======================================================================= |
|
4170 |
|
4171 // Refactoring (parametere) ************************************************************ |
|
4172 // color translastion |
|
4173 var qTweet_0 =0; |
|
4174 var qTweet_Q =0; |
|
4175 var qTweet_REF=0; |
|
4176 var qTweet_OK =0; |
|
4177 var qTweet_KO =0; |
|
4178 function colorTranslation(value){ |
|
4179 if(value == "Q"){ |
|
4180 qTweet_Q+=1; |
|
4181 return 2; |
|
4182 }else if(value =="REF"){ |
|
4183 qTweet_REF+=1; |
|
4184 return 4; |
|
4185 }else if(value =="OK"){ |
|
4186 qTweet_OK+=1; |
|
4187 return 1; |
|
4188 }else if(value =="KO"){ |
|
4189 qTweet_KO+=1; |
|
4190 return 3; |
|
4191 }else if(value ==""){ |
|
4192 qTweet_0+=1; |
|
4193 return 5; |
|
4194 } |
|
4195 } |
|
4196 |
|
4197 |
|
4198 this._serializer.sync(function(data) { loaded_callback.call(self, data); return; }); |
|
4199 |
|
4200 function loaded_callback (json) { |
|
4201 var view_type = this._serializer.getTweets(); |
|
4202 |
|
4203 |
|
4204 if (typeof(view_type) === "undefined") { |
|
4205 var view_type = this._serializer.getTweetIds()[0]; |
|
4206 if (typeof(view_type) === "undefined") { |
|
4207 // default to guessing if nothing else works. |
|
4208 var view = json.views[0]; |
|
4209 |
|
4210 if(typeof(view.annotation_types) !== "undefined") { |
|
4211 /* we need to be backward compatible with the old files which used to |
|
4212 feature only two lines : Chapitrage and Tweets. We've added a |
|
4213 "Contributions" line so we need to discriminate against that */ |
|
4214 if (view.annotation_types.length === 2 && typeof(this._serializer.getContributions()) === "undefined") { |
|
4215 var view_type = view.annotation_types[1]; |
|
4216 } else { |
|
4217 console.log("PolemicWidget: invalid file - minimizing"); |
|
4218 return; |
|
4219 } |
|
4220 } |
|
4221 } |
|
4222 } |
|
4223 |
|
4224 // Make and define the Raphael area |
|
4225 this.paper = Raphael(document.getElementById(this._id), this._config.width, this._config.height); |
|
4226 |
|
4227 // event handlers |
|
4228 this._Popcorn.listen("IriSP.search", IriSP.wrap(this, function(searchString) { this.searchHandler(searchString); })); |
|
4229 this._Popcorn.listen("IriSP.search.closed", IriSP.wrap(this, this.searchFieldClosedHandler)); |
|
4230 this._Popcorn.listen("IriSP.search.cleared", IriSP.wrap(this, this.searchFieldClearedHandler)); |
|
4231 this.selector.mouseleave(IriSP.wrap(this, function() { self.TooltipWidget.hide.call(self.TooltipWidget); })); |
|
4232 this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.sliderUpdater)); |
|
4233 this._Popcorn.listen("IriSP.Mediafragment.showAnnotation", IriSP.wrap(this, this.showAnnotation)); |
|
4234 |
|
4235 for(var i = 0; i < json.annotations.length; i++) { |
|
4236 var item = json.annotations[i]; |
|
4237 var MyTime = Math.floor(item.begin/duration*lineSize); |
|
4238 var Myframe = Math.floor(MyTime/lineSize*frameLength); |
|
4239 |
|
4240 if (typeof(item.meta) !== "undefined" |
|
4241 && typeof(item.meta["id-ref"]) !== "undefined" |
|
4242 && item.meta["id-ref"] === view_type) { |
|
4243 |
|
4244 var MyTJson = {}, |
|
4245 _source = IriSP.get_aliased(item.meta, ['dc:source', 'source']); |
|
4246 if (_source !== null) { |
|
4247 var MyTJson = JSON.parse(_source['content']); |
|
4248 } |
|
4249 |
|
4250 if (item.content['polemics'] != undefined |
|
4251 && item.content['polemics'][0] != null) { |
|
4252 |
|
4253 // a tweet can have many polemics at the same time. |
|
4254 for(var j=0; j<item.content['polemics'].length; j++){ |
|
4255 |
|
4256 this.tweets[numberOfTweet] = { |
|
4257 id:i, |
|
4258 qualification:colorTranslation(item.content['polemics'][j]), |
|
4259 yIndicator:MyTime, |
|
4260 yframe:Myframe, |
|
4261 title:item.content['title'], |
|
4262 timeframe:item.begin, |
|
4263 userId: MyTJson.id, |
|
4264 userScreenName: MyTJson.screen_name, |
|
4265 tsource:MyTJson, |
|
4266 cinecast_id: item.id |
|
4267 }; |
|
4268 numberOfTweet+=1; |
|
4269 |
|
4270 } |
|
4271 } |
|
4272 else { |
|
4273 this.tweets[numberOfTweet] = { |
|
4274 id:i, |
|
4275 qualification:colorTranslation(""), |
|
4276 yIndicator:MyTime, |
|
4277 yframe:Myframe, |
|
4278 title:item.content['title'], |
|
4279 timeframe:item.begin, |
|
4280 userId: MyTJson.id, |
|
4281 userScreenName: MyTJson.screen_name, |
|
4282 tsource:MyTJson, |
|
4283 cinecast_id: item.id |
|
4284 }; |
|
4285 numberOfTweet+=1; |
|
4286 } |
|
4287 |
|
4288 } |
|
4289 }; |
|
4290 |
|
4291 DrawTweets.call (this); // FIXME: ugly. |
|
4292 |
|
4293 }; |
|
4294 |
|
4295 // tweet Drawing (in raphael) |
|
4296 function DrawTweets (){ |
|
4297 // GROUPES TWEET ============================================ |
|
4298 // Count nbr of cluster and tweet in a frame an save int in "frames" |
|
4299 numberOfTweet = this.tweets.length; |
|
4300 for(var i=0; i<nbrframes; i++) { |
|
4301 for(var j=0; j<numberOfTweet; j++) { |
|
4302 |
|
4303 if (i==this.tweets[j].yframe){ |
|
4304 |
|
4305 var k = this.tweets[j].qualification; |
|
4306 |
|
4307 // make array for frame cluster |
|
4308 if(frames[i]==undefined){ |
|
4309 frames[i] = {id:i, |
|
4310 qualifVol:new Array(), |
|
4311 mytweetsID:new Array() |
|
4312 }; |
|
4313 } |
|
4314 // add my tweet to frame |
|
4315 frames[i].mytweetsID.push(this.tweets[j]); |
|
4316 |
|
4317 // count opinion by frame |
|
4318 if( frames[i].qualifVol[k] == undefined){ |
|
4319 frames[i].qualifVol[k] = 1; |
|
4320 }else{ |
|
4321 frames[i].qualifVol[k] += 1; |
|
4322 } |
|
4323 |
|
4324 } |
|
4325 } |
|
4326 } |
|
4327 |
|
4328 // GROUPES TWEET ============================================ |
|
4329 // max of tweet by Frame |
|
4330 var max = 0; |
|
4331 for(var i = 0; i < nbrframes; i++) { |
|
4332 var moy = 0; |
|
4333 for (var j = 0; j < 6; j++) { |
|
4334 if (frames[i] != undefined) { |
|
4335 if (frames[i].qualifVol[j] != undefined) { |
|
4336 moy += frames[i].qualifVol[j]; |
|
4337 } |
|
4338 } |
|
4339 } |
|
4340 |
|
4341 if (moy > max) { |
|
4342 max = moy; |
|
4343 } |
|
4344 } |
|
4345 |
|
4346 var tweetDrawed = new Array(); |
|
4347 var TweetHeight = 5; |
|
4348 var newHeight = TweetHeight * max + 10; |
|
4349 |
|
4350 |
|
4351 if (newHeight > this.height) { |
|
4352 this.paper.setSize(this.width, newHeight); |
|
4353 this.height = newHeight; |
|
4354 console.log("resizeing"); |
|
4355 } |
|
4356 |
|
4357 |
|
4358 // DRAW TWEETS ============================================ |
|
4359 for(var i = 0; i < nbrframes; i++) { |
|
4360 var addEheight = 5; |
|
4361 if (frames[i] != undefined){ |
|
4362 // by type |
|
4363 |
|
4364 for (var j = 6; j > -1; j--) { |
|
4365 if (frames[i].qualifVol[j] != undefined) { |
|
4366 // show tweet by type |
|
4367 for (var k = 0; k < frames[i].mytweetsID.length; k++) { |
|
4368 |
|
4369 if (frames[i].mytweetsID[k].qualification == j) { |
|
4370 var x = i * frameSize; |
|
4371 var y = this.height - addEheight; |
|
4372 |
|
4373 if (this.yMax > y) { |
|
4374 this.yMax = y; |
|
4375 } |
|
4376 |
|
4377 /* some tweets seem to be duplicated - so we make a check before |
|
4378 creating a new rect */ |
|
4379 if (this.svgElements.hasOwnProperty(frames[i].mytweetsID[k].cinecast_id)) |
|
4380 continue; |
|
4381 |
|
4382 var e = this.paper.rect(x, y, frameSize - margin, TweetHeight /* height */) |
|
4383 .attr({stroke:"#00","stroke-width":0.1, fill: colors[j]}); |
|
4384 |
|
4385 addEheight += TweetHeight; |
|
4386 |
|
4387 /* stick a lot of things into e because that's the easiest way |
|
4388 to do it */ |
|
4389 e.color = colors[j]; |
|
4390 e.time = frames[i].mytweetsID[k].timeframe; |
|
4391 e.title = frames[i].mytweetsID[k].title; |
|
4392 e.id = frames[i].mytweetsID[k].cinecast_id; |
|
4393 this.svgElements[e.id] = e; |
|
4394 |
|
4395 IriSP.jQuery(e.node).mouseenter(function(element) { return function (_e) { |
|
4396 self.TooltipWidget.show.call(self.TooltipWidget, element.title, element.attr("fill"), element.attrs.x + element.attrs.width / 2, element.attrs.y - 2); |
|
4397 element.displayed = true; |
|
4398 self._Popcorn.trigger("IriSP.TraceWidget.MouseEvents", { |
|
4399 "widget" : "StackGraphWidget", |
|
4400 "type": "mousemove", |
|
4401 "x": _e.pageX, |
|
4402 "y": _e.pageY, |
|
4403 "annotation_id": element.id |
|
4404 }); |
|
4405 }}(e)).mousedown(function(element) { return function () { |
|
4406 self._Popcorn.currentTime(element.time/1000); |
|
4407 self._Popcorn.trigger("IriSP.PolemicTweet.click", element.id); |
|
4408 } |
|
4409 }(e)); |
|
4410 |
|
4411 IriSP.jQuery(e.node).attr('id', 't' + k + ''); |
|
4412 IriSP.jQuery(e.node).attr('title', frames[i].mytweetsID[k].title); |
|
4413 IriSP.jQuery(e.node).attr('begin', frames[i].mytweetsID[k].timeframe); |
|
4414 } |
|
4415 } |
|
4416 } |
|
4417 } |
|
4418 } |
|
4419 |
|
4420 } |
|
4421 // DRAW UI :: resize border and bgd |
|
4422 this.paperBackground = this.paper.rect(0, 0, this.width, this.height).attr({fill:"#F8F8F8","stroke-width":0.1,opacity: 1}); |
|
4423 |
|
4424 // outer borders |
|
4425 this.outerBorders = []; |
|
4426 this.outerBorders.push(this.paper.rect(0, this.height - 1, this.width, 1).attr({fill:"#ababab",stroke: "none",opacity: 1})); |
|
4427 this.outerBorders.push(this.paper.rect(0, 0, this.width, 1).attr({fill:"#ababab",stroke: "none",opacity: 1})); |
|
4428 |
|
4429 // inner borders |
|
4430 this.innerBorders = []; |
|
4431 this.innerBorders.push(this.paper.rect(1, this.height - 2, this.width, 1).attr({fill:"#efefef",stroke: "none",opacity: 1})); |
|
4432 this.innerBorders.push(this.paper.rect(1, 1, this.width, 1).attr({fill:"#efefef",stroke: "none",opacity: 1})); |
|
4433 this.innerBorders.push(this.paper.rect(1, 1, 1, this.height - 2).attr({fill:"#d0d1d1",stroke: "none",opacity: 0.8})); |
|
4434 this.innerBorders.push(this.paper.rect(this.width - 2, 1, 1, this.height - 2).attr({fill:"#efefef",stroke: "none",opacity: 1})); |
|
4435 |
|
4436 |
|
4437 |
|
4438 this.paperSlider = this.paper.rect(0, 0, 0, this.height).attr({fill:"#D4D5D5", stroke: "none", opacity: 1}); |
|
4439 |
|
4440 // the small white line displayed over the slider. |
|
4441 this.sliderTip = this.paper.rect(0, 0, 1, this.height).attr({fill:"#fc00ff", stroke: "none", opacity: 1}); |
|
4442 // decalage |
|
4443 // tweetSelection = this.paper.rect(-100,-100,5,5).attr({fill:"#fff",stroke: "none",opacity: 1}); |
|
4444 |
|
4445 |
|
4446 this.paperSlider.toBack(); |
|
4447 this.paperBackground.toBack(); |
|
4448 this.sliderTip.toFront(); |
|
4449 } |
|
4450 |
|
4451 |
|
4452 } |
|
4453 |
|
4454 /** update the positionMarker as time passes */ |
|
4455 IriSP.PolemicWidget.prototype.sliderUpdater = function() { |
|
4456 |
|
4457 var time = +this._Popcorn.currentTime(); |
|
4458 var duration = this.getDuration(); |
|
4459 |
|
4460 this.paperSlider.attr("width", time * (this.width / (duration / 1000))); |
|
4461 |
|
4462 this.sliderTip.attr("x", time * (this.width / (duration / 1000))); |
|
4463 }; |
|
4464 |
|
4465 /** reacts to IriSP.search events */ |
|
4466 IriSP.PolemicWidget.prototype.searchHandler = function(searchString) { |
|
4467 if (searchString == "") |
|
4468 return; |
|
4469 |
|
4470 var matches = this._serializer.searchTweetsOccurences(searchString); |
|
4471 |
|
4472 if (IriSP.countProperties(matches) > 0) { |
|
4473 this._Popcorn.trigger("IriSP.search.matchFound"); |
|
4474 } else { |
|
4475 this._Popcorn.trigger("IriSP.search.noMatchFound"); |
|
4476 } |
|
4477 |
|
4478 |
|
4479 // decrease the opacity of the other elements. |
|
4480 for (var id in this.svgElements) { |
|
4481 var e = this.svgElements[id]; |
|
4482 e.attr({fill: e.color, opacity: 0.4}); |
|
4483 } |
|
4484 |
|
4485 |
|
4486 for (var id in matches) { |
|
4487 if (this.svgElements.hasOwnProperty(id)) { |
|
4488 var e = this.svgElements[id]; |
|
4489 this.svgElements[id].attr({fill: "#fc00ff", opacity: 1}); |
|
4490 } |
|
4491 } |
|
4492 |
|
4493 this.oldSearchMatches = matches; |
|
4494 }; |
|
4495 |
|
4496 /** reacts to IriSP.search.cleared messages */ |
|
4497 IriSP.PolemicWidget.prototype.searchFieldClearedHandler = function() { |
|
4498 for (var id in this.svgElements) { |
|
4499 var e = this.svgElements[id]; |
|
4500 e.attr({fill: e.color, opacity: 1}); |
|
4501 } |
|
4502 }; |
|
4503 |
|
4504 /** reacts to IriSP.search.closed messages by clearing the highlighted elements */ |
|
4505 IriSP.PolemicWidget.prototype.searchFieldClosedHandler = function() { |
|
4506 for (var id in this.svgElements) { |
|
4507 var e = this.svgElements[id]; |
|
4508 e.attr({fill: e.color, opacity: 1}); |
|
4509 } |
|
4510 |
|
4511 }; |
|
4512 |
|
4513 IriSP.PolemicWidget.prototype.showAnnotation = function(id) { |
|
4514 if (this.svgElements.hasOwnProperty(id)) { |
|
4515 var e = this.svgElements[id]; |
|
4516 this.TooltipWidget.show(e.title, e.attr("fill"), e.x - 103, e.y - 160); |
|
4517 } |
|
4518 }; |
|
4519 IriSP.SegmentsWidget = function(Popcorn, config, Serializer) { |
|
4520 |
|
4521 var self = this; |
|
4522 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
4523 this.oldSearchMatches = []; |
|
4524 |
|
4525 // event handlers |
|
4526 this._Popcorn.listen("IriSP.search", function(searchString) { self.searchHandler.call(self, searchString); }); |
|
4527 this._Popcorn.listen("IriSP.search.closed", function() { self.searchFieldClosedHandler.call(self); }); |
|
4528 this._Popcorn.listen("IriSP.search.cleared", function() { self.searchFieldClearedHandler.call(self); }); |
|
4529 |
|
4530 this.defaultColors = ["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"] |
|
4531 }; |
|
4532 |
|
4533 IriSP.SegmentsWidget.prototype = new IriSP.Widget(); |
|
4534 |
|
4535 IriSP.SegmentsWidget.prototype.draw = function() { |
|
4536 |
|
4537 var self = this; |
|
4538 var annotations = this._serializer._data.annotations; |
|
4539 |
|
4540 this.selector.addClass("Ldt-SegmentsWidget"); |
|
4541 this.selector.append(Mustache.to_html(IriSP.overlay_marker_template)); |
|
4542 |
|
4543 this.positionMarker = this.selector.find(".Ldt-SegmentPositionMarker"); |
|
4544 |
|
4545 this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.positionUpdater)); |
|
4546 var duration = this.getDuration(); |
|
4547 |
|
4548 if (this.cinecast_version) { |
|
4549 var segments_annotations = IriSP.underscore.filter( |
|
4550 this._serializer._data.annotations, |
|
4551 function(_a) { |
|
4552 return _a.type == "cinecast:MovieExtract"; |
|
4553 } |
|
4554 ); |
|
4555 } |
|
4556 else { |
|
4557 |
|
4558 var view_type = this._serializer.getChapitrage(); |
|
4559 if (typeof(view_type) === "undefined") { |
|
4560 view_type = this._serializer.getNonTweetIds()[0]; |
|
4561 } |
|
4562 |
|
4563 |
|
4564 var i = 0; |
|
4565 |
|
4566 var segments_annotations = []; |
|
4567 |
|
4568 for (i = 0; i < annotations.length; i++) { |
|
4569 var annotation = annotations[i]; |
|
4570 |
|
4571 /* filter the annotations whose type is not the one we want */ |
|
4572 if (view_type != "" && typeof(annotation.meta) !== "undefined" && typeof(annotation.meta["id-ref"]) !== "undefined" |
|
4573 && annotation.meta["id-ref"] != view_type) { |
|
4574 continue; |
|
4575 } |
|
4576 |
|
4577 segments_annotations.push(annotation); |
|
4578 } |
|
4579 } |
|
4580 var _w = this.selector.width(); |
|
4581 var lastSegment = IriSP.underscore.max(segments_annotations, function(annotation) { return annotation.end; }); |
|
4582 |
|
4583 for (i = 0; i < segments_annotations.length; i++) { |
|
4584 |
|
4585 var annotation = segments_annotations[i]; |
|
4586 var begin = (+ annotation.begin); |
|
4587 var end = (+ annotation.end); |
|
4588 var id = annotation.id; |
|
4589 |
|
4590 var startPixel = Math.floor(_w * (begin / duration)); |
|
4591 |
|
4592 var endPixel = Math.floor(_w * (end / duration)); |
|
4593 if (annotation.id !== lastSegment.id) |
|
4594 var pxWidth = endPixel - startPixel -1; |
|
4595 else |
|
4596 /* the last segment has no segment following it */ |
|
4597 var pxWidth = endPixel - startPixel; |
|
4598 |
|
4599 var divTitle = this.cinecast_version |
|
4600 ? annotation.content.data |
|
4601 : annotation.content.title + ( annotation.content.title ? "<br />" : "" ) + annotation.content.description.replace(/(^.{120,140})[\s].+$/,'$1…'); |
|
4602 |
|
4603 var thumbUrl = annotation.meta.thumbnail || ''; |
|
4604 |
|
4605 var hexa_color = typeof(annotation.content.color) !== "undefined" |
|
4606 ? '#' + IriSP.DEC_HEXA_COLOR(annotation.content.color) |
|
4607 : typeof(annotation.color) !== "undefined" |
|
4608 ? '#' + IriSP.DEC_HEXA_COLOR(annotation.color) |
|
4609 : this.defaultColors[i % this.defaultColors.length]; |
|
4610 |
|
4611 /* |
|
4612 if (hexa_color === "FFCC00") |
|
4613 hexa_color = "333"; |
|
4614 */ |
|
4615 if (hexa_color.length == 5) |
|
4616 hexa_color = hexa_color + '00'; |
|
4617 |
|
4618 |
|
4619 var annotationTemplate = Mustache.to_html(IriSP.annotation_template, |
|
4620 {"divTitle" : divTitle, "id" : id, "startPixel" : startPixel, |
|
4621 "pxWidth" : pxWidth, "hexa_color" : hexa_color, |
|
4622 "seekPlace" : Math.round(begin/1000), "thumbnailUrl": thumbUrl}); |
|
4623 |
|
4624 |
|
4625 this.selector.append(annotationTemplate); |
|
4626 |
|
4627 /* add a special class to the last segment and change its border */ |
|
4628 if (annotation.id === lastSegment.id) { |
|
4629 IriSP.jqId(id).addClass("Ldt-lastSegment").css("border-color", hexa_color); |
|
4630 } |
|
4631 } |
|
4632 // react to mediafragment messages. |
|
4633 this._Popcorn.listen("IriSP.Mediafragment.showAnnotation", |
|
4634 function(id, divTitle) { |
|
4635 |
|
4636 var divObject = IriSP.jqId(id); |
|
4637 if (divObject.length) { |
|
4638 divObject.fadeTo(0,1); |
|
4639 var offset_x = divObject.position().left + divObject.outerWidth() / 2; |
|
4640 self.TooltipWidget.show(divObject.attr("title"), IriSP.jQuery(this).css("background-color"), offset_x, 0); |
|
4641 IriSP.jQuery(document).one("mousemove", function() { divObject.fadeTo(0,.5); |
|
4642 self.TooltipWidget.hide(); }); |
|
4643 } |
|
4644 }); |
|
4645 |
|
4646 this.selector.find(".Ldt-iri-chapter") |
|
4647 .fadeTo(0, .5) |
|
4648 .click(function() { |
|
4649 self._Popcorn.trigger("IriSP.SegmentsWidget.click", this.id); |
|
4650 self._Popcorn.currentTime(IriSP.jQuery(this).attr("data-seek")); |
|
4651 }) |
|
4652 .mouseover( function(event) { |
|
4653 var divObject = IriSP.jQuery(this); |
|
4654 divObject.fadeTo(0,1); |
|
4655 var offset_x = divObject.position().left + divObject.outerWidth() / 2; |
|
4656 var thumb = divObject.attr("thumbnail-url"); |
|
4657 var txt = divObject.attr("title") + (thumb && thumb.length ? '<br /><img src="' + thumb + '" />' : ''); |
|
4658 self.TooltipWidget.show(txt, IriSP.jQuery(this).css("background-color"), offset_x, 0); |
|
4659 }) |
|
4660 .mouseout(function(){ |
|
4661 IriSP.jQuery(this).fadeTo(0,.5); |
|
4662 self.TooltipWidget.hide(); |
|
4663 }); |
|
4664 }; |
|
4665 |
|
4666 /* restores the view after a search */ |
|
4667 IriSP.SegmentsWidget.prototype.clear = function() { |
|
4668 this.selector.children(".Ldt-iri-chapter").fadeTo(0,.5); |
|
4669 }; |
|
4670 |
|
4671 IriSP.SegmentsWidget.prototype.clickHandler = function(annotation) { |
|
4672 this._Popcorn.trigger("IriSP.SegmentsWidget.click", annotation.id); |
|
4673 var begin = (+ annotation.begin) / 1000; |
|
4674 this._Popcorn.currentTime(Math.round(begin)); |
|
4675 }; |
|
4676 |
|
4677 IriSP.SegmentsWidget.prototype.searchHandler = function(searchString) { |
|
4678 |
|
4679 if (searchString == "") |
|
4680 return; |
|
4681 |
|
4682 var matches = this._serializer.searchOccurences(searchString); |
|
4683 |
|
4684 if (IriSP.countProperties(matches) > 0) { |
|
4685 this._Popcorn.trigger("IriSP.search.matchFound"); |
|
4686 } else { |
|
4687 this._Popcorn.trigger("IriSP.search.noMatchFound"); |
|
4688 } |
|
4689 |
|
4690 // un-highlight all the blocks |
|
4691 this.selector.children(".Ldt-iri-chapter").css("opacity", 0.1); |
|
4692 |
|
4693 // then highlight the ones with matches. |
|
4694 for (var id in matches) { |
|
4695 var factor = 0.5 + matches[id] * 0.2; |
|
4696 this.selector.find("#"+id).dequeue(); |
|
4697 this.selector.find("#"+id).animate({opacity:factor}, 200); |
|
4698 } |
|
4699 |
|
4700 |
|
4701 this.oldSearchMatches = matches; |
|
4702 }; |
|
4703 |
|
4704 IriSP.SegmentsWidget.prototype.searchFieldClearedHandler = function() { |
|
4705 this.clear(); |
|
4706 }; |
|
4707 |
|
4708 IriSP.SegmentsWidget.prototype.searchFieldClosedHandler = function() { |
|
4709 this.clear(); |
|
4710 }; |
|
4711 |
|
4712 IriSP.SegmentsWidget.prototype.positionUpdater = function() { |
|
4713 var duration = this.getDuration() / 1000; |
|
4714 var time = this._Popcorn.currentTime(); |
|
4715 //var position = ((time / duration) * 100).toFixed(2); |
|
4716 var position = ((time / duration) * 100).toFixed(2); |
|
4717 |
|
4718 this.positionMarker.css("left", position + "%"); |
|
4719 }; |
|
4720 |
|
4721 IriSP.SegmentsWidget.prototype.showAnnotation = function() { |
|
4722 |
|
4723 }; |
|
4724 /** A widget to create a new segment */ |
|
4725 IriSP.SliceWidget = function(Popcorn, config, Serializer) { |
|
4726 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
4727 |
|
4728 }; |
|
4729 |
|
4730 IriSP.SliceWidget.prototype = new IriSP.Widget(); |
|
4731 |
|
4732 IriSP.SliceWidget.prototype.draw = function() { |
|
4733 var templ = Mustache.to_html(IriSP.sliceWidget_template); |
|
4734 this.selector.append(templ); |
|
4735 |
|
4736 this.sliceZone = this.selector.find(".Ldt-sliceZone"); |
|
4737 |
|
4738 /* global variables used to keep the position and width |
|
4739 of the zone. |
|
4740 */ |
|
4741 this.zoneLeft = 0; |
|
4742 this.zoneWidth = 0; |
|
4743 |
|
4744 this.leftHandle = this.selector.find(".Ldt-sliceLeftHandle"); |
|
4745 this.rightHandle = this.selector.find(".Ldt-sliceRightHandle"); |
|
4746 |
|
4747 var left = this.selector.offset().left; |
|
4748 var top = this.selector.offset().top; |
|
4749 |
|
4750 // a bug in firefox makes it use the wrong format |
|
4751 if (!IriSP.jQuery.browser.mozilla) { |
|
4752 // contain the handles correctly - we cannot set |
|
4753 // containment: parent because it wouldn't allow to select the |
|
4754 // whole slice, so we have to compute a box in which the slice is |
|
4755 // allowed to move. |
|
4756 var containment = [left - 8, top, this.selector.width() + left, top]; |
|
4757 |
|
4758 // var containment = [left - 16, top, this.selector.width() + left - 8, top]; |
|
4759 this.leftHandle.draggable({axis: "x", |
|
4760 drag: IriSP.wrap(this, this.leftHandleDragged), |
|
4761 containment: containment |
|
4762 }); |
|
4763 |
|
4764 containment = [left, top, this.selector.width() + left, top]; |
|
4765 // containment = [left, top, this.selector.width() + left - 8, top]; |
|
4766 this.rightHandle.draggable({axis: "x", |
|
4767 drag: IriSP.wrap(this, this.rightHandleDragged), |
|
4768 containment: containment |
|
4769 }); |
|
4770 |
|
4771 } else { // firefox |
|
4772 // we need to define a containment specific to firefox. |
|
4773 |
|
4774 var containment = [left - 16, top, this.selector.width() + left - 8, top]; |
|
4775 this.leftHandle.draggable({axis: "x", |
|
4776 drag: IriSP.wrap(this, this.leftHandleDragged), |
|
4777 containment: containment |
|
4778 }); |
|
4779 |
|
4780 containment = [left, top, this.selector.width() + left - 8, top]; |
|
4781 this.rightHandle.draggable({axis: "x", |
|
4782 drag: IriSP.wrap(this, this.rightHandleDragged), |
|
4783 containment: containment |
|
4784 }); |
|
4785 } |
|
4786 |
|
4787 this.leftHandle.css("position", "absolute"); |
|
4788 this.rightHandle.css("position", "absolute"); |
|
4789 |
|
4790 this._Popcorn.listen("IriSP.SliceWidget.position", |
|
4791 IriSP.wrap(this, this.positionSliceHandler)); |
|
4792 |
|
4793 this._Popcorn.listen("IriSP.SliceWidget.show", IriSP.wrap(this, this.show)); |
|
4794 this._Popcorn.listen("IriSP.SliceWidget.hide", IriSP.wrap(this, this.hide)); |
|
4795 this.selector.hide(); |
|
4796 }; |
|
4797 |
|
4798 /** responds to an "IriSP.SliceWidget.position" message |
|
4799 @param params an array with the first element being the left distance in |
|
4800 percents and the second element the width of the slice in pixels |
|
4801 */ |
|
4802 IriSP.SliceWidget.prototype.positionSliceHandler = function(params) { |
|
4803 left = params[0]; |
|
4804 width = params[1]; |
|
4805 |
|
4806 this.zoneLeft = left; |
|
4807 this.zoneWidth = width; |
|
4808 this.sliceZone.css("left", left + "px"); |
|
4809 this.sliceZone.css("width", width + "px"); |
|
4810 this.leftHandle.css("left", (left - 7) + "px"); |
|
4811 this.rightHandle.css("left", left + width + "px"); |
|
4812 |
|
4813 this._leftHandleOldLeft = left - 7; |
|
4814 this._rightHandleOldLeft = left + width; |
|
4815 }; |
|
4816 |
|
4817 /** handle a dragging of the left handle */ |
|
4818 IriSP.SliceWidget.prototype.leftHandleDragged = function(event, ui) { |
|
4819 /* we have a special variable, this._leftHandleOldLeft, to keep the |
|
4820 previous position of the handle. We do that to know in what direction |
|
4821 is the handle being dragged |
|
4822 */ |
|
4823 |
|
4824 var currentX = this.leftHandle.offset().left; |
|
4825 var rightHandleX = Math.floor(this.rightHandle.position()["left"]); |
|
4826 |
|
4827 var container_offset = this.selector.offset().left; |
|
4828 |
|
4829 if (Math.floor(ui.position.left) >= rightHandleX - 7) { |
|
4830 /* prevent the handle from moving past the right handle */ |
|
4831 ui.position.left = rightHandleX - 7; |
|
4832 } |
|
4833 |
|
4834 this.zoneWidth = rightHandleX - Math.floor(ui.position.left) - 7; |
|
4835 this.zoneLeft = Math.floor(ui.position.left) + 8; |
|
4836 |
|
4837 this.sliceZone.css("width", this.zoneWidth); |
|
4838 this.sliceZone.css("left", this.zoneLeft + "px"); |
|
4839 |
|
4840 this._leftHandleOldLeft = ui.position.left; |
|
4841 this.broadcastChanges(); |
|
4842 |
|
4843 }; |
|
4844 |
|
4845 /** handle a dragging of the right handle */ |
|
4846 IriSP.SliceWidget.prototype.rightHandleDragged = function(event, ui) { |
|
4847 /* we have a special variable, this._leftHandleOldLeft, to keep the |
|
4848 previous position of the handle. We do that to know in what direction |
|
4849 is the handle being dragged |
|
4850 */ |
|
4851 |
|
4852 var currentX = this.leftHandle.position()["left"]; |
|
4853 var leftHandleX = Math.floor(this.leftHandle.position()["left"]); |
|
4854 |
|
4855 var container_offset = this.selector.offset().left + this.selector.width(); |
|
4856 |
|
4857 if (Math.floor(ui.position.left) < leftHandleX + 7) { |
|
4858 /* prevent the handle from moving past the left handle */ |
|
4859 ui.position.left = leftHandleX + 7; |
|
4860 } |
|
4861 |
|
4862 this.zoneWidth = Math.floor(ui.position.left) - (leftHandleX + 7); |
|
4863 |
|
4864 this.sliceZone.css("width", this.zoneWidth); |
|
4865 //this.sliceZone.css("left", this.zoneLeft + "px"); |
|
4866 this._rightHandleOldLeft = Math.floor(this._rightHandleOldLeft); |
|
4867 this.broadcastChanges(); |
|
4868 }; |
|
4869 |
|
4870 /** tell to the world that the coordinates of the slice have |
|
4871 changed |
|
4872 */ |
|
4873 IriSP.SliceWidget.prototype.broadcastChanges = function() { |
|
4874 var leftPercent = (this.zoneLeft / this.selector.width()) * 100; |
|
4875 var zonePercent = (this.zoneWidth / this.selector.width()) * 100; |
|
4876 |
|
4877 this._Popcorn.trigger("IriSP.SliceWidget.zoneChange", [leftPercent, zonePercent]); |
|
4878 }; |
|
4879 |
|
4880 IriSP.SliceWidget.prototype.show = function() { |
|
4881 this.selector.show(); |
|
4882 }; |
|
4883 |
|
4884 IriSP.SliceWidget.prototype.hide = function() { |
|
4885 this.selector.hide(); |
|
4886 };IriSP.SliderWidget = function(Popcorn, config, Serializer) { |
|
4887 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
4888 }; |
|
4889 |
|
4890 IriSP.SliderWidget.prototype = new IriSP.Widget(); |
|
4891 |
|
4892 IriSP.SliderWidget.prototype.draw = function() { |
|
4893 var self = this; |
|
4894 |
|
4895 this.selector.append(Mustache.to_html(IriSP.sliderWidget_template, {})); |
|
4896 this.selector.addClass("Ldt-SliderMinimized"); |
|
4897 |
|
4898 this.sliderBackground = this.selector.find(".Ldt-sliderBackground"); |
|
4899 this.sliderForeground = this.selector.find(".Ldt-sliderForeground"); |
|
4900 this.positionMarker = this.selector.find(".Ldt-sliderPositionMarker"); |
|
4901 |
|
4902 |
|
4903 // a special variable to stop methods from tinkering |
|
4904 // with the positionMarker when the user is dragging it |
|
4905 this.draggingOngoing = false; |
|
4906 |
|
4907 // another special variable used by the timeout handler to |
|
4908 // open or close the slider. |
|
4909 this.sliderMaximized = false; |
|
4910 this.timeOutId = null; |
|
4911 |
|
4912 |
|
4913 this.positionMarker.draggable({axis: "x", |
|
4914 start: IriSP.wrap(this, this.positionMarkerDraggingStartedHandler), |
|
4915 stop: IriSP.wrap(this, this.positionMarkerDraggedHandler), |
|
4916 containment: "parent" |
|
4917 }); |
|
4918 this.positionMarker.css("position", "absolute"); |
|
4919 |
|
4920 this.sliderBackground.click(function(event) { self.backgroundClickHandler.call(self, event); }); |
|
4921 this.sliderForeground.click(function(event) { self.foregroundClickHandler.call(self, event); }); |
|
4922 |
|
4923 this.selector.hover(IriSP.wrap(this, this.mouseOverHandler), IriSP.wrap(this, this.mouseOutHandler)); |
|
4924 |
|
4925 // update the positions |
|
4926 this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.sliderUpdater)); |
|
4927 |
|
4928 // special messages : |
|
4929 this._Popcorn.listen("IriSP.PlayerWidget.MouseOver", IriSP.wrap(this, this.mouseOverHandler)); |
|
4930 this._Popcorn.listen("IriSP.PlayerWidget.MouseOut", IriSP.wrap(this, this.mouseOutHandler)); |
|
4931 }; |
|
4932 |
|
4933 /* update the slider and the position marker as time passes */ |
|
4934 IriSP.SliderWidget.prototype.sliderUpdater = function() { |
|
4935 if(this.draggingOngoing || this._disableUpdate) |
|
4936 return; |
|
4937 |
|
4938 var time = this._Popcorn.currentTime(); |
|
4939 |
|
4940 var duration = this.getDuration() / 1000; |
|
4941 var percents = time / duration; |
|
4942 |
|
4943 /* we do these complicated calculations to center exactly |
|
4944 the position Marker */ |
|
4945 |
|
4946 var divWidth = this.selector.width(); |
|
4947 var pixels = Math.floor(this.selector.width() * percents); |
|
4948 var positionMarker_width = this.positionMarker.width(); |
|
4949 var correction = (positionMarker_width / 2); |
|
4950 |
|
4951 /* check that we don't leave the left side */ |
|
4952 var newPos = pixels - correction; |
|
4953 if (newPos <= 0) |
|
4954 newPos = 0; |
|
4955 |
|
4956 /* check that we don't leave the right side */ |
|
4957 var rightEdgePos = pixels + 1 * correction; |
|
4958 |
|
4959 if (rightEdgePos >= divWidth) |
|
4960 newPos = divWidth - 1 * correction - 1; |
|
4961 |
|
4962 this.sliderForeground.css("width", pixels + "px"); |
|
4963 this.positionMarker.css("left", newPos + "px"); |
|
4964 |
|
4965 }; |
|
4966 |
|
4967 IriSP.SliderWidget.prototype.backgroundClickHandler = function(event) { |
|
4968 /* this piece of code is a little bit convoluted - here's how it works : |
|
4969 we want to handle clicks on the progress bar and convert those to seeks in the media. |
|
4970 However, jquery only gives us a global position, and we want a number of pixels relative |
|
4971 to our container div, so we get the parent position, and compute an offset to this position, |
|
4972 and finally compute the progress ratio in the media. |
|
4973 Finally we multiply this ratio with the duration to get the correct time |
|
4974 */ |
|
4975 |
|
4976 var parentOffset = this.sliderBackground.parent().offset(); |
|
4977 var width = this.sliderBackground.width(); |
|
4978 var relX = event.pageX - parentOffset.left; |
|
4979 |
|
4980 var duration = this.getDuration() / 1000; |
|
4981 var newTime = ((relX / width) * duration).toFixed(2); |
|
4982 |
|
4983 this._Popcorn.currentTime(newTime); |
|
4984 }; |
|
4985 |
|
4986 /* same function as the previous one, except that it handles clicks |
|
4987 on the foreground element */ |
|
4988 IriSP.SliderWidget.prototype.foregroundClickHandler = function(event) { |
|
4989 var parentOffset = this.sliderForeground.parent().offset(); |
|
4990 var width = this.sliderBackground.width(); |
|
4991 var relX = event.pageX - parentOffset.left; |
|
4992 |
|
4993 var duration = this.getDuration() / 1000; |
|
4994 var newTime = ((relX / width) * duration).toFixed(2); |
|
4995 |
|
4996 this._Popcorn.currentTime(newTime); |
|
4997 }; |
|
4998 |
|
4999 /* handles mouse over the slider */ |
|
5000 IriSP.SliderWidget.prototype.mouseOverHandler = function(event) { |
|
5001 |
|
5002 if (this.timeOutId !== null) { |
|
5003 window.clearTimeout(this.timeOutId); |
|
5004 } |
|
5005 |
|
5006 this.sliderMaximized = true; |
|
5007 |
|
5008 this.sliderBackground.animate({"height": "9px"}, 100); |
|
5009 this.sliderForeground.animate({"height": "9px"}, 100); |
|
5010 this.positionMarker.animate({"height": "9px", "width": "9px"}, 100); |
|
5011 //this.positionMarker.css("margin-top", "-4px"); |
|
5012 |
|
5013 // this.selector.removeClass("Ldt-SliderMinimized"); |
|
5014 // this.selector.addClass("Ldt-SliderMaximized"); |
|
5015 }; |
|
5016 |
|
5017 /* handles when the mouse leaves the slider */ |
|
5018 IriSP.SliderWidget.prototype.mouseOutHandler = function(event) { |
|
5019 |
|
5020 this.timeOutId = window.setTimeout(IriSP.wrap(this, this.minimizeOnTimeout), |
|
5021 this.minimize_period); |
|
5022 }; |
|
5023 |
|
5024 IriSP.SliderWidget.prototype.minimizeOnTimeout = function(event) { |
|
5025 this.sliderBackground.animate({"height": "5px"}, 100); |
|
5026 this.sliderForeground.animate({"height": "5px"}, 100); |
|
5027 this.positionMarker.animate({"height": "5px", "width": "5px"}, 100); |
|
5028 this.positionMarker.css("margin-top", "0px"); |
|
5029 this.sliderMinimized = true; |
|
5030 |
|
5031 // this.selector.removeClass("Ldt-SliderMaximized"); |
|
5032 // this.selector.addClass("Ldt-SliderMinimized"); |
|
5033 |
|
5034 }; |
|
5035 |
|
5036 // called when the user starts dragging the position indicator |
|
5037 IriSP.SliderWidget.prototype.positionMarkerDraggingStartedHandler = function(event, ui) { |
|
5038 this.draggingOngoing = true; |
|
5039 }; |
|
5040 |
|
5041 IriSP.SliderWidget.prototype.positionMarkerDraggedHandler = function(event, ui) { |
|
5042 |
|
5043 /* this._disableUpdate = true; // disable slider position updates while dragging is ongoing. |
|
5044 window.setTimeout(IriSP.wrap(this, function() { this._disableUpdate = false; }), 500); |
|
5045 */ |
|
5046 var parentOffset = this.sliderForeground.parent().offset(); |
|
5047 var width = this.sliderBackground.width(); |
|
5048 var relX = event.originalEvent.pageX - parentOffset.left; |
|
5049 |
|
5050 var duration = this.getDuration() / 1000; |
|
5051 var newTime = ((relX / width) * duration).toFixed(2); |
|
5052 this._Popcorn.currentTime(newTime); |
|
5053 |
|
5054 this.draggingOngoing = false; |
|
5055 }; |
|
5056 |
|
5057 /** @class The constructor for the sparkline widget */ |
|
5058 IriSP.SparklineWidget = function(Popcorn, config, Serializer) { |
|
5059 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
5060 |
|
5061 this._oldAnnotation = null; |
|
5062 this._results = []; |
|
5063 |
|
5064 this.slices = this._config.slices || Math.floor(this.width/20); |
|
5065 if (!this.width) { |
|
5066 this.width = this.selector.width(); |
|
5067 } |
|
5068 if (!this.height) { |
|
5069 this.height = 40; |
|
5070 } |
|
5071 this.selector.css("height", this.height + "px"); |
|
5072 if (this._config.background) { |
|
5073 this.selector.css("background", this._config.background); |
|
5074 } |
|
5075 }; |
|
5076 |
|
5077 |
|
5078 IriSP.SparklineWidget.prototype = new IriSP.Widget(); |
|
5079 |
|
5080 IriSP.SparklineWidget.prototype.clear = function() { |
|
5081 |
|
5082 }; |
|
5083 |
|
5084 /** draw the sparkline using jquery sparkline */ |
|
5085 IriSP.SparklineWidget.prototype.draw = function() { |
|
5086 this.duration = this.getDuration(); |
|
5087 this.paper = new Raphael(this.selector[0], this.width, this.height); |
|
5088 var _this = this; |
|
5089 |
|
5090 var views = this._serializer._data.views; |
|
5091 var stat_view; |
|
5092 if (!IriSP.null_or_undefined(views)) { |
|
5093 for (var i = 0; i < views.length; i++) { |
|
5094 var view = views[i]; |
|
5095 if (view.id === "stat") { |
|
5096 stat_view = view; |
|
5097 break; |
|
5098 } |
|
5099 } |
|
5100 } |
|
5101 |
|
5102 var _ = IriSP.underscore; |
|
5103 // If we've found the correct view, feed the directly the data from the view |
|
5104 // to jquery sparkline. Otherwise, compute it ourselves. |
|
5105 if (!IriSP.null_or_undefined(stat_view)) { |
|
5106 //console.log("sparklinewidget : using stats embedded in the json"); |
|
5107 var _results = stat_view.meta.stat.split(","); |
|
5108 } else { |
|
5109 var _annotations = this._serializer._data.annotations; |
|
5110 if (this.cinecast_version) { |
|
5111 _annotations = _(_annotations).filter(function(_a) { |
|
5112 return _a.type !== "cinecast:MovieExtract"; |
|
5113 }); |
|
5114 } |
|
5115 var _sliceDuration = Math.floor( this.duration / this.slices), |
|
5116 _results = _(_.range(this.slices)).map(function(_i) { |
|
5117 return _(_annotations).filter(function(_a){ |
|
5118 return (_a.begin <= (1 + _i) * _sliceDuration) && (_a.end >= _i * _sliceDuration) |
|
5119 }).length; |
|
5120 }); |
|
5121 } |
|
5122 var _max = Math.max(1, _(_results).max()), |
|
5123 _h = this.height, |
|
5124 _scale = (_h - this.lineWidth) / _max, |
|
5125 _width = this.width / this.slices, |
|
5126 _y = _(_results).map(function(_v) { |
|
5127 return _h - (_scale * _v); |
|
5128 }), |
|
5129 _d = _(_y).reduce(function(_memo, _v, _k) { |
|
5130 return _memo + ( _k |
|
5131 ? 'C' + (_k * _width) + ' ' + _y[_k - 1] + ' ' + (_k * _width) + ' ' + _v + ' ' + ((_k + .5) * _width) + ' ' + _v |
|
5132 : 'M0 ' + _v + 'L' + (.5*_width) + ' ' + _v ) |
|
5133 },'') + 'L' + this.width + ' ' + _y[_y.length - 1], |
|
5134 _d2 = _d + 'L' + this.width + ' ' + this.height + 'L0 ' + this.height; |
|
5135 this.paper.path(_d2).attr({ |
|
5136 "stroke" : "none", |
|
5137 "fill" : this.fillColor |
|
5138 }); |
|
5139 |
|
5140 this.paper.path(_d).attr({ |
|
5141 "fill" : "none", |
|
5142 "stroke" : this.lineColor, |
|
5143 "stroke-width" : this.lineWidth |
|
5144 }); |
|
5145 |
|
5146 this.rectangleProgress = this.paper.rect(0,0,0,this.height) |
|
5147 .attr({ |
|
5148 "stroke" : "none", |
|
5149 "fill" : "#808080", |
|
5150 "opacity" : .3 |
|
5151 }); |
|
5152 this.ligneProgress = this.paper.path("M0 0L0 "+this.height).attr({"stroke":"#ff00ff", "line-width" : 2}); |
|
5153 // save the results in an array so that we can re-use them when a new annotation |
|
5154 // is added. |
|
5155 this._results = _results; |
|
5156 |
|
5157 this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeUpdateHandler)); |
|
5158 // this._Popcorn.listen("IriSP.createAnnotationWidget.addedAnnotation", IriSP.wrap(this, this.handleNewAnnotation)); |
|
5159 |
|
5160 this.selector.click(IriSP.wrap(this, this.clickHandler)); |
|
5161 }; |
|
5162 |
|
5163 /** react to a timeupdate event */ |
|
5164 IriSP.SparklineWidget.prototype.timeUpdateHandler = function() { |
|
5165 var _currentTime = this._Popcorn.currentTime(), |
|
5166 _x = (1000 * _currentTime / this.duration) * this.width; |
|
5167 this.rectangleProgress.attr({ |
|
5168 "width" : _x |
|
5169 }); |
|
5170 this.ligneProgress.attr({ |
|
5171 "path" : "M" + _x + " 0L" + _x + " " + this.height |
|
5172 }); |
|
5173 |
|
5174 } |
|
5175 |
|
5176 /** handle clicks on the widget */ |
|
5177 IriSP.SparklineWidget.prototype.clickHandler = function(event) { |
|
5178 var relX = event.pageX - this.selector.offset().left; |
|
5179 var newTime = ((relX / this.width) * this.duration/1000).toFixed(2); |
|
5180 |
|
5181 this._Popcorn.trigger("IriSP.SparklineWidget.clicked", newTime); |
|
5182 this._Popcorn.currentTime(newTime); |
|
5183 }; |
|
5184 |
|
5185 /** react when a new annotation is added */ |
|
5186 IriSP.SparklineWidget.prototype.handleNewAnnotation = function(annotation) { |
|
5187 // var num_columns = this._results.length; |
|
5188 // var duration = this._serializer.getDuration(); |
|
5189 // var time_step = Math.round(duration / num_columns); /* the time interval between two columns */ |
|
5190 // var begin = +annotation.begin; |
|
5191 // var end = +annotation.end; |
|
5192 // |
|
5193 // /* increment all the values between the beginning and the end of the annotation */ |
|
5194 // var index_begin = Math.floor(begin / time_step); |
|
5195 // var index_end = Math.floor(end / time_step); |
|
5196 // |
|
5197 // for (var i = index_begin; i < Math.min(index_end, this._results.length); i++) { |
|
5198 // this._results[i]++; |
|
5199 // } |
|
5200 // |
|
5201 // this.selector.find(".Ldt-sparkLine").sparkline(this._results, {lineColor: "#7492b4", fillColor: "#aeaeb8", |
|
5202 // spotColor: "#b70056", |
|
5203 // width: this.width, height: this.height}); |
|
5204 };IriSP.StackGraphWidget = function(Popcorn, config, Serializer) { |
|
5205 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
5206 } |
|
5207 |
|
5208 IriSP.StackGraphWidget.prototype = new IriSP.Widget(); |
|
5209 |
|
5210 IriSP.StackGraphWidget.prototype.draw = function() { |
|
5211 var _ = IriSP._; |
|
5212 this.height = this._config.height || 50; |
|
5213 this.width = this.selector.width(); |
|
5214 this.slices = this._config.slices || ~~(this.width/(this.streamgraph ? 20 : 5)); |
|
5215 _(this.tags).each(function(_a) { |
|
5216 _a.regexp = new RegExp(_(_a.keywords).map(function(_k) { |
|
5217 return _k.replace(/([\W])/gm,'\\$1'); |
|
5218 }).join("|"),"im") |
|
5219 }); |
|
5220 this.paper = new Raphael(this.selector[0], this.width, this.height); |
|
5221 this.groups = []; |
|
5222 this.duration = this.getDuration(); |
|
5223 |
|
5224 var _annotationType = this._serializer.getTweets(), |
|
5225 _sliceDuration = ~~ ( this.duration / this.slices), |
|
5226 _annotations = this._serializer._data.annotations, |
|
5227 _groupedAnnotations = _(_.range(this.slices)).map(function(_i) { |
|
5228 return _(_annotations).filter(function(_a){ |
|
5229 return (_a.begin <= (1 + _i) * _sliceDuration) && (_a.end >= _i * _sliceDuration) |
|
5230 }); |
|
5231 }), |
|
5232 _max = IriSP._(_groupedAnnotations).max(function(_g) { |
|
5233 return _g.length |
|
5234 }).length, |
|
5235 _scale = this.height / _max, |
|
5236 _width = this.width / this.slices, |
|
5237 _showTitle = !this._config.excludeTitle, |
|
5238 _showDescription = !this._config.excludeDescription; |
|
5239 |
|
5240 |
|
5241 var _paths = _(this.tags).map(function() { |
|
5242 return []; |
|
5243 }); |
|
5244 _paths.push([]); |
|
5245 |
|
5246 for (var i = 0; i < this.slices; i++) { |
|
5247 var _group = _groupedAnnotations[i]; |
|
5248 if (_group) { |
|
5249 var _vol = _(this.tags).map(function() { |
|
5250 return 0; |
|
5251 }); |
|
5252 for (var j = 0; j < _group.length; j++){ |
|
5253 var _txt = (_showTitle ? _group[j].content.title : '') + ' ' + (_showDescription ? _group[j].content.description : '') |
|
5254 var _tags = _(this.tags).map(function(_tag) { |
|
5255 return (_txt.search(_tag.regexp) == -1 ? 0 : 1) |
|
5256 }), |
|
5257 _nbtags = _(_tags).reduce(function(_a,_b) { |
|
5258 return _a + _b; |
|
5259 }, 0); |
|
5260 if (_nbtags) { |
|
5261 IriSP._(_tags).each(function(_v, _k) { |
|
5262 _vol[_k] += (_v / _nbtags); |
|
5263 }); |
|
5264 } |
|
5265 } |
|
5266 var _nbtags = _(_vol).reduce(function(_a,_b) { |
|
5267 return _a + _b; |
|
5268 }, 0), |
|
5269 _nbneutre = _group.length - _nbtags, |
|
5270 _h = _nbneutre * _scale, |
|
5271 _base = this.height - _h; |
|
5272 if (!this.streamgraph) { |
|
5273 this.paper.rect(i * _width, _base, _width - 1, _h ).attr({ |
|
5274 "stroke" : "none", |
|
5275 "fill" : this.defaultcolor |
|
5276 }); |
|
5277 } |
|
5278 _paths[0].push(_base); |
|
5279 for (var j = 0; j < this.tags.length; j++) { |
|
5280 _h = _vol[j] * _scale; |
|
5281 _base = _base - _h; |
|
5282 if (!this.streamgraph) { |
|
5283 this.paper.rect(i * _width, _base, _width - 1, _h ).attr({ |
|
5284 "stroke" : "none", |
|
5285 "fill" : this.tags[j].color |
|
5286 }); |
|
5287 } |
|
5288 _paths[j+1].push(_base); |
|
5289 } |
|
5290 this.groups.push(_(_vol).map(function(_v) { |
|
5291 return _v / _group.length; |
|
5292 })) |
|
5293 } else { |
|
5294 for (var j = 0; j < _paths.length; j++) { |
|
5295 _paths[j].push(this.height); |
|
5296 } |
|
5297 this.groups.push(_(this.tags).map(function() { |
|
5298 return 0; |
|
5299 })); |
|
5300 } |
|
5301 } |
|
5302 |
|
5303 if (this.streamgraph) { |
|
5304 for (var j = _paths.length - 1; j >= 0; j--) { |
|
5305 var _d = _(_paths[j]).reduce(function(_memo, _v, _k) { |
|
5306 return _memo + ( _k |
|
5307 ? 'C' + (_k * _width) + ' ' + _paths[j][_k - 1] + ' ' + (_k * _width) + ' ' + _v + ' ' + ((_k + .5) * _width) + ' ' + _v |
|
5308 : 'M0 ' + _v + 'L' + (.5*_width) + ' ' + _v ) |
|
5309 },'') + 'L' + this.width + ' ' + _paths[j][_paths[j].length - 1] + 'L' + this.width + ' ' + this.height + 'L0 ' + this.height; |
|
5310 this.paper.path(_d).attr({ |
|
5311 "stroke" : "none", |
|
5312 "fill" : (j ? this.tags[j-1].color : this.defaultcolor) |
|
5313 }); |
|
5314 } |
|
5315 } |
|
5316 this.rectangleFocus = this.paper.rect(0,0,_width,this.height) |
|
5317 .attr({ |
|
5318 "stroke" : "none", |
|
5319 "fill" : "#ff00ff", |
|
5320 "opacity" : 0 |
|
5321 }) |
|
5322 this.rectangleProgress = this.paper.rect(0,0,0,this.height) |
|
5323 .attr({ |
|
5324 "stroke" : "none", |
|
5325 "fill" : "#808080", |
|
5326 "opacity" : .3 |
|
5327 }); |
|
5328 this.ligneProgress = this.paper.path("M0 0L0 "+this.height).attr({"stroke":"#ff00ff", "line-width" : 2}) |
|
5329 |
|
5330 this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeUpdateHandler)); |
|
5331 var _this = this; |
|
5332 this.selector |
|
5333 .click(IriSP.wrap(this, this.clickHandler)) |
|
5334 .mousemove(function(_e) { |
|
5335 _this.updateTooltip(_e); |
|
5336 // Trace |
|
5337 var relX = _e.pageX - _this.selector.offset().left; |
|
5338 var _duration = _this.getDuration(); |
|
5339 var _time = parseInt((relX / _this.width) * _duration); |
|
5340 _this._Popcorn.trigger("IriSP.TraceWidget.MouseEvents", { |
|
5341 "widget" : "StackGraphWidget", |
|
5342 "type": "mousemove", |
|
5343 "x": _e.pageX, |
|
5344 "y": _e.pageY, |
|
5345 "time": _time |
|
5346 }); |
|
5347 |
|
5348 }) |
|
5349 .mouseout(function() { |
|
5350 _this.TooltipWidget.hide(); |
|
5351 _this.rectangleFocus.attr({ |
|
5352 "opacity" : 0 |
|
5353 }) |
|
5354 }) |
|
5355 } |
|
5356 |
|
5357 IriSP.StackGraphWidget.prototype.timeUpdateHandler = function() { |
|
5358 var _currentTime = this._Popcorn.currentTime(), |
|
5359 _x = (1000 * _currentTime / this.duration) * this.width; |
|
5360 this.rectangleProgress.attr({ |
|
5361 "width" : _x |
|
5362 }); |
|
5363 this.ligneProgress.attr({ |
|
5364 "path" : "M" + _x + " 0L" + _x + " " + this.height |
|
5365 }) |
|
5366 } |
|
5367 |
|
5368 IriSP.StackGraphWidget.prototype.clickHandler = function(event) { |
|
5369 /* Ctrl-C Ctrl-V'ed from another widget |
|
5370 */ |
|
5371 |
|
5372 var relX = event.pageX - this.selector.offset().left; |
|
5373 var newTime = ((relX / this.width) * this.duration/1000).toFixed(2); |
|
5374 this._Popcorn.trigger("IriSP.StackGraphWidget.clicked", newTime); |
|
5375 this._Popcorn.currentTime(newTime); |
|
5376 }; |
|
5377 |
|
5378 IriSP.StackGraphWidget.prototype.updateTooltip = function(event) { |
|
5379 var _segment = Math.max(0,Math.min(this.groups.length - 1, Math.floor(this.slices * (event.pageX - this.selector.offset().left)/this.width))), |
|
5380 _valeurs = this.groups[_segment], |
|
5381 _width = this.width / this.slices, |
|
5382 _html = '<ul style="list-style: none; margin: 0; padding: 0;">' + IriSP._(this.tags).map(function(_tag, _i) { |
|
5383 return '<li style="clear: both;"><span style="float: left; width: 10px; height: 10px; margin: 2px; background: ' |
|
5384 + _tag.color |
|
5385 + ';"></span>' |
|
5386 + ~~(100 * _valeurs[_i]) |
|
5387 + '% de ' |
|
5388 + _tag.description |
|
5389 + '</li>'; |
|
5390 }).join('') + '</ul>'; |
|
5391 this.TooltipWidget._shown = false; // Vraiment, on ne peut pas ouvrir le widget s'il n'est pas encore ouvert ? |
|
5392 this.TooltipWidget.show('','',(_segment + .5)* this.width / this.slices, 0); |
|
5393 this.TooltipWidget.selector.find(".tip").html(_html); |
|
5394 this.rectangleFocus.attr({ |
|
5395 "x" : _segment * _width, |
|
5396 "opacity" : .4 |
|
5397 }) |
|
5398 } |
|
5399 |
|
5400 IriSP.TagCloudWidget = function(Popcorn, config, Serializer) { |
|
5401 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
5402 } |
|
5403 |
|
5404 IriSP.TagCloudWidget.prototype = new IriSP.Widget(); |
|
5405 |
|
5406 IriSP.TagCloudWidget.prototype.draw = function() { |
|
5407 |
|
5408 var _urlRegExp = /https?:\/\/[0-9a-zA-Z\.%\/-_]+/g, |
|
5409 _stopWords = [ |
|
5410 'aussi', 'and', 'avec', 'aux', 'bien', 'car', 'cette', 'comme', 'dans', 'donc', 'des', 'elle', 'encore', 'entre', 'est', |
|
5411 'être', 'eux', 'faire', 'fait', 'http', 'ici', 'ils', 'les', 'leur', 'leurs', 'mais', 'mes', 'même', 'mon', 'notre', |
|
5412 'non', 'nos', 'nous', 'ont', 'par', 'pas', 'peu', 'peut', 'plus', 'pour', 'que', 'qui', 'sans', 'ses' ,'son', 'sont', 'sur', |
|
5413 'tes', 'très', 'the', 'ton', 'tous', 'tout', 'une', 'votre', 'vos', 'vous' ], |
|
5414 _regexpword = /[^\s\.&;,'"!\?\d\(\)\+\[\]\\\…\-«»:\/]{3,}/g, |
|
5415 _words = {}, |
|
5416 _showTitle = !this._config.excludeTitle, |
|
5417 _showDescription = !this._config.excludeDescription, |
|
5418 _excludePattern = this._config.excludePattern || null, |
|
5419 _tagCount = this._config.tagCount || 30; |
|
5420 if (typeof this._config.excludeWords !== "undefined" && this._config.excludeWords.length) { |
|
5421 IriSP._(this._config.excludeWords).each(function(_w) { |
|
5422 _stopWords.push(_w.toLowerCase()); |
|
5423 }); |
|
5424 } |
|
5425 |
|
5426 IriSP._(this._serializer._data.annotations).each(function(_annotation) { |
|
5427 if (_annotation.content && _annotation.content.description) { |
|
5428 var _txt = (_showTitle ? _annotation.content.title : '') + ' ' + (_showDescription ? _annotation.content.description : '') |
|
5429 IriSP._(_txt.toLowerCase().replace(_urlRegExp, '').match(_regexpword)).each(function(_mot) { |
|
5430 if (_stopWords.indexOf(_mot) == -1 && (_excludePattern == null || !_excludePattern.test(_mot))) { |
|
5431 _words[_mot] = 1 + (_words[_mot] || 0); |
|
5432 } |
|
5433 }) |
|
5434 } |
|
5435 }); |
|
5436 |
|
5437 _words = IriSP._(_words) |
|
5438 .chain() |
|
5439 .map(function(_v, _k) { |
|
5440 return { |
|
5441 "word" : _k, |
|
5442 "count" : _v |
|
5443 } |
|
5444 }) |
|
5445 .filter(function(_v) { |
|
5446 return _v.count > 2; |
|
5447 }) |
|
5448 .sortBy(function(_v) { |
|
5449 return - _v.count; |
|
5450 }) |
|
5451 .first(_tagCount) |
|
5452 .value(); |
|
5453 var _max = _words[0].count, |
|
5454 _min = Math.min(_words[_words.length - 1].count, _max - 1), |
|
5455 _scale = 16 / Math.sqrt(_max - _min), |
|
5456 _this = this, |
|
5457 _html = '<ul>' |
|
5458 + IriSP._(_words) |
|
5459 .chain() |
|
5460 .shuffle() |
|
5461 .map(function(_word) { |
|
5462 var _size = 10 + _scale * Math.sqrt(_word.count - _min); |
|
5463 return '<li class="Ldt-TraceMe" style="font-size:' |
|
5464 + _size |
|
5465 + 'px;">' |
|
5466 + _word.word |
|
5467 + '</li>' |
|
5468 }) |
|
5469 .value() |
|
5470 .join("") |
|
5471 + '</ul>'; |
|
5472 this.selector |
|
5473 .addClass("Ldt-TagCloud") |
|
5474 .html(_html); |
|
5475 this.selector.find("li").click(function() { |
|
5476 var _txt = this.textContent.replace(/(^[\s]+|[\s]+$)/g,''); |
|
5477 _this._Popcorn.trigger("IriSP.search.triggeredSearch", _txt); |
|
5478 }); |
|
5479 this._Popcorn.listen("IriSP.search", IriSP.wrap(this, function(searchString) { |
|
5480 var _rgxp = new RegExp("(" + searchString.replace(/(\W)/g,'\\$1') + ")","gi"); |
|
5481 this.selector.find("li").each(function(_i, _e) { |
|
5482 _e.innerHTML = searchString.length ? |
|
5483 _e.textContent.replace(_rgxp,'<span class="Ldt-TagCloud-actif Ldt-TraceMe">$1</span>') |
|
5484 : _e.textContent; |
|
5485 }); |
|
5486 })); |
|
5487 this._Popcorn.listen("IriSP.search.closed", IriSP.wrap(this, this.endsearch)); |
|
5488 this._Popcorn.listen("IriSP.search.cleared", IriSP.wrap(this, this.endsearch)); |
|
5489 } |
|
5490 |
|
5491 IriSP.TagCloudWidget.prototype.endsearch = function() { |
|
5492 this.selector.find("li").each(function(_i, _e) { |
|
5493 _e.innerHTML = _e.textContent; |
|
5494 }); |
|
5495 } |
|
5496 /* this widget displays a small tooltip */ |
|
5497 IriSP.TooltipWidget = function(Popcorn, config, Serializer) { |
|
5498 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
5499 this._shown = false; |
|
5500 this._displayedText = ""; |
|
5501 this._hideTimeout = -1; |
|
5502 }; |
|
5503 |
|
5504 |
|
5505 IriSP.TooltipWidget.prototype = new IriSP.Widget(); |
|
5506 |
|
5507 IriSP.TooltipWidget.prototype.draw = function() { |
|
5508 var templ = Mustache.to_html(IriSP.tooltipWidget_template); |
|
5509 // position the widget absolutely relative to document. --- NOOOO !!!! |
|
5510 this.selector.css({ |
|
5511 "position": "absolute", |
|
5512 "top": 0, |
|
5513 "left": 0 |
|
5514 }); |
|
5515 this.selector.parent().css({ |
|
5516 "position": "relative" |
|
5517 }); |
|
5518 this.selector.append(templ); |
|
5519 var _this = this; |
|
5520 this.selector.mouseover(function() { |
|
5521 _this.hide(); |
|
5522 }); |
|
5523 this.hide(); |
|
5524 |
|
5525 }; |
|
5526 |
|
5527 IriSP.TooltipWidget.prototype.clear = function() { |
|
5528 this.selector.find(".tiptext").html(""); |
|
5529 }; |
|
5530 |
|
5531 IriSP.TooltipWidget.prototype.show = function(text, color, x, y) { |
|
5532 |
|
5533 if (this._displayedText == text && this._shown) |
|
5534 return; |
|
5535 |
|
5536 this.selector.find(".tipcolor").css("background-color", color); |
|
5537 this._displayedText = text; |
|
5538 this.selector.find(".tiptext").html(text); |
|
5539 |
|
5540 var _tip = this.selector.find(".tip"); |
|
5541 _tip.show(); |
|
5542 _tip.css({ |
|
5543 "left": Math.floor(x - _tip.outerWidth() / 2)+"px", |
|
5544 "top": Math.floor(y - _tip.outerHeight())+"px" |
|
5545 }); |
|
5546 this._shown = true; |
|
5547 }; |
|
5548 |
|
5549 IriSP.TooltipWidget.prototype.hide = function() { |
|
5550 this.selector.find(".tip").hide(); |
|
5551 this._shown = false; |
|
5552 };IriSP.TraceWidget = function(Popcorn, config, Serializer) { |
|
5553 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
5554 this.lastEvent = ""; |
|
5555 var _this = this, |
|
5556 _listeners = { |
|
5557 "IriSP.createAnnotationWidget.addedAnnotation" : 0, |
|
5558 "IriSP.search.open" : 0, |
|
5559 "IriSP.search.closed" : 0, |
|
5560 "IriSP.search" : 0, |
|
5561 "IriSP.search.cleared" : 0, |
|
5562 "IriSP.search.matchFound" : 0, |
|
5563 "IriSP.search.noMatchFound" : 0, |
|
5564 "IriSP.search.triggeredSearch" : 0, |
|
5565 "IriSP.TraceWidget.MouseEvents" : 0, |
|
5566 "play" : 0, |
|
5567 "pause" : 0, |
|
5568 "volumechange" : 0, |
|
5569 "seeked" : 0, |
|
5570 "play" : 0, |
|
5571 "pause" : 0, |
|
5572 "timeupdate" : 2000 |
|
5573 }; |
|
5574 IriSP._(_listeners).each(function(_ms, _listener) { |
|
5575 var _f = function(_arg) { |
|
5576 _this.eventHandler(_listener, _arg); |
|
5577 } |
|
5578 if (_ms) { |
|
5579 _f = IriSP.underscore.throttle(_f, _ms); |
|
5580 } |
|
5581 _this._Popcorn.listen(_listener, _f); |
|
5582 }); |
|
5583 this._Popcorn.listen("timeupdate", IriSP.underscore.throttle(function(_arg) { |
|
5584 _this.eventHandler(_listener, _arg); |
|
5585 })); |
|
5586 |
|
5587 this.tracer = IriSP.TraceManager(IriSP.jQuery).init_trace("test", this._config); |
|
5588 this.tracer.set_default_subject("default_subject"); |
|
5589 this.tracer.trace("StartTracing", { "hello": "world" }); |
|
5590 |
|
5591 } |
|
5592 |
|
5593 IriSP.TraceWidget.prototype = new IriSP.Widget(); |
|
5594 |
|
5595 IriSP.TraceWidget.prototype.draw = function() { |
|
5596 this.mouseLocation = ''; |
|
5597 var _this = this; |
|
5598 IriSP.jQuery(".Ldt-Widget").bind("click mouseover mouseout dragstart dragstop", function(_e) { |
|
5599 var _widget = IriSP.jQuery(this).attr("widget-type"), |
|
5600 _class = _e.target.className; |
|
5601 var _data = { |
|
5602 "type": _e.type, |
|
5603 "x": _e.clientX, |
|
5604 "y": _e.clientY, |
|
5605 "widget": _widget |
|
5606 } |
|
5607 if (typeof _class == "string" && _class.indexOf('Ldt-TraceMe') != -1) { |
|
5608 var _name = _e.target.localName, |
|
5609 _id = _e.target.id, |
|
5610 _text = _e.target.textContent.trim(), |
|
5611 _title = _e.target.title, |
|
5612 _value = _e.target.value; |
|
5613 _data.target = _name + (_id.length ? '#' + IriSP.jqEscape(_id) : '') + (_class.length ? ('.' + IriSP.jqEscape(_class).replace(/\s/g,'.')).replace(/\.Ldt-(Widget|TraceMe)/g,'') : ''); |
|
5614 if (typeof _title == "string" && _title.length && _title.length < 140) { |
|
5615 _data.title = _title; |
|
5616 } |
|
5617 if (typeof _text == "string" && _text.length && _text.length < 140) { |
|
5618 _data.text = _text; |
|
5619 } |
|
5620 if (typeof _value == "string" && _value.length) { |
|
5621 _data.value = _value; |
|
5622 } |
|
5623 _this._Popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data); |
|
5624 } else { |
|
5625 //console.log(_e.type+','+_this.mouseLocation+','+_widget); |
|
5626 if (_e.type == "mouseover") { |
|
5627 if (_this.mouseLocation != _widget) { |
|
5628 _this._Popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data); |
|
5629 } else { |
|
5630 if (typeof _this.moTimeout != "undefined") { |
|
5631 clearTimeout(_this.moTimeout); |
|
5632 delete _this.moTimeout; |
|
5633 } |
|
5634 } |
|
5635 } |
|
5636 if (_e.type == "click") { |
|
5637 _this._Popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data); |
|
5638 } |
|
5639 if (_e.type == "mouseout") { |
|
5640 if (typeof _this.moTimeout != "undefined") { |
|
5641 clearTimeout(_this.moTimeout); |
|
5642 } |
|
5643 _this.moTimeout = setTimeout(function() { |
|
5644 if (_data.widget != _this.mouseLocation) { |
|
5645 _this._Popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data); |
|
5646 } |
|
5647 },100); |
|
5648 } |
|
5649 } |
|
5650 _this.mouseLocation = _widget; |
|
5651 }); |
|
5652 } |
|
5653 |
|
5654 IriSP.TraceWidget.prototype.eventHandler = function(_listener, _arg) { |
|
5655 var _traceName = 'Mdp_'; |
|
5656 if (typeof _arg == "string" || typeof _arg == "number") { |
|
5657 _arg = { "value" : _arg } |
|
5658 } |
|
5659 if (typeof _arg == "undefined") { |
|
5660 _arg = {} |
|
5661 } |
|
5662 switch(_listener) { |
|
5663 case 'IriSP.TraceWidget.MouseEvents': |
|
5664 _traceName += _arg.widget + '_' + _arg.type; |
|
5665 delete _arg.widget; |
|
5666 delete _arg.type; |
|
5667 break; |
|
5668 case 'timeupdate': |
|
5669 case 'play': |
|
5670 case 'pause': |
|
5671 _arg.time = this._Popcorn.currentTime() * 1000; |
|
5672 case 'seeked': |
|
5673 case 'volumechange': |
|
5674 _traceName += 'Popcorn_' + _listener; |
|
5675 break; |
|
5676 default: |
|
5677 _traceName += _listener.replace('IriSP.','').replace('.','_'); |
|
5678 } |
|
5679 this.lastEvent = _traceName; |
|
5680 this.tracer.trace(_traceName, _arg); |
|
5681 if (this._config.js_console) { |
|
5682 console.log("tracer.trace('" + _traceName + "', " + JSON.stringify(_arg) + ");"); |
|
5683 } |
|
5684 } |
|
5685 /* a widget that displays tweet - used in conjunction with the polemicWidget */ |
|
5686 |
|
5687 IriSP.TweetsWidget = function(Popcorn, config, Serializer) { |
|
5688 IriSP.Widget.call(this, Popcorn, config, Serializer); |
|
5689 |
|
5690 this._displayingTweet = false; |
|
5691 this._timeoutId = undefined; |
|
5692 this._hidden = false; /* hidden means that the createAnnotationWidget is shown */ |
|
5693 }; |
|
5694 |
|
5695 |
|
5696 IriSP.TweetsWidget.prototype = new IriSP.Widget(); |
|
5697 |
|
5698 |
|
5699 IriSP.TweetsWidget.prototype.drawTweet = function(annotation) { |
|
5700 if (this._hidden) |
|
5701 return; |
|
5702 |
|
5703 var title = IriSP.formatTweet(annotation.content.title); |
|
5704 var img = annotation.content.img.src; |
|
5705 if (typeof(img) === "undefined" || img === "" || img === "None") { |
|
5706 img = this.default_profile_picture; |
|
5707 } |
|
5708 |
|
5709 var imageMarkup = IriSP.templToHTML("<img src='{{src}}' alt='user image'></img>", |
|
5710 {src : img}); |
|
5711 |
|
5712 if (typeof(IriSP.get_aliased(annotation.meta, ["dc:source", "source"]).content) !== "undefined") { |
|
5713 var tweetContents = JSON.parse(IriSP.get_aliased(annotation.meta, ["dc:source", "source"]).content); |
|
5714 var creator = tweetContents.user.screen_name; |
|
5715 var real_name = tweetContents.user.name; |
|
5716 |
|
5717 imageMarkup = IriSP.templToHTML("<a href='http://twitter.com/{{creator}}'><img src='{{src}}' alt='user image'></img></a>", |
|
5718 {src : img, creator: creator}); |
|
5719 |
|
5720 var formatted_date = new Date(tweetContents.created_at).toLocaleDateString(); |
|
5721 title = IriSP.templToHTML("<a class='Ldt-tweet_userHandle' href='http://twitter.com/{{creator}}'>@{{creator}}</a> - " + |
|
5722 "<div class='Ldt-tweet_realName'>{{real_name}}</div>" + |
|
5723 "<div class='Ldt-tweet_tweetContents'>{{{ contents }}}</div>" + |
|
5724 "<div class='Ldt-tweet_date'>{{ date }}</div>", |
|
5725 {creator: creator, real_name: real_name, contents : title, date : formatted_date}); |
|
5726 |
|
5727 this.selector.find(".Ldt-TweetReply").attr("href", "http://twitter.com/home?status=@" + creator + ":%20"); |
|
5728 |
|
5729 |
|
5730 var rtText = Mustache.to_html("http://twitter.com/home?status=RT @{{creator}}: {{text}}", |
|
5731 {creator: creator, text: IriSP.encodeURI(annotation.content.title)}); |
|
5732 this.selector.find(".Ldt-Retweet").attr("href", rtText); |
|
5733 } |
|
5734 |
|
5735 this.selector.find(".Ldt-tweetContents").html(title); |
|
5736 this.selector.find(".Ldt-tweetAvatar").html(imageMarkup); |
|
5737 this.selector.show("blind", 250); |
|
5738 }; |
|
5739 |
|
5740 IriSP.TweetsWidget.prototype.displayTweet = function(annotation) { |
|
5741 if (this._displayingTweet === false) { |
|
5742 this._displayingTweet = true; |
|
5743 } else { |
|
5744 window.clearTimeout(this._timeoutId); |
|
5745 } |
|
5746 |
|
5747 this.drawTweet(annotation); |
|
5748 |
|
5749 var time = this._Popcorn.currentTime(); |
|
5750 this._timeoutId = window.setTimeout(IriSP.wrap(this, this.clearPanel), this.tweet_display_period); |
|
5751 }; |
|
5752 |
|
5753 |
|
5754 IriSP.TweetsWidget.prototype.clearPanel = function() { |
|
5755 this._displayingTweet = false; |
|
5756 this._timeoutId = undefined; |
|
5757 this.closePanel(); |
|
5758 |
|
5759 }; |
|
5760 |
|
5761 IriSP.TweetsWidget.prototype.closePanel = function() { |
|
5762 if (this._timeoutId != undefined) { |
|
5763 /* we're called from the "close window" link */ |
|
5764 /* cancel the timeout */ |
|
5765 window.clearTimeout(this._timeoutId); |
|
5766 this._timeoutId = null; |
|
5767 } |
|
5768 |
|
5769 this.selector.hide("blind", 400); |
|
5770 |
|
5771 }; |
|
5772 |
|
5773 /* cancel the timeout if the user clicks on the keep panel open button */ |
|
5774 IriSP.TweetsWidget.prototype.keepPanel = function() { |
|
5775 if (this._timeoutId != undefined) { |
|
5776 /* we're called from the "close window" link */ |
|
5777 /* cancel the timeout */ |
|
5778 window.clearTimeout(this._timeoutId); |
|
5779 this._timeoutId = null; |
|
5780 } |
|
5781 }; |
|
5782 |
|
5783 IriSP.TweetsWidget.prototype.draw = function() { |
|
5784 var _this = this; |
|
5785 |
|
5786 var tweetMarkup = IriSP.templToHTML(IriSP.tweetWidget_template, {"share_template" : IriSP.share_template}); |
|
5787 this.selector.append(tweetMarkup); |
|
5788 this.selector.hide(); |
|
5789 this.selector.find(".Ldt-tweetWidgetMinimize").click(IriSP.wrap(this, this.closePanel)); |
|
5790 this.selector.find(".Ldt-tweetWidgetKeepOpen").click(IriSP.wrap(this, this.keepPanel)); |
|
5791 |
|
5792 this._Popcorn.listen("IriSP.PolemicTweet.click", IriSP.wrap(this, this.PolemicTweetClickHandler)); |
|
5793 this._Popcorn.listen("IriSP.PlayerWidget.AnnotateButton.clicked", |
|
5794 IriSP.wrap(this, this.handleAnnotateSignal)); |
|
5795 }; |
|
5796 |
|
5797 IriSP.TweetsWidget.prototype.PolemicTweetClickHandler = function(tweet_id) { |
|
5798 var index, annotation; |
|
5799 for (index in this._serializer._data.annotations) { |
|
5800 annotation = this._serializer._data.annotations[index]; |
|
5801 |
|
5802 if (annotation.id === tweet_id) |
|
5803 break; |
|
5804 } |
|
5805 |
|
5806 if (annotation.id !== tweet_id) |
|
5807 /* we haven't found it */ |
|
5808 return; |
|
5809 |
|
5810 this.displayTweet(annotation); |
|
5811 return; |
|
5812 }; |
|
5813 |
|
5814 /** handle clicks on the annotate button by hiding/showing itself */ |
|
5815 IriSP.TweetsWidget.prototype.handleAnnotateSignal = function() { |
|
5816 if (this._hidden == false) { |
|
5817 this.selector.hide(); |
|
5818 this._hidden = true; |
|
5819 } else { |
|
5820 if (this._displayingTweet !== false) |
|
5821 this.selector.show(); |
|
5822 |
|
5823 |
|
5824 this._hidden = false; |
|
5825 } |
|
5826 };/** @class This class implement a serializer for the JSON-Cinelab format |
|
5827 @params DataLoader a dataloader reference |
|
5828 @url the url from which to get our cinelab |
|
5829 */ |
|
5830 IriSP.JSONSerializer = function(DataLoader, url) { |
|
5831 IriSP.Serializer.call(this, DataLoader, url); |
|
5832 }; |
|
5833 |
|
5834 IriSP.JSONSerializer.prototype = new IriSP.Serializer(); |
|
5835 |
|
5836 /** serialize data */ |
|
5837 IriSP.JSONSerializer.prototype.serialize = function(data) { |
|
5838 return JSON.stringify(data); |
|
5839 }; |
|
5840 |
|
5841 /** deserialize data */ |
|
5842 IriSP.JSONSerializer.prototype.deserialize = function(data) { |
|
5843 return JSON.parse(data); |
|
5844 }; |
|
5845 |
|
5846 /** load JSON-cinelab data and also sort the annotations by start time |
|
5847 @param callback function to call when the data is ready. |
|
5848 */ |
|
5849 IriSP.JSONSerializer.prototype.sync = function(callback, force_refresh) { |
|
5850 /* we don't have to do much because jQuery handles json for us */ |
|
5851 |
|
5852 var self = this; |
|
5853 |
|
5854 var fn = function(data) { |
|
5855 //TODO: seems taht data can be null here |
|
5856 if (data !== null) { |
|
5857 self._data = data; |
|
5858 if (typeof(self._data["annotations"]) === "undefined" || |
|
5859 self._data["annotations"] === null) |
|
5860 self._data["annotations"] = []; |
|
5861 |
|
5862 // sort the data too |
|
5863 self._data["annotations"].sort(function(a, b) |
|
5864 { var a_begin = +a.begin; |
|
5865 var b_begin = +b.begin; |
|
5866 return a_begin - b_begin; |
|
5867 }); |
|
5868 } |
|
5869 callback(data); |
|
5870 }; |
|
5871 this._DataLoader.get(this._url, fn, force_refresh); |
|
5872 }; |
|
5873 |
|
5874 /** @return the metadata about the media being read FIXME: always return the first media. */ |
|
5875 IriSP.JSONSerializer.prototype.currentMedia = function() { |
|
5876 return (typeof this._data.medias == "object" && this._data.medias.length) ? this._data.medias[0] : IriSP.__jsonMetadata.medias[0]; |
|
5877 }; |
|
5878 |
|
5879 IriSP.JSONSerializer.prototype.getDuration = function() { |
|
5880 var _m = this.currentMedia(); |
|
5881 if (_m === null || typeof _m.meta == "undefined") { |
|
5882 return 0; |
|
5883 } |
|
5884 return +(IriSP.get_aliased(_m.meta, ["dc:duration", "duration"]) || 0); |
|
5885 } |
|
5886 |
|
5887 |
|
5888 /** searches for an annotation which matches title, description and keyword |
|
5889 "" matches any field. |
|
5890 Note: it ignores tweets. |
|
5891 @return a list of matching ids. |
|
5892 */ |
|
5893 IriSP.JSONSerializer.prototype.searchAnnotations = function(title, description, keyword) { |
|
5894 /* we can have many types of annotations. We want search to only look for regular segments */ |
|
5895 /* the next two lines are a bit verbose because for some test data, _serializer.data.view is either |
|
5896 null or undefined. |
|
5897 */ |
|
5898 var view; |
|
5899 |
|
5900 if (typeof(this._data.views) !== "undefined" && this._data.views !== null) |
|
5901 view = this._data.views[0]; |
|
5902 |
|
5903 var searchViewType = ""; |
|
5904 |
|
5905 if(typeof(view) !== "undefined" && typeof(view.annotation_types) !== "undefined" && view.annotation_types.length > 1) { |
|
5906 searchViewType = view.annotation_types[0]; |
|
5907 } |
|
5908 |
|
5909 var filterfn = function(annotation) { |
|
5910 if( searchViewType != "" && |
|
5911 typeof(annotation.meta) !== "undefined" && |
|
5912 typeof(annotation.meta["id-ref"]) !== "undefined" && |
|
5913 annotation.meta["id-ref"] !== searchViewType) { |
|
5914 return true; // don't pass |
|
5915 } else { |
|
5916 return false; |
|
5917 } |
|
5918 }; |
|
5919 |
|
5920 return this.searchAnnotationsFilter(title, description, keyword, filterfn); |
|
5921 |
|
5922 }; |
|
5923 |
|
5924 /* only look for tweets */ |
|
5925 IriSP.JSONSerializer.prototype.searchTweets = function(title, description, keyword) { |
|
5926 /* we can have many types of annotations. We want search to only look for regular segments */ |
|
5927 /* the next two lines are a bit verbose because for some test data, _serializer.data.view is either |
|
5928 null or undefined. |
|
5929 */ |
|
5930 |
|
5931 var searchViewType = this.getTweets(); |
|
5932 if (typeof(searchViewType) === "undefined") { |
|
5933 var view; |
|
5934 |
|
5935 if (typeof(this._data.views) !== "undefined" && this._data.views !== null) |
|
5936 view = this._data.views[0]; |
|
5937 |
|
5938 if(typeof(view) !== "undefined" && typeof(view.annotation_types) !== "undefined" && view.annotation_types.length > 1) { |
|
5939 searchViewType = view.annotation_types[0]; |
|
5940 } |
|
5941 } |
|
5942 var filterfn = function(annotation) { |
|
5943 if( searchViewType != "" && |
|
5944 typeof(annotation.meta) !== "undefined" && |
|
5945 typeof(annotation.meta["id-ref"]) !== "undefined" && |
|
5946 annotation.meta["id-ref"] === searchViewType) { |
|
5947 return false; // pass |
|
5948 } else { |
|
5949 return true; |
|
5950 } |
|
5951 }; |
|
5952 |
|
5953 return this.searchAnnotationsFilter(title, description, keyword, filterfn); |
|
5954 |
|
5955 }; |
|
5956 |
|
5957 /** |
|
5958 search an annotation according to its title, description and keyword |
|
5959 @param filter a function to filter the results with. Used to select between annotation types. |
|
5960 */ |
|
5961 IriSP.JSONSerializer.prototype.searchAnnotationsFilter = function(title, description, keyword, filter) { |
|
5962 |
|
5963 var rTitle; |
|
5964 var rDescription; |
|
5965 var rKeyword; |
|
5966 /* match anything if given the empty string */ |
|
5967 if (title == "") |
|
5968 title = ".*"; |
|
5969 if (description == "") |
|
5970 description = ".*"; |
|
5971 if (keyword == "") |
|
5972 keyword = ".*"; |
|
5973 |
|
5974 rTitle = new RegExp(title, "i"); |
|
5975 rDescription = new RegExp(description, "i"); |
|
5976 rKeyword = new RegExp(keyword, "i"); |
|
5977 |
|
5978 var ret_array = []; |
|
5979 |
|
5980 var i; |
|
5981 for (i in this._data.annotations) { |
|
5982 var annotation = this._data.annotations[i]; |
|
5983 |
|
5984 /* filter the annotations whose type is not the one we want */ |
|
5985 if (filter(annotation)) { |
|
5986 continue; |
|
5987 } |
|
5988 |
|
5989 if (rTitle.test(annotation.content.title) && |
|
5990 rDescription.test(annotation.content.description)) { |
|
5991 /* FIXME : implement keyword support */ |
|
5992 ret_array.push(annotation); |
|
5993 } |
|
5994 } |
|
5995 |
|
5996 return ret_array; |
|
5997 }; |
|
5998 |
|
5999 /** breaks a string in words and searches each of these words. Returns an array |
|
6000 of objects with the id of the annotation and its number of occurences. |
|
6001 |
|
6002 @param searchString a string of words. |
|
6003 FIXME: optimize ? seems to be n^2 in the worst case. |
|
6004 */ |
|
6005 IriSP.JSONSerializer.prototype.searchOccurences = function(searchString) { |
|
6006 var ret = { }; |
|
6007 var keywords = searchString.split(/\s+/); |
|
6008 |
|
6009 for (var i in keywords) { |
|
6010 var keyword = keywords[i]; |
|
6011 |
|
6012 // search this keyword in descriptions and title |
|
6013 var found_annotations = [] |
|
6014 found_annotations = found_annotations.concat(this.searchAnnotations(keyword, "", "")); |
|
6015 found_annotations = found_annotations.concat(this.searchAnnotations("", keyword, "")); |
|
6016 |
|
6017 for (var j in found_annotations) { |
|
6018 var current_annotation = found_annotations[j]; |
|
6019 |
|
6020 if (!ret.hasOwnProperty(current_annotation.id)) { |
|
6021 ret[current_annotation.id] = 1; |
|
6022 } else { |
|
6023 ret[current_annotation.id] += 1; |
|
6024 } |
|
6025 |
|
6026 } |
|
6027 |
|
6028 }; |
|
6029 |
|
6030 return ret; |
|
6031 }; |
|
6032 |
|
6033 /** breaks a string in words and searches each of these words. Returns an array |
|
6034 of objects with the id of the annotation and its number of occurences. |
|
6035 |
|
6036 FIXME: optimize ? seems to be n^2 in the worst case. |
|
6037 */ |
|
6038 IriSP.JSONSerializer.prototype.searchTweetsOccurences = function(searchString) { |
|
6039 var ret = { }; |
|
6040 var keywords = searchString.split(/\s+/); |
|
6041 |
|
6042 for (var i in keywords) { |
|
6043 var keyword = keywords[i]; |
|
6044 |
|
6045 // search this keyword in descriptions and title |
|
6046 var found_annotations = [] |
|
6047 found_annotations = found_annotations.concat(this.searchTweets(keyword, "", "")); |
|
6048 found_annotations = found_annotations.concat(this.searchTweets("", keyword, "")); |
|
6049 |
|
6050 for (var j in found_annotations) { |
|
6051 var current_annotation = found_annotations[j]; |
|
6052 |
|
6053 if (!ret.hasOwnProperty(current_annotation.id)) { |
|
6054 ret[current_annotation.id] = 1; |
|
6055 } else { |
|
6056 ret[current_annotation.id] += 1; |
|
6057 } |
|
6058 |
|
6059 } |
|
6060 |
|
6061 }; |
|
6062 |
|
6063 return ret; |
|
6064 }; |
|
6065 |
|
6066 /** returns all the annotations that are displayable at the moment |
|
6067 NB: only takes account the first type of annotations - ignores tweets |
|
6068 currentTime is in seconds. |
|
6069 |
|
6070 @param currentTime the time at which we search. |
|
6071 @param (optional) the if of the type of the annotations we want to get. |
|
6072 */ |
|
6073 |
|
6074 IriSP.JSONSerializer.prototype.currentAnnotations = function(currentTime, id) { |
|
6075 var view; |
|
6076 var currentTimeMs = 1000 * currentTime; |
|
6077 |
|
6078 if (typeof(id) === "undefined") { |
|
6079 var legal_ids = this.getNonTweetIds(); |
|
6080 } else { |
|
6081 legal_ids = [id]; |
|
6082 } |
|
6083 |
|
6084 var ret_array = []; |
|
6085 |
|
6086 var i; |
|
6087 |
|
6088 for (i in this._data.annotations) { |
|
6089 var annotation = this._data.annotations[i]; |
|
6090 |
|
6091 if (IriSP.underscore.include(legal_ids, annotation.meta["id-ref"]) && |
|
6092 annotation.begin <= currentTimeMs && |
|
6093 annotation.end >= currentTimeMs) |
|
6094 ret_array.push(annotation); |
|
6095 } |
|
6096 |
|
6097 if (ret_array == []) { |
|
6098 console.log("ret_array empty, ", legal_ids); |
|
6099 } |
|
6100 |
|
6101 return ret_array; |
|
6102 }; |
|
6103 |
|
6104 /** return the current chapitre |
|
6105 @param currentTime the current time, in seconds. |
|
6106 */ |
|
6107 IriSP.JSONSerializer.prototype.currentChapitre = function(currentTime) { |
|
6108 return this.currentAnnotations(currentTime, this.getChapitrage())[0]; |
|
6109 }; |
|
6110 |
|
6111 /** returns a list of ids of tweet lines (aka: groups in cinelab) */ |
|
6112 IriSP.JSONSerializer.prototype.getTweetIds = function() { |
|
6113 if (IriSP.null_or_undefined(this._data.lists) || IriSP.null_or_undefined(this._data.lists) || |
|
6114 IriSP.null_or_undefined(this._data.views) || IriSP.null_or_undefined(this._data.views[0])) |
|
6115 return []; |
|
6116 |
|
6117 |
|
6118 /* Get the displayable types |
|
6119 We've got to jump through a few hoops because the json sometimes defines |
|
6120 fields with underscores and sometimes with dashes |
|
6121 */ |
|
6122 var annotation_types = IriSP.get_aliased(this._data.views[0], ["annotation_types", "annotation-types"]); |
|
6123 if (annotation_types === null) { |
|
6124 console.log("neither view.annotation_types nor view.annotation-types are defined"); |
|
6125 return; |
|
6126 } |
|
6127 |
|
6128 var available_types = IriSP.get_aliased(this._data, ["annotation_types", "annotation-types"]); |
|
6129 if (available_types === null) { |
|
6130 console.log("neither view.annotation_types nor view.annotation-types are defined"); |
|
6131 return; |
|
6132 } |
|
6133 |
|
6134 var potential_types = []; |
|
6135 |
|
6136 // Get the list of types which contain "Tw" in their content |
|
6137 for (var i = 0; i < available_types.length; i++) { |
|
6138 if (/Tw/i.test(IriSP.get_aliased(available_types[i], ['dc:title', 'title']))) { |
|
6139 potential_types.push(available_types[i].id); |
|
6140 } |
|
6141 } |
|
6142 |
|
6143 // Get the intersection of both. |
|
6144 var tweetsId = IriSP.underscore.intersection(annotation_types, potential_types); |
|
6145 |
|
6146 return tweetsId; |
|
6147 }; |
|
6148 |
|
6149 /** this function returns a list of lines which are not tweet lines */ |
|
6150 IriSP.JSONSerializer.prototype.getNonTweetIds = function() { |
|
6151 if (IriSP.null_or_undefined(this._data.lists) || IriSP.null_or_undefined(this._data.lists) || |
|
6152 IriSP.null_or_undefined(this._data.views) || IriSP.null_or_undefined(this._data.views[0])) |
|
6153 return []; |
|
6154 |
|
6155 /* Get the displayable types |
|
6156 We've got to jump through a few hoops because the json sometimes defines |
|
6157 fields with underscores and sometimes with dashes |
|
6158 */ |
|
6159 var annotation_types = IriSP.get_aliased(this._data.views[0], ["annotation_types", "annotation-types"]); |
|
6160 if (annotation_types === null) { |
|
6161 console.log("neither view.annotation_types nor view.annotation-types are defined"); |
|
6162 return; |
|
6163 } |
|
6164 |
|
6165 var available_types = IriSP.get_aliased(this._data, ["annotation_types", "annotation-types"]); |
|
6166 if (available_types === null) { |
|
6167 console.log("neither view.annotation_types nor view.annotation-types are defined"); |
|
6168 return; |
|
6169 } |
|
6170 |
|
6171 var potential_types = []; |
|
6172 |
|
6173 // Get the list of types which do not contain "Tw" in their content |
|
6174 for (var i = 0; i < available_types.length; i++) { |
|
6175 if (!(/Tw/i.test(IriSP.get_aliased(available_types[i], ['dc:title', 'title'])))) { |
|
6176 potential_types.push(available_types[i].id); |
|
6177 } |
|
6178 } |
|
6179 |
|
6180 // Get the intersection of both. |
|
6181 var nonTweetsId = IriSP.underscore.intersection(annotation_types, potential_types); |
|
6182 |
|
6183 return nonTweetsId; |
|
6184 |
|
6185 }; |
|
6186 |
|
6187 /** return the id of the ligne de temps which contains name |
|
6188 @param name of the ligne de temps |
|
6189 */ |
|
6190 IriSP.JSONSerializer.prototype.getId = function(name) { |
|
6191 var available_types = IriSP.get_aliased(this._data, ["annotation_types", "annotation-types"]); |
|
6192 |
|
6193 if (available_types == null) |
|
6194 return; |
|
6195 |
|
6196 name = name.toUpperCase(); |
|
6197 var e; |
|
6198 e = IriSP.underscore.find(available_types, |
|
6199 function(entry) { |
|
6200 if (IriSP.get_aliased(entry, ['dc:title', 'title']) === null) |
|
6201 return false; |
|
6202 return (entry["dc:title"].toUpperCase().indexOf(name) !== -1); |
|
6203 }); |
|
6204 |
|
6205 if (typeof(e) === "undefined") |
|
6206 return; |
|
6207 |
|
6208 var id = e.id; |
|
6209 |
|
6210 return id; |
|
6211 }; |
|
6212 |
|
6213 /** return the list of id's of the ligne de temps which contains name |
|
6214 @param name of the ligne de temps |
|
6215 */ |
|
6216 IriSP.JSONSerializer.prototype.getIds = function(name) { |
|
6217 var available_types = IriSP.get_aliased(this._data, ["annotation_types", "annotation-types"]); |
|
6218 |
|
6219 if (available_types == null) |
|
6220 return; |
|
6221 |
|
6222 name = name.toUpperCase(); |
|
6223 var e = []; |
|
6224 e = IriSP.underscore.filter(available_types, |
|
6225 function(entry) { return (IriSP.get_aliased(entry, ['dc:title', 'title']).toUpperCase().indexOf(name) !== -1) }); |
|
6226 return IriSP.underscore.pluck(e, "id"); |
|
6227 }; |
|
6228 |
|
6229 /** return the id of the ligne de temps named "Chapitrage" */ |
|
6230 IriSP.JSONSerializer.prototype.getChapitrage = function() { |
|
6231 var val = this.getId("Chapitrage"); |
|
6232 if (typeof(val) === "undefined") |
|
6233 val = this.getId("Chapter"); |
|
6234 if (typeof(val) === "undefined") |
|
6235 val = this.getId("Chapit"); |
|
6236 if (typeof(val) === "undefined") |
|
6237 val = this.getId("Chap"); |
|
6238 |
|
6239 return val; |
|
6240 }; |
|
6241 |
|
6242 /** return the id of the ligne de temps named "Tweets" */ |
|
6243 IriSP.JSONSerializer.prototype.getTweets = function() { |
|
6244 var val = this.getId("Tweets"); |
|
6245 if (typeof(val) === "undefined") |
|
6246 val = this.getId("Tweet"); |
|
6247 if (typeof(val) === "undefined") |
|
6248 val = this.getId("Twitter"); |
|
6249 if (typeof(val) === "undefined") |
|
6250 val = this.getId("twit"); |
|
6251 if (typeof(val) === "undefined") |
|
6252 val = this.getId("Polemic"); |
|
6253 |
|
6254 return val; |
|
6255 }; |
|
6256 |
|
6257 /** return the id of the ligne de temps named "Contributions" */ |
|
6258 IriSP.JSONSerializer.prototype.getContributions = function() { |
|
6259 var val = this.getId("Contribution"); |
|
6260 if (typeof(val) === "undefined") |
|
6261 val = this.getId("Particip"); |
|
6262 if (typeof(val) === "undefined") |
|
6263 val = this.getId("Contr"); |
|
6264 if (typeof(val) === "undefined") |
|
6265 val = this.getId("Publ"); |
|
6266 |
|
6267 return val; |
|
6268 }; |
|