yaz/commit
ADD annotation form to shot gardening
author | Michiel Hildebrand |
---|---|
Mon Oct 31 17:13:55 2011 +0100 | |
committer | Michiel Hildebrand |
Mon Oct 31 17:13:55 2011 +0100 | |
commit | 27ee4d57e5fa6d04b39de646904c41d8309ee9e6 |
tree | ed42bbf6d26b2fc8ef3676a33bb7dd05856967da |
parent | 78b53172f8a2cfab034c033b0111e1fde78a8cea |
Diff style: patch stat
diff --git a/applications/yaz_shot_annotation.pl b/applications/yaz_shot_annotation.pl index 6aba072..bc392f3 100644 --- a/applications/yaz_shot_annotation.pl +++ b/applications/yaz_shot_annotation.pl @@ -28,22 +28,24 @@ :- use_module(library(user_process)). :- http_handler(yaz(shot), http_yaz_shot, []). +:- http_handler(yaz(data/shot/get/annotations), http_data_shot_get_annotations, []). +:- http_handler(yaz(data/shot/set/annotation), http_data_shot_set_annotation, []). -reconcile_source(gtaa, +reconcile_source(person, 'Person', Server, Params) :- http_location_by_id(http_reconcile, Server), www_form_encode('[{"http://www.w3.org/2004/02/skos/core#inScheme":"http://data.beeldengeluid.nl/gtaa/GTAA"}]',Ps), atom_concat('&properties=',Ps,Params). -reconcile_source(cornetto, +reconcile_source(subject, 'Subject', Server, '&type=http://purl.org/vocabularies/cornetto/Synset') :- http_location_by_id(http_reconcile, Server). -reconcile_source(geonames, +reconcile_source(location, 'Location', 'http://api.kasabi.com/api/reconciliation-api-geonames', '&apikey=908177a484aa25f9b602d3eb76cf057d73e7aa39'). @@ -93,13 +95,14 @@ html_page(Video, Annotations, Sources) :- div([class('yui3-u'), id(main)], [ div(class(hd), '2. View content'), div(id(timeline), []), - div(id(videoplayer), []) + div(id(videoplayer), []), + div(id(annotationform), []) ]), div([class('yui3-u'), id('select-tag')], [ div(class(hd), '3. Select relevant tags'), div(id(taglist), []) ]), - div([class('yui3-u'), id('select-concept')], + div([class('yui3-u hidden'), id('select-concept')], [ div(class(hd), '4. Select concept'), div(id(taglinker), []) ]) @@ -130,22 +133,28 @@ html_video_page_yui(Video, Annotations, Sources) --> http_absolute_location(js('videoframes/videoframes.js'), Videoframes, []), http_absolute_location(js('tagplayer/taglist.js'), Taglist, []), http_absolute_location(js('tagplayer/tagLinker.js'), TagLinker, []), + http_absolute_location(js('annotation/annotation_form.js'), AnnotationForm, []), annotation_to_json(Annotations, JSONTags), - Frames = [{startTime:2000},{startTime:10000},{startTime:20000}, - {startTime:30000},{startTime:40000},{startTime:50000}] + Frames = [{uri:'http://semanticweb.cs.vu.nl/prestoprime/shot1', startTime:2000}, + {uri:'http://semanticweb.cs.vu.nl/prestoprime/shot2', startTime:10000}, + {uri:'http://semanticweb.cs.vu.nl/prestoprime/shot3', startTime:20000}, + {uri:'http://semanticweb.cs.vu.nl/prestoprime/shot4', startTime:30000}, + {uri:'http://semanticweb.cs.vu.nl/prestoprime/shot5', startTime:40000}, + {uri:'http://semanticweb.cs.vu.nl/prestoprime/shot6', startTime:50000}] }, html_requires(js('videoplayer/swfobject.js')), js_yui3([{modules:{'video-player':{fullpath:Videoplayer}, 'timeline':{fullpath:Timeline}, 'video-frames':{fullpath:Videoframes}, 'tag-list':{fullpath:Taglist}, - 'tag-linker':{fullpath:TagLinker} + 'tag-linker':{fullpath:TagLinker}, + 'annotation-form':{fullpath:AnnotationForm} }} ], [node,event,widget,anim, - 'json','jsonp','querystring-stringify-simple',io, + 'json-parse','jsonp','querystring-stringify-simple',io, 'video-player','video-frames',timeline, - 'tag-list','tag-linker' + 'tag-list','tag-linker','annotation-form' ], [ \js_new(videoPlayer, 'Y.mazzle.VideoPlayer'({filepath:FilePath, @@ -177,30 +186,38 @@ html_video_page_yui(Video, Annotations, Sources) --> })), \js_new(tagList, 'Y.mazzle.TagList'({tags:JSONTags, - height:425, - width:220 + height:400 })), \js_new(tagLinker, 'Y.mazzle.TagLinker'({sources:Sources, - width:220 + height:400 })), + \js_new(annotationForm, + 'Y.mazzle.AnnotationForm'({ + width:540 + })), \js_yui3_render(videoPlayer, #(videoplayer)), \js_yui3_render(timeline, #(timeline)), \js_yui3_render(videoFrames, #(videoframes)), \js_yui3_render(tagList, #(taglist)), \js_yui3_render(tagLinker, #(taglinker)), + \js_yui3_render(annotationForm, #(annotationform)), \js_yui3_on(videoFrames, frameSelect, \js_frame_select), \js_yui3_on(tagList, itemSelect, \js_tag_select), - \js_yui3_on(tagLinker, submit, \js_concept_select), + \js_yui3_on(tagLinker, conceptSelect, \js_concept_select(Video)), \js_yui3_on(tagLinker, cancel, \js_concept_cancel) ]). js_frame_select --> + { http_location_by_id(http_data_shot_get_annotations, Server) + }, js_function([e], \[ ' var frame = e.frame; var time = (frame.startTime/1000); - videoPlayer.setTime(time, true);\n' + videoPlayer.setTime(time, true); + Y.io("',Server,'?shot="+frame.uri, + {on:{success:function(e,o){annotationForm.set("annotations", Y.JSON.parse(o.response))}}});\n' ]). js_tag_select --> @@ -216,10 +233,77 @@ js_concept_cancel --> ' Y.one("#select-concept").addClass("hidden"); Y.one("#select-tag").removeClass("hidden");\n' ]). -js_concept_select --> +js_concept_select(Video) --> + { http_location_by_id(http_data_shot_set_annotation, Server) + }, js_function([e], \[ -' console.log(e.concept); +' var source = e.source, + concept = e.concept, + shot = videoFrames.get("selected").uri; Y.one("#select-concept").addClass("hidden"); - Y.one("#select-tag").removeClass("hidden");\n' + Y.one("#select-tag").removeClass("hidden"); + console.log(shot);\n', +' if(shot) { + Y.io("',Server,'?video=',Video,'&shot="+shot+"&value="+concept.id+"&label="+concept.name+"&type="+source, + {on:{success:function(e,o){annotationForm.addAnnotation(source, Y.JSON.parse(o.response)) }}}); + }\n' ]). + + + + +%% http_data_shot_annotations(+Request) +% +% + +http_data_shot_get_annotations(Request) :- + http_parameters(Request, + [ shot(Shot, + [description('URI of the shot')]) + ]), + findall(Type-json([entry=E, value=Value, label=Label]), + shot_annotation(Shot, Value, Label, Type, E), + As), + group_pairs_by_key(As, Annotations), + reply_json(json(Annotations)). + +http_data_shot_set_annotation(Request) :- + http_parameters(Request, + [ video(Video, + [description('URI of the video')]), + shot(Shot, + [description('URI of the shot')]), + value(Value, + [description('Annotation value')]), + label(Label, + [description('Preferred display label')]), + type(Type, + [description('type of annotation')]) + ]), + logged_on(User0, anonymous), + user_property(User0, url(User)), + ( current_user_process(Process), + rdf(Process, rdf:type, pprime:'ShotGarden'), + rdf(Process, opmv:used, Video) + -> true + ; create_user_process(User, [rdf:type=pprime:'ShotGarden', + opmv:used=Video + ], _GardenProcess) + ), + rdfh_transaction(assert_shot_annotation(Shot, Value, Label, Type, Event)), + reply_json(json([shot=Shot,value=Value,label=Label,entry=Event])). + +shot_annotation(Shot, Value, Label, Type, R) :- + rdf(R, pprime:shot, Shot), + rdf(R, pprime:value, Value), + rdf(R, pprime:type, Type), + rdf(R, pprime:label, Label). + +assert_shot_annotation(Shot, Value, Label, Type, R) :- + rdf_bnode(R), + rdfh_assert(R, rdf:type, pprime:'ShotAnnotation'), + rdfh_assert(R, pprime:shot, Shot), + rdfh_assert(R, pprime:value, Value), + rdfh_assert(R, pprime:type, Type), + rdfh_assert(R, pprime:label, Label). diff --git a/config-available/yaz.pl b/config-available/yaz.pl index 32e6078..228cabf 100644 --- a/config-available/yaz.pl +++ b/config-available/yaz.pl @@ -39,6 +39,7 @@ :- use_module(applications(yaz_player)). :- use_module(applications(yaz_fplayer)). :- use_module(applications(yaz_garden)). +:- use_module(applications(yaz_shot_annotation)). :- use_module(applications(yaz_video_stats)). % http path and handlers diff --git a/web/css/shotgarden.css b/web/css/shotgarden.css index 4a87078..ef0f081 100644 --- a/web/css/shotgarden.css +++ b/web/css/shotgarden.css @@ -45,6 +45,9 @@ #select-concept.hidden { display:none; } +#annotationform { + margin-top: 10px; +} @@ -150,30 +153,72 @@ background: transparent; } .yui3-tag-linker-content { - overflow: auto; - height: 398px; border: 1px solid #CCCCCC; } +.yui3-tag-linker .tag { + height: 17px; + padding: 4px; +} .yui3-tag-linker .sources { - height: 370px; + height: 348px; + overflow: auto; } .yui3-tag-linker .controls { height: 25px; } .yui3-tag-linker .controls button { - width: 50%; + width: 100%; height: 25px; border: none; } +.yui3-tag-linker .source-hd { + border-bottom: 1px solid #DDDDDD; + font-weight: bold; + margin: 4px 4px 0; +} .yui3-tag-linker ul { margin: 0; padding: 0; } .yui3-tag-linker li { list-style: none; - margin: 1px 0; - padding: 4px 8px; + padding: 4px; } .yui3-tag-linker li.hidden { display: none; -} \ No newline at end of file +} + +/* reconcile items */ +.reconcile-item { +} +.reconcile-item input { +} +.reconcile-item .desc { + padding-left: 16px; + font-size: 95%; + color: #888; +} + +/* annotation form */ +.yui3-annotation-form-content { + border: 1px solid #CCCCCC; +} +.yui3-annotation-form .an-list { + min-height: 25px; + padding-left: 6px; + overflow: auto; +} +.yui3-annotation-form .an-hd { + font-weight: bold; + margin: 4px 4px 0; +} +.yui3-annotation-form ul { + margin: 0; + padding: 0; +} +.yui3-annotation-form li { + list-style: none; + padding: 4px; + float: left; +} + diff --git a/web/js/annotation/annotation_form.js b/web/js/annotation/annotation_form.js new file mode 100644 index 0000000..87f54a2 --- /dev/null +++ b/web/js/annotation/annotation_form.js @@ -0,0 +1,115 @@ +YUI.add('annotation-form', function(Y) { + + var Lang = Y.Lang, + Widget = Y.Widget, + Node = Y.Node; + + var NS = Y.namespace('mazzle'); + NS.AnnotationForm = AnnotationForm; + + /* AnnotationForm class constructor */ + function AnnotationForm(config) { + AnnotationForm.superclass.constructor.apply(this, arguments); + } + + /* + * Required NAME static field, to identify the Widget class and + * used as an event prefix, to generate class names etc. (set to the + * class name in camel case). + */ + AnnotationForm.NAME = "annotation-form"; + + /* + * The attribute configuration for the AnnotationForm widget. Attributes can be + * defined with default values, get/set functions and validator functions + * as with any other class extending Base. + */ + AnnotationForm.ATTRS = { + target: { + value: null + }, + fields: { + value: { + person:{label:"Person"}, + location:{label:"Location"}, + subject:{label:"Subject"} + } + }, + annotations: { + value: {} + } + }; + + /* AnnotationForm extends the base Widget class */ + Y.extend(AnnotationForm, Widget, { + + initializer: function() { + }, + + destructor : function() { + }, + + renderUI : function() { + var fields = this.get("fields"); + + for (var key in fields) { + this._renderField(fields[key]); + } + }, + + bindUI : function() { + this.after("annotationsChange", function(e) {this.syncUI()}); + }, + + syncUI : function() { + var fields = this.get("fields"), + annotations = this.get("annotations"); + + for (var key in fields) { + if(key) { + this._setAnnotations(fields[key], annotations[key]); + } + } + }, + + _renderField : function(a) { + var content = this.get("contentBox"), + label = a.label; + + var box = content.appendChild(Node.create('<div class="an-source"></div>')); + box.appendChild('<div class="an-hd">'+label+'</div>'); + a.list = box.appendChild(Node.create('<ul class="an-list"></ul>')); + }, + + _setAnnotations : function(field, annotations) { + var list = field.list; + list.setContent(""); + if(annotations) { + for (var i=0; i < annotations.length; i++) { + list.appendChild(this.formatItem(annotations[i])); + } + } + }, + + formatItem : function(item) { + var value = item.value, + label = item.label; + + var html = "<li class='annotation'>"; + html += "<a target='_new' href='"+item.value+"' class='name'>"+label+"</a>"; + if(item.desc) { + html += "<div class='desc'>"+item.desc+"</div>"; + } + return html; + }, + + addAnnotation: function(field, annotation) { + var fields = this.get("fields"); + // annotations = this.get("annotations"); + //annotations[field].push[annotation]; + //this.set("annotations", annotations); + fields[field].list.appendChild(this.formatItem(annotation)); + } + }) + +}, 'gallery-2010.03.02-18' ,{requires:['node','event','widget','io','json']}); \ No newline at end of file diff --git a/web/js/tagplayer/tagLinker.js b/web/js/tagplayer/tagLinker.js index 3511bb9..0072ab4 100644 --- a/web/js/tagplayer/tagLinker.js +++ b/web/js/tagplayer/tagLinker.js @@ -52,15 +52,17 @@ YUI.add('tag-linker', function(Y) { var linkBox = content.appendChild(Node.create('<div class="sources"></div>')); var controls = content.appendChild('<div class="controls"></div>'); this.cancelButton = controls.appendChild('<button>cancel</button>'); - this.submitButton = controls.appendChild('<button>submit</button>'); this._renderSources(linkBox); this.tagNode = tagNode; }, bindUI : function() { + var sources = this.get("sources"); this.after("tagChange", this.syncUI); this.cancelButton.on("click", this._cancel, this); - this.submitButton.on("click", this._submit, this); + for(var key in sources) { + Y.delegate("click", this._itemSelect, sources[key]._list, "li input", this, key); + } }, syncUI : function() { @@ -79,15 +81,17 @@ YUI.add('tag-linker', function(Y) { this.fire("cancel", {"tag":tag}); }, - _submit: function() { + _itemSelect: function(e, type) { var tag = this.get("tag"), - selected = this.get("contentBox").one("input[name=reconcileItem]:checked"), - concept = selected.get("value"); - - if(concept) { - this.fire("submit", {"tag":tag, "concept":concept}); - } + source = this.get("sources")[type], + items = source.items; + var node = e.currentTarget.get("parentNode"), + index = e.container.all("li").indexOf(node), + item = items[index]; + + Y.log('selected concept '+item); + this.fire("conceptSelect", {"tag":tag, concept:item, source:type}); }, _renderSources : function(node) { @@ -104,11 +108,13 @@ YUI.add('tag-linker', function(Y) { } }, + + _renderSourceList : function() { var limit = this.get("limit"); var listNode = Node.create('<ul></ul>'); for (var i=0; i < limit; i++) { - listNode.appendChild("<li class='hidden'></li>"); + listNode.appendChild("<li class='reconcile-item hidden'></li>"); }; return listNode; }, @@ -137,6 +143,7 @@ YUI.add('tag-linker', function(Y) { Y.jsonp(request, function(response) { items.each(function(node, i) { var results = response.result; + source.items = results; if(results[i]) { node.setContent(oSelf.formatItem(results[i])); node.removeClass("hidden"); @@ -154,11 +161,11 @@ YUI.add('tag-linker', function(Y) { var html = "<input type=radio name='reconcileItem' value='"+id+"'>"; html += "<span class='name'>"+name+"</span>"; - html += "<div class='types'>"; + /*html += "<div class='types'>"; for (var i=0; i < types.length; i++) { html += "<span class='type'>"+types[i].name+"</span>"; } - html += "</div>"; + html += "</div>";*/ if(item.desc) { html += "<div class='desc'>"+item.desc+"</div>"; }