yaz/commit

ADD annotation form to shot gardening

authorMichiel Hildebrand
Mon Oct 31 17:13:55 2011 +0100
committerMichiel Hildebrand
Mon Oct 31 17:13:55 2011 +0100
commit27ee4d57e5fa6d04b39de646904c41d8309ee9e6
treeed42bbf6d26b2fc8ef3676a33bb7dd05856967da
parent78b53172f8a2cfab034c033b0111e1fde78a8cea
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>";
 			}