From 2431ed7933f1ccc58cca2210b879873dcd59e924 Mon Sep 17 00:00:00 2001 From: Anon Ray Date: Mon, 25 Aug 2014 18:52:49 +0530 Subject: [PATCH] Initial implementation of external API source (Open Cuultur Data) - An intial version to search in the Open Cuultur Data APIs and annotate images from search results. Right now fetching, and displaying of search results is implemented along with first attempt at annotating search result images. --- setup.py | 3 +- swtr/server.py | 87 +++++++++++++++++++--------- swtr/static/css/swtmaker.css | 35 +++++++++-- swtr/static/js/app.js | 1 + swtr/static/js/swtmaker.js | 132 +++++++++++++++++++++++++++++++++++++----- swtr/templates/index.html | 59 +++++++++++-------- 6 files changed, 248 insertions(+), 69 deletions(-) diff --git a/setup.py b/setup.py index 5a690a7..b063d34 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,8 @@ here = os.path.abspath(os.path.dirname(__file__)) requires = [ 'Flask', - 'requests' + 'requests', + 'lxml' ] setup(name='Swtr', diff --git a/swtr/server.py b/swtr/server.py index 76e6be9..6bc1c67 100644 --- a/swtr/server.py +++ b/swtr/server.py @@ -3,13 +3,14 @@ import flask from flask import session import lxml.html -import config import requests import json import StringIO import imghdr from datetime import datetime, timedelta +import config + app = flask.Flask(__name__) app.config['SECRET_KEY'] = config.secret_key @@ -80,9 +81,55 @@ def index(): url=flask.request.args.get('where')) +# endpoint to search the Open Cuultur Data APIs +# takes in `query`, `size`, and `from` parameters in query string +# returns a JSON response +@app.route('/search/ocd', methods=['GET']) +def search_ocd(): + query = flask.request.args.get('query') + #collection = flask.request.args.get('collection') + size = flask.request.args.get('size') or 10 + offset = flask.request.args.get('from') or 0 + + # if query parameter is not passed, return bad request. + if not query: + flask.abort(400) + + payload = { + 'query': query, + 'facets': { + 'collection': {}, + 'date': {'interval': 'day'} + }, + 'filters': { + 'media_content_type': {'terms': ['image/jpeg', 'image/png']} + }, + 'size': size, + 'from': offset + } + resp = requests.post('http://api.opencultuurdata.nl/v0/search', + data=json.dumps(payload)) + + response = flask.make_response() + response.data = json.dumps(resp.json()) + response.headers['Content-type'] = 'application/json' + return response + + +# resolve OCD Media URLs: http://docs.opencultuurdata.nl/user/api.html#resolver +@app.route('/resolve-ocd-media/', methods=['GET']) +def resolve_ocd_media_urls(media_hash): + + resp = requests.get('http://api.opencultuurdata.nl/v0/resolve/' + + media_hash) + + response = flask.make_response() + response.data = resp.url + return response + + @app.route('/annotate', methods=['GET']) def annotate(): - print flask.request.args['where'] # img = urllib2.urlopen(flask.request.args['where']).read() request = requests.get(flask.request.args['where']) content = request.text @@ -141,6 +188,18 @@ def annotate(): "js/lib/backbone-1.0.0.min.js")) backboneJS.set("type", "text/javascript") + # annotorious plugin + annotoriousCSS = root.makeelement('link') + root.body.append(annotoriousCSS) + annotoriousCSS.set("href", flask.url_for('static', + filename='css/annotorious.css')) + annotoriousCSS.set('rel', 'stylesheet') + + annotoriousJS = root.makeelement('script') + root.body.append(annotoriousJS) + annotoriousJS.set('src', flask.url_for('static', + filename='js/annotorious.okfn.0.3.js')) + if 'auth_tok' in session: auth_tok = session['auth_tok'] else: @@ -204,30 +263,6 @@ def annotate(): url=flask.request.args.get('where')) -@app.route("/search") -def search(): - if 'size' not in flask.request.args: - size = 10 - else: - size = flask.request.args['size'] - if 'from' not in flask.request.args: - fr = 0 - else: - fr = flask.request.args['from'] - - results = requests.post("http://api.opencultuurdata.nl/v0/search", - data=json.dumps({ - "query": flask.request.args['term'], - "facets": {"collection": {}, - "date": {"interval": "day"}}, - "filters": {"media_content_type": - {"terms": ["image/jpeg", - "image/png"]}}, - "size": size, - "from": fr})) - return flask.jsonify(results.json()) - - # if the app is run directly from command-line # assume its being run locally in a dev environment if __name__ == '__main__': diff --git a/swtr/static/css/swtmaker.css b/swtr/static/css/swtmaker.css index 1b89f7e..d21fb01 100644 --- a/swtr/static/css/swtmaker.css +++ b/swtr/static/css/swtmaker.css @@ -10,21 +10,29 @@ body { min-height: 35px; } - #swt-maker { } + +#control-panel { + margin-top: 10px; + margin-left: 0; +} + #signinview { margin 0 20px; padding: 10px; text-align: center; } -#img-annotation-wrapper { + +#img-annotation-wrapper, #ocd-results { margin: 30px auto 0 auto; } + #annotatable-img { margin: 0 auto; max-width: 1125px; } + #sweet-list-wrapper { width: 500px; min-height: 300px; @@ -38,17 +46,32 @@ body { padding: 30px; display: none; } + #sweet-list { margin-bottom: 10px; } -.btn-grp { - position: relative; - bottom: -10%; - right: -10%; + +.ocd-item { + padding-bottom: 4px; + border: 1px solid #ddd; + border-radius: 4px; + margin-bottom: 20px; + text-align: center; } + +.thumb-image { + background-position: center center; + background-repeat: no-repeat; + background-size: cover; + display: block; + height: 400px; + margin: 0 auto; +} + #sweet { display: none; } + #helpview { font-weight: bold; font-size: 1.3em; diff --git a/swtr/static/js/app.js b/swtr/static/js/app.js index 3471201..ed259aa 100644 --- a/swtr/static/js/app.js +++ b/swtr/static/js/app.js @@ -130,6 +130,7 @@ window.swtr = window.swtr || {}; this.annotator = new Annotator(document.body); swtr.anno = this.annotator; this.annotator.addPlugin("Tags"); + this.annotator.addPlugin("AnnotoriousImagePlugin"); this.listenTo(this.collection, "add", this.loadAnno); this.annotator.subscribe("annotationCreated", this.storeAnno); }, diff --git a/swtr/static/js/swtmaker.js b/swtr/static/js/swtmaker.js index 24a19b4..cca8d73 100644 --- a/swtr/static/js/swtmaker.js +++ b/swtr/static/js/swtmaker.js @@ -233,22 +233,27 @@ cleanUp: function() { //console.log('cleaning up'); $(this.el).hide(); + if(swtr.appView.source === 'ocd') { + $('#img-annotation-wrapper').hide(); + $('#ocd-results').show(); + } } }); var AppView = Backbone.View.extend({ el: $('#swt-maker'), events: { - 'click #img-url-load': 'setImage', - 'click #img-url-submit': 'setImage', + 'click #user-input-submit': 'submitUserInput', 'click #sweet': 'sweet', 'click #sign-in': 'signIn', 'click #setbox': 'showHide', - 'change #custom-dropdown ': 'getFormValue' + 'change #custom-dropdown ': 'getFormValue', + 'click #ocd-source': 'sourceChanged' //'mouseup .annotorious-editor-button-save': 'addnew_anno' }, initialize: function() { // initialize components + this.source = 'none'; this.helpview = new HelpView(); this.sweetsview = new SweetsView({collection: swtr.sweets}); @@ -277,7 +282,7 @@ this.imgURL = this.$img.attr('src'); if(this.imgURL) { this.initImageAnno(); - $('#img-url-input').val(this.imgURL); + $('#user-input').val(this.imgURL); } else { this.helpview.step(1); @@ -292,15 +297,27 @@ scopes: 'email,sweet' }); }, - setImage: function(event) { + submitUserInput: function(event) { event.preventDefault(); - var url = $('#img-url-input').val(); + var input = $('#user-input').val(); + if(this.source === 'ocd') { + this.loadOCDSearch(input); + } + else if (this.source === 'none') { + this.loadURL(input); + } + }, + loadURL: function(url) { + $('#ocd-results').hide(); + $('#img-annotation-wrapper').show(); + if(!url) { + return false; + } + // if image url then load the image annotation if(url.match(/.jpg|.jpeg|.png|.gif|.bmp|.svg/)) { - this.imgURL = $('#img-url-input').val(); - if(!this.imgURL) { - return false; - } + this.imgURL = url; + if(this.$img.attr('src') === this.imgURL) { return; } @@ -311,10 +328,8 @@ this.$img.attr('src', this.imgURL); return false; } + // else load text annotation else { - if(!url) { - return false; - } window.location.href = '/annotate?where=' + url; } }, @@ -537,6 +552,95 @@ userLoggedOut: function() { swtr.who = 'Guest'; $('#signinview').html('Logged out'); + }, + changeURLInputPlaceholder: function(source) { + switch (source) { + case 'ocd' : $('#user-input').attr('placeholder', 'Enter search query'); + break; + case 'none' : $('#user-input').attr('placeholder', 'Enter URL of image or web page'); + break; + } + }, + // function to change the source in the application and update the UI + changeSource: function(source) { + switch (source) { + case 'ocd' : this.source = 'ocd'; + this.helpview.step(11); + this.changeURLInputPlaceholder('ocd'); + break; + case 'none' : this.source = 'none'; + this.helpview.step(1); + this.changeURLInputPlaceholder('none'); + break; + } + }, + // event handler to capture control panel UI change of source + sourceChanged: function(event) { + if($('#ocd-source').is(':checked')) { + this.changeSource('ocd'); + } + else { + this.changeSource('none'); + } + }, + loadOCDSearch: function(input) { + var self = this; + $('#img-annotation-wrapper').hide(); + $('#ocd-results').show(); + $('#ocd-results').append('Loading..'); + $.ajax({ + type: 'GET', + url: '/search/ocd', + data: {query: input}, + success: function(data) { + self.ocdView = new OCDView({model: data.hits.hits}); + } + }); + } + }); + + var OCDView = Backbone.View.extend({ + el: $('#ocd-results'), + events: { + 'click .ocd-item a': 'onClickImg' + }, + initialize: function() { + this.item_template = _.template($('#ocd-item-template').html()); + this.render(); + }, + render: function() { + var $row_el; + this.$el.html(''); + _.each(this.model, function(item, idx) { + if(idx % 3 === 0) { + $row_el = $('
'); + this.$el.append($row_el); + } + $row_el.append(this.item_template({ + title: item._source.title, + media_url: item._source.media_urls[0].url, + authors: item._source.authors + })); + }, this); + this.resolve(); + }, + // resolve the OCD media URLs + resolve: function() { + var self = this; + $('.ocd-item').each(function(idx, elem) { + var temp_arr = self.model[idx]._source.media_urls[0].url.split('/'); + var media_hash = temp_arr[temp_arr.length - 1]; + $.get('/resolve-ocd-media/'+ media_hash, function(resp) { + $(elem).find('img').attr('src', resp); + }); + }); + }, + onClickImg: function(event) { + event.preventDefault(); + // TODO: init the image anno + var url = $(event.currentTarget).find('img').attr('src'); + swtr.appView.loadURL(url); + return false; } }); @@ -575,6 +679,8 @@ break; case 10: text = 'Oops! Something went wrong. We couldn\'t publish the sweets. Try again.' break; + case 11: text = 'Search in Open Cuultur Data API'; + break; } $(this.el).html(text); $(window).scrollTop(0, 0); diff --git a/swtr/templates/index.html b/swtr/templates/index.html index 9736189..82ebc62 100644 --- a/swtr/templates/index.html +++ b/swtr/templates/index.html @@ -3,14 +3,14 @@ - swtr - the default sweet web app + swtr - the canonical sweet web app - + - + @@ -36,25 +36,36 @@
- + + placeholder="Enter URL of an image or web page" id="user-input">
- +
-
-
- - -
-
- +
+
+
+
+ +
+
+ +
+
+ +
+
+
@@ -65,6 +76,7 @@ {% endif %}
+
@@ -122,16 +134,6 @@ - + -- 1.7.10.4