From b5d335672e332b861cf2ec3b054f163cbf768cbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 22 Sep 2016 15:29:18 +0200 Subject: [PATCH] Added shot editing. --- attract/shots/routes.py | 12 +- attract/static/js/tasks.js | 107 ++++++++++++++---- src/styles/_tasks.sass | 2 +- src/templates/attract/shots/for_project.jade | 9 +- .../shots/{shot.jade => view_shot.jade} | 5 +- .../attract/shots/view_shot_embed.jade | 33 ++++++ .../attract/tasks/view_task_embed.jade | 4 +- 7 files changed, 138 insertions(+), 34 deletions(-) rename src/templates/attract/shots/{shot.jade => view_shot.jade} (55%) create mode 100644 src/templates/attract/shots/view_shot_embed.jade diff --git a/attract/shots/routes.py b/attract/shots/routes.py index bfae4d2..1813185 100644 --- a/attract/shots/routes.py +++ b/attract/shots/routes.py @@ -4,6 +4,7 @@ from flask import Blueprint, render_template, request import flask import pillarsdk +import pillar.api.utils from pillar.web.system_util import pillar_api from attract.modules import attract_project_view @@ -67,10 +68,17 @@ def view_shot(project, attract_props, shot_id): api = pillar_api() shot = pillarsdk.Node.find(shot_id, api=api) + node_type = project.get_node_type(node_type_shot['name']) - return render_template('attract/shots/shot.html', + if request.is_xhr: + template = 'attract/shots/view_shot_embed.html' + else: + template = 'attract/shots/view_shot.html' + + return render_template(template, shot=shot, project=project, + shot_node_type=node_type, attract_props=attract_props) @@ -83,7 +91,7 @@ def save(project, shot_id): shot_dict = request.form.to_dict() shot = current_attract.shot_manager.edit_shot(shot_id, **shot_dict) - return flask.jsonify({'shot_id': shot_id, 'etag': shot._etag, 'time': shot._updated }) + return pillar.api.utils.jsonify(shot.to_dict()) # TODO: remove GET method once Pablo has made a proper button to call this URL with a POST. diff --git a/attract/static/js/tasks.js b/attract/static/js/tasks.js index 10d6db2..383ff23 100644 --- a/attract/static/js/tasks.js +++ b/attract/static/js/tasks.js @@ -47,6 +47,32 @@ function task_open(task_id, project_url) { }); } +/** + * Shows a shot in the #task-details div. + */ +function shot_open(shot_id, project_url) { + if (shot_id === undefined || project_url === undefined) { + if (console) console.log("shot_open(", shot_id, project_url, ") called."); + return; + } + + $('#shot-list').find('a').removeClass('active'); + $('#shot-link' + shot_id).addClass('active'); + + var shot_url = '/attract/' + project_url + '/shots/' + shot_id; + console.log('shot_url is ' + shot_url); + + $.get(shot_url, function(shot_data) { + $('#task-details').html(shot_data); + }).fail(function(xhr) { + if (console) { + console.log('Error fetching shot', shot_id, 'from', shot_url); + console.log('XHR:', xhr); + } + $('#task-details').html(xhr.responseText); + }); +} + /** * Create a task and show it in the #task-details div. */ @@ -75,48 +101,79 @@ function task_create(shot_id, project_url, task_type) { } -function task_save(task_id, task_url) { - console.log('Saving task to', task_url); - - var $form = $('#task-view form'); +function attract_form_save(form_id, item_id, item_save_url, options={}) +{ + var $form = $('#' + form_id); var $button = $form.find("button[type='submit']"); + var payload = $form.serialize(); - var task = '#task-' + task_id; - var $task = $(task); + var $item = $('#' + item_id); $button.attr('disabled', true); - $task.addClass('processing'); + $item.addClass('processing'); $('#status-bar').text('Saving task...'); if (console) console.log('Sending:', payload); - $.post(task_url, payload) - .done(function(saved_task) { - if (console) console.log('Done saving', saved_task); + $.post(item_save_url, payload) + .done(function(saved_item) { + if (console) console.log('Done saving', saved_item); + $('#status-bar').text('Saved item. ' + saved_item._updated); + if (options.done) options.done($item, saved_item); + }) + .fail(function(xhr_or_response_data) { + // jQuery sends the response data (if JSON), or an XHR object (if not JSON). + if (console) console.log('Failed saving', xhr_or_response_data); + $('#status-bar').text('Failed saving. ' + xhr_or_response_data.responseText); + + if (options.fail) options.fail($item, xhr_or_response_data); + }) + .always(function() { + $button.attr('disabled', false); + $item.removeClass('processing'); + + if (options.always) options.always($item); + }) + ; + + return false; // prevent synchronous POST to current page. +} + +function task_save(task_id, task_url) { + return attract_form_save('task_form', 'task-' + task_id, task_url, { + done: function($task, saved_task) { // Update the task list. // NOTE: this is tightly linked to the HTML of the task list in for_project.jade. $('.task-name-' + saved_task._id).text(saved_task.name).flashOnce(); $task.find('span.name').text(saved_task.name); $task.find('span.type').text(saved_task.task_type); $task.find('span.status').text(saved_task.properties.status.replace('_', ' ')); + + // FIXME: remove all existing status-XXX classes. $task .removeClass('col-list-item task-list-item') .addClass('col-list-item task-list-item status-' + saved_task.properties.status); - - $('#status-bar').text('Saved task. ' + saved_task._updated); - }) - .fail(function(xhr_or_response_data) { - // jQuery sends the response data (if JSON), or an XHR object (if not JSON). - if (console) console.log('Failed saving', xhr_or_response_data); + }, + fail: function($item, xhr_or_response_data) { $('#task-details').html(xhr_or_response_data.responseText); - $('#status-bar').text('Failed saving. ' + xhr_or_response_data.responseText); - }) - .always(function() { - $button.attr('disabled', false); - $task.removeClass('processing'); - }) - ; - - return false; // prevent synchronous POST to current page. + } + }); +} + +function shot_save(shot_id, shot_url) { + return attract_form_save('shot_form', 'shot-' + shot_id, shot_url, { + done: function($shot, saved_shot) { + // Update the shot list. + $('.shot-name-' + saved_shot._id).text(saved_shot.name).flashOnce(); + + // FIXME: remove all existing status-XXX classes. + $shot + .removeClass('col-list-item shot-list-item') + .addClass('col-list-item shot-list-item status-' + saved_shot.properties.status); + }, + fail: function($item, xhr_or_response_data) { + $('#task-details').html(xhr_or_response_data.responseText); + } + }); } diff --git a/src/styles/_tasks.sass b/src/styles/_tasks.sass index d25ca86..e7f1adc 100644 --- a/src/styles/_tasks.sass +++ b/src/styles/_tasks.sass @@ -1,4 +1,4 @@ -#task-view +.attract-form margin: 10px padding: 0 10px 10px +container-box diff --git a/src/templates/attract/shots/for_project.jade b/src/templates/attract/shots/for_project.jade index 85abd50..cd774b1 100644 --- a/src/templates/attract/shots/for_project.jade +++ b/src/templates/attract/shots/for_project.jade @@ -8,7 +8,7 @@ a#task-add(href="javascript:task_create('{{ project.url }}');") + Create Shot - .table + .table#shot-list .table-head .table-row .table-cell.shot-thumbnail Thumbnail @@ -21,7 +21,12 @@ .table-row .table-cell img(src="http://placehold.it/100x60") - .table-cell.shot-name {{ shot.name }} + .table-cell.shot-name + a( + id="shot-link-{{ shot._id }}" + href="javascript:shot_open('{{ shot._id }}', '{{ project.url }}');", + class="status-{{ shot.properties.status }}") + span(class="shot-name-{{ shot._id }}") {{ shot.name }} | {% for task_type in task_types %} .table-cell.task-name | {% for task in tasks_for_shots[shot._id][task_type] %} diff --git a/src/templates/attract/shots/shot.jade b/src/templates/attract/shots/view_shot.jade similarity index 55% rename from src/templates/attract/shots/shot.jade rename to src/templates/attract/shots/view_shot.jade index 5e231ed..9a0235f 100644 --- a/src/templates/attract/shots/shot.jade +++ b/src/templates/attract/shots/view_shot.jade @@ -2,7 +2,8 @@ | {% block page_title %}Shot {{ shot.name }}{% endblock %} | {% block body %} #col_main - h1 Shot {{ shot.name }} + h1 Shot  + span(class="shot-name-{{ shot._id }}") {{ shot.name }} #col_right - h1 Right + | {% include "attract/shots/shot_embed.html" %} | {% endblock %} diff --git a/src/templates/attract/shots/view_shot_embed.jade b/src/templates/attract/shots/view_shot_embed.jade new file mode 100644 index 0000000..d24e57f --- /dev/null +++ b/src/templates/attract/shots/view_shot_embed.jade @@ -0,0 +1,33 @@ +.attract-form + form#shot_form(onsubmit="return shot_save('{{shot._id}}', '{{ url_for('attract.shots.perproject.save', project_url=project['url'], shot_id=shot._id) }}')") + .input-transparent-group + input.input-transparent.shot-name( + name="name", + type=text, + placeholder='Name', + value="{{ shot.name | hide_none }}") + + .input-transparent-group + textarea.input-transparent( + name="description", + type=text, + placeholder='Description') {{ shot.description | hide_none }} + + .input-transparent-group + label(for="shot-status") Status: + select.input-transparent#shot-status( + name="status") + | {% for status in shot_node_type.dyn_schema.status.allowed %} + | + | {% endfor %} + + .input-transparent-group + textarea.input-transparent( + name="notes", + type=text, + placeholder='Notes') {{ shot.properties.notes | hide_none }} + + .input-group-separator + + .input-transparent-group + button.btn.btn-default.btn-block(type=submit) Save Changes diff --git a/src/templates/attract/tasks/view_task_embed.jade b/src/templates/attract/tasks/view_task_embed.jade index 12fa635..a3e4921 100644 --- a/src/templates/attract/tasks/view_task_embed.jade +++ b/src/templates/attract/tasks/view_task_embed.jade @@ -1,5 +1,5 @@ -#task-view - form(onsubmit="return task_save('{{task._id}}', '{{ url_for('attract.tasks.perproject.save', project_url=project['url'], task_id=task._id) }}')") +.attract-form + form#task_form(onsubmit="return task_save('{{task._id}}', '{{ url_for('attract.tasks.perproject.save', project_url=project['url'], task_id=task._id) }}')") .input-transparent-group input.input-transparent.task-name( name="name",