--- a/setup.py +++ b/setup.py @@ -7,7 +7,8 @@ requires = [ 'Flask', - 'requests' + 'requests', + 'lxml' ] setup(name='Swtr', --- a/swtr/server.py +++ b/swtr/server.py @@ -3,14 +3,15 @@ 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 @@ 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 @@ "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: @@ -202,30 +261,6 @@ refresh_token=auth_tok['refresh_token'], config=config, 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 --- a/swtr/static/css/swtmaker.css +++ b/swtr/static/css/swtmaker.css @@ -10,21 +10,29 @@ 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 @@ 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; --- a/swtr/static/js/app.js +++ b/swtr/static/js/app.js @@ -130,6 +130,7 @@ 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); }, --- 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,9 +552,98 @@ 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; + } + }); + var HelpView = Backbone.View.extend({ el: $('#helpview'), events: { @@ -574,6 +678,8 @@ case 9: text = 'You have to be signed in to sweet store to post sweets'; 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); --- 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 @@ - +