# HG changeset patch # User ymh # Date 1423843073 -3600 # Node ID c20df1c080e699f5e6520afc583334a0a19fa7bd # Parent 74ac0be7655ce083e17c9be38379608a7a78df93 integrate changes from github diff -r 74ac0be7655c -r c20df1c080e6 sbin/build/compil.sh --- a/sbin/build/compil.sh Fri Feb 13 16:48:05 2015 +0100 +++ b/sbin/build/compil.sh Fri Feb 13 16:57:53 2015 +0100 @@ -1,2 +1,3 @@ #!/bin/sh -sh ../res/ant/bin/ant -f client.xml +DIR=$(dirname $0) +sh ${DIR}/../res/ant/bin/ant -f ${DIR}/client.xml diff -r 74ac0be7655c -r c20df1c080e6 src/js/libs/jquery.splitter.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/js/libs/jquery.splitter.css Fri Feb 13 16:57:53 2015 +0100 @@ -0,0 +1,57 @@ +/*! + * StyleSheet for JQuery splitter Plugin + * Copyright (C) 2010 Jakub Jankiewicz + * + * Same license as plugin + */ +.splitter_panel { + position: relative; +} +.splitter_panel .vsplitter { + background-color: grey; + cursor: col-resize; + z-index:900; + width: 4px; +} + +.splitter_panel .hsplitter { + background-color: #5F5F5F; + cursor: row-resize; + z-index: 800; + height: 4px; +} +.splitter_panel .vsplitter.splitter-invisible, +.splitter_panel .hsplitter.splitter-invisible { + background: none; +} +.splitter_panel .vsplitter, .splitter_panel .left_panel, .splitter_panel .right_panel, +.splitter_panel .hsplitter, .splitter_panel .top_panel, .splitter_panel .bottom_panel { + position: absolute; + overflow: auto; +} +.splitter_panel .vsplitter, .splitter_panel .left_panel, .splitter_panel .right_panel { + height: 100%; +} +.splitter_panel .hsplitter, .splitter_panel .top_panel, .splitter_panel .bottom_panel { + width: 100%; +} +.splitter_panel .top_panel, .splitter_panel .left_panel, .splitter_panel .vsplitter { + top: 0; +} +.splitter_panel .top_panel, .splitter_panel .bottom_panel, .splitter_panel .left_panel, .splitter_panel .hsplitter { + left: 0; +} +.splitter_panel .bottom_panel { + bottom: 0; +} +.splitter_panel .right_panel { + right: 0; +} +.splitterMask { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + z-index: 1000; +} \ No newline at end of file diff -r 74ac0be7655c -r c20df1c080e6 src/js/libs/jquery.splitter.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/js/libs/jquery.splitter.js Fri Feb 13 16:57:53 2015 +0100 @@ -0,0 +1,278 @@ +/*! + * JQuery Spliter Plugin + * Copyright (C) 2010-2013 Jakub Jankiewicz + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +(function($, undefined) { + var count = 0; + var splitter_id = null; + var splitters = []; + var current_splitter = null; + $.fn.split = function(options) { + var data = this.data('splitter'); + if (data) { + return data; + } + var panel_1; + var panel_2; + var settings = $.extend({ + limit: 100, + orientation: 'horizontal', + position: '50%', + invisible: false, + onDragStart: $.noop, + onDragEnd: $.noop, + onDrag: $.noop + }, options || {}); + this.settings = settings; + var cls; + var children = this.children(); + if (settings.orientation == 'vertical') { + panel_1 = children.first().addClass('left_panel'); + panel_2 = panel_1.next().addClass('right_panel'); + cls = 'vsplitter'; + } else if (settings.orientation == 'horizontal') { + panel_1 = children.first().addClass('top_panel') + panel_2 = panel_1.next().addClass('bottom_panel'); + cls = 'hsplitter'; + } + if (settings.invisible) { + cls += ' splitter-invisible'; + } + var width = this.width(); + var height = this.height(); + var id = count++; + this.addClass('splitter_panel'); + var splitter = $('
').addClass(cls).mouseenter(function() { + splitter_id = id; + }).mouseleave(function() { + splitter_id = null; + }).insertAfter(panel_1); + var position; + + function get_position(position) { + if (typeof position === 'number') { + return position; + } else if (typeof position === 'string') { + var match = position.match(/^([0-9]+)(px|%)$/); + if (match) { + if (match[2] == 'px') { + return +match[1]; + } else { + if (settings.orientation == 'vertical') { + return (width * +match[1]) / 100; + } else if (settings.orientation == 'horizontal') { + return (height * +match[1]) / 100; + } + } + } else { + //throw position + ' is invalid value'; + } + } else { + //throw 'position have invalid type'; + } + } + + var self = $.extend(this, { + refresh: function() { + var new_width = this.width(); + var new_height = this.height(); + if (width != new_width || height != new_height) { + width = this.width(); + height = this.height(); + self.position(position); + } + }, + position: (function() { + if (settings.orientation == 'vertical') { + return function(n, silent) { + if (n === undefined) { + return position; + } else { + position = get_position(n); + var sw = splitter.width(); + var sw2 = sw/2; + if (settings.invisible) { + var pw = panel_1.width(position).outerWidth(); + panel_2.width(self.width()-pw); + splitter.css('left', pw-sw2); + } else { + var pw = panel_1.width(position-sw2).outerWidth(); + panel_2.width(self.width()-pw-sw); + splitter.css('left', pw); + } + } + if (!silent) { + self.find('.splitter_panel').trigger('splitter.resize'); + } + return self; + }; + } else if (settings.orientation == 'horizontal') { + return function(n, silent) { + if (n === undefined) { + return position; + } else { + position = get_position(n); + var sw = splitter.height(); + var sw2 = sw/2; + if (settings.invisible) { + var pw = panel_1.height(position).outerHeight(); + panel_2.height(self.height()-pw); + splitter.css('top', pw-sw2); + } else { + var pw = panel_1.height(position-sw2).outerHeight(); + panel_2.height(self.height()-pw-sw); + splitter.css('top', pw); + } + } + if (!silent) { + self.find('.splitter_panel').trigger('splitter.resize'); + } + return self; + }; + } else { + return $.noop; + } + })(), + orientation: settings.orientation, + limit: settings.limit, + isActive: function() { + return splitter_id === id; + }, + destroy: function() { + self.removeClass('splitter_panel'); + splitter.unbind('mouseenter'); + splitter.unbind('mouseleave'); + if (settings.orientation == 'vertical') { + panel_1.removeClass('left_panel'); + panel_2.removeClass('right_panel'); + } else if (settings.orientation == 'horizontal') { + panel_1.removeClass('top_panel'); + panel_2.removeClass('bottom_panel'); + } + self.unbind('splitter.resize'); + self.find('.splitter_panel').trigger('splitter.resize'); + splitters[id] = null; + splitter.remove(); + var not_null = false; + for (var i=splitters.length; i--;) { + if (splitters[i] !== null) { + not_null = true; + break; + } + } + //remove document events when no splitters + if (!not_null) { + $(document.documentElement).unbind('.splitter'); + $(window).unbind('resize.splitter'); + self.data('splitter', null); + splitters = []; + count = 0; + } + } + }); + self.bind('splitter.resize', function(e) { + var pos = self.position(); + if (self.orientation == 'vertical' && + pos > self.width()) { + pos = self.width() - self.limit-1; + } else if (self.orientation == 'horizontal' && + pos > self.height()) { + pos = self.height() - self.limit-1; + } + if (pos < self.limit) { + pos = self.limit + 1; + } + self.position(pos, true); + }); + //inital position of splitter + var pos; + if (settings.orientation == 'vertical') { + if (pos > width-settings.limit) { + pos = width-settings.limit; + } else { + pos = get_position(settings.position); + } + } else if (settings.orientation == 'horizontal') { + //position = height/2; + if (pos > height-settings.limit) { + pos = height-settings.limit; + } else { + pos = get_position(settings.position); + } + } + if (pos < settings.limit) { + pos = settings.limit; + } + self.position(pos, true); + if (splitters.length == 0) { // first time bind events to document + $(window).bind('resize.splitter', function() { + $.each(splitters, function(i, splitter) { + splitter.refresh(); + }); + }); + $(document.documentElement).bind('mousedown.splitter', function(e) { + if (splitter_id !== null && e.which == 1) { + current_splitter = splitters[splitter_id]; + $('
').css('cursor', splitter.css('cursor')).insertAfter(current_splitter); + current_splitter.settings.onDragStart(e); + return false; + } + }).bind('mouseup.splitter', function(e) { + if (current_splitter) { + $('.splitterMask').remove(); + current_splitter.settings.onDragEnd(e); + current_splitter = null; + } + }).bind('mousemove.splitter', function(e) { + if (current_splitter !== null) { + var limit = current_splitter.limit; + var offset = current_splitter.offset(); + if (current_splitter.orientation == 'vertical') { + var x = e.pageX - offset.left; + if (x <= current_splitter.limit) { + x = current_splitter.limit + 1; + } else if (x >= current_splitter.width() - limit) { + x = current_splitter.width() - limit - 1; + } + if (x > current_splitter.limit && + x < current_splitter.width()-limit) { + current_splitter.position(x, true); + current_splitter.trigger('splitter.resize'); + e.preventDefault(); + } + } else if (current_splitter.orientation == 'horizontal') { + var y = e.pageY-offset.top; + if (y <= current_splitter.limit) { + y = current_splitter.limit + 1; + } else if (y >= current_splitter.height() - limit) { + y = current_splitter.height() - limit - 1; + } + if (y > current_splitter.limit && + y < current_splitter.height()-limit) { + current_splitter.position(y, true); + current_splitter.trigger('splitter.resize'); + e.preventDefault(); + } + } + current_splitter.settings.onDrag(e); + } + }); + } + splitters.push(self); + self.data('splitter', self); + return self; + }; +})(jQuery); diff -r 74ac0be7655c -r c20df1c080e6 src/js/libs/mousetrap-global-bind.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/js/libs/mousetrap-global-bind.js Fri Feb 13 16:57:53 2015 +0100 @@ -0,0 +1,36 @@ +/** + * adds a bindGlobal method to Mousetrap that allows you to + * bind specific keyboard shortcuts that will still work + * inside a text input field + * + * usage: + * Mousetrap.bindGlobal('ctrl+s', _saveChanges); + */ +/* global Mousetrap:true */ +Mousetrap = (function(Mousetrap) { + var _globalCallbacks = {}, + _originalStopCallback = Mousetrap.stopCallback; + + Mousetrap.stopCallback = function(e, element, combo, sequence) { + if (_globalCallbacks[combo] || _globalCallbacks[sequence]) { + return false; + } + + return _originalStopCallback(e, element, combo); + }; + + Mousetrap.bindGlobal = function(keys, callback, action) { + Mousetrap.bind(keys, callback, action); + + if (keys instanceof Array) { + for (var i = 0; i < keys.length; i++) { + _globalCallbacks[keys[i]] = true; + } + return; + } + + _globalCallbacks[keys] = true; + }; + + return Mousetrap; +}) (Mousetrap); diff -r 74ac0be7655c -r c20df1c080e6 src/js/libs/mousetrap.min.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/js/libs/mousetrap.min.js Fri Feb 13 16:57:53 2015 +0100 @@ -0,0 +1,9 @@ +/* mousetrap v1.4.6 craig.is/killing/mice */ +(function(J,r,f){function s(a,b,d){a.addEventListener?a.addEventListener(b,d,!1):a.attachEvent("on"+b,d)}function A(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return h[a.which]?h[a.which]:B[a.which]?B[a.which]:String.fromCharCode(a.which).toLowerCase()}function t(a){a=a||{};var b=!1,d;for(d in n)a[d]?b=!0:n[d]=0;b||(u=!1)}function C(a,b,d,c,e,v){var g,k,f=[],h=d.type;if(!l[a])return[];"keyup"==h&&w(a)&&(b=[a]);for(g=0;gg||h.hasOwnProperty(g)&&(p[h[g]]=g)}e=p[d]?"keydown":"keypress"}"keypress"==e&&f.length&&(e="keydown");return{key:c,modifiers:f,action:e}}function F(a,b,d,c,e){q[a+":"+d]=b;a=a.replace(/\s+/g," ");var f=a.split(" ");1":".","?":"/","|":"\\"},G={option:"alt",command:"meta","return":"enter",escape:"esc",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},p,l={},q={},n={},D,z=!1,I=!1,u=!1;for(f=1;20>f;++f)h[111+f]="f"+f;for(f=0;9>=f;++f)h[f+96]=f;s(r,"keypress",y);s(r,"keydown",y);s(r,"keyup",y);var m={bind:function(a,b,d){a=a instanceof Array?a:[a];for(var c=0;c{{l10n.excerpt_from}} ' + '( - )' + '
{{l10n.creator_}}
' - + '{{#show_creator}}

' - + '
{{l10n.description_}}
{{/show_creator}}' - + '

' + + '{{#show_creator}}

{{/show_creator}}' + + '{{#show_description}}
{{l10n.description_}}
' + + '

{{/show_description}}' + '
' + '
{{l10n.tags_}}
    ' + '
    '; @@ -53,6 +53,7 @@ start_minimized: false, show_arrow : true, show_creator: true, + show_description: true, arrow_position: .5, site_name : "Lignes de Temps", search_on_tag_click: true, @@ -231,4 +232,4 @@ IriSP.Widgets.Annotation.prototype.maximize = function() { this.minimized = false; this.$.find('.Ldt-Annotation-Inner').removeClass("Ldt-Annotation-Minimized"); -}; \ No newline at end of file +}; diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/AnnotationsList.css --- a/src/widgets/AnnotationsList.css Fri Feb 13 16:48:05 2015 +0100 +++ b/src/widgets/AnnotationsList.css Fri Feb 13 16:57:53 2015 +0100 @@ -106,4 +106,12 @@ .Ldt-AnnotationsList-Play:hover { background-position: 0 bottom; -} \ No newline at end of file +} + +.Ldt-AnnotationsList-Control-Prev { + cursor: url(img/hand_left.png), pointer; +} + +.Ldt-AnnotationsList-Control-Next { + cursor: url(img/hand_right.png), pointer; +} diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/AnnotationsList.js --- a/src/widgets/AnnotationsList.js Fri Feb 13 16:48:05 2015 +0100 +++ b/src/widgets/AnnotationsList.js Fri Feb 13 16:57:53 2015 +0100 @@ -29,6 +29,8 @@ limit_count : 20, newest_first : false, show_audio: true, + show_creator: false, + show_controls: false, polemics : [{ keyword: "++", background_color: "#c9ecc6" @@ -58,15 +60,16 @@ IriSP.Widgets.AnnotationsList.prototype.template = '
    ' + '{{#show_audio}}
    {{/show_audio}}' + + '{{#show_controls}}
    Previous | Next
    {{/show_controls}}' + '
      ' + '
    ' + '
    '; IriSP.Widgets.AnnotationsList.prototype.annotationTemplate = - '
  • ' + '
  • ' + '' + '
    {{begin}} - {{end}}
    ' @@ -187,7 +190,7 @@ _description = _annotation.description, _thumbnail = (typeof _annotation.thumbnail !== "undefined" && _annotation.thumbnail ? _annotation.thumbnail : _this.default_thumbnail); // Update : display creator - if (_annotation.creator) { + if (_annotation.creator && _this.show_creator) { _title = _annotation.creator; } if (_annotation.title) { @@ -207,16 +210,23 @@ var _data = { id : _annotation.id, media_id : _annotation.getMedia().id, + atitle: IriSP.textFieldHtml(_annotation.title), htitle : IriSP.textFieldHtml(_title), hdescription : IriSP.textFieldHtml(_description), begin : _annotation.begin.toString(), end : _annotation.end.toString(), + begin_ms : _annotation.begin.milliseconds, + end_ms : _annotation.end.milliseconds, thumbnail : _thumbnail, url : _url, tags : _annotation.getTagTexts(), specific_style : (typeof _bgcolor !== "undefined" ? "background-color: " + _bgcolor : ""), l10n: _this.l10n }; + if (_this.show_controls) { + _this.$.find(".Ldt-AnnotationsList-Control-Prev").on("click", function (e) { e.preventDefault(); _this.navigate(-1); }); + _this.$.find(".Ldt-AnnotationsList-Control-Next").on("click", function (e) { e.preventDefault(); _this.navigate(+1); }); + } if (_this.show_audio && _annotation.audio && _annotation.audio.href && _annotation.audio.href != "null") { _data.audio = true; if (!_this.jwplayers[_annotation.id]) { diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/Controller.css --- a/src/widgets/Controller.css Fri Feb 13 16:48:05 2015 +0100 +++ b/src/widgets/Controller.css Fri Feb 13 16:57:53 2015 +0100 @@ -86,7 +86,7 @@ } input.Ldt-Ctrl-SearchInput { - width: 145px; height: 13px; margin: 2px; padding: 3px; + width: 145px; height: 20px; margin: 2px; padding: 3px; border: 1px solid #8080a0; border-radius: 3px; font-size: 13px; } diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/Controller.js --- a/src/widgets/Controller.js Fri Feb 13 16:48:05 2015 +0100 +++ b/src/widgets/Controller.js Fri Feb 13 16:57:53 2015 +0100 @@ -10,7 +10,8 @@ IriSP.Widgets.Controller.prototype.defaults = { disable_annotate_btn: false, disable_search_btn: false, - disable_ctrl_f: false + disable_ctrl_f: false, + always_show_search: false }; IriSP.Widgets.Controller.prototype.template = @@ -27,7 +28,7 @@ + '
    ' + '{{/disable_search_btn}}' + '' + '' + '
    ' @@ -102,6 +103,7 @@ this.$.find(".Ldt-Ctrl-SearchBtn").click(this.functionWrapper("searchButtonHandler")); this.$searchInput.keyup(this.functionWrapper("searchHandler")); + this.$searchInput.on("search", this.functionWrapper("searchHandler")); var _volctrl = this.$.find(".Ldt-Ctrl-Volume-Control"); this.$.find('.Ldt-Ctrl-Sound') @@ -168,7 +170,9 @@ annotations.on("search-cleared", function() { _this.hideSearchBlock(); }); - + if (_this.always_show_search) { + _this.showSearchBlock(); + } }; /* Update the elasped time div */ @@ -237,7 +241,9 @@ }; IriSP.Widgets.Controller.prototype.hideSearchBlock = function() { - this.$searchBlock.animate( { width: 0 }, 200); + if (! this.always_show_search) { + this.$searchBlock.animate( { width: 0 }, 200); + } }; /** react to clicks on the search button */ diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/CreateAnnotation.css --- a/src/widgets/CreateAnnotation.css Fri Feb 13 16:48:05 2015 +0100 +++ b/src/widgets/CreateAnnotation.css Fri Feb 13 16:57:53 2015 +0100 @@ -23,7 +23,7 @@ } .Ldt-CreateAnnotation-Main { - min-height: 150px; + min-height: 100px; } .Ldt-CreateAnnotation-Title, .Ldt-CreateAnnotation-Creator { @@ -47,6 +47,7 @@ position: absolute; bottom: 7px; right: 7px; + display: inline; color: #ffffff; cursor: pointer; background: url('img/submit_annotation.png'); @@ -163,4 +164,18 @@ a.Ldt-CreateAnnotation-Close:hover { background-position: -17px 0; -} \ No newline at end of file +} + +.Ldt-CreateAnnotation-Controls { + position: absolute; + top: 7px; + right: 7px; +} + +span[class^='Ldt-CreateAnnotation-Control-'] { + background-color: #ddd; + padding: 2px; + margin: 4px; + border-radius: 3px; + border: 1px solid #888; +} diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/CreateAnnotation.js --- a/src/widgets/CreateAnnotation.js Fri Feb 13 16:48:05 2015 +0100 +++ b/src/widgets/CreateAnnotation.js Fri Feb 13 16:57:53 2015 +0100 @@ -1,7 +1,16 @@ /* TODO: Add Social Network Sharing */ IriSP.Widgets.CreateAnnotation = function(player, config) { + var _this = this; IriSP.Widgets.Widget.call(this, player, config); + if (_this.api_method == 'local' && window.localStorage[_this.api_endpoint_template]) { + this.source.onLoad(function () { + var _export = _this.player.sourceManager.newLocalSource({serializer: IriSP.serializers[_this.api_serializer]}); + _export.deSerialize(window.localStorage[_this.api_endpoint_template]); + console.log("Loaded personal annotations", _export); + _this.source.merge(_export); + }); + }; }; IriSP.Widgets.CreateAnnotation.prototype = new IriSP.Widgets.Widget(); @@ -12,6 +21,7 @@ start_visible : true, always_visible : false, show_slice : true, + show_controls: false, show_arrow : true, show_mic_record: false, show_mic_play: false, @@ -46,7 +56,8 @@ api_method: "POST", after_send_timeout: 0, close_after_send: false, - tag_prefix: "#" + tag_prefix: "#", + slice_widget: null }; IriSP.Widgets.CreateAnnotation.prototype.messages = { @@ -104,8 +115,13 @@ + ' {{#show_slice}}{{l10n.from_time}} {{/show_slice}}{{^show_slice}}{{l10n.at_time}} {{/show_slice}} 00:00' + '{{#show_slice}} {{l10n.to_time}} {{end}}{{/show_slice}}' + '{{#show_creator_field}}{{l10n.your_name_}} {{/show_creator_field}}' + + '{{#show_controls}}
    ' + + 'IN' + + 'OUT' + + 'Play' + + '
    {{/show_controls}}' + '' - + '
    ' + + '{{#show_creator_field}}
    {{/show_creator_field}}' + '' + '{{#show_mic_record}}
    Add voice annotation
    ' + ' ' @@ -130,7 +146,7 @@ + ' pluginspage="http://www.macromedia.com/go/getflashplayer">' + ' ' + ' ' - + '
    {{/show_mic_record}}' + + '
    {{/show_mic_record}}' + '{{#tags.length}}
    {{l10n.add_keywords_}}
      ' + '{{#tags}}
    • {{title}}
    • {{/tags}}
    {{/tags.length}}' + '{{#polemics.length}}
    {{l10n.add_polemic_keywords_}}
      ' @@ -140,15 +156,15 @@ + '
      {{^always_visible}}{{/always_visible}}
      {{l10n.error_while_contacting}}
      ' + '
      {{^always_visible}}{{/always_visible}}
      {{l10n.annotation_saved}}
      ' + '
    '; - + IriSP.Widgets.CreateAnnotation.prototype.draw = function() { var _this = this; - + this.begin = new IriSP.Model.Time(); this.end = this.source.getDuration(); - + this.tag_prefix = this.tag_prefix || ""; - + if (this.tag_titles && !this.tags) { if(!(this.tag_titles.length==1 && this.tag_titles[0]=="")){ this.tags = IriSP._(this.tag_titles).map(function(_tag_title) { @@ -184,13 +200,13 @@ this.renderTemplate(); if (this.show_mic_record) { this.recorder = this.$.find("embed")[0]; - + window.setAudioUrl = function(_url) { _this.audio_url = _url; - } + }; } if (this.show_slice) { - this.insertSubwidget( + this.slice_widget = this.insertSubwidget( this.$.find(".Ldt-CreateAnnotation-Slice"), { type: "Slice", @@ -243,14 +259,34 @@ if (this.show_creator_field) { this.$.find(".Ldt-CreateAnnotation-Creator").bind("change keyup input paste", this.functionWrapper("onCreatorChange")); } - + this.$.find("[class^='Ldt-CreateAnnotation-Control-']").click(function() { + var action = this.className.replace('Ldt-CreateAnnotation-Control-', ''); + switch (action) { + case "In": + // Set In bound to current player time + _this.begin = new IriSP.Model.Time(_this.media.getCurrentTime() || 0); + _this.$.find(".Ldt-CreateAnnotation-Begin").html(_this.begin.toString()); + break; + case "Out": + // Set In bound to current player time + _this.end = new IriSP.Model.Time(_this.media.getCurrentTime() || _this.media.duration); + _this.$.find(".Ldt-CreateAnnotation-End").html(_this.end.toString()); + break; + case "Play": + _this.media.setCurrentTime(_this.begin); + _this.media.play() + break; + } + return false; + }); + if (this.start_visible) { this.show(); } else { this.$.hide(); this.hide(); } - + this.onMdpEvent("CreateAnnotation.toggle","toggle"); this.$.find("form").submit(this.functionWrapper("onSubmit")); }; @@ -294,10 +330,18 @@ }; IriSP.Widgets.CreateAnnotation.prototype.toggle = function() { + var _this = this; if (!this.always_visible) { if (this.visible) { this.hide(); } else { + _this.begin = new IriSP.Model.Time(_this.media.getCurrentTime() || 0); + _this.end = new IriSP.Model.Time(_this.media.getCurrentTime() || 0); + _this.$.find(".Ldt-CreateAnnotation-Begin").html(_this.begin.toString()); + _this.$.find(".Ldt-CreateAnnotation-End").html(_this.end.toString()); + if (_this.slice_widget) { + _this.slice_widget.setBounds(_this.begin, _this.end); + } this.show(); } } @@ -375,11 +419,11 @@ if (!this.onDescriptionChange() || (this.show_title_field && !this.onTitleChange()) || (this.show_creator_field && !this.onCreatorChange())) { return false; } - + if (this.recorder) { this.recorder.stopRecord(); } - + var _this = this, _exportedAnnotations = new IriSP.Model.List(this.player.sourceManager), /* Création d'une liste d'annotations contenant une annotation afin de l'envoyer au serveur */ _export = this.player.sourceManager.newLocalSource({serializer: IriSP.serializers[this.api_serializer]}), /* Création d'un objet source utilisant un sérialiseur spécifique pour l'export */ @@ -387,7 +431,7 @@ _annotationTypes = this.source.getAnnotationTypes().searchByTitle(this.annotation_type, true), /* Récupération du type d'annotation dans lequel l'annotation doit être ajoutée */ _annotationType = (_annotationTypes.length ? _annotationTypes[0] : new IriSP.Model.AnnotationType(false, _export)), /* Si le Type d'Annotation n'existe pas, il est créé à la volée */ _url = Mustache.to_html(this.api_endpoint_template, {id: this.source.projectId}); /* Génération de l'URL à laquelle l'annotation doit être envoyée, qui doit inclure l'ID du projet */ - + /* Si nous avons dû générer un ID d'annotationType à la volée... */ if (!_annotationTypes.length) { /* Il ne faudra pas envoyer l'ID généré au serveur */ @@ -395,7 +439,7 @@ /* Il faut inclure le titre dans le type d'annotation */ _annotationType.title = this.annotation_type; } - + /* * Nous remplissons les données de l'annotation générée à la volée * ATTENTION: Si nous sommes sur un MASHUP, ces éléments doivent se référer AU MEDIA D'ORIGINE @@ -403,20 +447,22 @@ _annotation.setMedia(this.source.currentMedia.id); /* Id du média annoté */ _annotation.setBegin(this.begin); /*Timecode de début */ _annotation.setEnd(this.end); /* Timecode de fin */ - + _annotation.created = new Date(); /* Date de création de l'annotation */ + _annotation.setAnnotationType(_annotationType.id); /* Id du type d'annotation */ + _annotation.description = this.$.find(".Ldt-CreateAnnotation-Description").val(); /* Champ description */ if (this.show_title_field) { /* Champ titre, seulement s'il est visible */ _annotation.title = this.$.find(".Ldt-CreateAnnotation-Title").val(); + } else { + _annotation.title = _annotation.description; } - _annotation.created = new Date(); /* Date de création de l'annotation */ - _annotation.description = this.$.find(".Ldt-CreateAnnotation-Description").val(); /* Champ description */ - + var tagIds = Array.prototype.map.call( this.$.find(".Ldt-CreateAnnotation-TagLi.selected"), function(el) { return IriSP.jQuery(el).attr("tag-id")} ); - + IriSP._(_annotation.description.match(/#[^\s#.,;]+/g)).each(function(_tt) { var _tag, _tag_title = _tt.replace(/^#/,''), @@ -431,9 +477,9 @@ if (tagIds.indexOf(_tag.id) === -1) { tagIds.push(_tag.id); } - + }) - + _annotation.setTags(IriSP._(tagIds).uniq()); /*Liste des ids de tags */ if (this.audio_url) { _annotation.audio = { @@ -448,47 +494,65 @@ _annotation.creator = this.creator_name; } _exportedAnnotations.push(_annotation); /* Ajout de l'annotation à la liste à exporter */ - _export.addList("annotation",_exportedAnnotations); /* Ajout de la liste à exporter à l'objet Source */ - - var _this = this; - /* Envoi de l'annotation via AJAX au serveur ! */ - IriSP.jQuery.ajax({ - url: _url, - type: this.api_method, - contentType: 'application/json', - data: _export.serialize(), /* L'objet Source est sérialisé */ - success: function(_data) { - _this.showScreen('Saved'); /* Si l'appel a fonctionné, on affiche l'écran "Annotation enregistrée" */ - if (_this.after_send_timeout) { /* Selon les options de configuration, on revient à l'écran principal ou on ferme le widget, ou rien */ - window.setTimeout( - function() { - _this.close_after_send - ? _this.hide() - : _this.show(); - }, - _this.after_send_timeout - ); + + if (this.api_method == 'local') { + // Append to existing localStorage annotations + // FIXME: handle movie ids + /* Use localStorage */ + /* Data will be serialized in the localStore property designated by api_endpoint_template */ + _export.addList("annotation", _exportedAnnotations); /* Ajout de la liste à exporter à l'objet Source */ + _this.source.merge(_export); /* On ajoute la nouvelle annotation au recueil original */ + // Import previously saved local annotations + if (window.localStorage[_this.api_endpoint_template]) { + _export.deSerialize(window.localStorage[_this.api_endpoint_template]); + } + // Save everything back + window.localStorage[_this.api_endpoint_template] = _export.serialize(); + if (_this.pause_on_write && _this.media.getPaused()) { + _this.media.play(); + } + _this.player.trigger("AnnotationsList.refresh"); /* On force le rafraîchissement du widget AnnotationsList */ + _this.$.find(".Ldt-CreateAnnotation-Description").val(""); + } else { + _export.addList("annotation",_exportedAnnotations); /* Ajout de la liste à exporter à l'objet Source */ + /* Envoi de l'annotation via AJAX au serveur ! */ + IriSP.jQuery.ajax({ + url: _url, + type: this.api_method, + contentType: 'application/json', + data: _export.serialize(), /* L'objet Source est sérialisé */ + success: function(_data) { + _this.showScreen('Saved'); /* Si l'appel a fonctionné, on affiche l'écran "Annotation enregistrée" */ + if (_this.after_send_timeout) { /* Selon les options de configuration, on revient à l'écran principal ou on ferme le widget, ou rien */ + window.setTimeout( + function() { + _this.close_after_send + ? _this.hide() + : _this.show(); + }, + _this.after_send_timeout + ); + } + _export.getAnnotations().removeElement(_annotation, true); /* Pour éviter les doublons, on supprime l'annotation qui a été envoyée */ + _export.deSerialize(_data); /* On désérialise les données reçues pour les réinjecter */ + _this.source.merge(_export); /* On récupère les données réimportées dans l'espace global des données */ + if (_this.pause_on_write && _this.media.getPaused()) { + _this.media.play(); + } + _this.player.trigger("AnnotationsList.refresh"); /* On force le rafraîchissement du widget AnnotationsList */ + }, + error: function(_xhr, _error, _thrown) { + IriSP.log("Error when sending annotation", _thrown); + _export.getAnnotations().removeElement(_annotation, true); + _this.showScreen('Error'); + window.setTimeout(function(){ + _this.showScreen("Main") + }, + (_this.after_send_timeout || 5000)); } - _export.getAnnotations().removeElement(_annotation, true); /* Pour éviter les doublons, on supprime l'annotation qui a été envoyée */ - _export.deSerialize(_data); /* On désérialise les données reçues pour les réinjecter */ - _this.source.merge(_export); /* On récupère les données réimportées dans l'espace global des données */ - if (_this.pause_on_write && _this.media.getPaused()) { - _this.media.play(); - } - _this.player.trigger("AnnotationsList.refresh"); /* On force le rafraîchissement du widget AnnotationsList */ - }, - error: function(_xhr, _error, _thrown) { - IriSP.log("Error when sending annotation", _thrown); - _export.getAnnotations().removeElement(_annotation, true); - _this.showScreen('Error'); - window.setTimeout(function(){ - _this.showScreen("Main") - }, - (_this.after_send_timeout || 5000)); - } - }); - this.showScreen('Wait'); - + }); + this.showScreen('Wait'); + } + return false; }; - diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/Highlighter.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/Highlighter.css Fri Feb 13 16:57:53 2015 +0100 @@ -0,0 +1,3 @@ +.currentAnnotation { + border: solid 3px red; +} diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/Highlighter.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/Highlighter.js Fri Feb 13 16:57:53 2015 +0100 @@ -0,0 +1,43 @@ +IriSP.Widgets.Highlighter = function(player, config) { + var _this = this; + IriSP.Widgets.Widget.call(this, player, config); + this.throttledRefresh = IriSP._.throttle(function() { + console.log("highlighter Refresh"); + _this.update(); + }, 800); +}; + +/** + * Highlighter widget + * This widgets highlights the current annotations by setting the + * .activeAnnotation class on appropriate .Ldt-Highlighter-Annotation + * elements. These elements *must* have data-begin and data-end properties specifying their bounds (in ms) (and data-media specifying the media-id) + */ +IriSP.Widgets.Highlighter.prototype = new IriSP.Widgets.Widget(); + +IriSP.Widgets.Highlighter.prototype.defaults = { +} + +IriSP.Widgets.Highlighter.prototype.update = function() { + var _this = this; + var _currentTime = _this.media.getCurrentTime(); + _this.$.find(".Ldt-Highlighter-Annotation", document).toggleClass("currentAnnotation", function () { + return (this.dataset.media === _this.media.id && this.dataset.begin <= _currentTime && _currentTime < this.dataset.end); + }); + console.log(_this.$.find(".currentAnnotation")); + return false; +}; + +IriSP.Widgets.Highlighter.prototype.draw = function() { + var _this = this; + + var _events = [ + "timeupdate", + "seeked", + "loadedmetadata" + ]; + for (var _i = 0; _i < _events.length; _i++) { + _this.onMediaEvent(_events[_i], _this.throttledRefresh); + } + _this.throttledRefresh(); +}; diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/ImageDisplay.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/ImageDisplay.css Fri Feb 13 16:57:53 2015 +0100 @@ -0,0 +1,32 @@ +/* Nothing */ +.Ldt-ImageDisplay-Container { + margin: auto; +} + +.Ldt-ImageDisplay-Image { + max-width: 100%; + max-height: 100%; +} + +.Ldt-ImageDisplay-Overlay { + width: 20%; + min-width: 20px; + height: 100%; + opacity: 0.1; + position: absolute; + top: 0px; + bottom: 0px; +} + +.Ldt-ImageDisplay-Overlay:hover { +} + +.Ldt-ImageDisplay-Overlay-Left { + left: 0px; + cursor: url(img/hand_left.png), pointer; +} + +.Ldt-ImageDisplay-Overlay-Right { + right: 0px; + cursor: url(img/hand_right.png), pointer; +} diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/ImageDisplay.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/ImageDisplay.js Fri Feb 13 16:57:53 2015 +0100 @@ -0,0 +1,42 @@ +/* This widget displays the image associated to the annotation in the given container */ + +IriSP.Widgets.ImageDisplay = function(player, config) { + IriSP.Widgets.Widget.call(this, player, config); +} + +IriSP.Widgets.ImageDisplay.prototype = new IriSP.Widgets.Widget(); + +IriSP.Widgets.ImageDisplay.prototype.defaults = { + annotation_type: "Slides", + // container: "imageContainer" +} + +IriSP.Widgets.ImageDisplay.prototype.template = '
    Slide Image
    '; + +IriSP.Widgets.ImageDisplay.prototype.annotationTemplate = ''; + +IriSP.Widgets.ImageDisplay.prototype.update = function(annotation) { + // Update the widget with data corresponding to the annotation + this.image.setAttribute("title", IriSP.textFieldHtml(annotation.title) + " - " + annotation.begin.toString()); + this.image.setAttribute("src", annotation.thumbnail); +}; + +IriSP.Widgets.ImageDisplay.prototype.draw = function() { + var _annotations = this.getWidgetAnnotations().sortBy(function(_annotation) { + return _annotation.begin; + }); + var _this = this; + _this.renderTemplate(); + _this.image = _this.$.find("img")[0]; + + _this.$.find(".Ldt-ImageDisplay-Overlay-Left").on("click", function () { _this.navigate(-1); }); + _this.$.find(".Ldt-ImageDisplay-Overlay-Right").on("click", function () { _this.navigate(+1); }); + + _annotations.forEach(function(_a) { + _a.on("enter", function() { + _this.update(_a); + }); + }); + if (_annotations.length) + _this.update(_annotations[0]); +} diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/MultiSegments.js --- a/src/widgets/MultiSegments.js Fri Feb 13 16:48:05 2015 +0100 +++ b/src/widgets/MultiSegments.js Fri Feb 13 16:57:53 2015 +0100 @@ -133,4 +133,4 @@ } });*/ } -}; \ No newline at end of file +}; diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/Segments.js --- a/src/widgets/Segments.js Fri Feb 13 16:48:05 2015 +0100 +++ b/src/widgets/Segments.js Fri Feb 13 16:57:53 2015 +0100 @@ -13,7 +13,9 @@ background: "#e0e0e0", overlap: .25, found_color: "#FF00FC", - faded_found_color: "#ff80fc" + faded_found_color: "#ff80fc", + // Display creator info in segment tooltip + show_creator: true }; IriSP.Widgets.Segments.prototype.template = @@ -77,7 +79,7 @@ color : color, medcolor: medcolor, lowcolor: lowcolor, - text: (_annotation.creator ? (_annotation.creator + " : ") : "" ) + _fulltext.replace(/(\n|\r|\r\n)/mg,' ').replace(/(^.{120,140})[\s].+$/m,'$1…'), + text: ((_this.show_creator && _annotation.creator) ? (_annotation.creator + " : ") : "" ) + _fulltext.replace(/(\n|\r|\r\n)/mg,' ').replace(/(^.{120,140})[\s].+$/m,'$1…'), left : _left, width : _width, top: _top, @@ -89,14 +91,17 @@ }; var _html = Mustache.to_html(_this.annotationTemplate, _data), _el = IriSP.jQuery(_html); - _el.mouseover(function() { + _el.mouseover(function() { _annotation.trigger("select"); + _this.player.trigger('annotation-select', _annotation); }) .mouseout(function() { _annotation.trigger("unselect"); + _this.player.trigger('annotation-unselect', _annotation); }) .click(function() { _annotation.trigger("click"); + _this.player.trigger('annotation-click', _annotation); }) .appendTo(list_$); IriSP.attachDndData(_el, { @@ -172,4 +177,4 @@ this.$.find('.Ldt-Segments-Position').css({ left: _x + "px" }); -}; \ No newline at end of file +}; diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/Shortcuts.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/Shortcuts.css Fri Feb 13 16:57:53 2015 +0100 @@ -0,0 +1,1 @@ +/* Empty file */ diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/Shortcuts.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/Shortcuts.js Fri Feb 13 16:57:53 2015 +0100 @@ -0,0 +1,58 @@ +IriSP.Widgets.Shortcuts = function(player, config) { + IriSP.Widgets.Widget.call(this, player, config); +}; + +/** + * Keyboard shortcuts widget + * This widgets add global shortcuts for common actions. + * The default shortcuts are: + * - Escape or Control-space for play/pause + * - Control-left for rewind (+shift to go faster) + * - Control-right for forward (+shift to go faster) + */ +IriSP.Widgets.Shortcuts.prototype = new IriSP.Widgets.Widget(); + +IriSP.Widgets.Shortcuts.prototype.defaults = { + // Time increment, in ms, for backward/forward navigation + time_increment: 2000 +} + +IriSP.Widgets.Shortcuts.prototype.draw = function() { + var _this = this; + + /* Standard shortcuts */ + Mousetrap.bindGlobal(["esc", "ctrl+space"], function (e) { + e.preventDefault(); + if (! _this.media.getPaused()) { + _this.media.pause(); + } else { + _this.media.play(); + } + return false; + }); + Mousetrap.bindGlobal("ctrl+left", function (e) { + // Backward + e.preventDefault(); + _this.media.setCurrentTime(Math.max(0, _this.media.getCurrentTime() - _this.time_increment)); + return false; + }); + Mousetrap.bindGlobal("ctrl+shift+left", function (e) { + // Backward + e.preventDefault(); + _this.media.setCurrentTime(Math.max(0, _this.media.getCurrentTime() - 5 * _this.time_increment)); + return false; + }); + Mousetrap.bindGlobal("ctrl+right", function (e) { + // Forward + e.preventDefault(); + _this.media.setCurrentTime(Math.min(_this.media.duration, _this.media.getCurrentTime() + _this.time_increment)); + return false; + }); + Mousetrap.bindGlobal("ctrl+shift+right", function (e) { + // Forward + e.preventDefault(); + _this.media.setCurrentTime(Math.min(_this.media.duration, _this.media.getCurrentTime() + 5 * _this.time_increment)); + return false; + }); + +}; diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/Slice.js --- a/src/widgets/Slice.js Fri Feb 13 16:48:05 2015 +0100 +++ b/src/widgets/Slice.js Fri Feb 13 16:57:53 2015 +0100 @@ -67,9 +67,16 @@ this.getWidgetAnnotations().forEach(function(_a) { _a.on("enter", function() { - _this.$slider.slider("values",[_a.begin, _a.end]); + _this.setBounds(_a.begin, _a.end); }); }); + this.player.on("annotation-click", function(_a) { + _this.setBounds(_a.begin, _a.end); + }); +}; + +IriSP.Widgets.Slice.prototype.setBounds = function(begin, end) { + this.$slider.slider("values", [ begin, end ]); }; IriSP.Widgets.Slice.prototype.show = function() { diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/SlideVideoPlayer.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/SlideVideoPlayer.css Fri Feb 13 16:57:53 2015 +0100 @@ -0,0 +1,23 @@ +/* Empty for now */ + +.Ldt-SlideVideoPlayer-panel { + display: flex; + width: 50%; + float: left; +} + +.Ldt-SlideVideoPlayer { + min-height: 400px; + max-height: 100%; +} + +.Ldt-SlideVideoPlayer { + width: 100%; + max-width: 100%; + height: 100%; + max-height: 100%; +} + +.Ldt-SlideVideoPlayer h2 { + display: none; +} diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/SlideVideoPlayer.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/SlideVideoPlayer.js Fri Feb 13 16:57:53 2015 +0100 @@ -0,0 +1,46 @@ +IriSP.Widgets.SlideVideoPlayer = function(player, config) { + IriSP.loadCss(IriSP.getLib("cssSplitter")); + IriSP.Widgets.Widget.call(this, player, config); +}; + +IriSP.Widgets.SlideVideoPlayer.prototype = new IriSP.Widgets.Widget(); + + +IriSP.Widgets.SlideVideoPlayer.prototype.defaults = { +}; + +IriSP.Widgets.SlideVideoPlayer.prototype.template = '
    \ +
    \ +
    \ +
    \ +
    '; + +IriSP.Widgets.SlideVideoPlayer.prototype.draw = function() { + var _this = this; + + _this.renderTemplate(); + this.insertSubwidget( + _this.$.find(".Ldt-SlideVideoPlayer-panel.Ldt-SlideVideoPlayer-slide"), + { + type: "ImageDisplay", + annotation_type: _this.annotation_type, + width: '100%' + }, + "slide" + ); + this.insertSubwidget( + _this.$.find(".Ldt-SlideVideoPlayer-panel.Ldt-SlideVideoPlayer-video"), + { + type: "HtmlPlayer", + video: _this.video, + width: '100%', + url_transform: _this.url_transform + }, + "player" + ); + // FIXME: this should be better implemented through a signal sent + // when widgets are ready (and not just loaded) + window.setTimeout(function () { + _this.$.find(".Ldt-SlideVideoPlayer").split({ orientation: 'vertical' }); + }, 1000); +} diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/Trace.js --- a/src/widgets/Trace.js Fri Feb 13 16:48:05 2015 +0100 +++ b/src/widgets/Trace.js Fri Feb 13 16:57:53 2015 +0100 @@ -68,9 +68,9 @@ this.tracer.trace("TraceWidgetInit", {}); - + _this.player.trigger("trace-ready"); this.mouseLocation = ''; - IriSP.jQuery(".Ldt-Widget").on("click mouseenter mouseleave", ".Ldt-TraceMe", function(_e) { + IriSP.jQuery(".Ldt-Widget").on("mousedown mouseenter mouseleave", ".Ldt-TraceMe", function(_e) { var _target = IriSP.jQuery(this); var _widget = _target.attr("widget-type") || _target.parents(".Ldt-Widget").attr("widget-type"), @@ -127,4 +127,4 @@ if (this.js_console && typeof window.console !== "undefined" && typeof console.log !== "undefined") { console.log("tracer.trace('" + _traceName + "', " + JSON.stringify(_arg) + ");"); } -}; \ No newline at end of file +}; diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/Transcript.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets/Transcript.js Fri Feb 13 16:57:53 2015 +0100 @@ -0,0 +1,37 @@ +/* This widget displays annotations as a transcript */ + +IriSP.Widgets.Transcript = function(player, config) { + IriSP.Widgets.Widget.call(this, player, config); +} + +IriSP.Widgets.Transcript.prototype = new IriSP.Widgets.Widget(); + +IriSP.Widgets.Transcript.prototype.defaults = { + annotation_type: "Caption" + // container: "transcriptContainer" +} + +IriSP.Widgets.Transcript.prototype.template = '
    '; + +IriSP.Widgets.Transcript.prototype.annotationTemplate = '{{ content }} '; + +IriSP.Widgets.Transcript.prototype.draw = function() { + var _annotations = this.getWidgetAnnotations(); + var _this = this; + var content; + + _this.renderTemplate(); + content = _this.$.find('.Ldt-TranscriptWidget'); + + _annotations.forEach(function(_a) { + var _data = { + id : _a.id, + content : IriSP.textFieldHtml(_a.title), + begin : _a.begin.toString(), + end : _a.end.toString() + }; + var _html = Mustache.to_html(_this.annotationTemplate, _data); + var _el = IriSP.jQuery(_html); + content.append(_el); + }); +}; diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/img/hand_left.png Binary file src/widgets/img/hand_left.png has changed diff -r 74ac0be7655c -r c20df1c080e6 src/widgets/img/hand_right.png Binary file src/widgets/img/hand_right.png has changed