From 94c2c6e5500b4b37a09b97d7a9e70a5652cbdc51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 5 Sep 2018 17:03:49 +0200 Subject: [PATCH] Start of "production videos", a.k.a. tagged assets overview Tagged assets are shown in a list per tag. The list is dynamically loaded with JavaScript. --- .gitignore | 3 +- cloud/__init__.py | 1 + cloud/routes.py | 5 ++ cloud/tagged/__init__.py | 3 + cloud/tagged/routes.py | 16 +++++ src/scripts/tagged_assets.js | 109 +++++++++++++++++++++++++++++++++++ src/templates/production.pug | 35 +++++++++++ 7 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 cloud/tagged/__init__.py create mode 100644 cloud/tagged/routes.py create mode 100644 src/scripts/tagged_assets.js create mode 100644 src/templates/production.pug diff --git a/.gitignore b/.gitignore index 0aacd71..6aadb1e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,7 @@ __pycache__ *.css.map /cloud/templates/ -/cloud/static/assets/css/ -/cloud/static/assets/js/bootstrap.min.js +/cloud/static/assets/ node_modules/ /config_local.py diff --git a/cloud/__init__.py b/cloud/__init__.py index af55054..abb7cd0 100644 --- a/cloud/__init__.py +++ b/cloud/__init__.py @@ -41,6 +41,7 @@ class CloudExtension(PillarExtension): 'EXTERNAL_SUBSCRIPTIONS_MANAGEMENT_SERVER': 'https://store.blender.org/api/', 'EXTERNAL_SUBSCRIPTIONS_TIMEOUT_SECS': 10, 'BLENDER_ID_WEBHOOK_USER_CHANGED_SECRET': 'oos9wah1Zoa0Yau6ahThohleiChephoi', + 'NODE_TAGS': ['animation', 'modelling', 'rigging'], } def eve_settings(self): diff --git a/cloud/routes.py b/cloud/routes.py index c05e7f1..68b5572 100644 --- a/cloud/routes.py +++ b/cloud/routes.py @@ -391,6 +391,11 @@ def privacy(): return render_template('privacy.html') +@blueprint.route('/production') +def production(): + return render_template('production.html') + + @blueprint.route('/emails/welcome.send') @login_required def emails_welcome_send(): diff --git a/cloud/tagged/__init__.py b/cloud/tagged/__init__.py new file mode 100644 index 0000000..e3afbdd --- /dev/null +++ b/cloud/tagged/__init__.py @@ -0,0 +1,3 @@ +"""Routes for fetching tagged assets.""" + + diff --git a/cloud/tagged/routes.py b/cloud/tagged/routes.py new file mode 100644 index 0000000..2f53551 --- /dev/null +++ b/cloud/tagged/routes.py @@ -0,0 +1,16 @@ +import logging +import datetime +import functools + +from flask import Blueprint, jsonify + +blueprint = Blueprint('cloud.tagged', __name__, url_prefix='/tagged') + +log = logging.getLogger(__name__) + + +@blueprint.route('/') +def index(): + """Return all tagged assets as JSON, grouped by tag.""" + + diff --git a/src/scripts/tagged_assets.js b/src/scripts/tagged_assets.js new file mode 100644 index 0000000..e2df620 --- /dev/null +++ b/src/scripts/tagged_assets.js @@ -0,0 +1,109 @@ +/** + * Support for fetching & rendering assets by tags. + */ +(function($) { + /* How many nodes to load initially, and when clicked on the 'Load Next' link. */ + const LOAD_INITIAL_COUNT = 5; + const LOAD_NEXT_COUNT = 3; + + /* Renders a node as a
  • element, returns a jQuery object. */ + function renderAsset(node) { + let li = $('
  • ').addClass('js-tagged-asset'); + let link = $('') + .attr('href', '/nodes/' + node._id + '/redir') + .appendTo(li); + + function warnNoPicture() { + li.addClass('warning'); + link.text('no picture for node ' + node._id); + } + + if (!node.picture) { + warnNoPicture(); + return li; + } + + // TODO: show 'loading' thingy + $.get('/api/files/' + node.picture) + .fail(function(error) { + let msg = xhrErrorResponseMessage(error); + li.addClass('error').text(msg); + }) + .done(function(resp) { + // Render the picture if it has the proper size. + var show_variation = null; + if (typeof resp.variations != 'undefined') { + for (variation of resp.variations) { + if (variation.size != 'm') continue; + show_variation = variation; + break; + } + } + + if (show_variation == null) { + warnNoPicture(); + return; + } + + let img = $('') + .attr('alt', node.name) + .attr('src', variation.link) + .attr('width', variation.width) + .attr('height', variation.height); + link.append(img); + }); + + return li; + } + + function loadNext(ul_element) { + let $ul = $(ul_element); + let tagged_assets = ul_element.tagged_assets; // Stored here by loadTaggedAssets(). + let already_loaded = $ul.find('li.js-tagged-asset').length; + + let load_next = $ul.find('li.js-load-next'); + + let nodes_to_load = tagged_assets.slice(already_loaded, already_loaded + LOAD_NEXT_COUNT); + for (node of nodes_to_load) { + let li = renderAsset(node); + load_next.before(li); + } + + if (already_loaded + LOAD_NEXT_COUNT >= tagged_assets.length) + load_next.remove(); + } + + $.fn.loadTaggedAssets = function(api_base_url) { + this.each(function(index, ul_element) { + // TODO(Sybren): show a 'loading' animation. + $.get('/api/nodes/tagged/' + ul_element.dataset.assetTag) + .fail(function(error) { + let msg = xhrErrorResponseMessage(error); + $('
  • ').addClass('error').text(msg).appendTo(ul_element); + }) + .done(function(resp) { + // 'resp' is a list of node documents. + // Store the response on the DOM